2010-3-10 sculla具有访问控制的字符设备 sbull块设备 代码阅读

sculla具有访问控制的字符设备

 

一、 sucll_access_init()

1、 与以前的简单scull scullp 一样,一开始就是分配设备号;

    (这个函数中没有为 scull_dev 结构申请内存,是因为这些结构的空间已经静态分配了)

2、在循环中调用 scull_access_setup() 函数,初始化每个设备(这个驱动中每个设备的访问控制方法不一样)。

sucll_access_setup() 中,主要完成以下工作:

   (1 )初始化 quantum qset 变量,初始化信号量;

   (2 )调用 cdev_init() 函数,分配和初始化 cdev 结构,并把它与 file_operation 结构连接起来;

   (3 )调用 kobject_set_name() 函数,这个貌似和设备模型之类的有关,暂时没管它;

   (4 )调用 cdev_add() 通知内核;

注: scull_access_setup的第二个参数 scull_dev_info 是一个指向结构数组的指针,被指向的结构和数组的定义如下。在 scull_access_devs 数组中第二个成员指向一个 scull_dev 结构,因为被指向的结构是静态申明的,所以不需要动态为之分配内存了;第三个成员指向各个设备的 file_operation 结构,于是对不同的设备将使用不同的文件操作函数(实际上只有 open release 函数不同)。

static struct scull_adev_info {

        char *name;

        struct scull_dev *sculldev;

        struct file_operations *fops;

} scull_access_devs[] = {

        { "scullsingle", &scull_s_device, &scull_sngl_fops },

        { "sculluid", &scull_u_device, &scull_user_fops },

        { "scullwuid", &scull_w_device, &scull_wusr_fops },

        { "sullpriv", &scull_c_device, &scull_priv_fops }

};

从上面的结构数组中,我们可以看到四类设备:scull_s_device scull_u_device scull_w_device scull_c_device ,这些分别对:

独占设备,每次只能由一个进程访问;

单用户使用的设备,每次只能由一个用户的进程访问;

阻塞型open 设备,每次只能由一个用户使用,后来访问的用户被阻塞,而不是返回 EBUSY

打开时复制设备,对每个进程分配一个设备。

这些设备的读、写、定位的函数都是一样的,不同的是open release ,现分别说明如下:

二、 scull_s_device

 scull_s_open

    1、对原子变量 scull_s_available 进行减一测试操作,如果返回值为 0 ,则将该原子变量加一,返回 -EBUSY

2、如果是可写的打开,则清空数据的存储区域。

 scull_s_release

1、对原子量进行加一操作。

三、 scull_u_device

 scull_u_open

1、加自旋锁(用户计数 scull_u_count 貌似是临界变量)

2、如果用户计数不为 0 、且当前的进程的 uid 与已获得文件反问权限的用户不同、且当前进程的用户不是 root 用户,则解自旋锁,返回 -EBUSY

3、如果用户计数为 0 ,则将当前进程的 uid 赋值给 scull_u_owner

4、用户计数加一;

5、解自旋锁。

6、如果是可读的访问,则清空数据存储区域。

 scull_u_release

1、加自旋锁;

2、用户计数减一;

3、解自旋锁。

四、 scull_w_device

 scull_w_open

1、加自旋锁;

2、在 while 的条件中调用 scull_w_available() 函数(该函数在用户计数为 0 或当前进程的 uid euid 与已取得权限的用户相同或当前用户是 root 时返回 1 ),

如果循环条件为1 scull_w_available() 返回 0 )则进行以下操作:

   (1 )解自旋锁;

   (2 )如果是非阻塞型打开文件则返回 -EAGAIN

   (3 )调用 wait_event_interruptible() 函数,将该进程放入等待队列 scull_w_wait 中,并设置判断条件为 scull_w_available() 返回值为 1

   (4 )加自旋锁。

3、 如果用户计数为0 ,则将当前进程的 uid 赋值给 scull_w_owner

4、 用户计数加一;

5、 解自旋锁;

6、 如果是可写打开,则调用scull_trim() 清空数据存储区。

 scull_w_release

1、加自旋锁;

2、用户计数减一,并将其赋值给变量 tmp

3、解自旋锁;

4、如果 tmp 0 ,则唤醒等待队列 scull_w_wait 上的所有进程。

五、 scull_c_device

    使用当前进程的控制终端的次设备号作为键值访问虚拟设备,于是运行在一个终端的所有进程共享设备。

 scull_c_open

1、 判断当前进程是否有对应的终端,如果没有则返回 -EINVAL

2、 通过tty_devnum 获得当前终端的此设备号,保存到 key 中;

3、 加自旋锁;

4、 调用scull_c_lookfor_device() 函数来查找该终端对应的设备是否已经存在,如果不存在则创建一个,该函数主要进行以下操作:

   (1 )在 scull_listitem 结构的链表中查找该终端对应的虚拟设备是否存在,如果存在则返回该设备 scull_dev 的指针;

   (2 )创建一个新的 scull_listitem 结构,清空 scull_dev 成员对应的数据存储区域,初始化信号量等;

   (3 )将该结构添加到链表中;

   (4 )返回该结构中 scull_dev 的指针。

5、 解自旋锁;

6、 如果是可写的打开文件,清空数据存储区;

7、 scull_dev 结构的指针赋给 filp->data (这很重要,要不以后读写数据的时候怎么找设备啊)

 scull_c_release

     return 0;

六、 scull_access_cleanup

    1、在循环中调用 cdev_del() 删除字符设备,并清空数据的存储区;

    2、遍历虚拟设备的链表,删除链表头( list_head ),清空每个虚拟设备的数据存储区域,释放 scull_listitem 结构;

    3、释放设备号。

 

 

 

 

 

 

 

 

 

 

 

sbull块设备

 

 

一、 sbull_init

    1、调用 register_blkdev() 函数想内核注册设备,并获得主设备号;

    2、为 sbull_dev 结构分配内存;

    3、在循环中调用 setup_device() ,初始化每个设备。 setup_device() 函数完成了初始化的主要工作,有必要详细叙述,故另起一节;

    

二、 setup_device

1、 初始化设备大小、数据存储区域和自旋锁(该自旋锁与请求队列相关)等

2、 创建定时器,将其与sbull_invalidate 函数联系起来(定时时间到了,则调用该函数“移除”设备);

3、 根据request_mode 的不同,选择不同的请求处理方式:

     RM_NOQUEUE  (不使用请求队列):

   (1 )调用函数 blk_alloc_queue() 函数分配一个请求对列(与 blk_queue_make_request 不同的是他并未真正建立一个保存请求的队列);

   (2 )队列申请成功后,调用 blk_requeue_make_request() 函数将“构造请求”函数 sbull_make_request 与之联系起来。在 sbull sbull_make_request 将处理所有 io 请求;

   RM_FULL (这种模式将传输request 中的 bio

  (1 )调用 blk_init_queue() 函数分配请求队列,并将其与自旋锁 dev->lock 和请求处理函数 sbull_full_request() 函数联系起来;

   RM_SIMPLE (简单模式,直接使用request ):

  (1 )调用 blk_init_queue() 函数分配请求队列,并将其与自旋锁 dev->lock 和请求处理函数 sbull_requset() 联系起来

    4、调用 blk_queue_hardsect_size() 函数通知内核设备所支持的扇区大小;

    5、调用 alloc_disk() 分配 gendisk 结构,继而初始化,以及调用 set_capacity() 函数设定硬件容量,然后调用 add_diak() 通知系统;

    

三、 sbull_exit

1、循环中调用 del_timer_sync() 函数删除定时器(该函数有什么特点还没具体看);

2、调用 del_gendisk() 函数卸载磁盘,之后调用 put_disk() 处理引用计数;

3、如果是不使用请求队列的模式则调用 blk_put_queue 释放请求队列,否则调用 blk_cleanup_queue() 函数释放请求队列;

4、释放数据储存区域;

5、释放设备号和 sbull_dev 结构的内存空间

四、 sbull_open

1、删除定时器;

2、加自旋锁;

3、如果用户计数为 0 ,则调用 check_disk_change() 检查设备介质是否改变;

4、用户计数加一;

5、解自旋锁;

五、 sbull_release

    1、获取自旋锁;

2、用户计数减一;

3、启动定时器;

5、解自旋锁;

六、 对移动设备的支持

    在setup_device() 函数中启用了一个定时器,时间到了后会调用 sbull_invalidate() 函数,如果此时无用户使用该设备或该设备上没有数据,则 sbull_invalidate() 函数将 dev->media_change 设置为 1

在块设备的block_device_operation 结构中 .media_change  .revalidate_disk 指针分别被赋值为 sbull_media_change sbull_revalidate

sbull_media_change函数只是返回 media_change 的值,返回非零值代表设备介质已改变;

介质改变后将调用sbull_revalidate ,在真实的设备中该函数应该完成一些必须的工作,入读取分区表等,在 sbull 中只将 media_change 的值赋为 0 ,然后清零数据存储区域。

七、 ioctl

sbull ioctl 只处理一个命令——对设备物理信息的查询请求。

八、 RM_SIMPLE

RM_SIMPLE模式中,请求处理函数为 sbull_request ,由该函数来处理数据请求:

1、 while 的循环条件中调用 elv_next_request() 函数获取未完成的 request ,在循环体中完成以下操作:

   (1 )如果该 request 不是文件系统请求(不是移动数据块),则调用 end_request 传递 0 ,表示不能完成该请求;

   (2 )调用 sbull_transfer() 函数传输数据(在 sbull_transfer() 函数中,调用 memcpy 传输数据);

   (3 )调用 end_request() 函数,传递 1 通知系统数据传递成功。

九、 RM_FULL

    RM_FULL模式中,请求处理函数为 sbull_full_request ,由该函数来处理数据请求:

    在while 的循环条件中调用 elv_next_request() 函数获取未完成的 request ,在循环体中完成以下操作:

1、 如果该request 不是文件系统请求(不是移动数据块),则调用 end_request 传递 0 ,表示不能完成该请求;

2、 调用函数sbull_xfer_requset() 进行数据传输, sbull_xfer_requset() 函数的主要工作是调用宏 re_for_each_bio 遍历请求中每个 bio ,对每个 bio 调用 sbull_xfer_bio 函数。 sbull_xfer_bio 函数调用宏 bio_for_each_segment() 遍历 bio 中的每个段,并在循环体中完成以下操作:

   (1 )为缓冲映射虚拟内存地址;

   (2 )调用函数 sbull_transfer 函数进行数据传输;

   (3 )取消虚拟内存的映射   

3、 调用end_that_requset_first 通知块设备子系统,若返回值为 0 表示数据都被传输完成,于是调用 blkdev_dequeue_request() 函数删除该 request ,之后调用 end_that_requset_last() 函数通知任何等待已经完成请求的对象,并重复利用该 request 机构。

十、 RM_NOQUEUE

为了优化硬盘的访问,I/O 调度程序会对块设备请求队列进行管理,完成合并、排序的操作,但是对于随机访问存储设备,这将不会对性能带来优化。(貌似空操作调度 noop 本来就不会对队列进行合并、排序等操作)。

RM_NOQUEUE模式使用的是“构造请求函数( make_request )”,它能够完成的事情:直接进行传输,或者对请求进行进一步操作(如:将请求与已存在请求合并等), sbull 实现的是直接传输,在 sbull_make_requset() 函数中的操作为:

1、调用 sbull_xfer_bio 完成 bio 请求;

2、调用 bio_endi ,告诉 bio 结构的创建者请求的完成情况。

你可能感兴趣的:(2010-3-10 sculla具有访问控制的字符设备 sbull块设备 代码阅读)