第六章的笔记。
Advanced Char Driver Operations
看完第六章之后,时间安排有了变化。接下来的第七、八章(Time, Delays, and Deffered Work 和 Allocating Memory)只能暂时略过,从第九章继续看。
第六章中介绍的是Char设备驱动中,除读写操作以外的常见操作,包括:
-
ioctl
-
Blocking I/O
-
poll and select
-
Asynchronous Notification
-
Seeking a Device
-
Access Control on a Device File
ioctl
ioctl以前给我感觉挺神秘的,原来就是选好cmd的号码,然后写个switch就行了。关键是这一章还没介绍如何真正操作硬件,所以用的ioctl的例子也只是在软件部分的一些动作。这样当然就没什么神秘的了。
Blocking I/O
使用sleep时应该注意的一点是,必须十分确定自己一定会被wake up,才可以开始sleep。其实前面讲Semaphore的时候,已经涉及到了sleep的概念。但是到了这里才开始真正介绍sleep的种类,以及进程如何使自己开始sleep的两种方式(simply调用wait_event()的方式,和所谓“manual”的方式)。
第一种,也是比较简单的sleep基本上是这样:
-
创建一个wait_queue_head。两种方法:
DECLARE_WAIT_QUEUE_HEAD(my_queue);
或
wait_queue_head_t my_queue;init_waitqueue_head(my_queue); -
调用wait_event()(或者个系列的其他函数):
wait_event(myqueue, condition);
其中的condition就是这个进程wake up的条件。
然后,当然了,应该会有另一个进程调用wake up进程的函数“wake_up(&my_queue)”。这个函数会wake up所有my_queue上的进程。(对应wait_event()系列的其他函数,wake_up()也有若干变形。)
第二种,manual sleep实际上就是把wait_event()这个宏拆开。这样可以做出更复杂的sleep(比如可以用prepare_to_wait_exclusive())。
使用第一种sleep方式时,一般的情况下,当wake_up(&my_queue)被调用时,如果my_queue上面有多个进程在等待condition这个条件,则它们将同时被wake up。在看了wait_event()的定义以后,发现其实它内部就是一个循环,一旦被wake_up,就去检查condition是否确实为真,以判断是否需要继续sleep。但是必须注意的是,由于race的存在,这也并不意味着从wait_event()返回后condition就一定是真。
另外,这一章里,还学到了schedule()函数。schedule()就是告诉Linux,你现在应该马上重新考虑下应该把CPU交给哪个进程使用,也就是当年操作系统课中讲的“从用户态切换到系统态”。这个动作就是一个进程在把自己的state设置为TASK_INTERRUPTIBLE或TASK_UNITERRUPTIBLE,并把自己加入sleep队列以后,要做的事情。这里还有一个有意思的事情就是,如果在进程完成了这两项动作以后,而还没有调用schedule()之前的空隙,有人调用了wake_up()试图唤醒这个进程,Linux并不会出乱子。因为wake_up()正好做了和那两项动作相反的事,所以当这个进程重新获得CPU,并继续执行到schedule()时,scheduler会直接返回。
回顾一下Blocking I/O这部分的内容,感觉最重要的就是要想清楚流程。down_interruptible()以后进入一个循环,除非condition成立才可以退出循环。在循环体内,当condition不成立时,要先up(),再开始sleep,睡醒了之后再down_interruptible()并回到循环开始,重新判断condition。而循环体内的sleep那部分也挺烦。要先prepare_to_wait(),然后在睡前最后确认一次condition,确实仍然不成立,才调用schedule()。schedule()返回以后先finish_wait(),再看看是怎么醒的。如果是某Signal给吵醒的,要return -ESYSRESTART;重新来过。否则是自然醒,那就算是睡眠结束,继续上面说的“睡醒了之后在down_interruptible()那段”。
poll and select
poll和select是Unix时代传下来的一个系统调用的两个版本,在file_operations结构中都是用poll()实现的。poll用来查看当前某设备可用的操作(读、写等)。在程序不希望进入sleep状态等待某个I/O设备时,它们通常会在操作前调用poll来查看设备状态。
poll涉及到一些乱七八糟的数据结构,什么poll_table之类的。这部分只了解了poll的作用,更深的没仔细看。
Asynchronous Notification
异步通知。也就是POSIX进程通信机制中的“Signal”,信号机制。其目的是为了让(尤其是低优先级的)进程可以更迅速地响应某些事件。这部分也没仔细看,只是知道进程需要用fcntl()在设备上注册自己。
Seeking a Device
就是讲了一些和fops.llseek()有关的内容,说有些设备不支持seek操作的,该怎么办。
Access Control on a Device File
scull系列的最后几个终于出场了。这章介绍了几种常见的访问控制方式:
-
Single Open
很直接的方法。在设备的结构中加个锁,第一次open就锁住,以后再open就返回一个-EBUSY。这样一个设备就只能open一次了。 -
Restricting Access to a Single User at a Time
有时候要求同一个设备可以被open多次,但必须只能是由同一个用户。有一种可能就是为了确保在进行多个进程在某个设备上的race时,不希望其他用户的进程打扰。这可以在open中判断uid,至于判断之后的处理方法有几种:-
Returning -EBUSY
直接返回“忙”,没啥说的。 -
Blocking open
让后来的用户的进程sleep。 -
Cloning the Device on open
为后来的用户的进程创建一个副本。一个例子就是console设备。
-
这就是第六章的内容了,很杂。单独写一篇看来是对了。







