2010-3-9 scull、scullp代码阅读(写文档)

scull简单驱动程序阅读

 

一、 驱动程序加载

module_init(scull_init_module);

指定了使用insmod加载模块时,调用scull_init_module进行初始化,在该函数中做的事情就是分配设备号等工作,具体如下:

1、 如果指定了主设备号,则使用register_chrdev_region()函数尝试静态分配设备号,否则使用alloc_chrdev_region()函数动态分配一个;

2、 为scull_nr_devs个设备分配scull_dev的内存空间,并清空;

3、 在循环中初始化每个scull_dev设备:初始化quantnum、qset等变量,初始化信号量、调用函数scull_setup_cdev();

    (在scull_setup_cdev()函数中,先调用cdev_init()函数初始化scull_dev结构中的cdev机构,并将其与scull_fops联系起来,之后通过调用cdev_add()函数通知内核。)

4、 调用scull_create_proc函数创建/proc/scullmen和/proc/scullseq两个文件。

    (在 scull_create_proc函数有点复杂,在“十、使用/proc”中再叙述)

二、 驱动程序卸载

module_exit(scull_cleanup_module);

指定了rmmod卸载模块时,调用函数scull_cleanup_module,主要用来释放已分配的内存、设备号等,具体如下:

1、 在循环中调用scull_trim()函数释放每个设备中存储数据的内存区域,然后调用cde_del()函数移除该设备对应的字符设备结构;

    (scull_dev结构中*data指向一个有scull_qset的链表,链表中每个scull_qset结构中的**data指向一个指针数组,数组中每个指针指向一片内存区域,这些内存区域用来 存储数据。而scull_trim函数则使用循环的方式释放这些内存区域和scull_qset结构)

2、 释放设备scull_dev结构;

3、 调用scull_reomve_proc()函数/proc中的入口项;

    (具体的内容在 “十、使用/proc” 中再叙述)

4、 调用unregister_chrdev_region()函数释放设备号。

三、文件操作

在设备驱动中有一个重要的数据结构——file_operations,这个结构指明了与设备相关的函数(Unix系列把设备当成文件)。scull中的file_operations如下:

struct file_operations scull_fops = {

.owner =    THIS_MODULE,

.llseek =   scull_llseek,

.read =     scull_read,

.write =    scull_write,

.ioctl =    scull_ioctl,

.open =     scull_open,

.release =  scull_release,

};

    于是指定了打开、关闭、写、读、定位、控制该设备的函数分别为scull_open、scull_release、scull_write、scull_read、scull_llseek、scull_ioctl。

四、 scull_open

    在scull中,scull_open的工作如下:

    1、使用container_of(),通过inode->i_cdev得到当前设备的指针,然后将其保存到filp->private_date中;

    2、如果是以可写的方式打开该设备,则使用down_interruptible()函数尝试获取信号量,获取信号量后调用scull_trim()函数清空存储数据的区域,之后up()释放信号量。

  (这样的直接结构是,每次用vi打开文件后,文件都是空的)

五、 scull_release

    该函数除了返回0,之外没有进行任何操作。但ldd中提到一般的设备都会维护一个使用计数,release函数中一般会减一。

六、 scull_write

该函数的具体过程如下:

1、 获取互斥信号量,down_interruptible();

2、 计算当前偏移对应第几个scull_qset、在scull_qset中data的下标、以及在*data指向的内存区域中的偏移;

3、 调用scull_follow()函数获得指向scull_qset的指针;

   (scull_follow()函数主要的工作就是根据当前要操作的sucll_qset的索引,返回其指针,如果在该索引之前的scull_qset结构不存在的话,为之分配内存)

4、 分配内存,拷贝数据,重置偏移量;

5、 释放信号量,up()。

七、 scull_read

该函数具体过程如下:

1、获取互斥信号量,down_interruptible();

2、计算偏移对应的内存区域指针,也调用函数scull_follow();

3、拷贝数据,重置偏移量;

4、释放信号量,up()。

八、 scull_llseek

该函数使用一个switch语句,根据whence(0:文件开始;1:当前偏移;2:文件末)重置filp->f_ops的值。

文件对象中的f_ops保存文件当前的位移量。

九、 scull_ioctl

这个函数中的内容我怎么看,主要是用来回应一些设备命令,大致流程为:

1、 提取“幻数”和虚幻,拒绝不恰当的ioclt(-ENOTTY);

2、 如果有数据传输,则使用access_ok来验证地址是否可用;

3、 之后,通过switch根据具体的命令进行具体的操作。

十、 使用/proc

ldd中提到的使用/proc接口的方法有两种,在scull中都是用到了:

 1、使用create_proc_read_entry

这种方法相对比较简单,只需先定义一个处理读/proc文件的函数(这里是scull_read_procmen),然后调用create_proc_read_entry()函数创建对应的/proc文件,指定读取函数即可。

最后,在卸载模块时,调用remove_proc_entry()函数即可。

 2、使用seq_file接口

  (1)使用seq_file接口,需要定义四个迭代器操作函数:start、next、stop、show:

start函数用于指定seq_file文件的读开始位置,如果指定的位置超过文件末尾,则返回NULL;

next函数 用于将迭代器移动到下一个位置;

stop函数 在内核使用迭代器后,用来进行清理工作;

show函数用于 将数据 格式化输出 到用户空间,在scull中于scull_read_procmem()类似

 (2)scull将这些迭代器操作函数填充到了一个seq_operation 结构中。之后定义创建了一个file_operation结构,以在略低的层次上连接到/proc。在这个file_operation结构中自己实现的方法只有一个(scull_proc_open连接到.open上),其余的使用的是fs/seq_file.c中定义的seq_read、seq_lseek和seq_release。

在scull_proc_open()函数中,调用seq_open将文件与seq_operation连接。

 (3)最后,调用create_proc_entry()函数创建/proc文件,之后将目录项中proc_fops指向scull_proc_ops。

 

 

 


scullp阻塞型驱动程序

 

一、 scull_p_init()

   1、如果指定了主设备号,则尝试调用 register_chrdev_region() 分配设备号,否则调用函数 alloc_chrdev_region() 函数动态分配一个;

   2、为 scull_p_dev 分配内存;

   3、在循环中初始化每个 scull_p_dev 设备:

   先调用init_waitqueue_head() 函数动态初始化写等待队列、和读等待队列;然后初始化互斥信号量;再对每个 scull_p_dev 调用 scull_p_setup_cdev()

   (在scull_p_setup_cdev() 中所做的工作只是调用 cdev_init() 分配和初始化 cdev 结构,将其与 file_operation 结构相连,之后调用 cdev_add() 通知系统。

    

二、 scull_p_cleanup()

   1、在循环中调用 cdev_del() 从系统中删除每个 cdev 结构,之后释放每个设备的环形缓冲区;

   2、释放 scull_p_dev 的内存;

   3、释放设备号。

三、 文件操作

struct file_operations scull_pipe_fops = {

        .owner =        THIS_MODULE,

        .llseek =       no_llseek,

        .read =         scull_p_read,

        .write =        scull_p_write,

        .poll =         scull_p_poll,

        .open =         scull_p_open,

        .release =      scull_p_release,

        .fasync =       scull_p_fasync,

};

在系统调用poll epoll select 查询某个或多个文件描述符上的读取或写入是否会被阻塞时, scull_p_poll 返回一个掩码,用来指出非阻塞的读取和写入是否可能。

fasync用于异步通知,在执行 F_SETFL 启用 FASYNC 时,驱动程序中的该方法就会被调用。而具体工作将由 fasync_helper 完成。

四、 scull_p_open

1、获得设备号,保存到文件描述符的 private 成员中;

2、尝试获得信号量, down_interruptible() ,之后为环形缓冲区分配内存,并初始化缓冲区头、尾指针和读、写指针;

3、根据打开文件的类型(读 / 写),为读者或写者计数加以;

4、释放信号量, up()

5、最后返回 noneseekable_open(inode,filp); 该函数的功能就是修改文件描述符的 f_mode ,具体为: filp->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE)

五、 scull_p_fasync

scull_p_fasync的所有工作都有 fasync_hepler 完成,以从相关的进程队列表中添加或删除文件;

六、 scull_p_release

1、调用 scull_p_fasync 方法,从活动的异步读取进程列表中删除该文件。( ldd 中提到这只有 FASYNC 标志设置了才有必要,但是调用它不会有坏处)。

2、获取信号量,之后对读者或写者计数减一;

3、如果读者和写者计数和为 0 ,则释放环形缓冲的内存;

4、释放信号量。

七、 scull_p_read

1、获取信号量;

2、在 while 的条件语句中判断读写指针是否重合,如果读写指针重合,则表示无数据可读,于是先释放信号量,然后:

如果读操作是不可阻塞的,则返回-EAGIN

调用wait_event_interruptible() ,将进程加入 inq 等待队列,并设置休眠前后要对表达式 dev->rp!=dev->wp

唤醒后,重新获得信号量。

3、 拷贝数据,重置读指针位置;

4、 释放信号量

5、 调用wake_up_interruptible() 唤醒 outq 上的写者进程;

八、 scull_p_write

1、获取信号量;

2、调用 scull_getwritespace() 函数,该函数中包含休眠的代码,主要过程如下:

while 循环条件中,调用函数 spacefree() 函数判断可写空间大小是否为 0 ,如果为 0 则进行以下操作:

使用DEFINE_WAIT wait )设置等待队列入口;

释放信号量;

如果写是不可阻塞的,返回-EAGAIN

调用prepare_to_wait() 函数将等待队列入口添加到队列中,并设置进程状态为 TASK_INTERRUPTIBLE( 可中断睡眠 )

再次调用spacefree() 函数检测是可写空间是否为 0 (不想被睡眠啊!),如果仍然没有空间则调用 schedule()

schedule()函数返回后调用 finish_wait() 函数进行一些清理工作;

调用signal_pending() 函数判断是否是因为信号而被唤醒,如果是则返回 -ERESTARTSYS 通知 fs 层做相应处理;

重新获取信号量。

3、 传输数据、重置写指针位置等

4、 释放信号量

5、 调用wake_up_inerruptible() 函数,唤醒 inq 队列上的等待的读进程;

6、 如果dev->async_queue 非空,说明异步通知队列中有文件,则调用 kill_fasync() 函数通知所有相关进程。

九、 no_llseek

   scullp设备不支持定位,但因为默认是、方法是允许定位的,所以不能不写(不写则使用默认的),于是指定 no_llseek

十、 scull_p_poll

   我从来没有使用过poll epoll select 等系统调用,对 poll 的作用不是很理解。

   结合ldd 的讲解,我的理解是驱动程序要做的是两件事:

   1、对该驱动程序中能改变 poll 所查询状态有关等待队列调用 poll-wait() 函数,将等待队列加到 poll_table 中(为什么要这么做呢?是不是说如果不能执行无阻塞的读写,那么进程会在该队列上等待呢?)

   2、返回一个位掩码来指示操作是否可以立即无阻塞执行。

   scull_p_poll中步骤如下:

   1、获得信号量;

   2、对 inq outq 队列进行 poll_wait() 调用;

   3、根据具体情况返回是否可以立即进行读写;

   4、释放信号量。

你可能感兴趣的:(工作,File,Module,文档,UP,2010)