Linux 시스템 콜 추가 및 이해

ejkim·2023년 3월 16일
1

과제의 목적은 일반 배포판 리눅스에 새로운 Kernel을 컴파일하고 수정해서 새로운 System Call을 추가하는 것이다. Kernel을 직접 컴파일하고, 이후 기존의 Kernel에 새로운 System Call을 추가한 후에 이를 호출할 수 있는 User-Application까지를 구현하였다.

1. 개발 환경

VirtualBox 6.0.4, Ubuntu 18.04.2, linux-4.20.11

2. 리눅스의 시스템 콜(호출 루틴 포함)에 대한 설명

Image source: http://wiki.kldp.org/KoreanDoc/html/EmbeddedKernel-KLDP/system-call-table.html

system call이란 linux kernel이 제공하는 서비스 중 하나로, User mode에서 실행중인 프로세스가 Kernel mode의 권한이 필요할 때 사용한다. User mode의 프로세스는 보안상 CPU에 직접 접근이 불가능하므로 CPU에 간접 접근을 가능하게 하기 위해 시스템 콜이 필요한 것이다. 예를 들어 fork()함수의 호출을 생각해 보면, 위의 그림과 같은 과정이 일어난다. 먼저, Wrapper Function을 이용해 (System Call이 할당된) 0x80번지에서 Trap Instruction이 발생한다. User space에서 Kernel Space으로 Trap이 발생하면 Trap을 처리하기 위한 Handler는 System Call Table에서 번호를 확인한다. 해당 번호에 맞는 System Call을 Table에서 찾아 호출하고(여기에서는 두 번째 table의 sys_fork()를 호출하게 된다), 결과값을 user process로 return하여 System Call이 종료되었음을 알리게 된다.

3. 수정 및 작성한 부분과 설명

A. (linux)/arch/x86/entry/syscalls/syscall_64.tbl

system call은 본 table을 참조하여 호출하기 때문에, 위의 그림과 같이 systemcall_64.tbl에 새로운 system call의 고유 번호를 저장하였다. 334까지는 이미 할당되어 있으므로 335번에 oslab_enqueue라는 이름으로 enqueue를 하는 시스템 콜을 추가하였고 336번에 oslab_dequeue라는 이름으로 dequeue를 하는 시스템 콜을 추가하였다.

B. (kernel)/include/linux/syscalls.h

함수의 prototype을 정의해야 헤더 파일을 읽는 참조가 가능해지기 때문에, 위의 그림과 같이 syscalls.h파일에 asmlinkage를 사용하여 system call 함수들을 정의하였다. sys_oslab_enqueue함수는 inteager 변수를 받아 queue에 삽입하고 sys_oslab_dequeue함수는 가장 먼저 삽입되었던 변수를 제거하고 그 값을 return해준다.

C. (kernel)/my_queue_syscall.c

#include<linux/syscalls.h>
#include<linux/kernel.h>
#include<linux/linkage.h>
#define MAXSIZE 500

int queue[MAXSIZE];
int front = 0; 
int rear = 0; 
int i = 0;
int res = 0;

SYSCALL_DEFINE1(oslab_enqueue, int, a ){
	// queue가 가득 찼을 경우 error 메시지 출력
	if (rear >= MAXSIZE - 1) { 
		printk(KERN_INFO "[Error] - QUEUE IS FULL-------------------\n");
		return -2;
	}
	
	// a 값이 기존 queue에 존재하는 값일 경우 error 메시지 출력
	for (i=front; i<=rear ;i++) { 
		if (a == queue[i]) {	
			printk(KERN_INFO "[Error] - Already existing value \n");
			return a;
		}
	}
	
	// queue의 rear에 a 원소 삽입
	queue[rear] = a ; 
	
	// queue의 front에서 rear까지 출력 
	printk(KERN_INFO "[System call] oslab_enqueue(); -------\n");
	printk("Queue Front---------------------\n");
	for (i=front; i <= rear ; i ++) {
		printk("%d \n", queue[i]); 
	}
	printk("Queue Rear---------------------\n");
	
	// queue rear 업데이트 
	rear = rear + 1;

	// enqueue된 원소 a return
	return a;
}

SYSCALL_DEFINE0(oslab_dequeue){
	// queue가 비어 있을 경우 error 메시지 출력
	if (rear == front) { 
		printk(KERN_INFO "[Error] - EMPTY QUEUE-------------------\n");
		return -2;
	}

	// result에 queue front 값 삽입
	res = queue[front]; 
	
	// queue의 i번째 원소는 i–1번째로, 한 칸씩 앞으로 이동
	for(i=front+1; i<MAXSIZE ; i++){
		queue[i-1] = queue[i]; 
	}

	// queue의 front에서 rear까지 출력 
	printk(KERN_INFO "[System call] oslab_enqueue(); -------\n");
	printk("Queue Front---------------------\n");
	for (i=front; i<=rear ; i++) {
		printk("%d \n", queue[i]); 
	}
	printk("Queue Rear---------------------\n");

	// queue rear 업데이트 
	rear = rear - 1;

	// dequeue 된 result return 
	return res;
}

enqueue, dequeue 함수의 parameter 개수가 다르기 때문에 사용을 위해 SYSCALL_DEFINEx를 사용하였고 #include<linux/syscalls.h>하였다.

i. SYSCALL_DEFINE1(oslab_enqueue, int, a)

  1. queue가 가득 찼는지 확인하기 위해 rear와 MAXSIZE-1을 비교하고 가득 찬 queue일 경우 에러 메시지를 출력한다.
  2. queue에 빈 공간이 있을 경우 for loop를 이용해 input a가 queue에 이미 존재하는지 체크한다. 존재하는 값일 경우 에러 메시지를 출력한다.
  3. 중복된 값이 아니라면 queue의 rear에 새로운 원소를 삽입한다.
  4. queue의 원소를 front에서 rear의 순서로 출력한다.

ii. SYSCALL_DEFINE0(oslab_dequeue)

  1. queue가 비어 있는지 확인하기 위해 rear와 front를 비교하고 비어 있는 queue일 경우 에러 메시지를 출력한다.
  2. queue가 비어 있지 않을 경우 res에 queue front를 대입하고, rear를 1 줄인다.
  3. queue front가 삭제되므로 queue의 i번째 원소는 i–1번째로, 한 칸씩 앞으로 이동한다.
  4. queue의 원소를 front에서 rear의 순서로 출력한다.

D. (kernel)/Makefile

커널 compile 시 새로 만든 system call을 포함시켜야 하기 때문에 위의 그림과 같이 Makefile 파일의 obj-y 부분에 my_queue_syscall.o를 추가했다.

E. oslab/call_my_queue.c

#include<unistd.h>
#include<stdio.h>

#define my_queue_enqueue 335 // my_queue_enqueue를 335로 mapping
#define my_queue_dequeue 336 // my_queue_dequeue를 336으로 mapping

int main(){
	int a = 0;
	
	// syscall 호출, queue에 1 삽입
	a = syscall(my_queue_enqueue, 1);
	printf("Enqueue : ");			
	printf("%d\n", a);
	
	// syscall 호출, queue에 2 삽입
	a = syscall(my_queue_enqueue, 2); 
	printf("Enqueue : ");			
	printf("%d\n", a);
	
	// syscall 호출, queue에 3 삽입
	a = syscall(my_queue_enqueue, 3); 
	printf("Enqueue : ");			
	printf("%d\n", a);
	
	// syscall 호출, queue에 3 삽입
	a = syscall(my_queue_enqueue, 3); 
	printf("Enqueue : ");			
	printf("%d\n", a);
	
	// syscall 호출, queue의 처음 원소 반환
	a = syscall(my_queue_dequeue); 
	printf("Dequeue : ");		
	printf("%d\n", a);
	
	// syscall 호출, queue의 처음 원소 반환
	a = syscall(my_queue_dequeue); 
	printf("Dequeue : ");		
	printf("%d\n", a);
	
	// syscall 호출, queue의 처음 원소 반환
	a = syscall(my_queue_dequeue); 
	printf("Dequeue : ");		
	printf("%d\n", a);
	
	return 0;
}

시스템 콜을 사용하는 user application이다. 본 코드에서는 1, 2, 3, 3을 차례로 enqueue하고 3번 dequeue했다.

4. 실행 결과 스냅샷

A. ./call_my_queue

B. dmesg

(중략)

5. 수행 과정 중 발생한 문제점과 해결 방법

진행 중 ./call_my_queue를 실행해 보니, Enqueue와 Dequeue의 모든 return 값이 -1이 되는 상황이 발생하였다. 이것은 System Call이 제대로 동작하지 않았기 때문에 일어나는 결과이므로, 문제 해결을 위해 다시금 kernel compile을 한 후 시스템을 reboot했다. 그 결과, ‘4. 실행 결과 스냅샷’ 처럼 올바른 return 값이 나오게 됨을 확인할 수 있었다.

profile
기록하자!

0개의 댓글