Linux - System call
컴퓨터 시스템 구조
하드웨어는 CPU, Memory, Storage, Peripherals 등으로 구성이 된다. 운영체제는 이러한 하드웨어를 운영 관리하는 시스템 소프트웨어이며 유닉스/리눅스의 경우 kernel이 하드웨어를 운영 관리하여 서비스를 제공한다.
- 프로세스 관리
- 여러 개의 응용 프로그램이 실행될 수 있도록 프로세스들을 스케줄링하여 동시에 수행되는 것처럼 보이는 효과를 낸다.
- 파일 관리
- 디스크와 같은 저장장치 상에 파일 시스템을 구성하여 파일을 관리한다.
- 메모리 관리
- 메인 메모리가 효과적으로 사용될 수 있도록 관리한다.
- 주변장치 관리
- 모니터, 키보드, 마우스 같은 장치를 사용할 수 있도록 관리한다.
응용 프로그램은 실행하는 중에 운영체제의 서비스가 필요할 때마다 시스템 호출을 통하여 운영체제에 서비스를 요청한다.
System Call
시스템 호출(System Call)은 운영 체제가 제공하는 서비스에 대한 프로그래밍 인터페이스이다. 응용 프로그램은 시스템 호출을 통해 필요할 때마다 커널에 서비스를 요청하며 결과적으로 시스템 호출은 응용 프로그램과 커널 사이의 인터페이스 역할을 한다. 응용 프로그램에서 파일을 사용하기 위해서는 파일에 직접 접근할 수 없으며 반드시 커널에 파일에 대한 서비스 예를 들면 파일 열기, 읽기, 쓰기 등을 요청해야 한다. 커널은 이러한 서비스 요청을 받으면 요청에 따라 파일 열기, 읽기, 쓰기 등을 수행한 후 결과를 응용 프로그램에게 돌려준다. 응용 프로그램은 사용자 모드(user mode)에서 실행하다가 시스템 호출을 하면 커널 모드(kernel mode)로 이동하여 커널 내의 코드를 실행한 후 다시 사용자 모드로 돌아온다. 사용자 모드에서는 실행할 수 있는 기계어 명령어에 제한이 있지만 커널 모드에서는 어떠한 기계어 명령어도 실행이 가능하다. 사용자 모드에서 실행중인 프로세스를 사용자 프로세스, 커널 모드로 실행중인 프로세스를 커널 프로세스라고 한다.
시스템 호출 동작
사용자 프로세스에서 만약에 open() 시스템을 호출을 하면 C실행시간 라이브러리(C runtime library)를 통해 커널내의 해당 코드로 점프하게 된다. C 실행시간 라이브러리에서는 커널로 점프하기 위해 필요한 작업을 미리 하는데 가장 대표적인 일이 레지스터를 통해 매개변수를 전달한다. 그 후에 trap명령으를 이용해 커널로 점프하고 각 시스템 호출의 시작 주소를 저장하고 있는 벡터 테이블을 통해 해당 시스템 호출의 커널 코드로 점프하게 된다. 이후 해당 커널 코드를 실행후 호출의 역순으로 값을 리턴한다.
예를들어 read(fd, buff, len) 함수는 파일 디스크립터로부터 데이터를 읽어서 버퍼에 저장한다. Application이 Kernel에게 메시지를 read라는 메시지를 fd, buff, len을 담아 보내고 Kernel이 메시지에 대한 응답을 하게되어있다. 이 예시는 read()에서의 예시이지만 다양한 커널이 인터페이스화를 통해 각 시스템 호출단에서의 제공을 해준다.
- fd(파일 디스커럽터)
- 읽을 파일을 식별하는 정수이다.
- buff
- 데이터를 읽어 저장할 메모리 공간이다. 이 매개변수는 char, void*로 전달이 된다.
- len
- 읽을 최대 바이트 수이다. 이 값만큼 데이터를 읽어오며, 실제 읽은 데이터 크기는 read함수가 반환한다.
예시코드
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/kernel.h>
#include <linux/types.h>
ssize_t sys_read(unsigned int fd, char __user *buf, size_t count) {
struct file *file;
ssize_t bytes_read;
file = fget(fd);
if (!file) {
return -EBADF;
}
if (!(file->f_mode & FMODE_READ)) {
fput(file);
return -EBADF;
}
bytes_read = vfs_read(file, buf, count, &file->f_pos);
fput(file);
return bytes_read;
}
리눅스 커널에서 sys_read() 시스템 호출을 처리하는 함수의 구현이다. 이 함수는 사용자가 read()를 호출할 때 파일 디스크립터로부터 데이터를 읽어와 사용자 공간에 복사하고 읽은 바이트 수를 반환한다. 먼저, fget(fd)로 파일을 찾고, 해당 파일이 읽기 모드인지 확인한다. 읽기 모드가 아니면 -EBADF 오류를 반환한다. 그런 다음 vfs_read()를 호출하여 파일에서 데이터를 읽고, 읽은 바이트 수를 bytes_read에 저장한다. 마지막으로 fput(file)로 파일을 닫고, bytes_read를 반환한다.
Reference
https://linux-kernel-labs.github.io/refs/heads/master/lectures/syscalls.html