阅读和理解源代码
进入/arm2410/exp/drivers/demo,使用vi编辑器查看源代码
(1)代码头部分析:
1部分表明这个模块将用于内核,也可以在编译时通过 –D选项指定,如gcc –D__KERNEL__;2部分表明这个驱动程序将以模块的方式编译和使用,也可以在编译时通过 –D选项指定如gcc –DMODULE,3定义了主设备号。
(2)逆序排列代码:
(3)读写函数代码
read 方法完成将数据从内核拷贝到应用程序空间(内核到用户态,即copy to user),write(copy from user) 方法相反,将数据从应用程序空间拷贝到内核。对于者两个方法,参数 filp 是文件指针,count 是请求传输数据的长度,buffer 是用户空间的数据缓冲区,ppos 是文件中进行操作的偏移量,类型为 64 位数。
(4)ioctl 方法主要用于对设备进行读写之外的其他控制,比如配置设备、进入或退出某种操作模式,这些操作一般都无法通过 read/write 文件操作来完成。
ioctl 通常实现一个基于 switch 语句的各个命令的处理,对于用户程序传递了不合适的命名参数时,POSIX 标准规定应返回-ENOTTY,返回-EINVAL 是以前常见的方法。不能使用与 LINUX 预定义命令相同的号码,因为这些命令号码会被内核 sys_ioctl 函数识别,并且不再将命令传递给驱动的 ioctl。
(5)open函数和release函数
(6)file_operations结构体
上述定义的5个函数就是file_operations结构体的成员函数,是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux的open(),write(),read(),release()等系统调用时最终被调用。
上述代码定义了一个file_operations类型的结构体demo_fops,并将其中的一些成员赋了初值。由于demo_fops是一个静态变量,所以其他成员的初值是“零”。
(7) 设备初始化代码
这两个函数,module_init()和module_exit(),用于告诉内核,当一个驱动程序加载和退出(或撤消)时,需要执行的操作。不同驱动程序在加载和退出时,除了基本的向内核注册设备驱动程序外,还有各自的针对具体设备的操作。
测试驱动程序
首先加载驱动模块demo.o
查看驱动模块加载的设备号,如果有正在使用的情况,需要修改代码中的设备号。
如下图可以看出,250设备号未使用,我们可以将demo.c程序修改宏定义
#define demo_MAJOR 250,再插入驱动模块。
代码修改:
对demo.c文件中write函数进行修改如下:
重新加载模块,注意需要先删除以前的模块使用rmmod demo命令,但是这个命令经常执行后提示设备正在使用,我只能重新启动了虚拟机就可以了。
运行结果如下:
小结:
1. file_operations结构体的概述在代码分析中已经叙述。
2. 从上面的代码框架结构可以看出一个驱动框架的编写包括程序头部的宏定义部分,定义驱动模块的编译方式和用到的内核模块;然后是几个设备驱动程序最基本的函数声明和定义,这几个函数将对于相应的几个系统调用,将写在file_operations结构体中,它使用名字对进行结构字段的初始化;最后声明两个函数告诉内核驱动程序的加载和退出。
3. 根据实验对内核模块的操作,我理解的Linux内核模块由加载和卸载函数、功能函数以及一系列声明组成,它可以被传入参数,也可以导出符号供其他模块加载使用。
由于Linux设备驱动以内核模块的形式而存在,我认为在具体的设备驱动开发中,将驱动编译为模块有很强的工程意义,因为如果将正在开发的驱动直接编译进内核,而开发过程中会不断修改驱动代码,则需要不断的编译内核并重启Linux,但是如果编译为模块,则只需要rmmod和insmod就可以完成原本复杂的工作,开发的效率明显提高了。