본문 바로가기
개발/Windows

커널드라이버 Buffered I/O 처리 하기 (MFC, CFile)

by lucidmaj7 2020. 1. 3.
728x90
반응형

드라이버에서 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;

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함수로 출력되는 것을 볼 수 있다.

결론

참고

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

728x90
반응형

댓글