1. 模块初始化:
module_init(scull_init_module);
这指定了模块的初始化函数为scull_init_module,当insmod的时候就会自动执行这个函数,
当这个函数执行完毕之后,整个模块就准备就绪,当然整个驱动程序连同设备就能够为用户提供服务了!
scull_init_module
@1:申请设备号:
@2:申请设备:
通过kmalloc向OS申请描述设备所需要的内存,并且用memset清零。因为可能同时申请多个设备共享一个主设备号,也就是多个设备共享一份驱动程序代码。
@3:软件设备结构初始化:
@3.1:初始化和设备自身逻辑以及软件设计相关的部分,和实现逻辑以及设计相关
@3.2:初始化和Linux Kernel接口相关的部分
这部分主要的工作有:
1.准备好和file_operations结构相关的函数,设备需要哪个函数实现哪个,不需要全部实现。
struct file_operations scull_ops = {
.owner = THIS_MODULE,
.read = scull_read,
.write = scull_write,
.open = scull_open,
.release = scull_release
};
这是最基本的四个函数,不能少的!
2.调用cdev_init函数
首先,将cdev交给Kernel完成初始化
然后,将自己实现的file_operations方法和cdev关联
3.调用cdev_add函数
将初始化好的cdev添加到Kernel中,着一个步骤完成之后,用户就可以访问设备了,所以必须确 保,在调用cdev_add之前,一切准备就绪。
@4:错误处理
在以上步骤中,涉及到申请内存,使用Kernel的设施,所以就有可能出现错误,这时候,必须进行错误处理。
在出错时,一般将特定的错误码保存到retval之类的变量中,然后采用goto语句跳转到错误处理函数,这个函数主要的作用就是撤销已经申请的资源!往往将这个函数单独实现,在模块清除函数中也会调用它。对于simple_scull来说,由于极其简单,这个scull_cleanup_module既是模块清除函数,也是错误处理函数。我个人觉得,模块的清除函数中应该只释放模块初始化函数申请的资源。其他的资源,例如由open函数申请的,就应该由release函数释放,应该有一个对应关系,这样代码更清晰!
2. 模块清除函数:
module_exit(scull_cleanup_module);
指定模块的清除函数,它负责释放有module_init指定的函数申请的所有资源,清除驱动程序和内核的接口设施,包括设备号和设备,将硬件设备调整到一个合适状态。
@1:注销和Kernel接口的设施
cdev_del
@2:释放由module_init申请的资源
@3:注销设备号
3. 和file_operations相关的函数:
没有必要实现其中的所有方法,这个和硬件设备有关,也和驱动程序设计相关,我觉得也和上层软件使用的API相关。
@1 scull_open:
主要提供驱动程序自身的初始化能力,因为如果设备没有被打开,就表示没有人使用它,那么提前为它分配任何功能性的资源就是一种不明智的选择,只有到了必须分配的时候在分配!open的时候,就必须分配了!
@1.1 检查硬件设备状态,是否可以工作,是否就绪
@1.2 如果设备是第一次被打开,那么进行设备初始化
@1.3 根据需要,可能要更新file_operations指针
例如,不同的次设备号,表示不同的操作方式或者配置方式,这时候就需要提供不同的方法。
@1.4 分配,并且填写filp->private_data中的数据结构(他就是一个指针)
这里面方的内容根据需要,自行决定。
一般为了方便,会将cdev结构指针放入其中。
还应该根据打开文件的方式作相应的调整,例如:只读形式打开,那么应该将文件长度减为零。
@2 scull_release:
@2.1 释放由open申请的资源,以及保存在filp->private中的所有内容。
@2.2 在最后一次关闭的时候,关闭设备。
并不是每一个close都会导致release方法的执行,Kernel保存file结构使用inode的计数,只有为0的时候,才会调用release方法。
@3 scull_write:
@3.1 申请用于缓存用户空间数据的内存
@3.2 使用合适的方法,将数据写入硬件设备。
@3.3 更新文件当前位置指针f_pos以及和用来表示设备的数据结构中的相应描述信息(scull_dev)
注意copy_from_user的时候
@4 scull_read:
@4.1 根据文件当前位置指针f_pos和count,从设备中读取数据
@4.2 将数据拷贝到用户空间copy_to_user
在read和write方法中,检查读写位置的合法性以及count字段,由于库函数在返回数据不足的时候会重新继续读取,所以,驱动程序即使一次没有返回count个字节,也不会出现问题,但是多次系统调用的开销需要考虑。
注意write和read的返回值,同时也要注意上层应用是否需要使用可以阻塞的方法,这时候,是否有需要实现其他的readv和writev方法。
simple_scull.zip
阅读(803) | 评论(0) | 转发(0) |