上一节已经说明了如何通过InterfaceGUID监测USB设备的状态,本节我们讨论的主题是如何通过设备名称获取通讯上下文的句柄。

应用程序获取设备句柄的一般步骤如下:


#define LIBUSB_DEVICE_NAME "\\\\.\\libusb0-"
bool CDemoMainFrame::OnBtnOpenDevice( void* param )
{ 
     _snprintf(chName, sizeof(chName) - 1,"%s%04d",LIBUSB_DEVICE_NAME, index);
     HANDLE dev = ::CreateFile( chName, 
			0,
			0,
			NULL,
			OPEN_EXISTING,
			FILE_FLAG_OVERLAPPED,
			NULL);
}

通过格式化获得的字符串为"\\\\.\\libusb0-0",这个名称的来历是什么呢?这还又得回到驱动程序AddDevice说起。AddDevice函数中包含以下操作:

status = IoCreateSymbolicLink(&symbolic_link_name, &nt_device_name);

这里有两个名称,链接名称与设备名称。他们的关系就好比快捷方式与App的关系,设备名称保存于/Device目录,且只能内核本身访问,应用程序访问设备必须通过链接名称,链接名称保存于/DosDevice目录。

这个symbolic_link_name就是我们关注的重点了。在内核驱动中symbolic_link_name的赋值可能是像这样:"\\DosDevices\\libusb0-0",貌似跟上层软件看到的名称有点不太一样!确实,windows系统规定,驱动程序中,链接名称的写法有以下两种:

L"//??//libusb0-0" --->/??/libusb0-0
L"//DosDevices//libusb0-0"--->/DosDevices/libusb0-0

而应用程序中的链接名称写法应该这样:

L".//libusb0-0"-->//./libusb0-0

由于C语言规定‘/’前面必须用/做转译字符,所以左边为C语言代码中的写法,右边为实际的链接名称,??是DosDevices的符号链接名;稍后我们会看到,在Iomanager内部系统会自动将//.转换为/??

CreateFile调用成功,返回一个内核对象的句柄Handle,通过句柄就可以实现ReadFile、WriteFile的操作了,CreateFile调用过程如下:

android usb hal开发_usb

1, 调用ntdll.dll中的Stub函数NtCreateFile,ntdll.dll相当于User到Kernel之间的中转站,而NtCreateFile通过INT指令或SysEnter指令调用系统中的XX号服务,Kernel.exe根据系统调用号选择对应的函数调用,实际上,内核中也有一个NtCreateFile的函数,通过调用号就可以将用户层与内核层的函数联系起来。

2, Kernel层,NtCreateFile通过IoCreateFile实现,IoCreateFile最核心的函数就属ObOpenObjectByName了。通过字面意思,这个函数要做的事情就是通过XX名称打开一个对象,所以需要一个体现对象名称的参数ObjectAttributes,还有访问权限DesiredAccess等作为输入,输出句柄LocalHandle。

3, 个人认为ObpLookupObjectName更名为ObpLookupObjectByName应该更能体现这个函数的真实意图,而且这个函数的可读性很差,绕了半天也没完全弄明白是怎么做的。我只知道在逐节点解析的过程当中,如果碰到了非对象目录,就要调用他自身注册的ParseRoution函数,对于一般的设备对象,这个被注册的函数就是IopParseFile,进入IopParseDevice的地界,就是我们熟悉的操作了,准备一个IRP,通过IoCallDriver调用下层驱动函数,函数返回,Wait信号量。因为我们调用的是CreateFile,所以下层驱动的响应动作也必然是OnMajorCreate。

4, 从ObpLookupObjectName返回之后,还有一个操作也是需要重点注意的:ObpCreateHandle,这个函数输入对象Object,输出Handle句柄,一切看起来都是那么美好,只不过真相总是超出我们的想象。事实上,这里输入的Object并不是我们的设备对象,而是一个文件对象FileObject,通过FileObject->DeviceObject可以访问真正的设备对象,这是操作系统需要操心的问题,我们获取文件对象的句柄Handle就可以实现对设备的读写操作了。


IRP_MJ_CREATE对驱动程序的影响不是很重要,因为这个操作的目的是获取句柄而已,我们调用如下函数,完成这个请求即可...

NTSTATUS complete_irp(IRP *irp, NTSTATUS status, ULONG info)
{
    irp->IoStatus.Status = status;
	irp->IoStatus.Information = info;
	IoCompleteRequest( irp, IO_NO_INCREMENT );
	return status;
}

关于CreateFile的工作过程就先讲到这里,这里留下了很多的疑问没有解决,比如IoParseDevice的工作细节、IoCompleteRequest的工作过程等,因为这两个函数牵扯到的知识点与内容太多了,为了分清主次轻重,后面我准备再起一个专题来说明。



参考资料: 

1, <<Windows内核情景分析>>

2, <<Windows内核原理与实现>>