编写设备驱动程序

选择锁定方案

在设计大多数设备驱动程序时,都应该确保锁定方案简单易懂。使用额外的锁允许更多并发,但会增加开销。使用的锁越少,占用的时间越短,但允许的并发会更少。通常,对每个数据结构使用一个互斥锁,对驱动程序必须等待的每个事件或条件使用一个条件变量,对驱动程序的每个主要全局数据集使用一个互斥锁。请避免长时间持有互斥锁。选择锁定方案时,请遵循以下指导原则:

要查看锁的用法,请使用 lockstat(1M)lockstat(1M) 可监视所有内核锁定事件、收集有关事件的频率和计时数据,并显示这些数据。

有关多线程操作的更多详细信息,请参见《多线程编程指南》

潜在的锁定缺点

同一线程不可重复获取互斥锁。如果已持有互斥锁,则再次尝试声明此互斥锁会导致产生以下故障消息:

panic: recursive mutex_enter. mutex %x caller %x

释放当前线程未持有的互斥锁会产生以下故障消息:

panic: mutex_adaptive_exit: mutex not held by thread

以下故障消息仅在单处理器上出现:

panic: lock_set: lock held and only one CPU

lock_set 故障消息指明线程持有自旋互斥锁 (spin mutex),并且该锁将会永久旋转,因为没有其他 CPU 可以释放此互斥锁。如果驱动程序忘记释放某个代码路径上的互斥锁,或在持有互斥锁时阻塞,则会发生此情况。

具有高级中断的设备调用的例程阻塞(如 cv_wait(9F))时通常会导致出现 lock_set 故障消息。另一个常见原因是高级处理程序通过调用 mutex_enter(9F) 获取自适应互斥锁。

线程无法接收信号

线程收到信号时,可以唤醒 sema_p_sig()cv_wait_sig()cv_timedwait_sig() 函数。由于某些线程无法接收信号,因此可能会出现问题。例如,如果由于应用程序调用 close(2) 而导致调用 close(9E),则可以收到信号。但是,如果是从 exit(2) 处理(关闭所有打开的文件描述符)中调用 close(9E),则线程无法收到信号。如果线程无法收到信号,则 sema_p_sig() 的行为与 sema_p() 相同,cv_wait_sig() 的行为与 cv_wait() 相同,cv_timedwait_sig() 的行为与 cv_timedwait() 相同。

对于可能永远不会发生的事件,请注意避免永久休眠。永远不会发生的事件会创建不可中止 (defunct) 的线程并使设备不可用,除非重新引导系统。失效进程无法接收信号。

要检测当前线程是否可接收信号,请使用 ddi_can_receive_sig(9F) 函数。如果 ddi_can_receive_sig() 函数返回 B_TRUE,则以上函数可在收到信号时唤醒。如果 ddi_can_receive_sig() 函数返回 B_FALSE,则以上函数无法在收到信号时唤醒。如果 ddi_can_receive_sig() 函数返回 B_FALSE,则驱动程序会使用替代方法(如 timeout(9F) 函数)重新唤醒。

出现此问题的一个重要情况是使用串行端口。如果远程系统声明了流量控制,并且 close(9E) 函数在尝试清空输出数据时阻塞,则端口会堵塞,直到解决流量控制情况或重新引导系统为止。此类驱动程序应检测到此情况并设置计时器,以便在流量控制情况持续过长时间时中止清空操作。

此问题还会影响 qwait_sig(9F) 函数。此函数将在《STREAMS Programming Guide》中的第 7  章 “STREAMS Framework – Kernel Level”中介绍。