드라이버에서 I/O처리 하기
드라이버는 File Object와 매칭되며 File I/O operation을 처리 할 수 있다고 했다. 정말 그렇다면 WriteFile
, ReadFile
함수를 통해 드라이버에 접근이 가능해야한다. 실제로 그런가?를 확인하기 위해 MFC의 CFile 클래스를 통해 접근해 보았다. CFile
클래스는 MFC에서 File에 대한 추상화 클래스이며 CreateFile
,WriteFile
, ReadFile
등 파일 Operation을 수행 할 수 있다. 또한 예외처리도 되어있어 Win32 api를 통한 파일 접근 보다 좀 더 간편하다.
IRP 처리
IRP는 I/O Request Packet의 약자로 I/O Manager에서 IRP라는 Packet통해 Device에게 I/O요청을 하게 된다. 각 기능 별로 Major Function, MinorFunctino으로 구분되어 그에 맞는 Dispatch Handler를 구분할 수 있다.
https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-major-function-codes
커널 드라이버에서 User Application Data에 접근하는 방법
MSDN에 따르면 커널드라이버에서 I/O요청으로 전달된 Data Buffer에 접근하는 방법은 3가지 방법이 존재한다.
- Buffered I/O : User Application의 버퍼와 크기가 같은 NonPaged System Buffer를 만들어 복사하고, 커널드라이버에서는 이 버퍼에 접근하여 Access한다. 말 그대로 Kernel과 User Application 사이에 버퍼를 두고 통신한다.
- Direct I/O : User Application의 버퍼를 MDL을 전달받아 Kernel드라이버에서 접근하는 방식이다. 때문에 엑세스 하는 동안 Memory가 잠기기된다.
- Neither Buffered Nor Direct I/O: User Application의 가상메모리의 시작 주소를 Kernel 드라이버에게 전달하여 접근하게 된다. 이때 드라이버는 User Application 쓰레드와 같아야 한다??
(뭔소린지 모르겠다.)
Buffered I/O
위에서 설명한 3가지 방법 중 가장 많이, 흔히 쓰일 수 있는 방법은 Buffered I/O이다. Bufferd I/O의 특징은 다음과 같다.
- 대화식, 느린 장치를 서비스하는 드라이버, 한번에 적은 양의 데이터를 주고 받는 드라이버가 사용 할 수 있다.
- Direct I/O방식에 비해 물리적인 페이지를 잠글 필요가 없기때문에 메모리 사용효율이 좋다.
- 비디오, 키보드, 마우스 등 장치가 이러한 방식을 사용한다.
Buffered I/O를 사용하기 위해서는 Kernel 드라이버 초기화시DO_BUFFERED_IO
플래그를 설정해 주어야 한다.// Set Buffered I/O pDeviceObject->Flags|= DO_BUFFERED_IO;
IRP_MJ_WRITE
처리 구현
예제로 Write Operation에 대응하는 IRP_MJ_WRITE
를 구현해 보았다. 우선 DriverEntry
에서 IRP_MJ_WRITE
의 핸들러를 등록한다.
pDriverObject->DriverUnload = DriverUnload;
pDriverObject->MajorFunction[IRP_MJ_CREATE] = CreateDispatch;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = CloseDispatch;
pDriverObject->MajorFunction[IRP_MJ_READ] = IoControlDispatch;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = IoControlDispatch;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoControlDispatch;
IRP_MJ_CREATE
, IRP_MJ_CLOSE
이외의 IRP는 IoControlDispatch
에서 처리하도록 구현하였다. 아래는 IoControlDispatch
구현 내용이다.
NTSTATUS
IoControlDispatch(
PDEVICE_OBJECT pDeviceObject,
PIRP pIrp
)
{
UNREFERENCED_PARAMETER(pDeviceObject);
UNREFERENCED_PARAMETER(pIrp);
PWCHAR buf = NULL;
PIO_STACK_LOCATION irpSp;
pIrp->IoStatus.Information = 0;
irpSp = IoGetCurrentIrpStackLocation(pIrp);
/*
How to read buffer from user application on IRP_MJ_WRITE?
BUFFERED I/O
*/
if (irpSp->MajorFunction == IRP_MJ_WRITE)
{
if (irpSp->Parameters.Write.Length)
{
buf = ExAllocatePoolWithTag(NonPagedPool, irpSp->Parameters.Write.Length, ALLOC_TAG);
if (buf)
{
//copy buffer
RtlCopyMemory(buf, pIrp->AssociatedIrp.SystemBuffer, irpSp->Parameters.Write.Length);
//print
KdPrint(("IoControlDispatch IRP_MJ_WRITE MajorFunction:%x Length:%lu %S<==\n", irpSp->MajorFunction, irpSp->Parameters.Write.Length, buf));
ExFreePool(buf);
}
//return written bytes
pIrp->IoStatus.Information = irpSp->Parameters.Write.Length;
}
}
KdPrint(("IoControlDispatch %x <==\n", irpSp->MajorFunction));
pIrp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
IRP 디스패치 루틴으로 들어온 IRP구조체에서 IoGetCurrentIrpStackLocation
를 이용해 현재 IRP 스택포인터를 가져온다. 가져온 스택포인터에서 MajorFunction필드를 참조하여 분기한다. User에서 전달한 Buffer Size는 irpSp->Parameters.Write.Length
를 통해 알아 낼 수 있고, 버퍼된 버퍼의 주소는 pIrp->AssociatedIrp.SystemBuffer
를 통해 접근 가능하다.
User Application
CFile을 이용한 장치 Open
winapi의 CreateFile을 이용하지 않고 MFC의 CFile클래스를 이용해 아래와 같이Open을 해보았다.
if (m_DeviceFile.m_hFile == CFile::hFileNull)
{
if (m_DeviceFile.Open(_T("\\\\.\\WDMHello"), CFile::modeReadWrite | CFile::typeBinary))
{
AfxMessageBox(_T("Open!!Device"));
}
else
AfxMessageBox(_T("Open!!fail Device"));
}
else
{
AfxMessageBox(_T("Close"));
m_DeviceFile.Close();
}
실행 해보니 아무 이상없이 열린다.
CFile::Write함수 호출
드라이버를 파일 함수를 통해 Open하였다면 Write함수를 호출하여 Write를 Operation을 수행 해보겠다. User Application의 Edit컨트롤에 텍스트를 Write함수를 통해 드라이버 핸들에 Write해보았다.
if (m_DeviceFile.m_hFile == CFile::hFileNull)
{
return;
}
if (m_ctrlWriteEdit.GetWindowTextLengthW())
{
CString str;
m_ctrlWriteEdit.GetWindowText(str);
DWORD dwAlloc = str.GetLength() + 1;
PWCHAR pBuf = new WCHAR[dwAlloc];
ZeroMemory(pBuf, sizeof(WCHAR) * dwAlloc);
wcsncpy_s(pBuf, dwAlloc, str.GetBuffer(), _TRUNCATE);
//pBuf[dwAlloc-1] = 0;
m_DeviceFile.Write(pBuf, sizeof(WCHAR) * dwAlloc);
delete[] pBuf;
AfxMessageBox(_T("write!!!"));
}
실행 결과 드라이버의 입력한 문자열이 KdPrint함수로 출력되는 것을 볼 수 있다.
결론
- Buffered I/O를 통해 IRP_MJ_WRITE IRP를 처리할 수 있다.
- 드라이버와 File I/O함수를 통해 통신 할 수 있다.
- MFC CFile로도 드라이버 통신이 가능하다.
- 전체 소스: https://github.com/lucidmaj7/WDM-BufferedIO-Example
참고
https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/methods-for-accessing-data-buffers
https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/using-buffered-i-o
'개발 > Windows' 카테고리의 다른 글
드라이버와 통신하기 IRP_MJ_DEVICE_CONTROL (0) | 2020.01.07 |
---|---|
Zw함수의 접두사의 의미는? (0) | 2020.01.03 |
장치 드라이버와 커널 오브젝트 (Device Driver & Kernel Object) (0) | 2019.12.30 |
장치 드라이버와 파일의 관계? (0) | 2019.12.30 |
드라이버란 무엇일까? (3) | 2019.12.27 |
댓글