In Veritate Libertas

The Restricted
Section

A collection of spells,
experiments, and forbidden knowledge.

Study/[ OS ] | 2026. 6. 24. 20:27

#06 시스템콜 System Call

728x90

시스템 콜 — 유저 프로그램이 OS한테 부탁하는 방법

지금까지 인터럽트, 컨텍스트 스위칭을 다뤘는데 계속 "시스템 콜"이라는 말이 끼어들었어. 파일 읽기, 프로세스 생성, I/O 처리 — 전부 시스템 콜 얘기였거든. 이번엔 이걸 제대로 파보자.


OS는 왜 중간에 끼어있어?

먼저 OS가 전체 시스템에서 어떤 위치에 있는지 보면 이해가 빨라.

유저 프로그램은 하드웨어에 직접 접근할 수 없어. 반드시 OS를 통해야 해. 이 구조가 있어야 어떤 앱이 실수로 (또는 악의적으로) 다른 프로세스 메모리를 건드리거나 하드웨어를 망가뜨리는 걸 막을 수 있어.

그 경계를 만드는 게 이중 동작 모드(Dual-mode Operation)야.

 

면접 포인트 ( •̀ ω •́ )✧

Q. 이중 동작 모드(Dual-mode)가 왜 필요해?

더보기

유저 프로그램이 하드웨어나 다른 프로세스 메모리에 함부로 접근하는 걸 막기 위해서야. 유저 모드는 접근 범위가 제한되고, 커널 모드만 모든 자원에 접근할 수 있어. 이 구분이 OS 보안의 기반이야.


커널이 뭔지 모를 수? 있어! 기죽지마.

OS 전체가 항상 메모리에 올라가 있으면 낭비가 심해. 그래서 OS 중에서 항상 필요한 부분만 컴퓨터가 켜지는 순간부터 메모리에 올려놓아. 그 부분이 커널이야.

나머지 OS 기능들은 필요할 때 메모리에 올라왔다가 다시 내려가.

 

커널이 하는 일은 크게 세 가지야.

보안 — 유저 프로그램이 하드웨어나 다른 프로세스 자원에 함부로 접근하지 못하게 막아. 이를 위해 유저 모드 / 커널 모드를 나눠서 관리해.

자원 관리 — CPU, 메모리, I/O 장치 같은 컴퓨터 자원을 여러 프로그램이 공평하고 효율적으로 쓸 수 있게 관리해. CPU 스케줄링, 메모리 관리, 입출력 관리, 파일 시스템 관리가 전부 여기 해당해.

추상화 — 하드웨어마다 동작 방식이 달라. 그걸 유저 프로그램이 신경 쓰지 않아도 되도록 일관된 인터페이스로 감춰줘.

처음엔 "커널이랑 OS가 뭐가 달라?" 싶었어. 비유하자면 OS는 회사 전체고, 커널은 그 중에서 24시간 상주하며 핵심 업무만 처리하는 팀이야. 임원진이 필요할 때만 출근하는 나머지 직원들을 관리하는 것처럼.


유저 모드 vs 커널 모드

OS는 두 가지 모드로 동작해.

 

유저 모드 (User Mode) — modebit = 1

일반 프로그램이 실행되는 모드야. 접근 가능한 자원이 제한돼 있어. CPU 명령어 중 위험한 것들은 여기서 실행할 수 없어. 하드웨어에 직접 접근하려면 반드시 OS한테 요청해야 해.

 

커널 모드 (Kernel Mode) — modebit = 0

OS가 실행되는 모드야. 수퍼바이저 모드, 특권 모드라고도 불러. 모든 메모리에 접근 가능하고 모든 CPU 명령을 실행할 수 있어. 커널 코드, 디바이스 드라이버가 여기서 돌아.

 

 

디바이스 드라이버가 커널의 일부로 동작한다는 게 처음엔 낯설었어. "그냥 하드웨어 제어 소프트웨어 아닌가?" 싶었는데, 드라이버가 유저 모드에서 실행되면 하드웨어 레지스터에 직접 접근을 못 해. 그래서 커널 모드에서 실행되어야 하는 거야.

 

modebit 하나로 이 두 모드를 구분해. 시스템 콜이 발생하면 modebit이 1 → 0으로 전환되고, 처리가 끝나면 다시 0 → 1로 돌아와.


시스템 콜이 뭐야?

유저 프로그램이 OS 서비스를 요청하기 위한 프로그래밍 인터페이스야.

파일 읽기, 프로세스 생성, 네트워크 통신 — 이런 작업들은 전부 하드웨어에 접근해야 해. 유저 모드에서는 직접 못 하니까 OS한테 "이거 해줘"라고 요청하는 창구가 필요한 거야. 그게 시스템 콜이야.

각 시스템 콜에는 번호가 할당되어 있고, 이 번호로 인덱스된 테이블(sys_call_table)로 관리돼.

보통 직접적으로 시스템 콜을 사용하기보다 API(라이브러리 함수)를 통해 사용하게 돼.

직접 쓰지 않고 API를 통해 쓰는 이유

// 시스템 콜 직접 호출 (Linux x86)
movl 2, %eax   // sys_fork = 2번
int $0x80      // 소프트웨어 인터럽트 발생

// API(라이브러리) 통해 호출
fork();        // 내부적으로 위의 과정을 알아서 해줌

OS마다 시스템 콜 번호, 호출 방식이 달라. Windows는 CreateProcess(), Unix는 fork(). 직접 시스템 콜을 쓰면 코드가 특정 OS에 종속돼버려.

API를 쓰면 같은 코드로 여러 OS에서 돌아가게 할 수 있어. 이게 이식성(Portability)이야. printf()가 Windows에서도 Linux에서도 동작하는 이유가 내부적으로 각 OS의 시스템 콜을 알아서 호출해주기 때문이야.

 

printf() 쓸 때 "파일에 쓰는 거랑 뭐가 달라?"라고 생각했는데, 실제로 printf()도 내부적으로는 write() 시스템 콜을 써. 차이는 printf()가 포맷 파싱, 버퍼링 등을 먼저 처리해주고 마지막에 write()를 호출한다는 거야. 래퍼 함수(Wrapper Function)라고 부르는 이유가 이거야.

 

 

면접 포인트 ( •̀ ω •́ )✧

Q. 시스템 콜이란? 

더보기

유저 프로그램이 OS의 서비스(파일 I/O, 프로세스 생성 등)를 사용하기 위한 프로그래밍 인터페이스야. 유저 모드에서 직접 하드웨어에 접근할 수 없으니 OS에 요청하는 창구가 필요하고 그게 시스템 콜이야.

Q. 시스템 콜을 직접 쓰지 않고 API를 쓰는 이유는?

더보기

OS마다 시스템 콜 번호와 호출 방식이 달라. API를 통하면 OS별 차이를 추상화해서 같은 코드로 여러 OS에서 동작할 수 있어. 이식성(Portability) 때문이야.

Q. 서로 다른 시스템 콜을 어떻게 구분해?

더보기

각 시스템 콜에 번호가 할당되어 있어. 호출 시 레지스터(eax)에 번호를 담고, sys_call_table에서 번호를 인덱스로 해당 함수를 찾아 실행해.


시스템 콜 동작 과정

fork() 호출을 예시로 전체 흐름을 따라가 보면 이래.

단계별로 따라가면 이래.

  1. 유저 프로그램이 fork()를 호출해 (실제론 libc의 래퍼 함수)
  2. libc가 내부적으로 int $0x80 명령을 실행해 — 소프트웨어 인터럽트(트랩) 발생
  3. IDT(Interrupt Descriptor Table)에서 0x80 인터럽트 → system_call()이라는 걸 확인
  4. eax 레지스터에 담긴 시스템 콜 번호(2 = sys_fork)로 sys_call_table에서 실제 함수를 찾아 실행
  5. 실행 완료 후 결과를 레지스터에 담고 유저 모드로 복귀

 

인터럽트 글에서 "소프트웨어 인터럽트(트랩)"가 시스템 콜이라고 했는데, 이제 왜 그런지 연결되지? int $0x80이 바로 그 트랩이야. IDT에서 처리하는 방식도 하드웨어 인터럽트랑 완전히 같은 구조야.

 

매개변수 전달 방식 — 시스템 콜을 호출할 때 파라미터도 넘겨야 해. 세 가지 방법이 있어.

  • 레지스터에 직접 담기 → 레지스터 수 제한이 있어서 파라미터가 많으면 한계
  • 메모리 블록에 담고 주소만 레지스터에 → 크기 제한 없어서 가장 많이 씀
  • 스택에 담기 → 결국 레지스터로 옮겨야 해서 1번과 큰 차이 없음

실제로는 2번째 방식(메모리 주소 전달)을 주로 써.

 

면접 포인트 ( •̀ ω •́ )✧

Q. 시스템 콜 동작 과정을 설명해봐.

더보기

유저 프로그램 → libc 래퍼 함수 호출 → int $0x80 트랩 발생 → IDT에서 system_call() 확인 → sys_call_table에서 번호로 실제 함수 찾기 → 커널 모드에서 실행 → 결과를 레지스터에 담아 유저 모드로 복귀.


시스템 콜의 종류

OS가 제공하는 시스템 콜은 크게 6가지로 분류해.

분류  설명  Unix 예시  Windows 예시
프로세스 제어 프로세스 생성·종료·대기 fork(), exit(), wait() CreateProcess(), ExitProcess()
파일 조작 파일 열기·읽기·쓰기 open(), read(), write() CreateFile(), ReadFile()
디바이스 조작 하드웨어 제어 ioctl(), read(), write() SetConsoleMode(), ReadConsole()
정보 유지 시스템 정보 조회·설정 getpid(), alarm(), sleep() GetCurrentProcessID(), Sleep()
통신 프로세스 간 통신 pipe(), mmap() CreatePipe(), MapViewOfFile()
보호 접근 권한 제어 chmod(), chown() SetFileSecurity()

같은 기능이 Unix와 Windows에서 이름이 다른 게 보이지? 이래서 API 레이어가 필요한 거야. 우리가 쓰는 언어의 표준 라이브러리가 이 OS별 차이를 추상화해서 같은 코드로 여러 OS에서 돌아가게 해줘.

 

실제 예시 — cp in.txt out.txt

파일 복사 명령 하나에도 시스템 콜이 여러 번 발생해.

1. 터미널에서 입력 받기              → I/O 시스템 콜
2. in.txt 존재 여부 확인             → 파일 조작 시스템 콜
3. 파일 없으면 에러 출력 후 종료     → 프로세스 제어 시스템 콜
4. out.txt 이름 중복 확인            → 파일 조작 시스템 콜
5. 파일 읽기 루프 (Read → Write)     → 파일 조작 시스템 콜 반복
6. 복사 완료 후 종료 메시지 출력     → I/O + 프로세스 제어 시스템 콜

단순해 보이는 cp 명령 하나가 실제로는 수십 번의 시스템 콜을 발생시켜.

 

fork()에 대해 추가로

프로세스 제어에서 자주 나오는 fork()는 현재 프로세스를 복제해서 자식 프로세스를 만드는 시스템 콜이야.

pid_t pid = fork();

if (pid == 0) {
    // 자식 프로세스 — pid가 항상 0
} else if (pid > 0) {
    // 부모 프로세스 — 자식의 실제 pid 반환
} else {
    // fork 실패 — -1 반환
}

fork()는 pid 값을 제외한 나머지를 전부 복제해. 자식 프로세스의 pid는 무조건 0이야. -1이면 fork 실패를 의미해.


오늘 공부는 여기까지 끝!!

728x90

'Study > [ OS ]' 카테고리의 다른 글

#08 동기화(뮤텍스, 세마포어, 스핀락)  (0) 2026.07.02
#07 PCB & Context Switching  (0) 2026.07.02
#05 인터럽트 Interrupt  (0) 2026.06.23
#04 CPU 스케줄링 알고리즘  (0) 2026.06.22
#03 프로세스와 스레드  (0) 2026.06.20
흐이서

흐이서

Writer

공순이 흐이서