MFC: DeviceIoControl 通过API访问设备驱动程序
转载:http://m.blog.csdn.net/article/details?id=21602051
DeviceIoControl的其实和ReadFile和WriteFile是一样的, 不过这个功能更强, 一次交互能够输入数据, 也可以输出数据.
DeviceIoControl内部创建的IRP是IRP_MJ_DEVICE_CONTROL类型的IRP, 然后操作系统会将这个IRP转发给驱动程序的分发函数中. 就是类似Win32下面的发送自定义消息了.. 但是又有点区别, 如果发送自定义消息, 如果跨进程, 那是不可以传递指针的, 但是和驱动那就无所谓了, 可以传递一块缓冲区过去。
利用该接口函数向指定的设备驱动发送正确的控制码及数据,然后分析它的响应,就可以达到我们的目的。
BOOL DeviceIoControl(HANDLE hDevice, // 设备句柄DWORD dwIoControlCode, // 控制码LPVOID lpInBuffer, // 输入数据缓冲区指针DWORD nInBufferSize, // 输入数据缓冲区长度LPVOID lpOutBuffer, // 输出数据缓冲区指针DWORD nOutBufferSize, // 输出数据缓冲区长度LPDWORD lpBytesReturned, // 输出数据实际长度单元长度LPOVERLAPPED lpOverlapped // 重叠操作结构指针
);
HANDLE CreateFile(LPCTSTR lpFileName, // 文件名/设备路径DWORD dwDesiredAccess, // 访问方式DWORD dwShareMode, // 共享方式LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全描述符指针DWORD dwCreationDisposition, // 创建方式DWORD dwFlagsAndAttributes, // 文件属性及标志HANDLE hTemplateFile // 模板文件的句柄
);
参数 | 类型及说明 |
hDevice | Long,设备句柄 |
dwIoControlCode | Long,带有 FSCTL_ 前缀的常数。参考设备控制选项的部分列表 |
lpInBuffer | Any,具体取决于dwIoControlCode参数。参考设备控制选项的部分列表 |
nInBufferSize | Long,输入缓冲区的长度 |
lpOutBuffer | Any,具体取决于dwIoControlCode参数。参考设备控制选项的部分列表 |
nOutBufferSize | Long,输出缓冲区的长度 |
lpBytesReturned | Long,实际装载到输出缓冲区的字节数量 |
lpOverlapped | OVERLAPPED,这个结构用于重叠操作。针对同步操作,请用ByVal As Long传递零值 |
软盘驱动器 | A:, B: |
硬盘逻辑分区 | C:, D:, E:, ... |
物理驱动器 | PHYSICALDRIVEx |
CD-ROM, DVD/ROM | CDROMx |
磁带机 | TAPEx |
typedef struct _GUID
{unsigned long Data1;unsigned short Data2;unsigned short Data3;unsigned char Data4[8];
} GUID, *PGUID;
const GUID DiskClassGuid = {0x53f56307L, 0xb6bf, 0x11d0, {0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b)};
DEFINE_GUID(DiskClassGuid, 0x53f56307L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
// SetupDiGetInterfaceDeviceDetail所需要的输出长度,定义足够大
#define INTERFACE_DETAIL_SIZE (1024)// 根据GUID获得设备路径
// lpGuid: GUID指针
// pszDevicePath: 设备路径指针的指针
// 返回: 成功得到的设备路径个数,可能不止1个
int GetDevicePath(LPGUID lpGuid, LPTSTR* pszDevicePath)
{HDEVINFO hDevInfoSet;SP_DEVICE_INTERFACE_DATA ifdata;PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail;int nCount;BOOL bResult;// 取得一个该GUID相关的设备信息集句柄hDevInfoSet = ::SetupDiGetClassDevs(lpGuid, // class GUID NULL, // 无关键字 NULL, // 不指定父窗口句柄 DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); // 目前存在的设备// 失败...if (hDevInfoSet == INVALID_HANDLE_VALUE){return 0;}// 申请设备接口数据空间pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)::GlobalAlloc(LMEM_ZEROINIT, INTERFACE_DETAIL_SIZE);pDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);nCount = 0;bResult = TRUE;// 设备序号=0,1,2... 逐一测试设备接口,到失败为止while (bResult){ifdata.cbSize = sizeof(ifdata);// 枚举符合该GUID的设备接口bResult = ::SetupDiEnumDeviceInterfaces(hDevInfoSet, // 设备信息集句柄NULL, // 不需额外的设备描述lpGuid, // GUID(ULONG)nCount, // 设备信息集里的设备序号&ifdata); // 设备接口信息if (bResult){// 取得该设备接口的细节(设备路径)bResult = SetupDiGetInterfaceDeviceDetail(hDevInfoSet, // 设备信息集句柄&ifdata, // 设备接口信息pDetail, // 设备接口细节(设备路径)INTERFACE_DETAIL_SIZE, // 输出缓冲区大小NULL, // 不需计算输出缓冲区大小(直接用设定值)NULL); // 不需额外的设备描述if (bResult){// 复制设备路径到输出缓冲区::strcpy(pszDevicePath[nCount], pDetail->DevicePath);// 调整计数值nCount++;}}}// 释放设备接口数据空间::GlobalFree(pDetail);// 关闭设备信息集句柄::SetupDiDestroyDeviceInfoList(hDevInfoSet);return nCount;
}
int i;char* szDevicePath[MAX_DEVICE]; // 设备路径// 分配需要的空间for (i = 0; i < MAX_DEVICE; i++){szDevicePath[i] = new char[256];}// 取设备路径nDevice = ::GetDevicePath((LPGUID)&DiskClassGuid, szDevicePath);// 逐一获取设备信息for (i = 0; i < nDevice; i++){// 打开设备hDevice = ::OpenDevice(szDevicePath[i]);if (hDevice != INVALID_HANDLE_VALUE){... ... // I/O操作::CloseHandle(hDevice);}}// 释放空间for (i = 0; i & lt; MAX_DEVICE; i++){delete []szDevicePath[i];}
// IOCTL控制码
#define IOCTL_STORAGE_QUERY_PROPERTY CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS)
// 存储设备的总线类型
typedef enum _STORAGE_BUS_TYPE {BusTypeUnknown = 0x00,BusTypeScsi,BusTypeAtapi,BusTypeAta,BusType1394,BusTypeSsa,BusTypeFibre,BusTypeUsb,BusTypeRAID,BusTypeMaxReserved = 0x7F
} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;// 查询存储设备属性的类型
typedef enum _STORAGE_QUERY_TYPE {PropertyStandardQuery = 0, // 读取描述PropertyExistsQuery, // 测试是否支持PropertyMaskQuery, // 读取指定的描述PropertyQueryMaxDefined // 验证数据
} STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE;// 查询存储设备还是适配器属性
typedef enum _STORAGE_PROPERTY_ID {StorageDeviceProperty = 0, // 查询设备属性StorageAdapterProperty // 查询适配器属性
} STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID;// 查询属性输入的数据结构
typedef struct _STORAGE_PROPERTY_QUERY {STORAGE_PROPERTY_ID PropertyId; // 设备/适配器STORAGE_QUERY_TYPE QueryType; // 查询类型 UCHAR AdditionalParameters[1]; // 额外的数据(仅定义了象征性的1个字节)
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;// 查询属性输出的数据结构
typedef struct _STORAGE_DEVICE_DESCRIPTOR {ULONG Version; // 版本ULONG Size; // 结构大小UCHAR DeviceType; // 设备类型UCHAR DeviceTypeModifier; // SCSI-2额外的设备类型BOOLEAN RemovableMedia; // 是否可移动BOOLEAN CommandQueueing; // 是否支持命令队列ULONG VendorIdOffset; // 厂家设定值的偏移ULONG ProductIdOffset; // 产品ID的偏移ULONG ProductRevisionOffset; // 产品版本的偏移ULONG SerialNumberOffset; // 序列号的偏移STORAGE_BUS_TYPE BusType; // 总线类型ULONG RawPropertiesLength; // 额外的属性数据长度UCHAR RawDeviceProperties[1]; // 额外的属性数据(仅定义了象征性的1个字节)
} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR;// 取设备属性信息
// hDevice -- 设备句柄
// pDevDesc -- 输出的设备描述和属性信息缓冲区指针(包含连接在一起的两部分)
BOOL GetDriveProperty(HANDLE hDevice, PSTORAGE_DEVICE_DESCRIPTOR pDevDesc)
{STORAGE_PROPERTY_QUERY Query; // 查询输入参数DWORD dwOutBytes; // IOCTL输出数据长度BOOL bResult; // IOCTL返回值// 指定查询方式Query.PropertyId = StorageDeviceProperty;Query.QueryType = PropertyStandardQuery;// 用IOCTL_STORAGE_QUERY_PROPERTY取设备属性信息bResult = ::DeviceIoControl(hDevice, // 设备句柄IOCTL_STORAGE_QUERY_PROPERTY, // 取设备属性信息&Query, sizeof(STORAGE_PROPERTY_QUERY), // 输入数据缓冲区pDevDesc, pDevDesc->Size, // 输出数据缓冲区&dwOutBytes, // 输出数据长度(LPOVERLAPPED)NULL); // 用同步I/O return bResult;
}
#include <ntddk.h>
#include "MyPort.h"// 设备类型定义
// 0-32767被Microsoft占用,用户自定义可用32768-65535
#define FILE_DEVICE_MYPORT 0x0000f000// I/O控制码定义
// 0-2047被Microsoft占用,用户自定义可用2048-4095
#define MYPORT_IOCTL_BASE 0xf00#define IOCTL_MYPORT_READ_BYTE CTL_CODE(FILE_DEVICE_MYPORT, MYPORT_IOCTL_BASE, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_MYPORT_WRITE_BYTE CTL_CODE(FILE_DEVICE_MYPORT, MYPORT_IOCTL_BASE+1, METHOD_BUFFERED, FILE_ANY_ACCESS)// IOPM是65536个端口的位屏蔽矩阵,包含8192字节(8192 x 8 = 65536)
// 0 bit: 允许应用程序访问对应端口
// 1 bit: 禁止应用程序访问对应端口#define IOPM_SIZE 8192typedef UCHAR IOPM[IOPM_SIZE];IOPM *pIOPM = NULL;// 设备名(要求以UNICODE表示)
const WCHAR NameBuffer[] = L"\\Device\\MyPort";
const WCHAR DOSNameBuffer[] = L"\\DosDevices\\MyPort";// 这是两个在ntoskrnl.exe中的未见文档的服务例程
// 没有现成的已经说明它们原型的头文件,我们自己声明
void Ke386SetIoAccessMap(int, IOPM *);
void Ke386IoSetAccessProcess(PEPROCESS, int);// 函数原型预先说明
NTSTATUS MyPortDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
void MyPortUnload(IN PDRIVER_OBJECT DriverObject);// 驱动程序入口,由系统自动调用,就像WIN32应用程序的WinMain
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{PDEVICE_OBJECT deviceObject;NTSTATUS status;UNICODE_STRING uniNameString, uniDOSString;// 为IOPM分配内存pIOPM = MmAllocateNonCachedMemory(sizeof(IOPM));if (pIOPM == 0){return STATUS_INSUFFICIENT_RESOURCES;}// IOPM全部初始化为0(允许访问所有端口)RtlZeroMemory(pIOPM, sizeof(IOPM));// 将IOPM加载到当前进程Ke386IoSetAccessProcess(PsGetCurrentProcess(), 1);Ke386SetIoAccessMap(1, pIOPM);// 指定驱动名字RtlInitUnicodeString(&uniNameString, NameBuffer);RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);// 创建设备status = IoCreateDevice(DriverObject, 0,&uniNameString,FILE_DEVICE_MYPORT,0, FALSE, &deviceObject);if (!NT_SUCCESS(status)){return status;}// 创建WIN32应用程序需要的符号连接status = IoCreateSymbolicLink (&uniDOSString, &uniNameString);if (!NT_SUCCESS(status)){return status;}// 指定驱动程序有关操作的模块入口(函数指针)// 涉及以下两个模块:MyPortDispatch和MyPortUnloadDriverObject->MajorFunction[IRP_MJ_CREATE] =DriverObject->MajorFunction[IRP_MJ_CLOSE] =DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyPortDispatch;DriverObject->DriverUnload = MyPortUnload;return STATUS_SUCCESS;
}// IRP处理模块
NTSTATUS MyPortDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{PIO_STACK_LOCATION IrpStack;ULONG dwInputBufferLength;ULONG dwOutputBufferLength;ULONG dwIoControlCode;PULONG pvIOBuffer;NTSTATUS ntStatus;// 填充几个默认值Irp->IoStatus.Status = STATUS_SUCCESS; // 返回状态Irp->IoStatus.Information = 0; // 输出长度IrpStack = IoGetCurrentIrpStackLocation(Irp);// Get the pointer to the input/output buffer and it's length// 输入输出共用的缓冲区// 因为我们在IOCTL中指定了METHOD_BUFFERED,pvIOBuffer = Irp->AssociatedIrp.SystemBuffer;switch (IrpStack->MajorFunction){case IRP_MJ_CREATE: // 与WIN32应用程序中的CreateFile对应break;case IRP_MJ_CLOSE: // 与WIN32应用程序中的CloseHandle对应break;case IRP_MJ_DEVICE_CONTROL: // 与WIN32应用程序中的DeviceIoControl对应dwIoControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;switch (dwIoControlCode){// 我们约定,缓冲区共两个DWORD,第一个DWORD为端口,第二个DWORD为数据// 一般做法是专门定义一个结构,此处简单化处理了case IOCTL_MYPORT_READ_BYTE: // 从端口读字节pvIOBuffer[1] = _inp(pvIOBuffer[0]);Irp->IoStatus.Information = 8; // 输出长度为8break;case IOCTL_MYPORT_WRITE_BYTE: // 写字节到端口_outp(pvIOBuffer[0], pvIOBuffer[1]);break;default: // 不支持的IOCTLIrp->IoStatus.Status = STATUS_INVALID_PARAMETER;}}ntStatus = Irp->IoStatus.Status;IoCompleteRequest (Irp, IO_NO_INCREMENT);return ntStatus;
}// 删除驱动
void MyPortUnload(IN PDRIVER_OBJECT DriverObject)
{UNICODE_STRING uniDOSString;if(pIOPM){// 释放IOPM占用的空间MmFreeNonCachedMemory(pIOPM, sizeof(IOPM));}RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);// 删除符号连接和设备IoDeleteSymbolicLink (&uniDOSString);IoDeleteDevice(DriverObject->DeviceObject);
}
// 安装驱动并启动服务
// lpszDriverPath: 驱动程序路径
// lpszServiceName: 服务名
BOOL StartDriver(LPCTSTR lpszDriverPath, LPCTSTR lpszServiceName)
{SC_HANDLE hSCManager; // 服务控制管理器句柄SC_HANDLE hService; // 服务句柄DWORD dwLastError; // 错误码BOOL bResult = FALSE; // 返回值// 打开服务控制管理器hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);if (hSCManager){// 创建服务hService = CreateService(hSCManager,lpszServiceName,lpszServiceName,SERVICE_ALL_ACCESS,SERVICE_KERNEL_DRIVER,SERVICE_DEMAND_START,SERVICE_ERROR_NORMAL,lpszDriverPath,NULL,NULL,NULL,NULL,NULL);if (hService == NULL){if (::GetLastError() == ERROR_SERVICE_EXISTS){hService = ::OpenService(hSCManager, lpszServiceName, SERVICE_ALL_ACCESS);}}if (hService){// 启动服务bResult = StartService(hService, 0, NULL);// 关闭服务句柄CloseServiceHandle(hService);}// 关闭服务控制管理器句柄CloseServiceHandle(hSCManager);}return bResult;
}// 停止服务并卸下驱动
// lpszServiceName: 服务名
BOOL StopDriver(LPCTSTR lpszServiceName)
{SC_HANDLE hSCManager; // 服务控制管理器句柄SC_HANDLE hService; // 服务句柄BOOL bResult; // 返回值SERVICE_STATUS ServiceStatus;bResult = FALSE;// 打开服务控制管理器hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);if (hSCManager){// 打开服务hService = OpenService(hSCManager, lpszServiceName, SERVICE_ALL_ACCESS);if (hService){// 停止服务bResult = ControlService(hService, SERVICE_CONTROL_STOP, &ServiceStatus);// 删除服务bResult = bResult && DeleteService(hService);// 关闭服务句柄CloseServiceHandle(hService);}// 关闭服务控制管理器句柄CloseServiceHandle(hSCManager);}return bResult;
}
// 全局的设备句柄
HANDLE hMyPort;// 打开设备
// lpszDevicePath: 设备的路径
HANDLE OpenDevice(LPCTSTR lpszDevicePath)
{HANDLE hDevice;// 打开设备hDevice = ::CreateFile(lpszDevicePath, // 设备路径GENERIC_READ | GENERIC_WRITE, // 读写方式FILE_SHARE_READ | FILE_SHARE_WRITE, // 共享方式NULL, // 默认的安全描述符OPEN_EXISTING, // 创建方式0, // 不需设置文件属性NULL); // 不需参照模板文件return hDevice;
}// 打开端口驱动
BOOL OpenMyPort()
{BOOL bResult;// 设备名为"MyPort",驱动程序位于Windows的"system32\drivers"目录中bResult = StartDriver("system32\\drivers\\MyPort.sys", "MyPort");// 设备路径为"\\.\MyPort"if (bResult){hMyPort = OpenDevice("\\\\.\\MyPort");}return (bResult && (hMyPort != INVALID_HANDLE_VALUE));
}// 关闭端口驱动
BOOL CloseMyPort()
{return (CloseHandle(hMyPort) && StopDriver("MyPort"));
}// 从指定端口读一个字节
// port: 端口
BYTE ReadPortByte(WORD port)
{DWORD buf[2]; // 输入输出缓冲区 DWORD dwOutBytes; // IOCTL输出数据长度buf[0] = port; // 第一个DWORD是端口
// buf[1] = 0; // 第二个DWORD是数据// 用IOCTL_MYPORT_READ_BYTE读端口::DeviceIoControl(hMyPort, // 设备句柄IOCTL_MYPORT_READ_BYTE, // 取设备属性信息buf, sizeof(buf), // 输入数据缓冲区buf, sizeof(buf), // 输出数据缓冲区&dwOutBytes, // 输出数据长度(LPOVERLAPPED)NULL); // 用同步I/O return (BYTE)buf[1];
}
// 将一个字节写到指定端口
// port: 端口
// data: 字节数据
void WritePortByte(WORD port, BYTE data)
{DWORD buf[2]; // 输入输出缓冲区 DWORD dwOutBytes; // IOCTL输出数据长度buf[0] = port; // 第一个DWORD是端口buf[1] = data; // 第二个DWORD是数据// 用IOCTL_MYPORT_WRITE_BYTE写端口::DeviceIoControl(hMyPort, // 设备句柄IOCTL_MYPORT_WRITE_BYTE, // 取设备属性信息buf, sizeof(buf), // 输入数据缓冲区buf, sizeof(buf), // 输出数据缓冲区&dwOutBytes, // 输出数据长度(LPOVERLAPPED)NULL); // 用同步I/O
}
// 0x70是CMOS索引端口(只写)
// 0x71是CMOS数据端口
BYTE ReadCmos(BYTE index)
{BYTE data;::WritePortByte(0x70, index);data = ::ReadPortByte(0x71);return data;
}// 0x61是speaker控制端口
// 0x43是8253/8254定时器控制端口
// 0x42是8253/8254定时器通道2的端口
void Sound(DWORD freq)
{BYTE data;if ((freq >= 20) && (freq <= 20000)){freq = 1193181 / freq;data = ::ReadPortByte(0x61);if ((data & 3) == 0){::WritePortByte(0x61, data | 3);::WritePortByte(0x43, 0xb6);}::WritePortByte(0x42, (BYTE)(freq % 256));::WritePortByte(0x42, (BYTE)(freq / 256));}
}void NoSound(void)
{BYTE data;data = ::ReadPortByte(0x61);::WritePortByte(0x61, data & 0xfc);
}
// 以下读出CMOS 128个字节for (int i = 0; i < 128; i++){BYTE data = ::ReadCmos(i);... ...}// 以下用C调演奏“多-来-米”// 1 = 262 Hz::Sound(262);::Sleep(200);::NoSound();// 2 = 288 Hz::Sound(288);::Sleep(200);::NoSound();// 3 = 320 Hz::Sound(320);::Sleep(200);::NoSound();
MFC: DeviceIoControl 通过API访问设备驱动程序相关推荐
- 实战DeviceIoControl 之中的一个:通过API訪问设备驱动程序
Q 在NT/2000/XP中,我想用VC编写应用程序訪问硬件设备,如获取磁盘參数.读写绝对扇区数据.測试光驱实际速度等,该从哪里入手呢? A 在NT/2000/XP中,应用程序能够通过API函数Dev ...
- usb扫描枪驱动下载 wince_wince下USB设备驱动程序
随着USB设备的不断增加,我们这些开发人员也就多了对USB设备进行驱动程序开发的工作.但是对于很多初学者来说,存在以下三个困难: 一是对WinCE的驱动程序结构了解得太少,没办法得心应手的专注于驱动程 ...
- windows 编写的硬件驱动_哪个是PXI硬件合适的设备驱动程序?VISA还是IVI?
理想的测试系统可以认为是其组成部分的总和,包括测量和激励硬件,信号切换,电缆以及可能 的大规模互连系统,UUT电源,外部PC或嵌入式控制器,操作系统(OS)和编程环境.每个部件根据诸如UUT测试参数, ...
- LINUX设备驱动程序的注意事项(两)建设和执行模块
<一>:设置測试系统 首先准备好一个内核源代码树,构造一个新内核,然后安装到自己的系统中. <二>:HelloWorld模块 #inclu ...
- linux设备驱动程序中的阻塞机制
阻塞与非阻塞是设备访问的两种方式.在写阻塞与非阻塞的驱动程序时,经常用到等待队列. 一.阻塞与非阻塞 阻塞调用是指调用结果返回之前,当前线程会被挂起,函数只有在得到结果之后才会返回. 非阻塞指不能立刻 ...
- Windows驱动——利用WinDriver开发PCI设备驱动程序
摘要 WinDriver是Jungo公司出版的一个设备驱动程序开发组件,它可以大大加速PCI设备驱动程序的开发.作者在实际的项目中采用了WinDriver来开发设备驱动程序,取得了相当好的运行效果.从 ...
- ibm aix_IBM AIX设备驱动程序开发
在传统的UNIX®中,术语" 设备"指的是硬件组件,例如磁盘驱动器,磁带驱动器,打印机,键盘,伪设备(例如控制台,错误特殊文件和null特殊文件)等等. 在AIX中,这些设备称为内 ...
- Linux 设备驱动程序(三)
系列文章目录 Linux 内核设计与实现 深入理解 Linux 内核(一) 深入理解 Linux 内核(二) Linux 设备驱动程序(一) Linux 设备驱动程序(二) Linux 设备驱动程序( ...
- Linux 设备驱动程序(二)
系列文章目录 Linux 内核设计与实现 深入理解 Linux 内核(一) 深入理解 Linux 内核(二) Linux 设备驱动程序(一) Linux 设备驱动程序(二) Linux 设备驱动程序( ...
最新文章
- 深度学习必备:随机梯度下降(SGD)优化算法及可视化
- python爬虫教程 百度云-如何使用python编程【python爬虫教程 百度云】
- 【专题】多角度深入解析开放原子开源基金会
- HDMI高清光端机产品特点及应用场合介绍
- css hover变成手_web前端入门到实战:彻底掌握css动画「transition」
- java调用Linux mahout,Mahout算法调用展示平台2.1
- 基于 HTML5 WebGL 的民航客机飞行监控系统
- c语言 ++ --运算符_了解C ++中的删除运算符
- visio保存后公式变形_固体力学中的变形分析
- 基于高频18000-3M3技术的RFID智能书架方案
- 全国OA系统下载地址(全)
- php呼叫平台,php – Twilio呼叫转发
- linux sort命令优化提高速度
- Android 判断是否安装应用宝,并跳到应用中去
- Extreme DAX中文第1章 商业智能中的DAX
- Xms Xmx PermSize MaxPermSize的含义
- ArcGIS空间数据查询与处理
- 3c计算机通讯消费类电子产品,什么是3c产品?3c产品具体包括哪些
- jquery $.fn $.fx是什么意思
- (转)Android Jetpack Compose 最全上手指南
热门文章
- 小科普:什么是屏幕分辨率
- ⊱静心抄经,是对抗这个浮躁社会的最好武器
- 关机状态下启动微型计算机叫做,湖南省计算机等级考试题库
- 刷脸支付商用之火真正出现燎原的苗头
- python 英语分词_自然语言处理 | NLTK英文分词尝试
- 我的雷电游戏(重力感应控制)
- python能不能互动执行_细思恐极,插上U盘就开始执行Python代码
- 上海天文台实习的一个项目-根据卫星数据绘制南极星空分布图
- Java项目:医院管理系统(java+Springboot+ssm+mysql+maven)
- [JZOJ6355] 【NOIP2019模拟】普