第 1 部分针对 Oracle Solaris 平台设计设备驱动程序
9. 直接内存访问 (Direct Memory Access, DMA)
14. 分层驱动程序接口 (Layered Driver Interface, LDI)
在传统 UNIX 系统中,可以通过显式调用 sleep(1) 以放弃处理器或通过硬件中断来终止内核代码的每个部分。Oracle Solaris OS 的运行方式与此不同。系统可随时抢占内核线程以运行其他线程。由于所有内核线程共享内核地址空间,并且通常需要读取和修改相同的数据,因此内核提供了大量的锁定原语以防止线程损坏共享数据。这些机制包括互斥锁、读取器/写入器锁以及信号量。
数据的存储类用于指示驱动程序是否需要采取显式步骤控制对数据的访问。共有三个数据存储类:
自动(栈)数据。每个线程有一个专用栈,因此驱动程序永远无需锁定自动变量。
全局静态数据。全局静态数据可由驱动程序中任意数量的线程共享。驱动程序有时可能需要锁定此类型的数据。
内核堆数据。驱动程序中任意数量的线程均可共享内核堆数据,如 kmem_alloc(9F) 分配的数据。驱动程序需要随时保护共享数据。
互斥锁 (mutex) 通常与一组数据关联,并控制对这些数据的访问。互斥锁提供了一种一次仅允许一个线程访问这些数据的方法。互斥锁函数包括:
释放任何关联的存储空间。
获取互斥锁。
释放互斥锁。
初始化互斥锁。
测试以确定当前线程是否持有互斥锁。仅限于在 ASSERT(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) 手册页。读取器/写入器锁函数包括:
销毁读取器/写入器锁
将读取器/写入器锁的持有者从写入器降级为读取器
获取读取器/写入器锁
释放读取器/写入器锁
初始化读取器/写入器锁
确定是否持有用于读/写操作的读取器/写入器锁
尝试在无需等待的情况下获取读取器/写入器锁
尝试将读取器/写入器锁的持有者从读取器升级为写入器
计数信号量可用作管理设备驱动程序中线程的替代原语。有关更多信息,请参见 semaphore(9F) 手册页。信号函数包括:
销毁信号。
初始化信号。
减小信号,可能会阻塞。
减小信号,但不会阻塞(如果信号处于待处理状态)。请参见线程无法接收信号。
尝试减小信号,但不阻塞。
增加信号,可能会解除阻塞等待者。