디바이스 드라이버9 - 블로킹IO

kenGwon·2024년 2월 28일
0

[Embedded Linux] BSP

목록 보기
32/36

리눅스가 인터럽트를 처리하는 방법


리눅스는 그 어떠한 경우에도 운영체제가 하나의 프로세스에 10ms의 실행시간을 보장한다. 그래서 그 10ms가 다 되지 않았는데 인터럽트가 발생한다면 인터럽트는 처리되지 않는다. 리눅스에서 인터럽트는 스케줄러가 다음 프로세스를 실행하기 전에 그 사이에 인터럽트 서비스 루틴을 넣어서 실행하게 된다.
반면에, RTOS는 프로세스가 실행되는 동안에도 그 시간을 완전히 보장하지 않고 인터럽트가 들어오면 그 즉시 인터럽트를 처리하는 구조로 되어있는 것이다.


목표

CPU를 100%나 점유하고 있는 현재의 구조를 개선해야 한다.
read()로 key를 읽었는데 값이 0이면 안눌렸다는거니까 sleep상태로 보내겠다는 것이다.
이러한 슬립기능 지원은 디바이스 드라이버 단에서 지원하는 것이다.
어플리케이션에서 무언가 슬립을 위한 로직을 구성할 필요는 없다.
어플리케이션에서는 디바이스 파일을 열 때 default 옵션인 blocking모드로 open()하기만 하면 되는 것이다.

디바이스 드라이버는 read()나 write()에 대한 file_operation을 실행할 때 해당 함수 안에서 file 구조체 포인터의 f_flag멤버변수를 조사하여 디바이스파일이 블로킹 모드로 열렸는지 조사하고 그것을 바탕으로 프로세스를 sleep()시킬지 말지 결정하게 된다.

대기 큐 생성

...
#include <linux/wait.h> // blockio
...
DECLARE_WAIT_QUEUE_HEAD(WaitQueue_Read); // blockio
// 재울 때와 깨울 때 WaitQueue_Read 변수를 사용할 것이다.
...

프로세스를 재우는 코드(read()안에서..)

static ssize_t ledkey_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
    //char kbuff;
    keyData* pKeyData = (keyData*)filp->private_data;

    //kbuff = gpioKeyGet(); // 이렇게 폴링으로 하지 않고 인터럽트를 적용해야하는 부분
    //kbuff = pKeyData->keyNumber; // 전역변수 keyNumber

    /*프로세스를 슬립모드로 보내는 경우*/
    if (pKeyData->keyNumber == 0) // 키가 눌리지 않았다면.. 블로킹 처리를 해야함
    {
        // 만약 open()할때 O_NONBLOCK 플래그를 주지 않고 열었다면 프로세스를 sleep상태로 만들어줘야함
        if ( !(filp->f_flags & O_NONBLOCK) )
        {
#if 1
            /* 인터럽트 콜백 함수를 이용하여 깨우는 방법(1) */
            wait_event_interruptible(WaitQueue_Read, pKeyData->keyNumber); // 이게 프로세스를 재우는 함수이다
            // 여기서 두번째 매개변수로 준 keyNumber의 값이 1이 되면 깨어나는 거고, 0이면 그대로 있는 것이다
#else
            /* timeout으로 일정 시간마다 깨우는 방법(2) */
            wait_event_interruptible_timeout(WaitQueue_Read, pKeyData->keyNumber, 100); // 1초마다 깨어남
            // 커널 타이머가 100hz이기 때문에 T = 1/100 = 0.01sec이고 0.01 * 100이므로 1sec
            // 이렇게 해도 인터럽트에 의해 깨어나는 것은 동일하다.(위의 상황에다가 timeout기능만 추가한 것)
#endif
        }
        // 사용자가 key를 누르면 '커널은' key_isr() 함수를 호출한다.
        //그래서 우리는 커널히 호출하는 해당 함수쪽에서 프로세스를 깨우는 함수를 추가할 것이다.

    }

    //put_user(kbuff, buf);
    put_user(pKeyData->keyNumber, buf);
    //result = copy_to_user(buf, &(pKeyData->keyNumber), count);
    if(pKeyData->keyNumber)
        pKeyData->keyNumber = 0;

#if DEBUG
    printk("ledkey read -> buf : %08X, count : %08X \n", (unsigned int)buf, count);
#endif
    return count;
}

프로세스를 깨우는 코드(interrupt service routine 안에서..)

irqreturn_t key_isr(int irq, void *data) // 이 함수는 '커널'에서 호출하는 함수이다.
{
    int i;
    keyData* pKeyData = (keyData*)data; // void포인터로 받아오기 때문에  크기정보가 없다. 그래서 크기정보를 주기 위해 형변환 해준다.

    for(i = 0; i < GPIOKEYCNT; i++)
    {
        if(irq == pKeyData->key_irq[i])
        {
            pKeyData->keyNumber = i + 1; // 키 번호를 0~7이 아니라 1~8번 범위로 보기 위함
            break;
        }
    }
#if DEBUG
    printk(KERN_DEBUG "key_isr() irq: %d, keyNumber: %d\n", irq, pKeyData->keyNumber);
#endif

    // 다시 한번 말하지만 이 key_isr()함수는 하드웨어 스위치를 누른것을 인식하여 '커널이' 호출하는 함수이다. 그래
서 이 커널이 호출한 함수를 통해서 사용자 영역 프로세스를 깨울 수 있게 되는 것이다.(잠든 프로세스는 스스로 깨어나>지 못한다. 그래서 다른 주체인 커널의 힘을 빌려서 프로세스를 깨우는 구조인 것이다. )
    wake_up_interruptible(&WaitQueue_Read); // 프로세스를 깨우는 함수
    return IRQ_HANDLED;
}

다시 한번 말하지만 이 key_isr()함수는 하드웨어 스위치를 누른것을 인식하여 '커널이' 호출하는 함수이다. 그래서 이 커널이 호출한 함수를 통해서 사용자 영역 프로세스를 깨울 수 있게 되는 것이다.(잠든 프로세스는 스스로 깨어나지 못한다. 그래서 다른 주체인 커널의 힘을 빌려서 프로세스를 깨우는 구조인 것이다. )

profile
스펀지맨

0개의 댓글