Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

19113
1478
166889


[리눅스커널] 시그널: ERESTARTSYS 매크로와 signal_pending() 에 대해서 12. 시그널

-ERESTARTSYS 는 리눅스 커널의 '시그널' 서브 시스템과 연관된 매크로로 시스템 콜을 다시 실행시키려고 할 때 반환하는 매크로이다. 

ERESTARTSYS 매크로의 의미

보통 커널이 다시 시스템 콜을 재실행을 시키려는 이유는 '어떤 액션의 동기화'를 맞추기 위해서이다. 음, 내가 써도 무슨 소리인지 모르겠네. 나중에 이 글을 읽을 '나 자신'을 위해 이해하기 쉽도록 비유를 하나 들자.

* 침대에서 잠을 든 상태이고 난 침대에서 잘 들어 있어야 한다.
* 그런데 갑자기 택배가 와서 초인종이 울린다.
* 일어나서 택배를 받고 다시 침대에 들어가 잠든 상태에 있어야 한다.

여기서 중요한 사실은 '난 침대에 잠 들어 있어야 한다.'라는 사실이다. 자, 위에서 든 예시를 조금 어려운 용어로 바꿔보자.

* 프로세스는 휴면 상태에 있는 상태이고 프로세스는 휴면 상태를 유지해야 한다.
* 그런데 갑자기 시그널이 전달돼 깨어나 시그널을 처리한다.
* 시스템 콜을 다시 재실행하도록 설정한다.
* 다시 시스템 콜이 재실행이 되서 프로세스는 이전과 같이 휴면 상태를 유지한다.

이번에는 다른 예시를 들어보자.

* 시스템 콜 컨텍스트로 실행이 된 프로세스는 I/O 상태라 휴면 상태에 있는 상태이고 프로세스는 휴면 상태를 유지해야 한다.
   왜냐면, 블록 디바이스와 같은 저장 매체로 I/O 할 때 프로세스는 휴면 상태에서 I/O가 끝나기를 기다려야 한다.
* 그런데 갑자기 시그널이 전달돼 I/O Wait 상태의 프로세스를 깨우면 프로세스는 깨어나 시그널을 처리한다.
* 시스템 콜을 다시 재실행하도록 설정한다.
* 다시 시스템 콜이 재실행이 되서 프로세스는 이전과 같이 휴면 상태를 유지하면서 I/O wait 를 한다.

ERESTARTSYS 매크로를 사용하는 예제 코드 확인하기

그런데 ERESTARTSYS 매크로를 사용하는 패턴은 정해져 있다. 즉, 프로세스에게 시그널이 전달됐다는 정보와 함께 사용한다는 점이다.

먼저 pipe_write() 함수를 보자.

https://elixir.bootlin.com/linux/v4.19.30/source/fs/pipe.c
static ssize_t
pipe_write(struct kiocb *iocb, struct iov_iter *from)
{
    struct file *filp = iocb->ki_filp;
...
        if (signal_pending(current)) {
            if (!ret)
                ret = -ERESTARTSYS;
            break;
        }

signal_pending() 함수를 호출해 현재 실행 중인 프로세스에게 시그널이 전달이 됐다면,
-ERESTARTSYS 매크로를 반환한다.

이어서 do_wait_intr_irq() 함수를 보자.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/sched/wait.c
int do_wait_intr_irq(wait_queue_head_t *wq, wait_queue_entry_t *wait)
{
if (likely(list_empty(&wait->entry)))
__add_wait_queue_entry_tail(wq, wait);

set_current_state(TASK_INTERRUPTIBLE);
if (signal_pending(current))
return -ERESTARTSYS;

signal_pending() 함수가 true를 반환해 프로세스 자신에게 시그널이 전달됐다는 사실을 파악하면,
'-ERESTARTSYS' 매크로를 반환한다.

위와 같은 코드를 커널에서 보면 다음과 같이 머릿 속으로 그리며 해석하며 된다.

   * 이 함수는 시스템 콜을 통해 실행됐다.
   * 시그널을 받은 후 시그널을 처리한 후 시스템 콜이 다시 재실행된다.
   * 그 다음에 위에서 본 함수로 다시 되돌아 올 것이다.

반환된 ERESTARTSYS 매크로를 처리하는 시그널 처리 함수

프로세스가 시그널을 받은 후 호출되는 do_signal() 함수를 보자.

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/kernel/signal.c
static int do_signal(struct pt_regs *regs, int syscall)
{
unsigned int retval = 0, continue_addr = 0, restart_addr = 0;
struct ksignal ksig;
int restart = 0;

/*
* If we were from a system call, check for system call restarting...
*/
if (syscall) {
continue_addr = regs->ARM_pc;
restart_addr = continue_addr - (thumb_mode(regs) ? 2 : 4);
retval = regs->ARM_r0;

/*
* Prepare for system call restart.  We do this here so that a
* debugger will see the already changed PSW.
*/
switch (retval) {
case -ERESTART_RESTARTBLOCK:
restart -= 2;
case -ERESTARTNOHAND:
case -ERESTARTSYS:
case -ERESTARTNOINTR:
restart++;
regs->ARM_r0 = regs->ARM_ORIG_r0;
regs->ARM_pc = restart_addr;
break;
}
}

위 코드에서 다음 주석을 감상하자.

/*
* If we were from a system call, check for system call restarting...
*/

역시 시스템 콜을 리스타트 한다는 내용이다.

프로세스가 시그널을 받아 처리하는 do_signal() 함수가 호출되는 경로(패스)는 다음과 같다.

   * ret_fast_syscall 레이블: 시스템 콜을 처리한 후 유저 공간으로 되돌아가기 직전
   * __irq_usr 레이블: 유저 프로세스가 유저 공간에서 실행하는 도중 인터럽트가 발생. 이후 인터럽트 핸들러를 처리한 다음 유저 공간으로 되돌아가기 직전

관련 커널 뉴스 레터


조금 더 자세한 내용은 다음 링크의 '커널 뉴스 레터'를 참고하자.  

제목: A new system call restart mechanism
https://lwn.net/Articles/17744/

System calls often have to wait for things - I/O completion, availability of a resource, or simply for a timeout to expire, for example. Normally the process making the system call becomes unblocked at the appropriate time, and the call completes its work and returns to user space. What happens, though, if a signal is queued for the process while it is waiting? In that case, the system call needs to abort its work and allow the actual delivery of the signal. For this reason, kernel code which sleeps tends to follow the sleep with a test like:
    if (signal_pending(current))
return -ERESTARTSYS;
...

아, 리눅스 커널의 '시그널' 서브 시스템은 정말 어려운 것 같다.


덧글

댓글 입력 영역