第 9 章 设备驱动 --- 9.2 一个"老式"驱动模块的实例
本节以 drivers/base/beep/beep.c 为例剖析一个完整的老式(NT 4 风格)设备驱动模块。 9.1 节介绍了 WDM 框架,本节通过 beep.sys(PC 扬声器发声驱动)展示一个 真实可工作 的最小驱动实例,涵盖 DriverEntry 初始化、IRP 派发、同步 I/O、设备对象管理等所有关键概念。beep.c 是 ReactOS 内置的最小驱动之一,约 300 行,是学习 NT 驱动开发的经典教材。
概述
beep.sys 是 PC 扬声器(system speaker)的驱动,对应 Win32 Beep() API。它实现:
IRP_MJ_CREATE:打开扬声器IRP_MJ_CLOSE:关闭扬声器IRP_MJ_WRITE:发声(写特定 IOCTL 或直接 WriteFile)IRP_MJ_DEVICE_CONTROL:控制频率与持续时间IRP_MJ_CLEANUP:清理挂起的请求
它采用 老式驱动 模式(无 PnP),通过 DriverEntry 直接 IoCreateDevice 创建设备对象。
本节内容概览
- 9.2.0 框架图
- 9.2.1 beep 驱动的整体结构
- 9.2.2 DriverEntry 完整剖析
- 9.2.3 设备扩展结构
- 9.2.4 IRP_MJ_CREATE/CLOSE 派发
- 9.2.5 IRP_MJ_WRITE 派发
- 9.2.6 IRP_MJ_DEVICE_CONTROL 派发
- 9.2.7 IRP_MJ_CLEANUP 派发
- 9.2.8 扬声器硬件操作(HAL)
- 9.2.9 总结与代码索引
学习目标
- 能够完整读懂
beep.c的每一行 - 理解老式驱动的初始化与设备对象管理
- 掌握 IOCTL 码的编码与解码
- 理解
IofCallDriver路径与扬声器硬件交互
涉及的内核子系统
| 子系统 | 头文件/源文件 | 核心作用 |
|---|---|---|
| beep 驱动 | drivers/base/beep/beep.c(file:///d:/reactos/drivers/base/beep/beep.c) | 完整驱动实例 |
| 扬声器 HAL | hal/halx86/generic/beep.c(file:///d:/reactos/hal/halx86/generic/beep.c) | HalMakeBeep、HalStopBeep |
| I/O 管理器 | ntoskrnl/io/iomgr/(file:///d:/reactos/ntoskrnl/io/iomgr/) | IRP 派发与设备对象管理 |
| 蜂鸣 API(用户态) | dll/win32/kernel32/client/console.c(file:///d:/reactos/dll/win32/kernel32/client/console.c) | Beep() Win32 API |
| 注册表 | drivers/base/beep/beep_reg.inf(file:///d:/reactos/drivers/base/beep/beep_reg.inf) | 设备注册项 |
| 资源文件 | drivers/base/beep/beep.rc(file:///d:/reactos/drivers/base/beep/beep.rc) | 版本信息 |
9.2.0 框架图
用户进程
|
v
+-------------------+
| Beep(dwFreq, dwDur)| <- Win32 API
+-------------------+
|
v
+-------------------+
| ntdll NtDeviceIoControlFile
+-------------------+
|
v
+-------------------+
| 内核 NtDeviceIoControlFile
| (syscall) |
+-------------------+
|
v IRP_MJ_DEVICE_CONTROL
+--------------------+
| beep.sys |
| DispatchControl |
+--------------------+
|
v (HalMakeBeep / HalStopBeep)
+--------------------+
| HAL HalMakeBeep |
+--------------------+
|
v
+--------------------+
| I/O 端口 0x43, 0x61 | <- 直接操作 8254 计时器
+--------------------+
设备栈(简化):
+-----------------+
| \Device\Beep | <- beep.sys 创建
+-----------------+
9.2.1 beep 驱动的整体结构
beep.c 的代码结构如下(精简版):
c
/* 全局变量 */
BEEP_DEVICE_EXTENSION BeepDeviceExtension;
/* DriverEntry */
NTSTATUS NTAPI DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath);
/* 派发函数 */
static NTSTATUS BeepCreateClose(PDEVICE_OBJECT DeviceObject, PIRP Irp);
static NTSTATUS BeepWrite(PDEVICE_OBJECT DeviceObject, PIRP Irp);
static NTSTATUS BeepDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp);
static NTSTATUS BeepCleanup(PDEVICE_OBJECT DeviceObject, PIRP Irp);
/* 扬声器控制 */
static BOOLEAN BeepStart(ULONG Frequency);
static VOID BeepStop(VOID);
beep.c 的核心数据是 BEEP_DEVICE_EXTENSION(设备扩展)。整个驱动仅使用一个全局扩展而非多设备,这反映其作为简单单例设备的特性。
9.2.2 DriverEntry 完整剖析
c
NTSTATUS NTAPI
DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
PDEVICE_OBJECT DeviceObject;
NTSTATUS Status;
UNICODE_STRING DeviceName;
UNREFERENCED_PARAMETER(RegistryPath);
/* 1. 注册派发函数 */
DriverObject->MajorFunction[IRP_MJ_CREATE] = BeepCreateClose;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = BeepCreateClose;
DriverObject->MajorFunction[IRP_MJ_WRITE] = BeepWrite;
DriverObject->MajorFunction[IRP_MJ_CLEANUP] = BeepCleanup;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = BeepDeviceControl;
/* 2. 创建设备对象 \Device\Beep */
RtlInitUnicodeString(&DeviceName, L"\\Device\\Beep");
Status = IoCreateDevice(DriverObject,
sizeof(BEEP_DEVICE_EXTENSION),
&DeviceName,
FILE_DEVICE_BEEP,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&DeviceObject);
if (!NT_SUCCESS(Status)) return Status;
/* 3. 初始化设备扩展 */
RtlZeroMemory(DeviceObject->DeviceExtension, sizeof(BEEP_DEVICE_EXTENSION));
/* 4. 清除 DO_DEVICE_INITIALIZING 标志 */
DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
}
关键观察
- 不使用 PnP :无
AddDevice、无IRP_MJ_PNP派发------纯老式驱动 - 设备路径硬编码 :
\Device\Beep(不是符号链接) - 设备类型 :
FILE_DEVICE_BEEP(0x00000001) - 共享设备 :
Exclusive = FALSE(多进程可同时打开) - 直接 I/O:默认是缓冲 I/O(write 路径不需要 MDL)
用户态如何访问
用户态通过 Win32 Beep() API 访问:
c
// user32/console.c(伪代码)
BOOL Beep(DWORD dwFreq, DWORD dwDuration)
{
HANDLE hBeep = CreateFileW(L"\\\\.\\Beep", GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, 0, NULL);
if (hBeep == INVALID_HANDLE_VALUE) return FALSE;
BEEP_SET_PARAMETERS BeepParams;
BeepParams.Frequency = dwFreq;
BeepParams.Duration = dwDuration;
DWORD BytesReturned;
DeviceIoControl(hBeep, IOCTL_BEEP_SET, &BeepParams, sizeof(BeepParams),
NULL, 0, &BytesReturned, NULL);
CloseHandle(hBeep);
return TRUE;
}
\\\\.\\Beep 通过 Win32 子系统映射到 \Device\Beep(详见 11.3)。
9.2.3 设备扩展结构
c
typedef struct _BEEP_DEVICE_EXTENSION {
KDPC Dpc; // DPC 对象(用于延迟关闭扬声器)
KTIMER Timer; // 定时器(持续时间到时停止发声)
BOOLEAN BeepState; // 扬声器是否正在发声
} BEEP_DEVICE_EXTENSION, *PBEEP_DEVICE_EXTENSION;
设备扩展的作用
- 设备状态保存:单例设备的运行状态
- DPC 关联 :
BeepStopDpcRoutine在定时器到时停止发声 - 定时器对象 :
BeepTimerRoutine触发 DPC - 无 IOCTL 缓冲:因为是单例,不需要互斥保护
初始化
c
RtlZeroMemory(DeviceObject->DeviceExtension, sizeof(BEEP_DEVICE_EXTENSION));
KeInitializeTimer(&Extension->Timer);
KeInitializeDpc(&Extension->Dpc, BeepStopDpcRoutine, Extension);
KeInitializeDpc 把 DPC 与其回调函数 BeepStopDpcRoutine 关联。
9.2.4 IRP_MJ_CREATE/CLOSE 派发
beep.c 把 CREATE 和 CLOSE 共用同一个派发函数 BeepCreateClose:
c
static NTSTATUS
BeepCreateClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PIO_STACK_LOCATION IoStackLocation = IoGetCurrentIrpStackLocation(Irp);
/* 同步完成 */
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
简化的原因
beep不需要为 CREATE/CLOSE 维护任何状态- 真正的资源管理在 CLEANUP(IRP_MJ_CLEANUP)中
9.2.5 IRP_MJ_WRITE 派发
BeepWrite 处理直接写设备:
c
static NTSTATUS
BeepWrite(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PIO_STACK_LOCATION IoStackLocation = IoGetCurrentIrpStackLocation(Irp);
NTSTATUS Status = STATUS_SUCCESS;
/* WriteFile 风格:直接传频率作为 ULONG */
if (IoStackLocation->Parameters.Write.Length < sizeof(ULONG))
{
Status = STATUS_INVALID_BUFFER_SIZE;
}
else
{
ULONG Frequency = *(PULONG)Irp->AssociatedIrp.SystemBuffer;
BeepStart(Frequency);
}
Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Status;
}
缓冲 I/O
因为 beep.sys 没有设置 DO_DIRECT_IO,I/O 管理器为每次 WriteFile 分配一个内核缓冲,地址在 Irp->AssociatedIrp.SystemBuffer。驱动可安全访问该缓冲。
9.2.6 IRP_MJ_DEVICE_CONTROL 派发
c
static NTSTATUS
BeepDeviceControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PIO_STACK_LOCATION IoStackLocation = IoGetCurrentIrpStackLocation(Irp);
ULONG IoControlCode = IoStackLocation->Parameters.DeviceIoControl.IoControlCode;
PVOID InputBuffer = Irp->AssociatedIrp.SystemBuffer;
ULONG InputBufferLength = IoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
NTSTATUS Status = STATUS_SUCCESS;
LARGE_INTEGER DueTime;
switch (IoControlCode)
{
case IOCTL_BEEP_SET:
if (InputBufferLength < sizeof(BEEP_SET_PARAMETERS))
{
Status = STATUS_INVALID_BUFFER_SIZE;
break;
}
{
PBEEP_SET_PARAMETERS BeepParams = (PBEEP_SET_PARAMETERS)InputBuffer;
if (BeepParams->Frequency == 0)
{
/* 停止发声 */
BeepStop();
BeepDeviceExtension->BeepState = FALSE;
}
else
{
/* 启动发声 */
if (BeepStart(BeepParams->Frequency))
{
BeepDeviceExtension->BeepState = TRUE;
/* 设置定时器 */
DueTime.QuadPart = -(LONGLONG)BeepParams->Duration * 10000LL;
KeSetTimer(&BeepDeviceExtension->Timer, DueTime, &BeepDeviceExtension->Dpc);
}
else
{
Status = STATUS_INVALID_PARAMETER;
}
}
}
break;
default:
Status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Status;
}
IOCTL 码
c
#define IOCTL_BEEP_SET CTL_CODE(FILE_DEVICE_BEEP, 0, METHOD_BUFFERED, FILE_ANY_ACCESS)
IOCTL_BEEP_SET 的编码:
- DeviceType =
FILE_DEVICE_BEEP(1) - Function = 0
- Method =
METHOD_BUFFERED(0) - Access =
FILE_ANY_ACCESS(0)
关键流程
- 解析 IOCTL 码 :
IoStackLocation->Parameters.DeviceIoControl.IoControlCode - 频率 0 = 停止 :调用
BeepStop(),取消定时器 - 频率 > 0 = 发声 :调用
BeepStart(Frequency),设置定时器 - 定时器到时 :
BeepStopDpcRoutine触发,停止发声
BEEP_SET_PARAMETERS
c
typedef struct _BEEP_SET_PARAMETERS {
ULONG Frequency; // 频率(Hz),0 = 停止
ULONG Duration; // 持续时间(毫秒)
} BEEP_SET_PARAMETERS, *PBEEP_SET_PARAMETERS;
9.2.7 IRP_MJ_CLEANUP 派发
c
static NTSTATUS
BeepCleanup(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
/* 取消所有挂起的 IRP */
IoCancelIrp(Irp); /* 实际是取消本线程所有未完成 IRP */
BeepStop();
BeepDeviceExtension->BeepState = FALSE;
KeCancelTimer(&BeepDeviceExtension->Timer);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
CLEANUP 的语义
IRP_MJ_CLEANUP 在 最后一个 handle 关闭 时发送,表示:"进程退出前清理"。驱动应:
- 取消所有挂起的 IRP(用
IoCancelIrp标记为 canceled) - 重置设备状态
- 完成清理 IRP
9.2.8 扬声器硬件操作(HAL)
BeepStart / BeepStop 调用 HAL:
c
static BOOLEAN
BeepStart(IN ULONG Frequency)
{
return HalMakeBeep(Frequency);
}
static VOID
BeepStop(VOID)
{
HalStopBeep();
}
HalMakeBeep
定义于 hal/halx86/generic/beep.c(file:///d:/reactos/hal/halx86/generic/beep.c):
c
BOOLEAN NTAPI
HalMakeBeep(IN ULONG Frequency)
{
if (Frequency < 20 || Frequency > 20000) return FALSE;
/* 设置 8254 计时器 2 的分频 */
USHORT Divider = (USHORT)(PIT_FREQUENCY / Frequency);
WRITE_PORT_UCHAR((PUCHAR)0x43, 0xB6); // 选择计数器 2,方波模式
WRITE_PORT_UCHAR((PUCHAR)0x42, LOBYTE(Divider));
WRITE_PORT_UCHAR((PUCHAR)0x42, HIBYTE(Divider));
/* 启用扬声器 */
UCHAR PortValue = READ_PORT_UCHAR((PUCHAR)0x61);
if ((PortValue & 0x03) != 0x03)
{
WRITE_PORT_UCHAR((PUCHAR)0x61, PortValue | 0x03);
}
return TRUE;
}
HalStopBeep
c
VOID NTAPI
HalStopBeep(VOID)
{
UCHAR PortValue = READ_PORT_UCHAR((PUCHAR)0x61);
WRITE_PORT_UCHAR((PUCHAR)0x61, PortValue & ~0x03);
}
硬件原理
PC 扬声器由 8254 可编程间隔定时器(Programmable Interval Timer, PIT) 驱动:
- 计数器 2(端口 0x42):方波输出
- 分频值 :
PIT_FREQUENCY (1.193182 MHz) / Frequency - 扬声器门控(端口 0x61):位 0(计数器 2 启用)、位 1(扬声器启用)
HalMakeBeep 设置计数器 2 的频率并打开扬声器,HalStopBeep 仅关闭扬声器门控。
9.2.9 BeepStopDpcRoutine
BeepStopDpcRoutine 是 DPC 回调,在定时器到时由 DPC 分发器在 DISPATCH_LEVEL 调用:
c
static VOID NTAPI
BeepStopDpcRoutine(IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2)
{
PBEEP_DEVICE_EXTENSION Extension = (PBEEP_DEVICE_EXTENSION)DeferredContext;
UNREFERENCED_PARAMETER(Dpc);
UNREFERENCED_PARAMETER(SystemArgument1);
UNREFERENCED_PARAMETER(SystemArgument2);
/* 关闭扬声器 */
BeepStop();
Extension->BeepState = FALSE;
}
DPC 的作用
DPC(Deferred Procedure Call)用于在 低优先级(DISPATCH_LEVEL) 上执行工作,避免中断处理在 DIRQL 上停留过久。在 beep 中:
- 定时器到时(DPC_DISPATCH_LEVEL)→ 关闭扬声器
- 不需要长时间操作,所以 DPC 是合适的
- 如果工作量大,应该用工作项(
IoQueueWorkItem)
9.2.A ReactOS beep.c 源代码深度剖析
设备扩展结构的真实实现
在 ReactOS 源代码(drivers/base/beep/beep.c(file:///d:/reactos/drivers/base/beep/beep.c))中,DEVICE_EXTENSION 的实际定义比文档中简化的版本更为复杂:
c
typedef struct _BEEP_DEVICE_EXTENSION
{
LONG ReferenceCount; // 引用计数(支持多进程同时打开)
FAST_MUTEX Mutex; // 快速互斥锁(保护引用计数)
KTIMER Timer; // 定时器对象(控制发声持续时间)
LONG TimerActive; // 定时器活动标志(原子操作)
PVOID SectionHandle; // 内存段句柄(用于分页锁定)
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
这个结构揭示了几个重要的设计决策:
-
引用计数机制 :
ReferenceCount跟踪有多少进程同时打开了\Device\Beep。当第一个进程打开设备时(ReferenceCount从 0 变为 1),驱动会锁定代码段以防止分页;当最后一个进程关闭设备时(ReferenceCount从 1 变为 0),驱动解锁代码段并取消任何活动的定时器。 -
FAST_MUTEX 保护 :
Mutex是一个快速互斥锁,用于保护ReferenceCount的并发修改。与自旋锁不同,快速互斥锁在PASSIVE_LEVEL运行,允许线程休眠等待。这对于IRP_MJ_CREATE和IRP_MJ_CLOSE这类不会在高 IRQL 调用的派发函数是合适的。 -
TimerActive 原子标志 :
TimerActive使用InterlockedIncrement和InterlockedDecrement进行原子操作,确保在 DPC 和派发函数之间对定时器状态的并发访问是安全的。这是一个典型的内核同步模式。 -
SectionHandle 分页控制 :
SectionHandle存储MmLockPagableDataSection返回的句柄。这个机制确保当设备被打开时,驱动的代码和数据不会被分页到磁盘,从而避免在DISPATCH_LEVEL(DPC 上下文)访问分页内存导致的蓝屏。
BeepCreate:首次打开的分页锁定
c
DRIVER_DISPATCH BeepCreate;
NTSTATUS NTAPI
BeepCreate(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PDEVICE_EXTENSION DeviceExtension = DeviceObject->DeviceExtension;
ExAcquireFastMutex(&DeviceExtension->Mutex);
if (++DeviceExtension->ReferenceCount == 1)
{
/* 首次打开,锁定可分页代码段 */
DeviceExtension->SectionHandle = MmLockPagableDataSection(BeepCreate);
}
ExReleaseFastMutex(&DeviceExtension->Mutex);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
MmLockPagableDataSection(BeepCreate) 的作用是锁定包含 BeepCreate 函数的整个可分页代码段。这确保了当 DPC 在 DISPATCH_LEVEL 调用 BeepDPC 时,BeepDPC 的代码一定在物理内存中,不会因为页面错误而崩溃。
这个设计反映了一个重要的内核编程原则:任何可能在 DISPATCH_LEVEL 或更高 IRQL 执行的代码,必须位于非分页内存中,或者在访问前锁定分页内存。
BeepClose:最后关闭的资源清理
c
DRIVER_DISPATCH BeepClose;
NTSTATUS NTAPI
BeepClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PDEVICE_EXTENSION DeviceExtension = DeviceObject->DeviceExtension;
ExAcquireFastMutex(&DeviceExtension->Mutex);
if (!(--DeviceExtension->ReferenceCount))
{
/* 最后一个引用,检查定时器 */
if (DeviceExtension->TimerActive)
{
if (KeCancelTimer(&DeviceExtension->Timer))
{
InterlockedDecrement(&DeviceExtension->TimerActive);
}
}
/* 解锁分页代码段 */
MmUnlockPagableImageSection(DeviceExtension->SectionHandle);
}
ExReleaseFastMutex(&DeviceExtension->Mutex);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
BeepClose 的关键逻辑是:当 ReferenceCount 降为 0 时,驱动必须取消任何活动的定时器并解锁分页段。KeCancelTimer 返回 TRUE 表示定时器成功取消(尚未触发),返回 FALSE 表示定时器已经触发或不在队列中。只有在成功取消时才递减 TimerActive,避免与已经执行的 DPC 产生竞争。
9.2.B StartIo 机制与异步 IRP 处理
BeepDeviceControl:异步排队的决策
c
DRIVER_DISPATCH BeepDeviceControl;
NTSTATUS NTAPI
BeepDeviceControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PIO_STACK_LOCATION Stack;
PBEEP_SET_PARAMETERS BeepParam;
NTSTATUS Status;
Stack = IoGetCurrentIrpStackLocation(Irp);
BeepParam = (PBEEP_SET_PARAMETERS)Irp->AssociatedIrp.SystemBuffer;
if (Stack->Parameters.DeviceIoControl.IoControlCode != IOCTL_BEEP_SET)
{
Status = STATUS_NOT_IMPLEMENTED;
}
else
{
if (Stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(BEEP_SET_PARAMETERS))
{
Status = STATUS_INVALID_PARAMETER;
}
else if ((BeepParam->Frequency != 0) && !(BeepParam->Duration))
{
/* 频率非零但持续时间为零,立即完成 */
Status = STATUS_SUCCESS;
}
else
{
/* 需要排队处理 */
Status = STATUS_PENDING;
}
}
Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = 0;
if (Status == STATUS_PENDING)
{
IoMarkIrpPending(Irp);
IoStartPacket(DeviceObject, Irp, NULL, BeepCancel);
}
else
{
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
return Status;
}
这段代码展示了异步 IRP 处理的标准模式:
- 参数验证:首先检查 IOCTL 码和缓冲区大小
- 快速路径 :对于可以立即完成的操作(如频率非零但持续时间为零),直接返回
STATUS_SUCCESS - 慢速路径 :对于需要排队的操作,返回
STATUS_PENDING,调用IoMarkIrpPending标记 IRP,然后调用IoStartPacket将 IRP 加入设备队列
IoStartPacket 的第四个参数 BeepCancel 是取消例程。如果 IRP 在队列中等待时被取消,I/O 管理器会调用这个取消例程。
BeepStartIo:队列处理的入口
c
DRIVER_STARTIO BeepStartIo;
VOID NTAPI
BeepStartIo(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PDEVICE_EXTENSION DeviceExtension = DeviceObject->DeviceExtension;
KIRQL CancelIrql;
PIO_STACK_LOCATION IoStack;
PBEEP_SET_PARAMETERS BeepParam;
LARGE_INTEGER DueTime;
NTSTATUS Status;
/* 获取取消锁并验证 IRP */
IoAcquireCancelSpinLock(&CancelIrql);
if (!Irp)
{
IoReleaseCancelSpinLock(CancelIrql);
return;
}
/* 移除取消例程 */
(VOID)IoSetCancelRoutine(Irp, NULL);
IoReleaseCancelSpinLock(CancelIrql);
BeepParam = (PBEEP_SET_PARAMETERS)Irp->AssociatedIrp.SystemBuffer;
IoStack = IoGetCurrentIrpStackLocation(Irp);
if (IoStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_BEEP_SET)
{
/* 取消之前的定时器 */
if (DeviceExtension->TimerActive)
{
if (KeCancelTimer(&DeviceExtension->Timer))
{
InterlockedDecrement(&DeviceExtension->TimerActive);
}
}
/* 启动新的蜂鸣 */
if (HalMakeBeep(BeepParam->Frequency))
{
Status = STATUS_SUCCESS;
DueTime.QuadPart = BeepParam->Duration * -10000LL;
InterlockedIncrement(&DeviceExtension->TimerActive);
KeSetTimer(&DeviceExtension->Timer, DueTime, &DeviceObject->Dpc);
}
else
{
Status = STATUS_INVALID_PARAMETER;
}
}
else
{
Status = STATUS_INVALID_PARAMETER;
}
Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = 0;
IoStartNextPacket(DeviceObject, TRUE);
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
BeepStartIo 是 StartIo 机制的核心。当 IoStartPacket 将 IRP 加入设备队列后,如果设备当前没有活动的 IRP,I/O 管理器会立即调用 DriverStartIo(即 BeepStartIo)。
关键步骤:
- 取消锁保护:获取取消自旋锁并检查 IRP 是否为 NULL。如果 IRP 为 NULL,说明 IRP 已被取消,直接返回。
- 移除取消例程 :调用
IoSetCancelRoutine(Irp, NULL)移除取消例程,防止在StartIo执行期间 IRP 被取消。 - 硬件操作 :调用
HalMakeBeep启动蜂鸣器,设置定时器在指定持续时间后停止。 - 启动下一个 :调用
IoStartNextPacket从设备队列中取出下一个 IRP 并再次调用StartIo。
BeepCancel:取消例程的实现
c
DRIVER_CANCEL BeepCancel;
VOID NTAPI
BeepCancel(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
if (Irp == DeviceObject->CurrentIrp)
{
/* 这是当前正在处理的 IRP */
DeviceObject->CurrentIrp = NULL;
IoReleaseCancelSpinLock(Irp->CancelIrql);
IoStartNextPacket(DeviceObject, TRUE);
}
else
{
/* 这是在队列中等待的 IRP */
KeRemoveEntryDeviceQueue(&DeviceObject->DeviceQueue,
&Irp->Tail.Overlay.DeviceQueueEntry);
IoReleaseCancelSpinLock(Irp->CancelIrql);
}
Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
取消例程在取消自旋锁(CancelIrql)持有期间被调用。它必须区分两种情况:
- 当前 IRP :如果 IRP 是
DeviceObject->CurrentIrp(正在StartIo中处理),清除CurrentIrp指针并启动下一个 IRP。 - 队列中的 IRP :如果 IRP 还在设备队列中等待,使用
KeRemoveEntryDeviceQueue将其从队列中移除。
无论哪种情况,最后都以 STATUS_CANCELLED 完成 IRP。
9.2.C BeepDPC:定时器到时的处理
c
VOID NTAPI
BeepDPC(IN PKDPC Dpc,
IN PDEVICE_OBJECT DeviceObject,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2)
{
PDEVICE_EXTENSION DeviceExtension = DeviceObject->DeviceExtension;
UNREFERENCED_PARAMETER(Dpc);
UNREFERENCED_PARAMETER(SystemArgument1);
UNREFERENCED_PARAMETER(SystemArgument2);
/* 停止蜂鸣 */
HalMakeBeep(0);
/* 禁用定时器标志 */
InterlockedDecrement(&DeviceExtension->TimerActive);
}
BeepDPC 是定时器到时时调用的 DPC 例程。它在 DISPATCH_LEVEL 执行,因此必须遵守 DPC 的所有限制:
- 不能访问分页内存(但
HalMakeBeep在非分页内存中,所以安全) - 不能使用等待原语
- 必须快速完成
HalMakeBeep(0) 通过向端口 0x61 写入来关闭扬声器门控信号,停止发声。InterlockedDecrement 原子地递减 TimerActive,确保与 BeepClose 中的取消操作不会竞争。
9.2.D BeepCleanup:进程退出时的清理
c
DRIVER_DISPATCH BeepCleanup;
NTSTATUS NTAPI
BeepCleanup(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
KIRQL OldIrql, CancelIrql;
PKDEVICE_QUEUE_ENTRY Packet;
PIRP CurrentIrp;
KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
IoAcquireCancelSpinLock(&CancelIrql);
CurrentIrp = DeviceObject->CurrentIrp;
DeviceObject->CurrentIrp = NULL;
while (CurrentIrp)
{
(VOID)IoSetCancelRoutine(CurrentIrp, NULL);
CurrentIrp->IoStatus.Status = STATUS_CANCELLED;
CurrentIrp->IoStatus.Information = 0;
IoReleaseCancelSpinLock(CancelIrql);
IoCompleteRequest(CurrentIrp, IO_NO_INCREMENT);
IoAcquireCancelSpinLock(&CancelIrql);
Packet = KeRemoveDeviceQueue(&DeviceObject->DeviceQueue);
if (Packet)
{
CurrentIrp = CONTAINING_RECORD(Packet, IRP, Tail.Overlay.DeviceQueueEntry);
}
else
{
CurrentIrp = NULL;
}
}
IoReleaseCancelSpinLock(CancelIrql);
KeLowerIrql(OldIrql);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
HalMakeBeep(0);
return STATUS_SUCCESS;
}
IRP_MJ_CLEANUP 在进程的最后一个句柄关闭时发送。BeepCleanup 的任务是取消所有挂起的 IRP:
- 提升到 DISPATCH_LEVEL:保护设备队列的访问
- 获取取消锁:确保取消操作的原子性
- 取消当前 IRP :如果
CurrentIrp非空,移除其取消例程并以STATUS_CANCELLED完成 - 遍历队列 :使用
KeRemoveDeviceQueue从设备队列中取出所有等待的 IRP,逐一取消 - 停止蜂鸣 :调用
HalMakeBeep(0)确保扬声器停止
这个清理过程确保了当进程退出时,不会有挂起的 IRP 遗留,也不会继续发声。
9.2.E HalMakeBeep 的硬件操作细节
ReactOS 的 HalMakeBeep 实现(hal/halx86/generic/beep.c(file:///d:/reactos/hal/halx86/generic/beep.c))展示了直接硬件操作的典型模式:
c
BOOLEAN NTAPI HalMakeBeep(IN ULONG Frequency)
{
SYSTEM_CONTROL_PORT_B_REGISTER SystemControl;
TIMER_CONTROL_PORT_REGISTER TimerControl;
ULONG Divider;
BOOLEAN Result = FALSE;
HalpAcquireCmosSpinLock();
/* 关闭定时器输出和扬声器门控 */
SystemControl.Bits = __inbyte(SYSTEM_CONTROL_PORT_B);
SystemControl.SpeakerDataEnable = FALSE;
SystemControl.Timer2GateToSpeaker = FALSE;
__outbyte(SYSTEM_CONTROL_PORT_B, SystemControl.Bits);
if (Frequency)
{
Divider = PIT_FREQUENCY / Frequency;
if (Divider <= 0x10000)
{
/* 编程 PIT 为方波模式(模式 3) */
TimerControl.BcdMode = FALSE;
TimerControl.OperatingMode = PitOperatingMode3;
TimerControl.Channel = PitChannel2;
TimerControl.AccessMode = PitAccessModeLowHigh;
__outbyte(TIMER_CONTROL_PORT, TimerControl.Bits);
/* 写入通道 2 的重载值 */
__outbyte(TIMER_CHANNEL2_DATA_PORT, Divider & 0xFF);
__outbyte(TIMER_CHANNEL2_DATA_PORT, (Divider >> 8) & 0xFF);
/* 重新连接扬声器 */
SystemControl.Bits = __inbyte(SYSTEM_CONTROL_PORT_B);
SystemControl.SpeakerDataEnable = TRUE;
SystemControl.Timer2GateToSpeaker = TRUE;
__outbyte(SYSTEM_CONTROL_PORT_B, SystemControl.Bits);
Result = TRUE;
}
}
HalpReleaseCmosSpinLock();
return Result;
}
8254 PIT 编程细节
PC 扬声器使用 8254 可编程间隔定时器(PIT)的通道 2:
- PIT_FREQUENCY:1.193182 MHz(1193182 Hz),这是 PIT 的输入时钟频率
- 通道 2:专门用于 PC 扬声器(通道 0 用于系统时钟 IRQ0,通道 1 用于 DRAM 刷新)
- 模式 3(方波发生器):产生方波输出,适合驱动扬声器
- 分频值 :
Divider = PIT_FREQUENCY / Frequency,例如 1000 Hz 需要分频值 1193
端口操作
- 0x43(定时器控制端口):写入控制字配置 PIT 的工作模式
- 0x42(通道 2 数据端口):写入分频值(先低字节,后高字节)
- 0x61(系统控制端口 B) :
- 位 0:定时器 2 门控到扬声器
- 位 1:扬声器数据使能
CMOS 自旋锁
HalpAcquireCmosSpinLock 获取 CMOS 访问锁。这是因为端口 0x61 与 CMOS RAM 共享,需要防止并发访问导致的数据损坏。
9.2.F 与 Windows 实现的对比
分页锁定机制
Windows 的 beep.sys 实现与 ReactOS 非常相似,但在细节上有所不同:
- Windows XP/2003 :使用
MmLockPagableDataSection锁定代码段,与 ReactOS 一致 - Windows Vista+:引入了 Driver Verifier 集成,可以在运行时检查驱动的正确性
- Windows 10:beep.sys 仍然存在,但默认被禁用(需要手动启用)
StartIo 队列
Windows 和 ReactOS 都使用 IoStartPacket / IoStartNextPacket 机制管理设备的 IRP 队列。这个机制是旧式驱动(非 WDM)的标准模式。在 WDM 驱动中,通常使用驱动自定义的队列或 WDF 框架的队列。
IOCTL 码定义
IOCTL_BEEP_SET 的定义在 Windows 和 ReactOS 中完全一致:
c
#define IOCTL_BEEP_SET CTL_CODE(FILE_DEVICE_BEEP, 0, METHOD_BUFFERED, FILE_ANY_ACCESS)
这确保了 Windows 应用程序可以使用相同的 IOCTL 码与 ReactOS 的 beep.sys 交互。
9.2.G 调试技巧与常见问题
使用 WinDbg 调试 beep 驱动
!devobj \Device\Beep:查看 beep 设备对象的状态!devstack \Device\Beep:查看设备栈(beep 是单设备,栈很简单)!pool <设备扩展地址>:查看设备扩展的内存分配!timer:列出所有活动的定时器,可以看到 beep 的定时器
常见问题
-
无声输出 :如果
HalMakeBeep返回FALSE,可能是频率超出范围(< 20 Hz 或 > 20000 Hz)或分频值超过 0x10000。 -
持续发声不停止 :如果定时器设置失败或 DPC 未执行,扬声器会持续发声。检查
TimerActive标志和定时器状态。 -
蓝屏 DISPATCH_LEVEL 违规:如果 DPC 例程访问了分页内存(例如未锁定的代码段),会触发蓝屏。确保在设备打开时正确锁定分页段。
-
IRP 泄漏 :如果
BeepDeviceControl返回STATUS_PENDING但未调用IoMarkIrpPending,I/O 管理器会报错。必须确保异步返回前标记 IRP。 -
取消竞争 :如果取消例程和
StartIo同时访问 IRP,可能导致数据损坏。使用取消自旋锁保护所有取消相关操作。
总结
beep.c 展示了一个 最小可工作 的老式驱动,其要点:
- 不实现 PnP :纯老式风格,
DriverEntry中创建设备 - 无符号链接 :
\Device\Beep名称(Win32 子系统通过\\\\.\\Beep访问) - 同步完成:所有派发函数同步完成 IRP,无异步
- 单例设备 :用全局
BEEP_DEVICE_EXTENSION状态 - IOCTL 控制 :
IOCTL_BEEP_SET启动/停止 - HAL 操作硬件 :
HalMakeBeep/HalStopBeep操作 8254 PIT - DPC + Timer :
KeSetTimer持续时间到时触发BeepStopDpcRoutine - CLEANUP 取消 IRP:进程退出时清理
虽然 beep 简单,但包含 NT 驱动的所有核心概念:派发表、设备对象、设备扩展、IRP、IOCTL、同步 I/O、IRP 完成。理解 beep 是学习更复杂驱动(PnP、电源、磁盘)的基础。
本章代码索引
| 文件 | 内容 |
|---|---|
| beep.c(file:///d:/reactos/drivers/base/beep/beep.c) | 完整 beep 驱动(DriverEntry + 派发 + HAL 调用) |
| beep.h(file:///d:/reactos/drivers/base/beep/beep.h) | beep 驱动头文件 |
| beep.rc(file:///d:/reactos/drivers/base/beep/beep.rc) | 版本信息资源 |
| beep_reg.inf(file:///d:/reactos/drivers/base/beep/beep_reg.inf) | 设备注册 INF |
| hal/beep.c(file:///d:/reactos/hal/halx86/generic/beep.c) | HalMakeBeep、HalStopBeep |
| iotypes.h(file:///d:/reactos/sdk/include/xdk/iotypes.h) | IRP_MJ_* 主功能码 |
| dll/win32/kernel32/client/console.c(file:///d:/reactos/dll/win32/kernel32/client/console.c) | Beep() Win32 API |
| ntdll/include/iotypes.h(file:///d:/reactos/dll/ntdll/include/iotypes.h) | IOCTL_BEEP_SET 定义 |
| iotypes.h CTL_CODE(file:///d:/reactos/sdk/include/xdk/iotypes.h) | CTL_CODE 宏 |