2.10. System Call
시스템 콜(system call)이란 프로세스가 커널에 서비스를 요청하는 것이다. 즉, 프로세스와 커
널간의 대화 창구 역할을 한다고 하겠다. 커널은 시스템 콜 인터페이스(interface)를 제공하
며, 이것은 헤더(header) 파일 형태로 사용자 프로세스에 제공된다. 사용자 프로세스는 직접적
으로 혹은 라이브러리(library)를 이용해서 커널에 접근하게 되며, 시스템 콜을 하게 되는 순
간부터 사용자 모드에서 커널 모드로 전환되어 수행된다. 따라서, 시스템 콜을 시작하면 커널
의 코드가 사용자 프로세스의 환경(context) 아래에서 동작하게 되는 것이다. 실제로 시스템
콜은 운영체제가 제공하는 라이브러리라고 생각하면 된다.
리눅스에서의 시스템 콜은 인터럽트를 사용해서 구현된다. 인터럽트 0x80이 이를 위해서 준비
되어 있다. 리눅스에서는 ~/include/arch/unistd.h에 있는 _syscallX 매크로(macro)를 사용해
서 시스템 콜을 할 수 있도록 하고 있으며, 이곳에서 인터럽트 0x80을 호출한다. 넘겨줘야 할
파라미터(parameter)들은 레지스터(register)를 통해서 전달되며, 돌려 받는 값도 레지스터를
통한다.
~/arch/i386/kernel/traps.c의 trap_init()에서 시스템 콜에 대한 인터럽트 벡터가 초기화 설
정이 되며, 여기에 들어가는 것이 system_call()함수이다. system_call()함수는
~/arch/i386/kernel/entry.S에 어셈블리(assembly)어로 정의되어 있다. 또한
~/arch/i386/kernel/entry.S를 보면, 모든 시스템에서 제공하는 시스템 콜은 sys_call_table[]
내에 들어간다는 것을 알 수 있다.
ENTRY(system_call)
pushl %eax # save orig_eax
SAVE_ALL
GET_CURRENT(%ebx)
cmpl $(NR_syscalls),%eax
jae badsys
testb $0x02,tsk_ptrace(%ebx) # PT_TRACESYS
jne tracesys
call *SYMBOL_NAME(sys_call_table)(,%eax,4)
movl %eax,EAX(%esp) # save the return value…
코드 15. entry.S(1)
system_call()을 보면, 먼저 원래의 eax레지스터를 보관한 다음, 레지스터 들을 스택(stack)
에 push하는 SAVE_ALL 매크로를 실행한다. 현재의 프로세스의 task_struct를 가져오기 위해서
GET_CURRENT 매크로를 실행하고, 시스템 콜 숫자가 맞는지 확인한다. 만약 올바른 값으로 시스
템 콜을 했다면, 현재 프로세스가 트레이스(trace)되고 있는지 확인한 다음, 그렇지 않을 경
우 sys_call_table[]에 등록된 함수를 호출하게 된다. 호출의 결과는 eax에 저장되며, 이것을
나중에 돌려주기 위해서 다시 스택에 저장한다.
만약 트레이스가 되고 있다면, 다시 ~/arch/i386/kernel/ptrace.c에 정의된 syscall_trace()
를 호출해준다. 이곳에서는 현재 프로세스의 상태를 TASK_STOPPED로 만들고, 부모 프로세스에
게 알려준 후 스케줄링을 요청한다.
…
ENTRY(ret_from_sys_call)
#ifdef CONFIG_SMP
movl processor(%ebx),%eax
shll $CONFIG_X86_L1_CACHE_SHIFT,%eax
movl SYMBOL_NAME(irq_stat)(,%eax),%ecx # softirq_active
testl SYMBOL_NAME(irq_stat)+4(,%eax),%ecx # softirq_mask#else
movl SYMBOL_NAME(irq_stat),%ecx # softirq_active
testl SYMBOL_NAME(irq_stat)+4,%ecx # softirq_mask
#endif jne handle_softirq
ret_with_reschedule:
cmpl $0,need_resched(%ebx)
jne reschedule
cmpl $0,sigpending(%ebx)
jne signal_returnrestore_all:
RESTORE_ALL
…
코드 16.entry.S(2)
복귀(return)는 ret_from_sys_call에서 처리가 된다. 소프트 인터럽트가 설정되었는지를 확인
및 처리하게 되며, 현재의 프로세스가 need_resched필드가 설정되었는지를 확인, 만약 설정 되
었다면 스케줄링 요구를 처리한다. 또한 처리할 시그널이 있는지를 확인하고, 그것을 다시 처
리하도록 한다. 그리고 나서 RESTORE_ALL 매크로를 실행해서 이전에 저장했던 레지스터 값들
을 다시 불러드린다. 이 과정에서 복귀(return) 값을 넘겨주고 iret를 수행한다.
여기서 한가지 주의 할 점은 만약 현재 프로세스가 trace가 되고 있다면, 각각의 시스템 콜의
앞뒤로 syscall_trace()가 호출된다는 사실이다. 이렇게 함으로써 부모 프로세스는 자식 프로
세스에 대해서 완전한 제어를 할 수 있게 된다.
시스템 콜(system call)이란 프로세스가 커널에 서비스를 요청하는 것이다. 즉, 프로세스와 커
널간의 대화 창구 역할을 한다고 하겠다. 커널은 시스템 콜 인터페이스(interface)를 제공하
며, 이것은 헤더(header) 파일 형태로 사용자 프로세스에 제공된다. 사용자 프로세스는 직접적
으로 혹은 라이브러리(library)를 이용해서 커널에 접근하게 되며, 시스템 콜을 하게 되는 순
간부터 사용자 모드에서 커널 모드로 전환되어 수행된다. 따라서, 시스템 콜을 시작하면 커널
의 코드가 사용자 프로세스의 환경(context) 아래에서 동작하게 되는 것이다. 실제로 시스템
콜은 운영체제가 제공하는 라이브러리라고 생각하면 된다.
리눅스에서의 시스템 콜은 인터럽트를 사용해서 구현된다. 인터럽트 0x80이 이를 위해서 준비
되어 있다. 리눅스에서는 ~/include/arch/unistd.h에 있는 _syscallX 매크로(macro)를 사용해
서 시스템 콜을 할 수 있도록 하고 있으며, 이곳에서 인터럽트 0x80을 호출한다. 넘겨줘야 할
파라미터(parameter)들은 레지스터(register)를 통해서 전달되며, 돌려 받는 값도 레지스터를
통한다.
~/arch/i386/kernel/traps.c의 trap_init()에서 시스템 콜에 대한 인터럽트 벡터가 초기화 설
정이 되며, 여기에 들어가는 것이 system_call()함수이다. system_call()함수는
~/arch/i386/kernel/entry.S에 어셈블리(assembly)어로 정의되어 있다. 또한
~/arch/i386/kernel/entry.S를 보면, 모든 시스템에서 제공하는 시스템 콜은 sys_call_table[]
내에 들어간다는 것을 알 수 있다.
ENTRY(system_call)
pushl %eax # save orig_eax
SAVE_ALL
GET_CURRENT(%ebx)
cmpl $(NR_syscalls),%eax
jae badsys
testb $0x02,tsk_ptrace(%ebx) # PT_TRACESYS
jne tracesys
call *SYMBOL_NAME(sys_call_table)(,%eax,4)
movl %eax,EAX(%esp) # save the return value…
코드 15. entry.S(1)
system_call()을 보면, 먼저 원래의 eax레지스터를 보관한 다음, 레지스터 들을 스택(stack)
에 push하는 SAVE_ALL 매크로를 실행한다. 현재의 프로세스의 task_struct를 가져오기 위해서
GET_CURRENT 매크로를 실행하고, 시스템 콜 숫자가 맞는지 확인한다. 만약 올바른 값으로 시스
템 콜을 했다면, 현재 프로세스가 트레이스(trace)되고 있는지 확인한 다음, 그렇지 않을 경
우 sys_call_table[]에 등록된 함수를 호출하게 된다. 호출의 결과는 eax에 저장되며, 이것을
나중에 돌려주기 위해서 다시 스택에 저장한다.
만약 트레이스가 되고 있다면, 다시 ~/arch/i386/kernel/ptrace.c에 정의된 syscall_trace()
를 호출해준다. 이곳에서는 현재 프로세스의 상태를 TASK_STOPPED로 만들고, 부모 프로세스에
게 알려준 후 스케줄링을 요청한다.
…
ENTRY(ret_from_sys_call)
#ifdef CONFIG_SMP
movl processor(%ebx),%eax
shll $CONFIG_X86_L1_CACHE_SHIFT,%eax
movl SYMBOL_NAME(irq_stat)(,%eax),%ecx # softirq_active
testl SYMBOL_NAME(irq_stat)+4(,%eax),%ecx # softirq_mask#else
movl SYMBOL_NAME(irq_stat),%ecx # softirq_active
testl SYMBOL_NAME(irq_stat)+4,%ecx # softirq_mask
#endif jne handle_softirq
ret_with_reschedule:
cmpl $0,need_resched(%ebx)
jne reschedule
cmpl $0,sigpending(%ebx)
jne signal_returnrestore_all:
RESTORE_ALL
…
코드 16.entry.S(2)
복귀(return)는 ret_from_sys_call에서 처리가 된다. 소프트 인터럽트가 설정되었는지를 확인
및 처리하게 되며, 현재의 프로세스가 need_resched필드가 설정되었는지를 확인, 만약 설정 되
었다면 스케줄링 요구를 처리한다. 또한 처리할 시그널이 있는지를 확인하고, 그것을 다시 처
리하도록 한다. 그리고 나서 RESTORE_ALL 매크로를 실행해서 이전에 저장했던 레지스터 값들
을 다시 불러드린다. 이 과정에서 복귀(return) 값을 넘겨주고 iret를 수행한다.
여기서 한가지 주의 할 점은 만약 현재 프로세스가 trace가 되고 있다면, 각각의 시스템 콜의
앞뒤로 syscall_trace()가 호출된다는 사실이다. 이렇게 함으로써 부모 프로세스는 자식 프로
세스에 대해서 완전한 제어를 할 수 있게 된다.