Beep蜂鸣器驱动程序分析

<span style="font-size:14px;">typedef struct _BEEP_DEVICE_EXTENSION
{
	LONG ReferenceCount;
	FAST_MUTEX Mutex;
	KTIMER Timer;
	LONG TimerActive;
	PVOID SectionHandle;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;</span>

上面是蜂鸣器设备的扩展结构体,其中ReferenceCount是设备的引用计数,而FAST_MUTEX用于互斥访问这个计数器,Timer用于蜂鸣器定时,而TimerActive则计数多少个定时器被激活。SectionHandle则保存整个系统的内存句柄。

根据惯例,驱动程序的入口处有一个创建设备的过程,不过在一般的创建之后需要额外加上一个对设备扩展体的初始化:

	DeviceExtension->ReferenceCount = 0;
	DeviceExtension->TimerActive = FALSE;
	IoInitializeDpcRequest(DeviceObject, (PIO_DPC_ROUTINE)BeepDPC);
	KeInitializeTimer(&DeviceExtension->Timer);
	ExInitializeFastMutex(&DeviceExtension->Mutex);
	MmPageEntireDriver(DriverEntry);

最后一个函数将整个内存给换页出去,因为创建设备之后整个设备并不一定是需要的。所以可以暂时将这段代码给换页出去,当然这里的DriverEntry是指一个内存地址——函数本身具备地址的含义(具体可以参考之前的博客http://blog.csdn.net/dayenglish/article/details/21345093)。

当然,既然在初始化整个设备和驱动程序的时候讲内存给换页出去了,那么就需要在某一个阶段将整个驱动锁定到内存当中——也就是讲内存给设置为非换页内存。而一个设备的访问起点往往是CreateXxx调用,当控制权转移到驱动程序的时候,相对应的是IRP_MJ_CREATE控制处理函数。创建过程很简单,直接通过快速互斥体将整个设备的计数给保护起来,然后将驱动代码给锁定到非换页内存当中,最后返回成功。

	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;

因为蜂鸣器是一个比较简单的设备,所以没有过多的读写操作。所有的控制都发送给IRP_MJ_DEVICE_CONTROL控制码处理函数。这里的处理仅仅是做一些参数检验,如果蜂鸣器的频率和持续时间均为0,那么返回STATUS_PENDING,并且将这个IRP放入到队列当中处理。否则。直接调用IoCompleteRequest例程完成IO请求

	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);
	}

当控制权从IoStartPacket转入到驱动程序的SatrtIo函数的时候,表明整个系统已经开始处理相应的IRP请求了,所以第一步就是防止相应的IRP请求被取消。如果定时器还没有激发,就将定时器给取消。否则直接发出一个蜂鸣器响应,然后根据延迟时间设置定时器在定时器超时的时候进行蜂鸣器响应。当然,系统超时使用了DPC例程,当所有的处理都结束的时候,系统处理排队当中的下一个IO请求。并且返回成功。

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 * -10000;
			InterlockedIncrement(&DeviceExtension->TimerActive);
			KeSetTimer(&DeviceExtension->Timer, DueTime, &DeviceObject->Dpc);
		}
	}

由于上面试分析IO请求没有被取消的过程,如果IO请求被取消的时候,系统会转向调用在IoStartPacket函数当中设置的取消例程,也就是BeepCancel例程,这个历程的执行分为两步,第一步检验,正在被处理的是否就是将要被取消的IRP,如果是则直接调用处理下一个IRP。否则将需要取消的IRQP从队列当中移除。

	if (Irp == DeviceObject->CurrentIrp)
	{
		DeviceObject->CurrentIrp = NULL;

		IoReleaseCancelSpinLock(Irp->CancelIrql);
		IoStartNextPacket(DeviceObject, TRUE);
	}
	else
	{
		KeRemoveEntryDeviceQueue(&DeviceObject->DeviceQueue,
			&Irp->Tail.Overlay.DeviceQueueEntry);
		IoReleaseCancelSpinLock(Irp->CancelIrql);
	}

而作为定时器的DPC处理过程也很简单,仅仅发出蜂鸣,然后将定时器技术递减1

	PDEVICE_EXTENSION DeviceExtension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
	HalMakeBeep(0);
	InterlockedDecrement(&DeviceExtension->TimerActive);
蜂鸣器的关闭操作和创建是一个相对的反过程,还有一个是蜂鸣器的卸载。蜂鸣器的卸载主要是看定时器操作有没有结束,如果没有结束,则强制将定时器给取消,然后调用IoDeleteDevice删除设备对象。较为复杂的是一个蜂鸣器的清理例程,蜂鸣器通过设备的当前 IRP循环整个 IRP队列,将所有的 IRP都处理掉,当然返回值肯定不能是 STATUS_SUCCESS,而应该是 STATUS_CANCELLED,因为并没有实质性的蜂鸣处理。

	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);
完整的代码参见资源页面


你可能感兴趣的:(windows,内核)