第一章Linux设备驱动程序简介Linux Kernel 系统架构图一、驱动程序的特点∙是应用和硬件设备之间的一个软件层。
∙这个软件层一般在内核中实现∙设备驱动程序的作用在于提供机制,而不是提供策略,编写访问硬件的内核代码时不要给用户强加任何策略o机制:驱动程序能实现什么功能。
o策略:用户如何使用这些功能。
二、设备驱动分类和内核模块∙设备驱动类型。
Linux 系统将设备驱动分成三种类型o字符设备o块设备o网络设备∙内核模块:内核模块是内核提供的一种可以动态加载功能单元来扩展内核功能的机制,类似于软件中的插件机制。
这种功能单元叫内核模块。
∙通常为每个驱动创建一个不同的模块,而不在一个模块中实现多个设备驱动,从而实现良好的伸缩性和扩展性。
三、字符设备∙字符设备是个能够象字节流<比如文件)一样访问的设备,由字符设备驱动程序来实现这种特性。
通过/dev下的字符设备文件来访问。
字符设备驱动程序通常至少需要实现 open、close、read 和 write 等系统调用所对应的对该硬件进行操作的功能函数。
∙应用程序调用system call<系统调用),例如:read、write,将会导致操作系统执行上层功能组件的代码,这些代码会处理内核的一些内部事务,为操作硬件做好准备,然后就会调用驱动程序中实现的对硬件进行物理操作的函数,从而完成对硬件的驱动,然后返回操作系统上层功能组件的代码,做好内核内部的善后事务,最后返回应用程序。
∙由于应用程序必须使用/dev目录下的设备文件<参见open调用的第1个参数),所以该设备文件必须事先创建。
谁创建设备文件呢?∙大多数字符设备是个只能顺序访问的数据通道,不能前后移动访问指针,这点和文件不同。
比如串口驱动,只能顺序的读写设备。
然而,也存在和数据区或者文件特性类似的字符设备,访问它们时可前后移动访问指针。
例如framebuffer设备就是这样一个设备,应用程序可以用mmap 或 lseek 访问图象的各个区域。
四、块设备∙块设备通常是按照块为单位来访问数据,比如一块为512字节。
∙块设备也是通过 /dev 目录下的文件系统节点来访问。
块设备和字符设备的区别仅仅在于内核内部管理数据的方式,也就是内核和驱动程序的接口不同。
∙块设备除了给内核提供和字符设备一样的接口外,还提供了专门面向块设备的接口,块设备的接口必须支持挂装文件系统,通过此接口,块设备能够容纳文件系统,因此应用程序一般通过文件系统来访问块设备上的内容。
∙文件系统可能是除驱动程序外 Linux 系统中最重要的模块类型,与块设备驱动程序联系紧密。
五、网络设备驱动和网络接口∙网络设备驱动不同于字符设备和块设备,不在/dev下以文件节点为代表,而是通过单独的网络接口(eth0、eth1>来代表。
∙任何网络事务都要经过一个网络接口,即一个能够和其它主机交换数据的设备。
通常接口代表一个硬件设备(如网卡>,但也可能是个纯软件设备。
∙内核和网络驱动程序间的通讯完全不同于内核和字符设备以及块设备驱动程序之间的通信,内核调用一套和数据包传输相关的函数。
六、设备文件和设备驱动∙设备文件是文件系统上的一个节点,是一种特殊的文件,叫做设备文件。
每个设备文件在用户空间代表了一个设备。
∙设备文件一般存在/dev目录下,用mknod命令创建。
设备文件有主、次设备号与其关联。
∙设备文件是用户应用程序和设备驱动的接口。
应用程序一般只能通过设备文件来使用设备驱动的功能。
∙字符和块设备驱动必须有相应的设备文件来对应。
很明显,操作系统内部不可能用设备文件名来与物理设备及其驱动进行绑定。
其实,操作系统内部是用设备号来与物理设备及其驱动进行绑定的。
习惯上,用主设备号与驱动进行关联,用次设备号与具有相同驱动的不同物理设备关联<例如:2个硬盘)。
dennis@dennis-desktop:~$ ls -l /dev/sd[a-c]brw-rw---- 1 root disk 8, 0 2018-04-13 13:38 /dev/sdabrw-rw---- 1 root disk 8, 16 2018-04-13 13:38 /dev/sdbbrw-rw---- 1 root disk 8, 32 2018-04-13 13:38 /dev/sdc当用户程序运行open("/dev/ttyS0",…>时,由于设备文件/dev/ttyS0有一个设备号与其关联,因此操作系统可以获知应用程序想操控的设备的设备号,而操作系统内部又将设备号与物理设备及其驱动进行了绑定,因此操作系统就可以知道应该调用哪一个驱动去控制哪一个设备。
当然这一切的前提是,操作系统内部要将设备号与物理设备及其驱动进行绑定,那么操作系统内部是用什么手段完成这种绑定关系的呢?实际上,在操作系统内部存在一个结构体链表<就是上图中的Char device list,以后称它为设备链表),链表的每个节点代表一个绑定关系<也就是说:节点至少含有2个字段,1个用于记录设备号,另1个用于记录寻找驱动的信息,通常是一个指向驱动函数结构体的指针)。
那么是谁生成节点并将它链入链表的呢?当然是驱动程序!七、构造和运行模块1、Kernel Module的特点∙模块只是先注册自己以便服务于将来的某个请求,然后就立即结束。
∙模块可以是实现驱动程序,文件系统,或者其他功能。
∙加载模块后,模块运行在内核空间,和内核链接为一体。
2、模块与内核的接口函数<除掉read、write等功能函数)生成节点并将它链入设备链表这个操作由驱动中的函数实现,这些函数什么时机运行呢?当然最合适的时机是内核加载模块<insmod 模块)的时候。
∙函数 init_module:内核加载模块的时候调用。
主要功能是:为以后使用模块里的函数和变量预先做准备∙函数cleanup_module:模块的第二个入口点,内核在模块即将卸载之前调用它。
3、操作模块相关的命令∙insmod: 加载模块。
后面参数是模块文件名。
# insmod /lib/modules/hello.koHello, world∙rmmod:卸载模块。
后面参数是模块名称。
# rmmod helloGoodbye, cruel world∙lsmod:列出当前内核使用的模块。
或者查看/proc/modules文件。
∙depmod:扫描/lib/modules/<kernel version>/目录下的所有内核模块,从而给内核模块生成依赖文件。
o生成/lib/modules/<kernel version>/modules.dep文件,其中<kernel version>是当前运行内核的版本号∙modprobe:根据modules.dep文件探测并加载内核模块。
只需要给出模块名称,自动寻找适合的模块文件,并进行加载。
注意和insmod的不同之处。
o可以自动寻找模块文件并加载。
o自动寻找并加载依赖的模块。
#cat /lib/modules/2.6.22.6/modules.dep/lib/modules/s3c24xx_buttons.ko: /lib/modules/leds.ko/lib/modules/leds.ko:# lsmodModule Size Used by Not tainted# modprobe s3c24xx_buttonsleds initializedbuttons initialized# lsmodModule Size Used by Not tainteds3c24xx_buttons 5944 0leds 3592 1 s3c24xx_buttons # rmmod ledsrmmod: leds: Resource temporarily unavailable# rmmod s3c24xx_buttonsbuttons driver unloaded# lsmodModule Size Used by Not taintedleds 3592 0# rmmod ledsleds driver unloaded# lsmodModule Size Used by Not tainted# insmod s3c24xx_buttonss3c24xx_buttons: Unknown symbol ledoffs3c24xx_buttons: Unknown symbol ledoninsmod: cannot insert '/lib/modules/s3c24xx_buttons.ko': Unknown symbol in module (-1>: No such file or directory∙modinfo:查看模块文件的基本信息dennis@dennis-desktop:/work/studydriver/buttons$ modinfos3c24xx_buttons.kofilename: s3c24xx_buttons.kolicense: GPLdescription: S3C2410/S3C2440 BUTTON Driverauthor: YangZhu E-mail: scyz@depends:vermagic: 2.6.22.6 mod_unload ARMv44、内核模块的编译方法内核源码树:指的是内核源代码tar包解压缩后形成的目录<包含其下级所有目录和文件)已编译内核源码树:指的是已经成功生成过内核的内核源码树<即:已经成功执行过make uImage的内核源码树)驱动大多都编译为模块,2.6内核中要想编译模块,必须先存在已经成功编译了的内核源码树<即:已编译内核源码树),且该源码树编译出来的内核就是该模块即将运行在其上的内核。
编译方法1:∙编写Makefile:obj-m := hello.o∙编译命令:make –C 内核源码树目录 M=`pwd` modules。
例如:dennis@dennis-desktop:/work/studydriver/examples/misc-modules$ make -C /work/system/linux-2.6.22.6/ M=`pwd` modules对该make命令的解释:要想编译内核模块,只需要在内核源码树的顶层目录下输入make modules来编译Makefile中的modules目标即可,剩下的事情,由内核构造系统全权替我们处理。
但由于目前不处于内核源码树的顶层目录,并且当前目录下的Makefile 也没有modules目标,因此使用-C参数来告知make程序需要在执行之前切换到/work/system/linux-2.6.22.6/目录。