接着上篇博客,下图为设备驱动的初始化函数和退出函数。
第44行定义整形变量 err ,用来保存错误码。
第45行是一个调试函数,他与函数pr_devel 差不多,对应的输出级别都为7级。
第47到51行为动态分配设备号,函数第一个参数为输出型参数,用于存放分配到的设备号。第二个参数为要注册的次设备号的起始值,第三个参数为要注册的设备号的个数,第四个参数表示设备号的名称。此处kpad.dev存放分配到的设备号,次设备号的起始值为0,分配个数为1,设备的名称为“kpad”。
需要注意的是分配设备号时内核自动决定的实际上是主设备号,所属的次设备号的起始值和个数都是由用户提供的。最终由第一个参数存储的是分配到的第一个设备号。分配到的设备号为以注册状态,不需要再调用注册设备号的接口函数进行注册。
其中49行中为
“-err”,而不是err是因为上篇博客已经说过,内核的函数在失败时返回的是一个负数,代表失败的原因,称为错误码,内核的错误码都是以宏的形式定义在头文件中,这些宏定义都是正数,所以返回时要加一个负号。
第50行也是需要注意学习的,
这是goto语句的一个非常经典的用法。用于错误处理的,经常放在函数体的最后。第74到81都是错误处理的情况。75行为释放所分配的内存,78行为注销所注册的设备号。
上述的各个清理操作按初始化时的倒序排列,错处时跳到相应的位置。这是驱动编程中的常用的做法。
第53行输出调试信息,主设备号和次设备号。主设备号由函数MAJOR()获得,次设备号由函数MINOR()函数获得。
第55到60行为分配内核缓冲区。其中58行 “-ENOMEM” 此错误号表示内存不足
,要加负号。
第62行为保存分配的缓冲区的长度。
第63和64行为初始化字符设备的内容。cdev_init()函数有两个参数,第一个为需要初始化的字符设备,第二个参数为字符设备所支持的一系列文件操作。
这样设备和文件操作就联系起来了。原型为cdev_init(struct cdev *cdev, const struct file_operations *fops);初始化字符设备实际上就是对cdev参数所指向的结构体中的成员进行初始化,结构体cdev的定义如下:
其中标有 “内部使用” 的成员为内核管理字符设备所使用的成员,在驱动中一般不要对其进行修改。owner成员指向一个struct module 型的结构体,这个结构体用于管理内核模块的,对应于每一个加载的模块都有一份相应的数据。owner成员的主要用途是正确处理模块的引用计数,当字符设备被打开使用时,它所属的模块的引用计数将增加。而引用计数为0 的模块才能被卸载。初始化字符设备的函数并没有设置这个成员,需要另外赋值,一般情况下都会赋值为THIS_MODULE,这是一个宏,代表本模块。
其中64行即为对这个成员的赋值。
第66行为注册字符设备,
初始化或创建得到的字符设备还仅仅是一个结构体变量,要使内核中真正产生一个字符设备,需要将其注册。从此这个字符设备就和设备号绑定了。
截止到目前为止。kpad设备的设备号和设备都已经向内核注册好了,设备所支持的文件爱操作也规划好了,下面的内容就是一些列的文件操作的实现
。从上面的内容可以看出,驱动注册的步骤为:首先是对设备号的处理(分配、注册),然后初始化字符设备(cdev 成员的初始化,所支持的文件操作),最后是注册字符设备(把字符设备和设备号绑定了 cdev_add函数),内核提供了很多函数,要注意不同函数的搭配。
至于一系列的文件操作,将在下篇文章介绍。
阅读(1213) | 评论(0) | 转发(0) |