📄 (ldd) ch05-字符设备驱动程序的扩展操作.txt
字号:
就绪时调用了read,或者在缓冲区没有空间时调用了write,系统简单地返回-EAGAIN。
如你所料,非阻塞型操作立即返回,允许应用查询数据。当使用stdio函数处理非阻塞型
文件时,由于你很容易误将非阻塞返回认做是EOF,应用程序应该非常小心。你必须始终
检查errno。
你也许可以从它的名字猜到,O_NONBLOCK在open方法也可有作用。当open调用可能会阻
塞很长时间时,就需要O_NONBLOCK了;例如,当打开一个FIFO文件而又(尚)无写者时
塞很长时间时,就需要O_NONBLOCK了;例如,当打开一个FIFO文件而又(尚)无写者时
,或是访问一个被锁住的磁盘文件时。通常,打开设备成功或失败,无需等待外部事件
。但是,有时打开设备需要需要很长时间的初始化,你可以选择打开O_NONBLOCK,如果
设置了标志,在设备开始初始化后,会立即返回一个-EAGAIN(再试一次)。你还可以为
支持访问策略选择实现阻塞型open,方式与文件锁类似。我们稍后将在“替代EBUSY的阻
塞型打开”一节中看到这样一种实现。
只有read,write和open文件操作受非阻塞标志的影响。
样例实现:scullpipe
/dev/scullpipe设备(默认有4个设备)是scull模块的一部分,用来展示如何实现阻塞
型I/O。
在驱动程序内部,阻塞在read调用的进程在数据达到时被唤醒;通常会发出一个中断来
通知这样一种事件,驱动程序在处理中断时唤醒进程。由于你应该无需任何特殊硬件―
―没有任何中断处理函数,就可以在任何计算机上运行scull,scull的目标与传统驱动
程序完全不同。我选择的方法是,利用另一个进程产生数据,唤醒读进程;类似地,用
读进程唤醒写者。这种实现非常类似与一个FIFO(或“命名管道”)文件系统节点的实
现,设备名就出自此。
设备驱动程序使用一个包含两个等待队列和一个缓冲区的设备结构。缓冲区的大小在通
常情况下是可以配置的(编译时,加载时和运行时)。
(代码)
read实现管理阻塞型和非阻塞型数据,如下所示:
(代码)
如你所见,我在代码中留下了PDEBUG语句。当你编译驱动程序时,你可以打开消息,这
样就可以更容易地看到不同进程间的交互了。
跟在interruptible_sleep_on后的if语句处理信号处理。这条语句保证对信号恰当和预
定的处理过程,它会让内核完成系统调用重启或返回-EINTR(内核在内部处理-ERESTART
SYS,最终返回到用户空间的是-EINTR)。我不想让内核对阻塞信号完成这样的处理,主
要时我想忽略这些信号。否则,我们可以返回-ERESTARTSYS错误给内核,让它完成它的
处理工作。我们将在所有的read和write实现中使用一样的语句进行信号处理。
write的实现与read非常相似。它唯一的“特殊”功能时,它从不完全填充缓冲区,总时
留下至少一个字节的空洞。因此,当缓冲区空的时候,wp和rp时相等的;当存在数据时
,它们是不等的。
(代码)
正如我所构想的,设备没有实现阻塞型open,这要比实际的FIFO要简单得多。如果你想
正如我所构想的,设备没有实现阻塞型open,这要比实际的FIFO要简单得多。如果你想
要看看实际的代码,你可以在内核源码的fs/pipe.c中找到那些代码。
要测试scullpipe设备的阻塞型操作,你可以在其上运行一些应用,象往常一样,可以使
用输入/输出重定义等方法。由于普通程序不执行非阻塞型操作,测试非阻塞活动要麻烦
些。misc-progs源码目录中包含了一个很简单的程序,称为nbtest,用它来测试非阻塞
型操作,该程序罗列如下。它所做的就是使用非阻塞型I/O复制它的输入和输出,并在期
间稍做延迟。延迟时间可以通过命令行传递,默认情况下时1秒钟。
(代码)
Select
在使用非阻塞型I/O时,应用程序经常要利用select系统调用,当涉及设备文件时,它依
赖于一个设备方法。这个系统调用还用来实现不同源输入的多路复用。在下面的讨论中
,我假设你知道在用户空间中select的语义的用法。注意,内核2.1.23引入了poll系统
调用,因此为了支持这两个系统调用,它改变驱动程序方法的工作方式。
为了保存所有正在等待文件(或设备)的信息,Linux 2.0的select系统调用的实现使用
了select_table结构。再次提醒你,你无需了解它的内部结构(但不管怎样,我们一会
会稍做介绍),而且只允许调用操作该结构的函数。
当select方法发现无需阻塞时,它返回1;当进程应该等待,它应该“几乎”进入睡眠状
态。在这种情况下,要在select_table结构中加入等待队列,并且返回0。
态。在这种情况下,要在select_table结构中加入等待队列,并且返回0。
仅当选择的文件中没有一个可以接收或返回数据时,进程才真正进入睡眠状态。这一过
程发生在fs/select.c的sys_select中。
写select操作的代码要比介绍它要容易得多,现在就可以scull中时如何实现的:
(代码)
这里没有代码处理“第3种形式的选择”,选择异常。这种形式的选择时通过mode ==
SEL_EX标别的,但大多数时候你都将其编写为默认情况,在其他选择均失败时执行。异
常事件的含义与设备有关,所以你可以选择是否在你自己的驱动程序中实现它们。这种
功能将只会为专为你的驱动程序设计的程序使用,但那并不它的初衷。在这方面,它与
依赖于设备的ioctl调用很相似。在实际使用中,select中异常条件的主要用途是,通知
网络连接上带外(加急)数据的达到,但它还用在终端层以及管道/FIFO实现中(你可以
查看fs/pipe.c中的SEL_EX)。不过要注意,其他Unix系统的管道和FIFO没有实现异常条
件。
这里给出的select代码缺少对文件尾的支持。当read调用达到文件尾时,它应该返回0,
select必须通过通告设备可读来支持这种行为,这样应用程序就不会永远等待调用read
了。例如,在实际的FIFO中,当所有的写者都关闭了文件时,读者会看到文件尾,而在s
cullpipe中,读者永远也看不到文件尾。设计这种不同行为的原因时,一般将FIFO当做
两个进程间的通信通道,而scullpipe是一个只要至少有一个读者,所有人就都可以输入
两个进程间的通信通道,而scullpipe是一个只要至少有一个读者,所有人就都可以输入
数据的垃圾筒。此外,也没有必要重新实现内核中已经有了的设备。
象FIFO那样实现文件尾意味着要在read和读select中检查dev->nwriters,并做相应处理
。不过很遗憾,如果读者在写者前打开设备,它马上就看到文件尾了,没有机会等待数
据到达。最好的解决这个问题的方法是,实现阻塞型open,这个任务做为练习留给读者
。
与read和write的交互
select调用的目的是事先判断是否有I/O操作会阻塞。从这个方面说,它时对read和writ
e的补充。由于select可以让驱动程序同时等待多个数据流(但这与这里的情况无关),
select在这方面也时很有用途的。
为了让应用正确工作,正确实现这3个调用时非常重要的。尽管下面的规则已经多多少少
谈过了一些,我还要在这里再总结一下。
从设备读取数据
如果在输入缓冲区中有数据,即便比所请求的数据少,而且驱动程序可以保证剩下的数
据会马上达到,read调用应该不经过任何可以察觉的延迟立即返回。如果你至少可以返
回1个字节,而且很方便的话,你总可以返回比请求少的数据(我们在scull就是这样做
的)。当前内核中总线鼠标的实现在这方面就时错的,某些程序(如dd)无法正确读取
这些设备。
如果输入缓冲区中没有数据,在至少有一个字节可读前read必须阻塞,除非设置了O_NON
BLOCK。非阻塞型read立即返回-EAGAIN(尽管在这种情况下某些旧的System V会返回0)
。在至少有一个字节可读前,select必须报告设备不可读。只要有数据可读,我们就使
用上一条规则。
如果我们到了文件尾,,无论是否有O_NONBLOCK,read都应该立即返回0。select应该报
告说文件可读。
向设备写数据
如果输出缓冲区有空间,write应该不做任何延迟返回。它可以接收少于请求数目的数据
,但是它须接收至少一个字节。在这种情况下,select应该报告设备可写。
如果输出缓冲区是满的,在空间释放前write一直阻塞,除非设置了O_NONBLOCK标志。非
阻塞型write立即返回,返回值为-EAGAIN(或者在某些条件为0,如前面旧版本的System
V所说)。select应该报告文件不可写。但另一方面,如果设备不能接收任何数据,无
论是否设置了O_NONBLOCK,write都返回-ENOSPC(“设备无可用空间”)。
如果使用设备的程序需要确保等候在输出队列中的数据真的完成了传送,驱动程序必须
提供一个fsync方法。例如,可移动设备使用提供fsync入口点。千万不要在调用返回前
让write调用等待数据传送结束。这是因为,需要应用程序都可用select检查设备是否时
可以写的。如果设备报告可以写,write调用应该保持一致,不能阻塞。
刷新待处理输出
我们已经看到write方法为什么不能满足所有数据输出的需求。通过同名系统调用调用的
fsync函数弥补了这一空缺。
如果某些应用需要确保数据传送到设备上,设备就必须实现fsync方法。无论是否设置了
O_NONBLOCK标志,fsync调用应该仅在设备已经完全刷新数据后才能返回,甚至花些时间
也要如此。
fsync方法没有什么不寻常的功能。调用不是时间关键的,所以每个设备驱动程序都可以
按照作者的风
--
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -