2010-3-10 sculla具有访问控制的字符设备 sbull块设备 代码阅读
2010-03-10 18:00
267 查看
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
结构的创建者请求的完成情况。
一、
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
结构的创建者请求的完成情况。
相关文章推荐
- Linux设备驱动程式学习(6)-高级字符驱动程式操作[(3)设备文档的访问控制]
- ldd3学习之十二(4):高级字符驱动程序操作--llseek、设备文件的访问控制
- Linux设备驱动程序学习(6) -高级字符驱动程序操作[(3)设备文件的访问控制]
- Linux设备驱动程序学习(6)-高级字符驱动程序操作[(3)设备文件的访问控制]
- Linux设备驱动程序学习(6) -高级字符驱动程序操作[(3)设备文件的访问控制]
- 控制设备背景代码
- [国嵌攻略][116][字符设备控制技术]
- 字符设备驱动框架3:深入探讨—完整的驱动代码工程
- HM编码器代码阅读(41)——码率控制(二)
- 字符设备驱动笔记——中断方式按键驱动之代码(六)
- Linux 字符设备控制技术
- 宋宝华 《Linux设备驱动开发详解》示例代码之基本字符设备驱动
- Virtualmen字符设备驱动代码-----虚拟磁盘设备
- 设备驱动学习之字符设备驱动内核代码分析(二)——字符设备结构体cdev
- 字符设备控制技术
- 示例代码--控制设备背景代码
- Linux字符设备驱动程序之并发控制
- HEVC码率控制浅析——HM代码阅读之四
- homerHEVC代码阅读(24)——编码器控制函数HOMER_enc_control
- x265代码阅读:码率控制(一)