
시스템 호출(system call) 이란 사용자 수준 응용 프로그램에게 커널이 자신의 서비스를 제공하는 인터페이스이다.
리눅스 커널은 각 시스템 호출을 함수(시스템 호출 핸들러)로 구현해놓고, 각 시스템 호출이 요청되었을 때 대응되는 함수를 호출하여 서비스를 제공한다.
libc.a 의 fork() 호출| 순서 | x86 | ARM |
|---|---|---|
| 1 | movl eax, %2 | STR r7, 2 |
| 2 | int 0x80 | swi |
| 3 | 사용자 > 커널 수준 (문맥교환) | 동일 |
| 4 | system_call(eax) | eax > r7 외 동일 |
| 5 | sys_call_table 인덱싱 | 동일 |
| 6 | sys_fork() 호출 | 동일 |
| ------- | -------------------------- | --------------------- |
여기에서 eax 혹은 r7 에 들어가는 2 는 fork() 에 할당된 고유 번호이다. 레지스터에 시스템 번호에 해당하는 값을 해당한 후 int 혹은 swi 를 통해 인터럽트(트랩)를 발생시킨다.
새로운 시스템 호출의 구현은 아래의 순서로 이뤄진다:

ARM64 의 syscall table 이다. 현재 441 개의 시스템 콜이 사용 중으로 확인된다. (Linux Kernel 5.10)
아키텍쳐에 따른 시스템 호출 테이블은 아래의 경로에 존재한다:
ARM64 - include/uapi/asm-generic/unistd.h
ARM32 - arch/arm/tools/syscall.tbl
x86 - arch/x86/syscalls/syscall_64.tbl
앞서 말했듯 시스템 호출을 위해선 시스템 호출 함수에 대응하는 고유한 번호가 필요하다. 따라서 시스템 호출 핸들러 구현에 앞서 시스템 호출 번호를 등록해야 한다:

다음의 내용을 include/uapi/asm-generic/unistd.h 파일의 맨 아래에 기입했다. 새로운 시스템 호출 번호로 460 을 등록했다.

asmlinkage long sys_newsyscall(void);
위 내용을 include/linux/syscalls.h 에 등록했다. 새롭게 등록할 시스템 호출인 sys_newsyscall 은 인자를 받지 않는 아주 단순한 함수이다.

sys_newsyscall 시스템 호출은 Hello Linux, I'm in kernel! 이라는 단순한 문자열을 출력한다. 위 함수는 kernel/sys.c 에 구현했다.

make O=../build/arm64 ARCH=64 CROSS_COMPILE=aarch64-linux-gnu -j$(nproc)
다음과 같이 크로스 컴파일을 진행했다.

컴파일 결과로 완성된 Image 파일 (arch/arm6/boot/Image) 을 가지고 QEMU 를 통해 실행했다.
자세한 실행 방법은 필자가 작성한 다음의 글을 참고하길 바란다.

실행결과는 위와 같았다.
기본적으로 유저 모드의 프로그램은 커널 영역의 자료 구조에 직접적으로 접근할 수 없다. 이번에는 커널 모드에서만 접근 가능한 자료 구조의 내용을 출력해보려 한다:
#include <linux/unistd.h> #include <linux/errno.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/syscalls.h> SYSCALL_DEFINE0(gettaskinfo) { printk("<0> PID: %d \n", current->pid); printk("<0> TGID: %d \n", current->tgid); printk("<0> PPID: %d \n", current->parent->pid); printk("<0> STATE: %ld \n", current->state); printk("<0> PRIORITY: %d \n", current->prio); printk("<0> POLICY: %d \n", current->policy); printk("<0> Number of MAJOR FAULT: %ld \n", current->maj_flt); printk("<0> Number of MINOR FAULT: %ld \n", current->min_flt); return 0; }
2. 새로운 시스템 호출 구현 에선 kernel/sys.c 에 함수를 작성했지만, 새로운 소스에 작성하는 것도 가능하다.
kernel/gettaskinfo.c 라는 파일을 생성하여 위 내용을 기입했고, kernel/Makefile 를 아래와 같이 수정했다:
obj-y = fork.o exec_domain.o panic.o \ cpu.o exit.o softirq.o resource.o \ sysctl.o capability.o ptrace.o user.o \ signal.o sys.o umh.o workqueue.o pid.o task_work.o \ extable.o params.o \ kthread.o sys_ni.o nsproxy.o \ notifier.o ksysfs.o cred.o reboot.o \ async.o range.o smpboot.o ucount.o regset.o \ gettaskinfo.o
obj-y 변수에 gettaskinfo.o 라는 오브젝트 파일을 추가하여 커널 컴파일 시, kernel/gettaskinfo.c 라는 파일이 컴파일 될 수 있게 만든다.
실행 결과는 아래와 같았다:

유저 모드의 프로그램이 커널 영역의 자료구조에 접근할 수 없는 것과 마찬가지로, 커널 모드에서 실행되고 있는 프로그램 역시 유저 영역에 직접 접근할 수 없고, copy_to_user 와 같은 매크로를 통해서만 접근 가능하다.
#include <linux/unistd.h> #include <linux/kernel.h> #include <linux/syscalls.h> #include <asm/uaccess.h> SYSCALL_DEFINE3(access_user, int, x, int, y, int *, res) { int err, i; int compute; err = access_ok(res, sizeof(*res)); if (err < 0) { printk("failed to access_ok(): %d\n", err); return err; } compute = x * y; i = copy_to_user(res, &compute, sizeof(compute)); return 0; }
SYSCALL_DEFINE3 매크로를 통해 두 개의 정수와 하나의 포인터 주소를 인자로 가지는 access_user 라는 시스템 호출을 정의한다. 이 시스템 호출은 인자로 전달받은 두 개의 정수를 곱하여 세 번째 포인터 주소를 통해 반환한다.
#include <stdio.h> #include <linux/unistd.h> int main(void) { int num1, num2, num3; syscall(460); syscall(461); printf("Enter two number: "); scanf("%d %d", &num1, &num2); syscall(462, num1, num2, &num3); printf("num3: %d\n", num3); return 0; }
위 코드를 컴파일 하여 실행 결과를 확인해보면 아래와 같다.

리눅스 커널은 모놀리딕(monolithic) 커널으로 운영체제가 제공해야 하는 모든 기능(태스크 관리, 메모리 관리, 파일 시스템, 디바이스 드라이버, 통신 프로토콜)을 단일한 커널 공간에 구현한다.
반대인 마이크로 커널(micro kernel) 은 커널 공간에 반드시 필요한 기능(문맥 교환, 주소 교환, 시스템 호출 처리, 디바이스 드라이버 일부)들만 구현한다.
리눅스 커널은 모놀리딕 커널이지만, 많은 기능들 중 필요한 기능만을 적재하여 사용하는 모듈 개념을 적용하여 마이크로 커널의 장점을 취할 수 있다.
모듈 프로그래밍 방법은 다음의 글을 참조하라.
[책] 리눅스 커널: 내부구조 (백승제, 최종무 저)
[사이트] https://reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table
[사이트] https://www.linuxbnb.net/home/adding-a-system-call-to-linux-arm-architecture/
혹시 include/uapi/asm-generic/unistd.h 등록할 떄 __NR_syscalls 도 바꿔줘야 하나요?
NR_syscalls 값은436이고 시스템콜은 435까지 차있어서 하나 추가하면 NR_syscalls도 1 더하는게 맞는건가요?
감사합니다 ㅜ,ㅜ