커널 스터디(iamroot 18기) 6주차 내용 정리 #1, 시스템 호출

문연수·2021년 8월 23일
0

iamroot (Linux Internal)

목록 보기
12/24
post-thumbnail

1. 시스템 호출 처리 과정

시스템 호출(system call) 이란 사용자 수준 응용 프로그램에게 커널이 자신의 서비스를 제공하는 인터페이스이다.

리눅스 커널은 각 시스템 호출을 함수(시스템 호출 핸들러)로 구현해놓고, 각 시스템 호출이 요청되었을 때 대응되는 함수를 호출하여 서비스를 제공한다.

libc.afork() 호출

순서x86ARM
1movl eax, %2STR r7, 2
2int 0x80swi
3사용자 > 커널 수준 (문맥교환)동일
4system_call(eax)eax > r7 외 동일
5sys_call_table 인덱싱동일
6sys_fork() 호출동일
------------------------------------------------------

여기에서 eax 혹은 r7 에 들어가는 2 는 fork() 에 할당된 고유 번호이다. 레지스터에 시스템 번호에 해당하는 값을 해당한 후 int 혹은 swi 를 통해 인터럽트(트랩)를 발생시킨다.

2. 새로운 시스템 호출 구현

새로운 시스템 호출의 구현은 아래의 순서로 이뤄진다:

  1. 유일한 시스템 호출 번호를 할당
  2. 시스템 콜을 처리할 함수를 테이블에 등록
  3. 시스템 호출 처리 함수 구현

시스템 호출 등록

ARM64syscall 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 를 통해 실행했다.

자세한 실행 방법은 필자가 작성한 다음의 글을 참고하길 바란다.


실행결과는 위와 같았다.

3. 커널 고유 정보 출력

기본적으로 유저 모드의 프로그램은 커널 영역의 자료 구조에 직접적으로 접근할 수 없다. 이번에는 커널 모드에서만 접근 가능한 자료 구조의 내용을 출력해보려 한다:

#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 라는 파일이 컴파일 될 수 있게 만든다.

실행 결과는 아래와 같았다:

4. 유저 영역과 상호작용

유저 모드의 프로그램이 커널 영역의 자료구조에 접근할 수 없는 것과 마찬가지로, 커널 모드에서 실행되고 있는 프로그램 역시 유저 영역에 직접 접근할 수 없고, 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;
}

위 코드를 컴파일 하여 실행 결과를 확인해보면 아래와 같다.

5. 모듈 프로그래밍

리눅스 커널은 모놀리딕(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/

profile
2000.11.30

14개의 댓글

comment-user-thumbnail
2023년 9월 25일

감사합니다 ㅜ,ㅜ

답글 달기
comment-user-thumbnail
2024년 3월 15일

혹시 include/uapi/asm-generic/unistd.h 등록할 떄 __NR_syscalls 도 바꿔줘야 하나요?

NR_syscalls 값은436이고 시스템콜은 435까지 차있어서 하나 추가하면 NR_syscalls도 1 더하는게 맞는건가요?

1개의 답글
comment-user-thumbnail
2024년 3월 21일

감사합니다! 덕분에 시스템콜 추가 실습을 순조롭게 완료할 수 있었습니다!
근데 arm64아키텍쳐에서 커널 컴파일을 진행하는데 매번 전체를 컴파일하기엔 너무 귀찮고 오래걸리는것 같습니다
혹시 추가한 기능만 컴파일 해서 추가할 수 있는 방법에 대해서 알고 계신가요?

지금은 시스템콜을 추가하기위해서 커널 컴파일을 처음부터 진행하는 방법으로 하고있습니다.

1개의 답글