260 likes | 509 Views
10 장 시스템 콜. 김 태 훈. API 와 시스템 콜의 차이. API 는 특정 서비스를 어떻게 받을지를 지정하는 함수 시스템 콜은 소프트웨어 인터럽트를 통해 커널에 하는 직접적인 요청 libc 에서 정의하는 API 중 일부는 시스템 콜을 호출하는 목적으로만 사용하는 “ 래퍼루틴 ” 이 있다 . 보통 각 시스템 콜마다 대응하는 래퍼 루틴이 하나씩 있으며 , 래퍼 루틴은 애플리케이션에서 사용할 API 를 정의한다 .
E N D
10장시스템 콜 김 태 훈
API와 시스템 콜의 차이 • API는 특정 서비스를 어떻게 받을지를 지정하는 함수 • 시스템 콜은 소프트웨어 인터럽트를 통해 커널에 하는 직접적인 요청 • libc에서 정의하는 API중 일부는 시스템 콜을 호출하는 목적으로만 사용하는 “래퍼루틴”이 있다. • 보통 각 시스템 콜마다 대응하는 래퍼 루틴이 하나씩 있으며, 래퍼 루틴은 애플리케이션에서 사용할 API를 정의한다. • 그러나 그 역은 성립하지 않는다. 즉, API는 특정 시스템 콜에 대응할 필요가 없다.
시스템 콜 핸들러와 서비스 루틴 • 사용자 모드 프로세스가 시스템 콜을 호출하면, CPU는 커널 모드로 전환해서 커널함수를 실행하기 시작한다. • 커널은 서로 다른 많은 시스템 콜을 구현하므로 사용자 모드 프로세스는 자신이 원하는 시스템 콜을 구별하는 ‘System Call Number’를 매개 변수로 전달해야 한다. • eax레지스터를 사용한다. • 모든 시스템 콜은 정수 값을 반환한다. • 양수나 0은 성공, 실패는 음수이다. • 에러가 발생한 경우 errno변수를 통해 에러 코드는 나타낸다. • 커널은error변수를 사용하지 않음. • 대신 래퍼 루틴이 시스템 콜에서 돌아온 후 변수를 설정하는 일을 한다.
시스템 콜핸들러와 서비스 루틴 • 시스템 콜 핸들러의 역할 • 레지스터 내용을 커널 모드 스택에 저장한다. • ‘System Call Service Routine’이라는 C함수를 호출하여 시스템 콜을 처리한다. • 핸들러에서 빠져 나온다. • 커널 모드 스택에 저장된 값을 레지스터들에 로딩하고, CPU는 커널모드에서 사용자 모드로 다시 돌아온다.
시스템 콜 핸들러와 서비스 루틴 • 그림 10-1 • SYSCALL, SYSEXIT는 사용자 모드에서 커널 모드로, 커널 모드에서 사용자 모드로 CPU를 전환하는 실제 어셈블리 명령어가 있는 곳을 나타낸다. • 커널은 시스템 콜 번호와 해당 시스템 콜 서비스 루틴을 서로 연계하기 위해 ‘System Call Dispatch Table’을 사용한다. • 이 테이블은 sys_call_table배열에 들어 있다. • NR_syscalls개의 엔트리를 포함한다. • __NR_syscall_max개 (279개) • 이 값은 구현 가능한 시스템 콜의 최대 개수로, 실제 구현하고 있는 시스템 콜 수를 의미하지는 않는다.
시스템 콜로의 진입과 복귀 • 시스템 콜을 호출하는 두 가지 방법 • int $0x80 어셈블리 명령을 수행한다. • 과거버전에서 사용자 모드에서 커널 모드로 전환하는 유일한 방법 • sysenter어셈블리어를 수행한다. • 2.6 커널 이상 부터 지원함. • 펜티엄 2 프로세서 부터 지원함. • 사용자 모드로 전환하는 두 가지 방법 • iret어셈블리 명령어를 수행한다. • sysexit어셈블리 명령어를 수행한다.
시스템 콜로의 진입과 복귀 • 두 가지 방법이 혼재함으로 인해 고려해야 할 사항. • 커널은int $x80 명령어만 사용하는 라이브러리와 sysenter를 사용하는 라이브러리를 동시에 지원해야 한다. • sysenter를 사용하는 라이브러리는 int $x80만 지원하는 커널에 대처해야 한다. • 커널과 표준 라이브러리는 sysenter명령어를 포함하지 않는 구식 프로세서와 sysenter명령어를 포함하는 최신 프로세서에서 모두 실행될 수 있어야 한다.
int $0x80 명령어를 통한 시스템 콜 발생 • trap_init함수는 128번 (0x80)에 해당하는 ‘Interrupt Descriptor Table’ 엔트리를 다음과 같이 설정한다. • set_system_gate(0x80, &system_call); • 이는 gate descriptor의 필드를 다음 값으로 지정한다. • 세그먼트 셀렉터 • __KERNEL_CS • 오프셋 • system_call() 예외 핸들러에 대한 포인터이다. • 종류(Type) • 15로 설정. • 예외 종류가 Trap이고, 해당 핸들러는 마스크 가능한 인터럽트를 금지하지 않음. • DPL(Descriptor Privilege Level) • 3으로 설정 • 사용자 모드의 프로세스가 예외 핸들러를 호출할 수 있게 한다. • 사용자모드 프로세스가 int $0x80 명령어를 발생시킬 때 CPU는 커널 모드로 전환하고 system_call주소에서 명령어를 수행한다.
system_call() 함수 • 시스템콜 번호와 함께 예외 핸들러가 사용할 수 있도록 eflags와 cs, eip, ss, esp레지스터를 제외한 모든 CPU레지스터를 스택에 저장한다. • SAVE_ALL 매크로는 ds와 es를 각각 커널 데이터 세그먼트의 세그먼트 셀렉터로 설정한다.
system_call() 함수 • ebx레지스터에 현재 프로세스의 thread_info자료 구조의 주소를 저장한다. • thread_info자료 구조의 flags 필드를 검사하여 디버거의 추적이 있는지 확인한다. • TIF_SYSCALL_TRACE, TIF_SYSCALL_AUDIT • 이 경우, do_syscall_trace() 함수를 시스템 콜 실행 전후에 호출한다. • 사용자 모드 프로세스가 전달한 시스템 콜 번호가 올바른지 검사한다. • 시스템 콜 번호가 잘못된 것이면eax를 저장했던 스택위치에–ENOSYS값을 저장한 후 resume+userspace()를 호출한다.
sysenter명령어를 통한 시스템 콜 발생 • low-latency system call • 다음의 세 가지 레지스터를 사용한다. • SYSENTER_CS_MSR • 커널 코드 세그먼트의 세그먼트 셀렉터 • SYSENTER_EIP_MSR • 커널진입점의 선형 주소 • SYSENTER_ESP_MSR • 커널스택 포인터 • sysenter명령어 수행시, • SYSENTER_CS_MSR의 내용을 cs레지스터에 복사한다. • 커널 영역의 코드 세그먼트 정보 • SYSENTER_EIP_MSR의 내용을 eip레지스터에 복사한다. • sysenter_entry() 함수의 주소 • SYSENTER_ESP_MSR의 내용을 esp레지스터에 복사한다. • TSS(Task State Segment)의 주소 • SYSENTER_CS_MSR의 값에 8을 더한 후 ss레지스터에 값을 로딩한다.
vsyscall • 커널이 정의한 실행 코드를 사용자 공간에서 참조할 수 있도록 하기 위해 library에서 실행시키는 것. • 실행하고 있는 CPU에 따라 int 0x80을 사용하거나, sysenter명령을 사용한다. • 장점 : 커널 호출이 불필요한 경우, 실제 시스템 콜을 발생하지 않고 처리를 끝낸다. • 예) gettimeofday • 표준 라이브러리의 래퍼 루틴이 시스템 콜을 호출할 때는 항상 __kernel_vsyscall() 함수를 호출한다.
vsyscall • CPU가 sysenter를 지원하지 않는 경우 • CPU가 sysenter를 지원하는 경우
시스템 콜로의 진입 • eax레지스터에 시스템 콜 번호를 로딩한다. • __kernel_vsyscall() 함수는 ecx, edx, ebp레지스터 값을 스택에push한다. • esp(사용자 스택 포인터)를 ebp에 복사한다. • sysenter명령어를 수행한다. • CPU는 현재 모드를 커널 모드로 전환한다. • SYSENTER_EIP_MSR 레지스터가 가리키는 sysenter_entry()함수를 호출한다. • 커널스택 포인터를 설정한다. • 지역 인터럽트를 허용한다. • 다음을 스택에push한다. • 사용자 데이터 세그먼트(__USER_DS) • 사용자 스택 포인터 • 사용자 코드 세그먼트의 세그먼트 셀렉터 • 리턴시 수행될 명령어 주소 • 래퍼 루틴이 전달한 레스터 초기 값을 ebp레지스터에 복구한다.
시스템 콜에서 빠져 나오기 • eax레지스터에 시스템 콜의 반환 값을 저장한다. • 지역 인터럽트를 금지한다. • 현재 thread_info의 flag를 검사한다. ebp sysenter sysexit edx ecx
sysexit명령어 • 커널 모드에서 사용자 모드로 전환한다. • sysexit명령어 수행시CPU는, • SYSENTER_CS_MSR 레지스터 값에 16을 더해서 cs레지스터에 로딩한다. • 사용자 코드의 세그멘트셀렉터 로딩 • edx레지스터 값을 eip레지스터에 복사한다. • SYSENTER_CS_MSR 레지스터에 24를 더해서 ss레지스터에 로딩한다. • 사용자 데이터 세그먼트의 세그먼트 셀렉터 로딩 • ecx레지스터 값을 esp레지스터에 복사한다.
SYSENTER_RETURN 코드 • sysenter로 시작된 시스템 콜이 iret이나 sysexit명령에 의해 종료중일때 수행된다. • vsyscall페이지에 저장되어 있다. • 스택에 저장된 레지스터의 값을 복구한 후, 표준 라이브러리의 래퍼 루틴으로 제어권을 반환한다.
매개 변수 전달 • 매개변수의 형식 • 실제 값(숫자), 사용자 주소 공간의 변수, 사용자 공간의 자료구조 주소 • 필수 매개 변수 • eax레지스터로 전달하는 시스템 콜 번호 • 예) fork()를 호출하면, int $0x80 or sysenter를 실행하기 전에 eax레지스터를 __NR_fork로 설정한다. • 일반적인 C프로그램에서 매개변수는 스택을 통하여 전달된다. • 그러나 시스템 콜은 모드가 변경되는 특별한 함수이기 때문에 양쪽 어느 곳의 스택도 사용할 수 없다. • 시스템 콜 서비스 루틴 수행 전 매개 변수를 CPU 레지스터에 저장한다. • 저장된 매개 변수를 커널 모드의 스택으로 복사한다. • 사용자 모드의 스택을커널 모드의 스택으로 직접 복사는 난해하므로 사용하지 않는다.
매개 변수 전달 • 매개 변수를 레지스터로 전달하기 위한 두가지 조건 • 각 매개 변수는 레지스터 크기인 32비트를 넘을 수 없다. : 32비트 CPU의 경우에만 해당 • 레지스터의 수는 제한적이므로 6개를 넘길 수 없다.(eax포함) • 6개를 초과하는 매개 변수 전달법 • 하나의 레지스터를 매개 변수 값을 저장하고 있는 프로세스 주소 공간 내의 메모리 영역을 가르키게 한다. • 사용되는 레지스터의 종류 • eax, ebx, ecx, edx, esi, edi, ebp
매개변수 확인 • 공통적인 검사 유형 • 매개 변수가 주소인 경우 커널은 이 주소가 프로세스 주소 공간 내에 있는지 검사해야 한다. • 선형 주소가 프로세스 주소 공간에 속하는지, 속하면 주소를 포함하는 메모리영역이 해당 접근 권한에 있는지 확인한다. • 초기 커널의 형태 : 전달된 매개 변수를 모두 검사하므로 시간이 오래 걸림. • 선형 주소가 PAGE_OFFSET보다 낮은지 확인한다. • 즉, 커널용으로 예약한 주소 범위에 들어가지 않는지.. • 커널2.2 이후 부터 사용
프로세스 주소 공간 접근 • 프로세스 주소 공간에 접근을 위한 매크로 제공
동적 주소 검사 – 수선 코드 (The Fix-up Code) • 커널 모드에서 페이지 폴트가 일어날 수 있는 네 가지 경우 • 해당 페이지 프레임이 존재하지 않거나읽기 전용 페이지에 쓰려고 한 경우. • 해당 페이지 테이블 엔트리가 초기화되지 않은 경우 • 커널함수의 버그 또는 일시적인 H/W에러 • 시스템 콜에 전달한 주소의 메모리 영역에 읽기/쓰기를 할 때 해당 주소가 프로세스 주소 공간에 들어 있지 않은 경우
예외 테이블 • 사용자 공간에 접근 하는 명령어 개수는 매우 적다. • 프로세스 주소 공간에 접근하는 각 커널 명령의 주소를 ‘예외 테이블(Exception Table)’ 구조체로 모은다. • 만일 페이지 폴트 예외가 발생하면, do_page_fault핸들러를 이용해 예외 테이블을 검사한다. • 예외를 일으킨 명령의 주소를 찾으면 시스템 콜 매개 변수가 원인이 되고, 그렇지 않으면 다른 버그 때문이다. • 예외 테이블의 엔트리 • insn : 프로세스 주소 공간에 접근하는 명령어의 주소 • fixup : 예외가 발생할 때 호출할 어셈블리 코드 주소
예외 테이블 • 예외 테이블은 커널을컴파일할 때 링크된다. • 예외 테이블 항목은 __ex_table섹션에 저장된다. • 예외가 발생했을 때 처리 코드는 커널 코드 세그먼트 중 .fixup섹션에 저장된다. • search_exception_table() 함수 • 예외 테이블에 지정한 주소가 있는지 찾는다. • 주소가 테이블에 있으면 해당 exception_table_entry의 포인트를, • 없으면 0을 반환한다.
커널래퍼 루틴 • 시스템 콜은 주로 사용자 모드 프로세스에서 사용한다. • 하지만 커널스레드에서도 사용할 수 있도록 래퍼 루틴(Wrapper Routine) 선언을 위해 _syscall0 ~ _syscall6 까지 매크로를 선언하고 있다. • 매크로의 숫자는 시스템 콜이 사용하는 매개변수의 개수이다. • 여기서는 6개가 넘는 매개변수는 사용할 수 없다.
커널래퍼 루틴 • 각 매크로는 (2 + 2xn)개의 매개변수를 받는다. • 예 • _syscall0(int, fork) • int : 반환타입 • fork : 시스템 콜 이름 • _syscall3(int, write, int, fd, const char*, buf, unsigned int, count) • int : 반환타입 • write : 시스템 콜 이름 • int : type, fd : 첫번째 매개 변수 • const char * : type, buf: 두번째매개 변수 • unsigned int: type, count : 세번째매개 변수 • int write(intfd, const char *buf, unsigned int count)