Linux kernel v4.4에서 간단한 블록 장치 드라이버 만들어보기

시스템콜 초기화

이번 강좌는 인텔프로세서와 어셈블리에 대한 내용이 들어갑니다. 익숙하지않으시면 넘기셔도 좋습니다.

syscall_init

참고문서 https://lwn.net/Articles/604287/ 에서 설명하듯이 시스템콜을 초기화하는 함수는 syscall_init입니다. 저는 참고문서를 보기전에 int_ret_from_sys_call을 호출하는 함수들을 추적하다가 syscall_init에서 시스템콜을 초기화한다는걸 알았습니다. 코드로 알던 문서로 알던 상관은 없겠지요.

wrmsrl이라는 함수가 나오는데 결국 wrmsr 이라는 어셈블리 명령을 실행하는 함수입니다.


static inline void native_write_msr(unsigned int msr,
    			    unsigned low, unsigned high)
{
	asm volatile("wrmsr" : : "c" (msr), "a"(low), "d" (high) : "memory");
}

wrmsr이 뭔지는 인텔프로세서 메뉴얼을 참고하셔도 좋고 구글로 검색해서 http://x86.renejeschke.de/html/file_module_x86_id_326.html 같은 사이트를 찾아봐도 좋습니다. 어쨌든 wrmsr 명령은 유저레벨 어플이 시스템콜을 호출했을 때 프로세서가 알아서 점프해야할 주소를 프로세서의 특수 레지스터에 저장하는 명령입니다. ecx레지스터에는 특수 레지스터의 주소를 저장하고, edx:eax 레지스터에 64비트의 주소값을 32비트씩 나눠서 저장하면 됩니다. 이런 어셈블리 명령 호출을 C 함수로 wrapper를 만든게 바로 wrmsrl입니다.

유저어플에서 어떤 어셈블리 명령을 쓰는지는 상관할 필요가 없고, 어쨌든 미리 정해진 규칙에 따라 시스템콜을 호출한다는 것만 생각하면 됩니다. 그럼 프로세서는 알아서 wrmsrl 함수로 전달된 entry_SYSCALL_64 주소로 점프합니다.

entry_SYSCALL_64

그럼 entry_SYSCALL_64는 뭘까요? 함수인지 뭔지 grep으로 찾아보겠습니다. 

$ grep entry_SYSCALL_64 * -IR
arch/x86/entry/entry_64.S:ENTRY(entry_SYSCALL_64)
arch/x86/entry/entry_64.S:GLOBAL(entry_SYSCALL_64_after_swapgs)
arch/x86/entry/entry_64.S:entry_SYSCALL_64_fastpath:
arch/x86/entry/entry_64.S:    jmp	entry_SYSCALL_64_fastpath	/* and return to the fast path */
arch/x86/entry/entry_64.S:END(entry_SYSCALL_64)
arch/x86/include/asm/proto.h:void entry_SYSCALL_64(void);
arch/x86/kernel/cpu/common.c:	wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);
arch/x86/xen/xen-asm_64.S:	jmp entry_SYSCALL_64_after_swapgs
System.map:ffffffff8188b810 T entry_SYSCALL_64
System.map:ffffffff8188b813 T entry_SYSCALL_64_after_swapgs
System.map:ffffffff8188b85c t entry_SYSCALL_64_fastpath

arch/x86/include/asm/proto.h에 함수로 선언을 해놨네요. 그리고 arch/x86/entry/entry_64.S 파일에 코드가 있습니다. ENTRY라는 매크로와 END라는 매크로가 어셈블리 코드에서 함수의 시작과 끝을 표시하는 매크로라고 생각하면 됩니다. 자세히 볼 필요는 없으니 그냥 간단히만 알아보겠습니다.

시스템콜 자체를 보려는게 아니라 int_ret_from_sys_call이 어디서 언제 호출되는지를 알려는게 목적이니까 entry_SYSCALL_64 자체를 분석할 필요는 없습니다. 중요한건 시스템콜이 호출되면 entry_SYSCALL_64가 호출된다는 것입니다. 그리고 entry_SYSCALL_64에서 sys_call_table을 이용해서 시스템콜을 호출하고, 마지막으로 int_ret_from_sys_call을 호출한다는 것입니다.

결론은 int_ret_from_sys_call은 시스템콜이 끝날때 호출되므로 시스템콜이 끝날때 블럭장치의 페이지캐시가 flush된다는 것입니다.

참고로 sys_call_table은 함수 포인터의 배열인데 C 코드에서 정의하고있지 않습니다. arch/x86/entry/syscall_64.c파일에 보면 sys_call_table 배열이 정의되어있는데, 그 값들이 하드코딩된게 아니라  #include <asm/syscalls_64.h> 만 써있습니다. syscalls_64.h 파일을 찾아보면 arch/x86/include/generated/asm/syscalls_64.h에 보일수도 있고 안보일 수도 있습니다. 결론적으로는 커널이 빌드될 때 arch/x86/entry/syscalls/Makefile 파일이 실행되고, syscalltbl.sh 스크립트를 이용해서 syscall_64.tbl 파일을 읽고, syscalls_64.h 파일을 생성합니다. 시스템콜이 언제든 추가될 수 있으니 이렇게 동적으로 테이블을 만들도록 구현한 것입니다.

댓글

댓글 본문
작성자
비밀번호
버전 관리
gurugio
현재 버전
선택 버전
graphittie 자세히 보기