参看朱有鹏老师嵌入式视屏,第五部分第二章
字符驱动设备基础
一、环境的搭建
(1)正常运行linux系统的开发板。要求开发板中的linux的zImage必须是自己编译的,不能是别人编译的。
(2)内核源码树,其实就是一个经过了配置编译之后的内核源码。
(3)nfs挂载的rootfs,主机ubuntu中必须搭建一个nfs服务器。
二、常用模块指令及其讲解
1、常用模块指令
(1)lsmod(list module,将模块列表显示),功能是打印出当前内核中已经安装的模块列表
(2)insmod(install module,安装模块),功能是向当前内核中去安装一个模块,用法是insmod xxx.ko
ko文件是kernel object的缩写,也即内核的目标文件。【.ko】文件就是Linux中的驱动文件。
(3)modinfo(module information,模块信息),功能是打印出一个内核模块的自带信息。,用法是modinfo xxx.ko
(4)rmmod(remove module,卸载模块),功能是从当前内核中卸载一个已经安装了的模块,用法是rmmod xxx(加不加.ko后缀都行)
2、执行模块指令的一些细节问题
(1)先lsmod再insmod看安装前后系统内模块记录。实践测试标明内核会将最新安装的模块放在lsmod显示的最前面。
(2)insmod与module_init宏。模块源代码中用module_init宏声明了一个函数(在我们这个例子里是chrdev_init函数),作用就是指定chrdev_init这个函数和insmod命令绑定起来,也就是说当我们insmod module_test.ko时,insmod命令内部实际执行的操作就是帮我们调用chrdev_init函数。
(3)同理,module_exit和rmmod的对应,也就是说执行rmmod指令,实际在代码中执行的是module_exit()绑定的函数。
(4)insmod时模块的vermagic必须和内核的相同,否则不能安装,报错信息为:
insmod: ERROR: could not insert module module_test.ko: Invalid module format
模块的vermagic是如何得到的?
有一套源码,这个源码配置编译之后会得到一个zImage,把这个zImage下载到开发板。然后再用这一套内核源码树去编译内核、去编译模块,这样就可以保证模块这个模块一定可以在这个内核下运行。
3、模块中常用的一些宏
(1)MODULE_LICENSE,模块的许可证。一般声明为GPL许可证,而且最好不要少,否则可能会出现莫名其妙的错误(譬如一些明显存在的函数提示找不到)。
(2)MODULE_AUTHOR
(3)MODULE_DESCRIPTION
(4)MODULE_ALIAS
三、代码中的一些细节问题
1、printk函数是用来设置打印级别的,用法类似于C语言中的printf
2、函数修饰符
(1)__init,本质上是个宏定义,在内核源代码中就有#define __init xxxx。这个__init的作用就是将被他修饰的函数放入.init.text段中去(本来默认情况下函数是被放入.text段中)。
整个内核中的所有的这类函数都会被链接器链接放入.init.text段中,所以所有的内核模块的__init修饰的函数其实是被统一放在一起的。内核启动时统一会加载.init.text段中的这些模块安装函数,加载完后就会把这个段给释放掉以节省内存。
(2)__exit
注意:内核源码中函数前面加一个 _ 的函数是给内核用的;内核源码中函数前面加 __ 的函数是内核中比较核心的代码;内核源代码中加三个 ___ 的函数,基本可以不动这个函数
3、关于驱动模块中的头文件
(1)驱动源代码中包含的头文件和原来应用编程程序中包含的头文件不是一回事。应用编程中包含的头文件是应用层的头文件,是应用程序的编译器带来的(譬如gcc的头文件路径在 /usr/include下,这些东西是和操作系统无关的)。驱动源码属于内核源码的一部分,驱动源码中的头文件其实就是内核源代码目录下的include目录下的头文件。
四、驱动编译的Makefile代码分析
(1)KERN_DIR,变量的值就是我们用来编译这个模块的内核源码树的目录
(2)obj-m += module_test.o,这一行就表示我们要将module_test.c文件编译成一个模块
(3)make -C $(KERN_DIR) M=`pwd` modules 这个命令用来实际编译模块,工作原理就是:利用make -C进入到我们指定的内核源码树目录下,然后在源码目录树下借用内核源码中定义的模块编译规则去编译这个模块,编译完成后把生成的文件还拷贝到当前目录下,完成编译。
`pwd`,表示要将pwd当成shell命令执行。这里pwd打印出当前路径,是为了后面可以返回这个路径。
(4)make clean ,用来清除编译痕迹
总结:模块的makefile非常简单,本身并不能完成模块的编译,而是通过make -C进入到内核源码树下借用内核源码的体系来完成模块的编译链接的。这个Makefile本身是非常模式化的,3和4部分是永远不用动的,只有1和2需要动。1是内核源码树的目录,你必须根据自己的编译环境做相应的改变。
下面是Makefile的源码
1. # 开发板的linux内核的源码树目录 2. KERN_DIR = /210/jiuding_kernel/kernel 3. 4. 5. obj-m += module_test.o 6. 7. all: 8. make -C $(KERN_DIR) M=`pwd` modules 9. arm-linux-gcc app.c -o app 10. 11. cp: 12. cp *.ko /root/porting_x210/rootfs/rootfs/driver_test -rf 13. cp app /root/porting_x210/rootfs/rootfs/driver_test -rf 14. 15. .PHONY: clean 16. clean: 17. make -C $(KERN_DIR) M=`pwd` modules clean 18. rm app
五、字符驱动原理讲解
1、系统整体工作原理
(1)应用层->API->设备驱动->硬件
(2)API:open、read、write、close等
(3)驱动源码中提供真正的open、read、write、close等函数实体
2、file_operations结构体
(1)file_operations结构体定义在内核源码路径:kernel/include/linux/fs.h L1487
(2)file_opertations是内核源码中定义的一个结构体,这个结构体中几乎全部都是函数指针,都是实现从APP到OS中驱动文件的映射(见上图)。
3、注册字符设备驱动
(1)为何要注册驱动(注册之后才可以在内核中找到这个设备)
(2)谁去负责注册(自己写的驱动函数、自己去注册)
(3)向谁注册(向内核注册)
(4)注册函数从哪里来(调用内核中的函数来注册,register_chrdev()函数)
六、字符驱动的一些函数讲解
1、字符设备的注册与注销
(1)register_chrdev详解(#include <linux/fs.h>)
static inline void register_chrdev(unsigned int major, const char *name, const struct file_opertations * fops)
作用:用来注册字符设备驱动
用法:
注册成功返回1,注册失败返回0;
参数 major表示设备编号(人的身份证号);name表示设备名字(人的名字);结构体指针变量fops,在内核内部与驱动设备挂钩。
(2)unregister_chrdev(unsigned int major, const char *name);
作用:用来注销字符驱动驱动
2、两个宏module_init与module_exit
module_init(chrdev_init);
module_exit(chrdev_exit);
这两个分别对应两个函数,在insmod的时候去执行chrdev_init(),在rmmod的时候去执行chrdev_exit()。
3、file_operations结构体
每个设备驱动都需要一个file_operations结构体类型变量,目的是实现重应用层到OS中驱动的映射。
在应用层调用open去打开这个驱动设备的时候,就会去执行驱动层对应的.pen函数,也就是说test_chrdev_open,我们在这里去操作硬件;同理在应用层close设备驱动时候,执行驱动层的test_chrdev_release()函数,在这个函数里去执行相应的硬件操作;在应用层执行write的时候,通过copy_from_user (kbuf, ubuf, count)将应用层的数据传给驱动,然后驱动就可以执行想用的指令;在应用层高执行read的是哦户,通过copy_to_user(ubuf, kbuf, count)将驱动层的数据传至应用层。
七、驱动中操作硬件与逻辑操作硬件的不同
寄存器地址不同。原来是直接用物理地址,现在需要用该物理地址在内核虚拟地址空间相对应的虚拟地址。目前只体会到驱动操作硬件用的是虚拟地址。
八、在开发板下载代码实验的一些细节
1、安装驱动之前要lsmod看下当前的驱动有那些;
2、cat /proc/devices,查看有那些设备文件及其编号
3、安装我们自己的驱动之后再去查看cat /proc/devices
4、mknod /dev/test c 250 0的理解
在SecureCRT命令行底下执行了这句指令之后,才可以在/dev目录下找到/test目录,才可以到应用去调用驱动层代码驱动相应的硬件。