"照葫芦画瓢"与"改例子代码为已所用"是软件工程师的一条捷径.
无论是Windows WDK/DDK还是Linux, 都提供了数不胜数的实例, 以供驱动编程学习与实际项目的需要.
以USB驱动为例, 在WDK的例子程序中, 涵盖了KMDF, UMDF, EXE一系列代码例子.
而Linux中则更为丰富, 其包含了Host, composite, hub, 以及各类USB设备的驱动源代码, 让有兴趣了解USB的学习者能够更加深入地掌握USB的软件编程与部分原理.
但是, 无论复杂的软件还是系统, 都有一个对应的总体设计目标, 以及各种不同的条条框框规则.
初看这些规则,感觉教条, 无趣, 不知所以然, 随着对驱动以及操作系统内核的接触的深入, 渐渐地明白了其中的一些规则.
因此, 我们可以学习Windows WDK/DDK或者Linux中的例子或者内核源代码, 最终将这些例子或者源代码"为我的项目所用", 但要感悟体会内核驱动程序甚至系统的设计目标, 并非一朝一夕所能达成.
以Windows WDK中对内核驱动的设计目标为例:
The goal of the preemptible, interruptible design of the operating system is to maximize system performance. Any thread can be preempted by a thread with a higher priority, and any driver's interrupt service routine (ISR) can be interrupted by a routine that runs at a higher interrupt request level (IRQL).
可抢占与可被中断的设计目标是为了系统性能的最大化.
线程优先级决定了可抢占
中断请求级别决定了可被中断
The kernel component determines when a code sequence runs, according to one of these prioritizing criteria:
Every thread in the system has an associated priority attribute. In general, most threads havevariable priority attributes: they are always preemptible and are scheduled to run round-robin with all other threads that are currently at the same priority level. Some threads havereal-time priority attributes: these time-critical threads run to completion unless they are preempted by a thread that has a higher real-time priority attribute. The Microsoft Windows architecture does not provide an inherently real-time system.
Whatever its priority attribute, any thread in the system can be preempted when hardware interrupts and certain types of software interrupts occur.
The kernel prioritizes hardware and software interrupts so that some kernel-mode code, including most drivers, runs at higher IRQLs, thereby making it have a higher scheduling priority than other threads in the system. The particular IRQL at which a piece of kernel-mode driver code executes is determined by the hardware priority of its underlying device.
Kernel-mode code is always interruptible: an interrupt with a higher IRQL value can occur at any time, thereby causing another piece of kernel-mode code that has a higher system-assigned IRQL to be run immediately on that processor. However, when a piece of code runs at a given IRQL, the kernel masks all interrupt vectors with a lesser or equal IRQL value on the processor.
The lowest IRQL level is called PASSIVE_LEVEL. At this level, no interrupt vectors are masked. Threads generally run at IRQL=PASSIVE_LEVEL. The next higher IRQL levels are for software interrupts. These levels include APC_LEVEL, DISPATCH_LEVEL or, for kernel debugging, WAKE_LEVEL. Device interrupts have still higher IRQL values. The kernel reserves the highest IRQL values for system-critical interrupts, such as those from the system clock or bus errors.
列举了几个不同级别的IRQL.
Some system support routines run at IRQL=PASSIVE_LEVEL, either because they are implemented as pageable code or access pageable data, or because some kernel-mode components set up their own threads.
某些系统例程运行在最低级别的IRQL=PASSIVE_LEVEL上, 纠其原因:
代码是可被分页的
代码访问了可被分页的数据
或者是某些系统模块创建了他们自己的线程(? 这个为什么也是运行在IRQL= PASSIVE_LEVEL的原因, 不是特别清楚)
Similarly, some standard driver routines usually run at IRQL=PASSIVE_LEVEL. However, several standard driver routines run either at IRQL=DISPATCH_LEVEL or, for a lowest-level driver, at device IRQL (also calledDIRQL). For more information about IRQLs, see Managing Hardware Priorities.
最底层的驱动, 很大概率上, 将会运行在DIRQL上.
Every routine in a driver is interruptible. This includes any routine that is running at a higher IRQL than PASSIVE_LEVEL. Any routine that is running at a particular IRQL retains control of the processor only if no interrupt for a higher IRQL occurs while that routine is running.
任何驱动例程都是可被中断的, 包括运行高级别的IRQL上的例程.
Unlike the drivers in some older personal computer operating systems, a Microsoft Windows driver's ISR isnever a large, complex routine that does most of the driver's I/O processing. This is because any driver'sinterrupt service routine (ISR) can be interrupted by another routine (for example, by another driver's ISR) that runs at a higher IRQL. Thus, the driver's ISR does not necessarily retain control of a CPU, uninterrupted, from the beginning of its execution path to the end.
In Windows drivers, an ISR typically saves hardware state information, queues adeferred procedure call (DPC), and then quickly exits. Later, the system dequeues the driver's DPC so that the driver can complete I/O operations at a lower IRQL (DISPATCH_LEVEL). For good overall system performance, all routines that run at high IRQLs must relinquish control of the CPU quickly.
ISR尽快处理该由它处理的事情, 将可以推迟处理的事情交由DPC.
In Windows, all threads have a thread context. This context consists of information that identifies the process that owns the thread, plus other characteristics such as the thread's access rights.
线程与线程上下文的关系
In general, only a highest-level driver is called in the context of the thread that is requesting the driver's current I/O operation. An intermediate-level or lowest-level driver cannever assume that it is executing in the context of the thread that requested its current I/O operation.
最高层的驱动往往运行在发起IO请求的线程环境下.
Consequently, driver routines usually execute in an arbitrary thread context—the context of whatever thread is current when a standard driver routine is called. For performance reasons (to avoid context switches), very few drivers set up their own threads.
驱动例程与任意线程上下文的关系
The Microsoft Windows NT-based operating system is designed to run uniformly on uniprocessor and symmetric multiprocessor (SMP) platforms, and kernel-mode drivers should be designed to do likewise.
In any Windows multiprocessor platform, the following conditions exist:
To run safely on an SMP platform, an operating system must guarantee that code that executes on one processor does not simultaneously access and modify data that another processor is accessing and modifying. For example, if a lowest-level driver's ISR is handling a device interrupt on one processor, it must have exclusive access to device registers or critical, driver-defined data, in case its device interrupts simultaneously on another processor.
SMP上必须保证数据的同步问题.
Furthermore, drivers' I/O operations that are serialized in a uniprocessor machine can be overlapped in an SMP machine. That is, a driver's routine that processes incoming I/O requests can be executing on one processor while another routine that communicates with the device executes concurrently on another processor. Whether kernel-mode drivers are executing on a uniprocessor or symmetric multiprocessor machine, they must synchronize access to any driver-defined data or system-provided resources that are shared among driver routines, and synchronize access to the physical device, if any.
在单处理器上的IO操作是串行化处理的.
而在SMP上, 就存在, 一个CPU在处理IO(运行IO处理的驱动例程), 同时在另外一个CPU上则在处理与设备的通迅工作(运行了另外一段驱动例程).
同样, 无论是单处理器还是SMP, 都必须包括数据同步问题的解决,以及对硬件访问的同步.
The Windows NT kernel component exports a synchronization mechanism, called a spin lock, that drivers can use to protect shared data (or device registers) from simultaneous access by one or more routines that are running concurrently on a symmetric multiprocessor platform. The kernel enforces two policies regarding the use of spin locks:
These policies prevent a driver routine that usually runs at a lower IRQL but currently holds a spin lock from being preempted by a higher-priority driver routine that is trying to acquire the same spin lock. Thus, a deadlock is avoided.
The IRQL that is assigned to a spin lock is generally that of the highest-IRQL routine that can acquire the spin lock.
For example, a lowest-level driver's ISR frequently shares a state area with the driver's DPC routine. The DPC routine calls a driver-supplied critical section routine to access the shared area. The spin lock that protects the shared area has an IRQL equal to the DIRQL at which the device interrupts. As long as the critical-section routine holds the spin lock and accesses the shared area at DIRQL, the ISR cannot be run in either a uniprocessor or SMP machine.
A set of kernel-mode threads can synchronize access to shared data or resources by waiting for one of the kernel's dispatcher objects: an event, mutex, semaphore, timer, or another thread. However, most drivers do not set up their own threads because they have better performance when they avoid thread-context switches. Whenever time-critical kernel-mode support routines and drivers run at IRQL = DISPATCH_LEVEL or at DIRQL, they must use the kernel's spin locks to synchronize access to shared data or resources.
内核同步对象也可以起到内核数据同步的作用.
但是, 为了减少线程环境切换的消耗, 驱动往往避免创建内核线程.
For more information, see Spin Locks, Managing Hardware Priorities, and Kernel Dispatcher Objects.
The I/O manager provides asynchronous I/O support so that the originator of an I/O request (usually a user-mode application but sometimes another driver) can continue executing, rather than wait for its I/O request to be completed. Asynchronous I/O support improves both the overall system throughput and the performance of any code that makes an I/O request.
With asynchronous I/O support, kernel-mode drivers do not necessarily process I/O requests in the same order in which they were sent to the I/O manager. The I/O manager, or a higher-level driver, can reorder I/O requests as they are received. A driver can split a large data transfer request into smaller transfer requests. Moreover, a driver can overlap I/O request processing, particularly in a symmetric multiprocessor platform, as mentioned inMultiprocessor-Safe.
Furthermore, a kernel-mode driver's processing of an individual I/O request is not necessarily serialized. That is, a driver does not necessarily process each IRP to completion before it starts processing the next incoming I/O request.
When a driver receives an IRP, it responds by carrying out as much IRP-specific processing as it can. If the driver supports asynchronous IRP processing, it can send an IRP to the next driver, if necessary, and begin processing the next IRP without waiting for the first one to be completed. The driver can register a "completion routine," which the I/O manager calls when another driver has finished processing an IRP. Drivers provide a status value in the IRP's I/O status block, which other drivers can access to determine the status of an I/O request.
Drivers can maintain state information about their current I/O operations in a special part of their device objects, called adevice extension.
For more information, see Handling IRPs and Input/Output Techniques.