编写设备驱动程序

锁定原语

在传统 UNIX 系统中,每一部分内核代码都采用两种方法终止:通过显式调用 sleep(1) 放弃对处理器的控制权或通过硬件中断。Solaris OS 的运行方式与此不同。系统可随时抢占内核线程以运行其他线程。由于所有内核线程共享内核地址空间,并且通常需要读取和修改相同的数据,因此内核提供了大量的锁定原语以防止线程损坏共享数据。这些机制包括互斥锁(又称为 mutex)、读取器/写入器锁以及信号量。

驱动程序数据的存储类

数据的存储类用于指示驱动程序是否需要采取显式步骤控制对数据的访问。共有三个数据存储类:

互斥锁

互斥锁 (mutex) 通常与一组数据关联,并控制对这些数据的访问。互斥锁提供了一种一次仅允许一个线程访问这些数据的方法。互斥锁函数包括:

mutex_destroy(9F)

释放任何关联的存储空间。

mutex_enter(9F)

获取互斥锁。

mutex_exit(9F)

释放互斥锁。

mutex_init(9F)

初始化互斥锁。

mutex_owned(9F)

测试以确定当前线程是否持有互斥锁。仅限于在 ASSERT(9F) 中使用。

mutex_tryenter(9F)

获取互斥锁(如果可用),但不阻塞。

设置互斥锁

设备驱动程序通常会为每个驱动程序数据结构分配一个互斥锁。互斥锁通常是该结构中类型为 kmutex_t 的字段。调用 mutex_init(9F) 以初始化要使用的互斥锁。通常在执行 attach(9E) 时为每个设备互斥锁进行此调用,在执行 _init(9E) 时为全局驱动程序互斥锁进行此调用。

例如,

struct xxstate *xsp;
/* ... */
mutex_init(&xsp->mu, NULL, MUTEX_DRIVER, NULL);
/* ... */

有关互斥锁初始化的较完整示例,请参见第 6 章

驱动程序在卸载之前必须使用 mutex_destroy(9F) 销毁互斥锁。通常在执行 detach(9E) 时为每个设备互斥锁进行销毁操作,在执行 _fini(9E) 时为全局驱动程序互斥锁进行销毁操作。

使用互斥锁

驱动程序如果需要读写共享数据结构,必须执行以下操作:

互斥锁的作用域(即互斥锁保护的数据)完全由程序员决定。仅当访问数据结构的每个代码路径都保护数据结构并且同时持有互斥锁时,互斥锁才会保护该数据结构。

读取器/写入器锁

读取器/写入器锁可控制对数据集的访问。读取器/写入器锁之所以这样命名,是由于许多线程可同时持有读锁,但仅有一个线程可持有写锁。

大多数设备驱动程序不使用读取器/写入器锁。这些锁的速度比互斥锁要慢。这些锁仅当保护通常进行读取但不经常写入的数据时才会提高性能。在此情况下,对互斥锁的争用可能会成为一个瓶颈,因此使用读取器/写入器锁效率可能更高。下表概述了读取器/写入器函数。有关详细信息,请参见 rwlock(9F) 手册页。读取器/写入器锁函数包括:

rw_destroy(9F)

销毁读取器/写入器锁

rw_downgrade(9F)

将读取器/写入器锁的持有者从写入器降级为读取器

rw_enter(9F)

获取读取器/写入器锁

rw_exit(9F)

释放读取器/写入器锁

rw_init(9F)

初始化读取器/写入器锁

rw_read_locked(9F)

确定是否持有用于读/写操作的读取器/写入器锁

rw_tryenter(9F)

尝试在无需等待的情况下获取读取器/写入器锁

rw_tryupgrade(9F)

尝试将读取器/写入器锁的持有者从读取器升级为写入器

信号

计数信号量可用作管理设备驱动程序中线程的替代原语。有关更多信息,请参见 semaphore(9F) 手册页。信号函数包括:

sema_destroy(9F)

销毁信号。

sema_init(9F)

初始化信号。

sema_p(9F)

减小信号,可能会阻塞。

sema_p_sig(9F)

减小信号,但不会阻塞(如果信号处于待处理状态)。请参见线程无法接收信号

sema_tryp(9F)

尝试减小信号,但不阻塞。

sema_v(9F)

增加信号,可能会解除阻塞等待者。