2008. 10. 3. 20:38

커널스택 정리

커널스택.bmp
  • 태스크마다 커널 스택이 주어진다.
    • 이것은 커널이 각 태스크 마다의 스택을 한 곳에 쌓아두어 처리하는 게 아니라, 태스크마다 커널을 스택을 두어 처리한 다는 의미로, 커널이 스택 관리를 일일이 하지 않는다는 의미가 된다.
  • 태스크당 2페이지의 크기를 가진다.
    • 32비트 아키텍쳐 -> 8KB (한 페이지당 4KB)
    • 64비트 아키텍쳐 -> 16KB (한 페이지당 8KB)
  • 커널 스택 안에는 무엇이 들어가는가?
    • 유저 프로세스로 복귀할 리턴 주소
    • 유저 프로세스가 커널에 넘기는 매개 변수들
    • 시스템 콜 핸들러에 등록된 함수의 자동 변수와 같은 스택값
    • 인터럽트 핸들러에서 사용하는 스택값
  • 2.6 커널부터 싱글페이지 커널 스택 옵션이 제공
    • 프로세스당 메모리 소모를 더 줄이게 되었다.
    • 시스템이 오래 가동할 수록 물리적으로 연속된 가용한 두 개의 페이지를 찾기가 점점 어려워지기 때문에, 즉 메모리가 단편화될수록 새 태스크를 할당항하기 위해 가상메모리가 들여야 하는 비용은 증가하게 된다.
  • 인터럽트 스택
    • 싱글페이지 사용시 인터럽트 핸들러가 더 이상 들어맞지 않게 될 수 있다.
    • 따라서 추가 옵션을 두어, 인터럽트 스택을 각 프로세스마다 따로둘 수도 있다.
  • 스택 사용법
    • 최대 200~300바이트 이내로 사용하자.
    • 큰 배열이나 구조체와 같은 큰 데이터를 스택에 정적으로 할당하는 것은 매우 위험하다.
    • 커널은 스택을 관리하지 않으므로, 스택이 오버플로우 되는 경우 초과된 데이터는 스택의 경계를 넘어가게 된다. 우선, 이 자리에는 thread_info 구조체가 있다. 스택너머에는 어떤 커널 데이터가 숨어있을지 알 수 없으므로, 스택이 오버플로우 되면 최선의 경우 시스템이 크래쉬(Crash)할 것이고, 최악의 경우 커널의 일부 데이터가 오염될 것이다
2008. 10. 3. 20:37

[다운] WDM스터디 발표자료


 스터디 발표자료 : WDM발표자료_-_서지환(Chapter3).ppt

2008. 10. 3. 20:37

Buffered I/O Mode & Direct I/O Mode


 Buffered I/O Mode & Direct I/O Mode

There are three I/O modes in Windows kernel, they are Buffer, Direct and Neither modes. Now, we'll talk about Buffered I/O, and this article will not involve Neither mode for data transfer if processing under user-thread occupied memory space, it might be dangerous!! If client application is going to read/write data to and from driver, the memory address of data source will not be directly referenced by the underlying driver. System kernel will allocate another data buffer with equivalent size in kernel. All data transferred must be copied into this area before they are to the target place. Usually, you will call ReadFile/WriteFile or fread/fwrite to make read/write request.

 


 (허접 해석~~)윈도우 커널에서의 I/O모드는 3가지로 Buffer, Direct, Neitehr Mode가 있다. 만약 유저Application이 드라이버로 부터 또는 드라이버에 쓰거나 읽는 다고 한다면 데이터 소스의 메모리 주소는 밑단의 드라이버에 의해 바로

접근되지 않는다.

시스템 커널은 커널안에서 동일한 사이즈의 다른 데이터 버퍼를 할당해준다. 모든 데이터 교환은 반드시
쓰고 읽기 위해 목적지에 가기 전에 이 영역에 카피 되어진다. 대개 ReadFile/WriteFile 또는 Fread/Fwrite를 콜한다.


 

 

 

Below code segment demos the workflow in I/O handle for read request. As we can see, the routine that is registered for reading is PsdoDispatchRead in DriverEntry, this member routine will read data out of Driver's internal member - DataBuffer to client application:

 

NTSTATUS
PsdoDispatchRead(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
    PVOID Buf; //Buffer provided by user program
    ULONG BufLen; //Buffer length for user provided buffer
    LONGLONG Offset;//Buffer Offset
    PVOID DataBuf; //Buffer provided by Driver
    ULONG DataLen; //Buffer length for Driver Data Buffer
    ULONG ByteTransferred;
    PIO_STACK_LOCATION p_IO_STK;
    PDEVICE_EXTENSION p_DVCEXT;

    DbgPrint("IRP_MJ_READ : Begin\r\n");
    //Get I/o Stack Location & Device Extension
    p_IO_STK = IoGetCurrentIrpStackLocation(Irp);
    p_DVCEXT = DeviceObject->DeviceExtension;

    //Get User Output Buffer & Length
    BufLen = p_IO_STK->Parameters.Read.Length;
    Offset = p_IO_STK->Parameters.Read.ByteOffset.QuadPart;
    Buf = (PUCHAR)(Irp->AssociatedIrp.SystemBuffer) + Offset;

    //Get Driver Data Buffer & Length
    DataBuf = p_DVCEXT->DataBuffer;
    if (DataBuf == NULL)
        DataLen = 0;
    else
        DataLen = 1024;

    IoAcquireRemoveLock(&p_DVCEXT->RemoveLock, Irp);

    DbgPrint("Output Buffer Length : %d\r\n", BufLen);
    DbgPrint("Driver Data Length : %d\r\n", DataLen);
    //
    if (BufLen <= DataLen) {
        ByteTransferred = BufLen;
    } else {
        ByteTransferred = DataLen;
    }

    RtlCopyMemory(
        Buf, DataBuf,
        ByteTransferred);

    IoReleaseRemoveLock(&p_DVCEXT->RemoveLock, Irp);
    CompleteRequest(Irp, STATUS_SUCCESS, ByteTransferred);

    DbgPrint("IRP_MJ_READ : End\r\n");
    return STATUS_SUCCESS;
}

 

Below code segment demos the possible task items in workflow that can support the normal I/O requests to write data from application to driver.

 

NTSTATUS
PsdoDispatchWrite(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
    PVOID Buf; //Buffer provided by user program
    ULONG BufLen; //Buffer length for user provided buffer
    LONGLONG Offset;//Buffer Offset
    PVOID DataBuf; //Buffer provided by Driver
    ULONG DataLen; //Buffer length for Driver Data Buffer
    ULONG ByteTransferred;
    PIO_STACK_LOCATION p_IO_STK;
    PDEVICE_EXTENSION p_DVCEXT;
    NTSTATUS status;

    DbgPrint("IRP_MJ_WRITE : Begin\r\n");

    //Get I/o Stack Location & Device Extension
    p_IO_STK = IoGetCurrentIrpStackLocation(Irp);
    p_DVCEXT = DeviceObject->DeviceExtension;

    //Get User Input Buffer & Length
    BufLen = p_IO_STK->Parameters.Write.Length;
    Offset = p_IO_STK->Parameters.Read.ByteOffset.QuadPart;
    Buf = (PUCHAR)(Irp->AssociatedIrp.SystemBuffer) + Offset;

    //Get Driver Data Buffer & Length
    DataBuf = p_DVCEXT->DataBuffer;
    DataLen = 1024;

    IoAcquireRemoveLock(&p_DVCEXT->RemoveLock, Irp);

    DbgPrint("Input Buffer Length : %d\r\n", BufLen);
    DbgPrint("Driver Data Length : %d\r\n", DataLen);

    if (BufLen <= DataLen) {
        ByteTransferred = BufLen;
    } else {
        ByteTransferred = DataLen;
    }

    ByteTransferred = BufLen;
        RtlZeroMemory(
        p_DVCEXT->DataBuffer,
        1024);

    RtlCopyMemory(
        DataBuf,
        Buf,
        ByteTransferred);

    IoReleaseRemoveLock(&p_DVCEXT->RemoveLock, Irp);
    CompleteRequest(Irp, STATUS_SUCCESS, ByteTransferred);

    DbgPrint("IRP_MJ_WRITE : End\r\n");
    return STATUS_SUCCESS;
}

 

 

In DDK, some MMXxx routines are provided to help you to get MDL that maps to physical address of user-provided buffer.


 즉 MDL(메모리 설명자 리스트)는 User-Provided Data Buffer의 데이터를 Buffer I/O처럼 복사하지 않고 물리주소

를 직접 제공해 줌으로써 직접적인 주기억장치에 Access가 가능하게 해주는 것이다.   ->(Direct I/O)

그리고 Direct I/O방식은 보통 대량의 데이터를 전송하는 DMA드라이버에만 사용된다. 


 

Below code segment contains the statements that can support data reading under Direct I/O mode. It is achieved by Mmxxx routine, please read it carefully, and you can also find the full code in the zip file. The most important MmXxx you will use in this mode should be - MmGetSystemAddressForMdlSafe, it can obtain the MDL that references the physical address of user-buffer.

NTSTATUS
PsdoDispatchRead(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
    PVOID Buf; //Buffer provided by user program
    ULONG BufLen; //Buffer length for user provided buffer
    ULONG Offset;//Buffer Offset
    PVOID DataBuf; //Buffer provided by Driver
    ULONG DataLen; //Buffer length for Driver Data Buffer
    ULONG ByteTransferred;
    PIO_STACK_LOCATION p_IO_STK;
    PDEVICE_EXTENSION p_DVCEXT;

    DbgPrint("IRP_MJ_READ : Begin\r\n");
    //Get I/o Stack Location & Device Extension
    p_IO_STK = IoGetCurrentIrpStackLocation(Irp);
    p_DVCEXT = DeviceObject->DeviceExtension;

    //Get User Output Buffer & Length 
    Buf = MmGetSystemAddressForMdlSafe(
        Irp->MdlAddress, HighPagePriority);

    if (Buf == NULL) {
        DbgPrint("Can't get Virtual Address from MDL\r\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    BufLen = MmGetMdlByteCount(Irp->MdlAddress);
    Offset = MmGetMdlByteOffset(Irp->MdlAddress);

    //Get Driver Data Buffer & Length
    DataBuf = p_DVCEXT->DataBuffer;
    if (DataBuf == NULL)
        DataLen = 0;
    else
        DataLen = 1024;

    IoAcquireRemoveLock(&p_DVCEXT->RemoveLock, Irp);

    DbgPrint("Output Buffer Length : %d\r\n", BufLen);
    DbgPrint("Offset for Buffer in the Memory Page: %d\r\n", Offset);
    DbgPrint("Driver Data Length : %d\r\n", DataLen);
    //
    if (BufLen <= DataLen) {
        ByteTransferred = BufLen; 
    } else {
        ByteTransferred = DataLen;
    }

    RtlCopyMemory(
        Buf, 
        DataBuf, 
        ByteTransferred);

    IoReleaseRemoveLock(&p_DVCEXT->RemoveLock, Irp);
    CompleteRequest(Irp, STATUS_SUCCESS, ByteTransferred);

    DbgPrint("IRP_MJ_READ : End\r\n");
    return STATUS_SUCCESS;
}

Below code segment demos the possible workflow to write data from user application to driver:

NTSTATUS
PsdoDispatchWrite(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
    PVOID Buf; //Buffer provided by user program
    ULONG BufLen; //Buffer length for user provided buffer
    ULONG Offset;//Buffer Offset
    PVOID DataBuf; //Buffer provided by Driver
    ULONG DataLen; //Buffer length for Driver Data Buffer
    ULONG ByteTransferred;
    PIO_STACK_LOCATION p_IO_STK;
    PDEVICE_EXTENSION p_DVCEXT;
    NTSTATUS status;

    DbgPrint("IRP_MJ_WRITE : Begin\r\n");

    //Get I/o Stack Location & Device Extension
    p_IO_STK = IoGetCurrentIrpStackLocation(Irp);
    p_DVCEXT = DeviceObject->DeviceExtension;

    //Get User Input Buffer & Length 
    Buf = MmGetSystemAddressForMdlSafe(
        Irp->MdlAddress, HighPagePriority);

    if (Buf == NULL) {
        DbgPrint("Can't get Virtual Address from MDL\r\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    BufLen = MmGetMdlByteCount(Irp->MdlAddress);
    Offset = MmGetMdlByteOffset(Irp->MdlAddress);

    //Get Driver Data Buffer & Length
    DataBuf = p_DVCEXT->DataBuffer;
    DataLen = 1024;

    IoAcquireRemoveLock(&p_DVCEXT->RemoveLock, Irp);

    DbgPrint("Input Buffer Length : %d\r\n", BufLen);
    DbgPrint("Offset for Buffer in the Memory Page: %d\r\n", Offset);
    DbgPrint("Driver Data Length : %d\r\n", DataLen);

    if (BufLen <= DataLen) {
        ByteTransferred = BufLen; 
    } else {
        ByteTransferred = DataLen;
    }

    ByteTransferred = BufLen;
    RtlZeroMemory(
        p_DVCEXT->DataBuffer,
        1024);

    RtlCopyMemory(
        DataBuf,
        Buf, 
        ByteTransferred);

    IoReleaseRemoveLock(&p_DVCEXT->RemoveLock, Irp);
    CompleteRequest(Irp, STATUS_SUCCESS, ByteTransferred);

    DbgPrint("IRP_MJ_WRITE : End\r\n");
    return STATUS_SUCCESS;
}

출처 : http://www.codeproject.com/system/WDM_Driver_development.asp?df=100&forumid=121122&exp=0&select=1066254


2008. 10. 3. 20:36

DMA에 대한 고찰


우선 DMA에 대한 자세한 설명은 생략하겠다.

DMA는 시스템버스를 통하여 데이터 교환이 이루어지는데 그것은 곧 CPU와 같은 버스를 이용하겠다는

의미이다. 하드웨어쪽에서 데이터 교환을 위해 CPU에게 버스 사용권에 대해 허락요청을 하게되고

상황에 따라 grant를 해주면 DMA를 수행하게 되는 것이다.

( 언밀히 말하자만 CPU가 허락을 해주는 것은 아니다. 버스를 관장하는 아비터가 grant를 해준다.)

 그리고 요청한다고 무조건 허락해주는 것은 아니다. 어떠한 환경이냐에 따라 다르겠지만

멀티미디어 장비라고 했을 때 어떠한 영상을 뿌려주는 것이 주 임무이기 때문에 cpu에서의 일보다는

하드웨어의 요청이 먼저일수 있을 것이다. 즉 어떠한 임베디드 환경이냐에 따라 요청에 따른 grant여부가

달라진다는 것이다.

그리고 보통 데이터교환이 모두 이루어 지고나서 사용권을 넘겨 준다고 오해할 수도 있는데. 그것도

잘못된 것이라 할 수 있다. 각 임베디드 시스템에 따라 데이터를 주고 받는 단위가 있는데 그 단위로

한꺼번에 계속하여 다 받는 것이 아니라 한 단위를 받고 나서 다시 grant요청을 한다고 보면 되겠다.

 

내 생각을 좀 더 더하자면 만약 두 단위씩 주고 받는다고 한다면 차라리 그 두 단위의 양을 단위로 하면

두번 받지 않고 한번에 받을 수 있지 않을까 생각되는데 또 어떻게 보면 어떠한 환경에 의해서

한꺼번에 한단위로 읽어 오는게 무리가 있을 수도 있지 않을까 생각된다.

 
ps. 현철형~~짱!!!ㅋㅋ

2008. 10. 3. 20:35

DISPATCH_LEVEL, PASSIVE_LEVEL

 

User mode 의 모든 thread와 kernel mode의 대부분의 thread는
PASSIVE_LEVEL즉 가장 낮은 IRQL level에서 실행되면서
스케쥴링 됩니다 하드웨어 인터럽트발생시 ISR은 DIRQL level에서
실행되며, 커널모드에서는 dispatcher object를 사용하여 thread를
DISPATCH_LEVEL로 강제로 올릴수 있습니다

정리하면 PASSIVE_LEVEL은 아무런 일이 발생하지 않은
가장 낮은 level 로 스케쥴링되는 level 이고
DISPATCH_LEVEL은 PASSIVE_LEVEL로 실행되고 있는
다른 모든 THREAD를 block시키고 우선권을 갖고 실행되는
소프트웨어 인터럽트라고 할수 있는 level입니다


2008. 10. 3. 20:35

CALLBACK


1.gif 

콜백의 기본 개념

일반적인 의미에서 콜백이란 호출자(Caller)가 피호출자(Callee)를 호출하는 것이 아니라 피호출자가 호출자를 호출하는 것을 말한다(<그림 1> 참조). 콜백이 많이 사용되는 전형적인 예는 WIN32 API이다. 대개의 경우 응용 프로그램이 WIN32 API를 호출하는 거서이 일반적이지만 때때로 윈도우 시스템이 응용 프로그램을 호출해야 할 때가 있다. 이때 응용 프로그램은 콜백 함수를 윈도우 시스템에게 알려주고 어떤 조건이 만족되면 윈도우 시스템이 콜백 함수를 호출해 준다. WIN32 API의 EnumWindow, SetTimer 함수 등이 콜백을 사용하며 윈도우 프로시저(window procedure) 역시 콜백 개념을 사용한다.

 2.gif

 

<그림 2>는 전형적인 콜백 메커니즘을 보여주고 있다. 콜백 메커니즘의 순서로써

(1) 호출자는 콜백 메서드의 참조(함수 포인터)를 매개 변수로 하여 피호출 메서드를 호출한다.

(2) 피호출 메서드는 매개 변수로 전달된 콜백 메서드에 대한 참조를 필드와 같은 곳에 기록해 둔다.

(3) 이제 콜백을 수행할 어떤 조건(이 조건은 다양할 수 있다)이 만족되면 ...

(4) 기록해 둔 콜백 메서드 참조를 이용하여 콜백 메서드를 호출하게 된다.

물론 모든 콜백이 <그림 2>와 같은 순서를 따르는 것은 아니지만 많은 경우 이와 같은 시나리오를 따르는 것이 일반적이다. 콜백을 수행할 조건을 만족하는지 지속적으로 검사하는 과정이 필요하기 때문에 별도의 스레드를 이용하는 경우가 대부분이며, 콜백 메서드를 호출하는 스레드 역시 조건을 검사하는 스레드이기 때문에

 콜백 메서드는 서로 다른 스레드에서 호출되는 것이 일반적이다. 이렇게 다중 스레드를 사용하기 때문에 비동기(asynchronous) 작업을 수행할 때 비동기 작업이 완료되었음을 알리기 위한 방법으로 콜백 메커니즘이 많이 사용되곤 한다.

 

참조: http://imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=29268

 

 

OS에 의해 직접 불려지는 함수를 CALLBACK 함수라고 한다. 예를 들면 WndProc 콜백 함수는 EVENT 발생시 OS에 의해 직접 호출되며, 이때 Message 종류와 내용을 Parameter로 전달 받는다.

 

Callback routines are defined by the user program, instructing the graphic system to call a specific function when a specific event occurs. Normally they are used to automatically redraw a window when its content has changed.

 

CALLBACK 함수는 메시지를 처리한다. 윈도우즈 자체가 메시지 기반으로 작동하기 때문에 우리가 아무것도 안하면 혼자  놀고 있다가 버튼이라도 하나 누르면 그 버튼 ID에 따른 처리를 해준다. 이런 처리를 해주는 부분이 CALLBACK 함수이다.

 

CALLBACK으로 선언된 함수는 윈도우 운영체제에서 특정한 사건이 발생될 때 운영체제로부터 호출돼는 함수이다. 예로 Timer가 있다. Timer는 사용자가 프로그램에서 얼마 간격으로 SetTimer를 설정하면 운영체제가 그 시간이 돼면 OnTimer 함수를 호출한다.

 

 

CALLBACK 함수는 불려지는 쪽에서 부르는 쪽의 데이터를 참조하거나 핸들링 하는 함수이다. 윈도우는 data segment와 code segment가 분리되어 있다. code는 공유를 하나 data는 공유 하지 않는다. 공유하게 되면 심각한 문제가 발생할 것이다. 그래서 나온 것이 CALLBACK 함수이다.

 

예를 들어 설명하면,
'A'라는 DLL이 'B'라는 DLL을 불러 쓰고 'A'라는 DLL에는 동적으로 메모리를 할당 받을 변수와 메모리를 할당하고 해제하는 함수가 있다고 가정하고 'B'라는 DLL에서 이 변수에 동적으로 메모리를 할당하고 처리하려 한다고 할 때 'B'에서 'A'의 변수를 인자로 받아 메모리를 할당하고 처리하는 것은 무의미 하거나 위험하다. 앞에서 말했듯이 'A'와 'B'는 서로 독립된 DATA SEGMENT를 갖기 때문이다. 그렇다면 B는 A의 메모리 할당 함수를 이용해서 A의 변수에 메모리를 할당하고 해제 해야한다. 여기에서 A에서 B로 넘겨주는 메모리 할당 함수를 CALLBACK 함수라고한다. 다시말해서 불려지는 쪽에서 부르는 쪽의 DATA를 참조하기 위한 교량 역할을 하는 것이 CALLBACK 함수인 것이다. WNDPROC가 CALLBACK 함수인 이유도 여기에 있다. 애플리케이션에 의해 불려지는 운영체제에서 디폴트 윈도우 프로시져의 위치에 부르는 함수이기 때문이다.