Windows NT 系列 (NT/2000/XP/後續版本) 採用核心/用戶態分離架構,應用程式 (Ring 3) 無法直接執行 IN/OUT 特權指令存取硬體端口。本文完整示範:編寫 WDM 核心驅動 MyPort.sys、動態安裝/啟動服務、用戶態 DLL 封裝 DeviceIoControl 呼叫,實現 CMOS 讀取與 PC Speaker 控制。
用戶態應用 → ReadPortByte/WritePortByte (DLL)
→ DeviceIoControl(hDevice, IOCTL, ...)
→ 核心驅動 MyPort.sys (Ring 0)
→ _inp/_outp (編譯器內建) / 直接 IN/OUT 指令
→ 硬體端口 (0x70/0x71 CMOS, 0x42/0x43/0x61 Speaker)
#define FILE_DEVICE_MYPORT 0x0000F000
#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)
MmAllocateNonCachedMemory,全清零 (允許所有端口)。
void Ke386SetIoAccessMap(int MapNum, IOPM *pIOPM); // 載入 IOPM 到系統表
void Ke386IoSetAccessProcess(PEPROCESS Process, int MapNum); // 將進程關聯到 IOPM
在 DriverEntry 中對當前進程啟用:
Ke386IoSetAccessProcess(PsGetCurrentProcess(), 1);
Ke386SetIoAccessMap(1, pIOPM);
switch (IrpStack->Parameters.DeviceIoControl.IoControlCode) {
case IOCTL_MYPORT_READ_BYTE:
pvIOBuffer[1] = _inp((USHORT)pvIOBuffer[0]); // 輸入緩衝[0]=端口, 輸出緩衝[1]=資料
Irp->IoStatus.Information = 8; break;
case IOCTL_MYPORT_WRITE_BYTE:
_outp((USHORT)pvIOBuffer[0], (UCHAR)pvIOBuffer[1]); break;
}
緩衝模式 METHOD_BUFFERED:輸入/輸出共用 SystemBuffer,簡單安全。
BOOL StartDriver(LPCTSTR lpszDriverPath, LPCTSTR lpszServiceName) {
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
SC_HANDLE hSvc = CreateService(hSCM, lpszServiceName, lpszServiceName,
SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL, lpszDriverPath, NULL, NULL, NULL, NULL, NULL);
if (!hSvc && GetLastError()==ERROR_SERVICE_EXISTS)
hSvc = OpenService(hSCM, lpszServiceName, SERVICE_ALL_ACCESS);
StartService(hSvc, 0, NULL);
CloseServiceHandle(hSvc); CloseServiceHandle(hSCM);
return TRUE;
}
對應 StopDriver 執行 ControlService(SERVICE_CONTROL_STOP) + DeleteService。
HANDLE hMyPort;
BOOL OpenMyPort() {
StartDriver(_T("system32\\drivers\\MyPort.sys"), _T("MyPort"));
hMyPort = CreateFile(_T("\\\\.\\MyPort"), GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
return hMyPort != INVALID_HANDLE_VALUE;
}
BYTE ReadPortByte(WORD port) {
DWORD buf[2] = {port, 0}, dwOut;
DeviceIoControl(hMyPort, IOCTL_MYPORT_READ_BYTE, buf, 8, buf, 8, &dwOut, NULL);
return (BYTE)buf[1];
}
void WritePortByte(WORD port, BYTE data) {
DWORD buf[2] = {port, data}, dwOut;
DeviceIoControl(hMyPort, IOCTL_MYPORT_WRITE_BYTE, buf, 8, buf, 8, &dwOut, NULL);
}
BYTE ReadCmos(BYTE index) {
WritePortByte(0x70, index); // 索引埠 (唯寫)
return ReadPortByte(0x71); // 資料埠
}
void Sound(DWORD freq) { // 8253/8254 Timer Channel 2 + Speaker
if (freq < 20 || freq > 20000) return;
freq = 1193181 / freq;
BYTE b = ReadPortByte(0x61);
if ((b & 3) == 0) { WritePortByte(0x61, b | 3); WritePortByte(0x43, 0xB6); }
WritePortByte(0x42, (BYTE)(freq & 0xFF));
WritePortByte(0x42, (BYTE)(freq >> 8));
}
void NoSound() {
BYTE b = ReadPortByte(0x61);
WritePortByte(0x61, b & 0xFC);
}
測試:迴圈讀取 CMOS 128 bytes;播放 Do-Re-Mi (262/288/320 Hz)。
完整流程:驅動編譯 → 服務安裝 → 設備開啟 → IOCTL → 端口存取。若覺得繁瑣,可將上層封裝為 MyPort.dll (DRV/DLL/APP 三層分離),應用僅呼叫 ReadCmos/Sound 等高階 API。參考 Yariv Kaplan WinIO 專案獲取更成熟的實作 (含實體記憶體存取)。