CS

System call과 Context switch 차이

Enchantée 2026. 7. 7. 08:54
728x90
반응형

파일을 읽고, 로그를 쓰고, 네트워크 socket으로 데이터를 보내고, DB connection을 여는 일은 모두 평범한 application code처럼 보입니다.

하지만 이 작업들은 대부분 어느 순간 운영체제 kernel의 도움을 받아야 합니다.

개발자가 작성한 함수 호출만으로는 disk, network, process, memory protection 같은 자원에 직접 접근할 수 없기 때문입니다.

 

System call은 user mode에서 실행 중인 program이 kernel mode의 운영체제 기능을 안전하게 요청하는 경계입니다.

 

이번 글에서는 System call이 무엇인지, User mode와 Kernel mode는 왜 나뉘는지, Context switch와는 무엇이 다른지, 그리고 작은 IO를 너무 자주 호출하면 왜 성능 문제가 생기는지 정리해보겠습니다.

 

System call은 application code가 file, socket, process 같은 운영체제 자원을 사용하기 위해 넘어가는 경계입니다.

 


1. System call은 무엇인가?

System call은 program이 운영체제 kernel에게 작업을 요청하는 공식 인터페이스입니다.

예를 들어 file read/write, socket send/recv, process 생성, memory mapping, time 조회, permission 확인 같은 작업은 user code가 혼자 처리할 수 없습니다.

이런 작업은 kernel이 hardware와 system resource를 관리하면서 수행합니다.

작업 대표 System call 개발자가 보는 코드
파일 열기 open std::fstream, fopen, open
파일 읽기 read read, fread, input stream
파일 쓰기 write write, fwrite, output stream
네트워크 송신 send, write HTTP client, socket API
Process 생성 fork, execve, posix_spawn shell command 실행, subprocess API

C++에서 std::ofstream으로 파일에 쓰더라도 내부 어딘가에서는 운영체제에 write 계열 요청을 보내야 합니다.

다만 standard library, libc, runtime layer가 buffer를 두기 때문에 application code 한 줄이 곧바로 System call 한 번과 1:1로 대응한다고 단정하면 안 됩니다.

 


2. User mode와 Kernel mode는 왜 나뉘는가?

운영체제는 program이 hardware와 memory를 마음대로 건드리지 못하게 privilege level을 나눕니다.

일반 application은 User mode에서 실행되고, 운영체제 kernel은 Kernel mode에서 실행됩니다.

이 구분이 없다면 잘못된 pointer 하나가 다른 process memory를 깨뜨리거나, application bug가 disk, network device, scheduler 상태를 직접 망가뜨릴 수 있습니다.

구분 User mode Kernel mode
실행 주체 일반 application code 운영체제 kernel code
접근 권한 제한된 memory와 API hardware와 system resource 관리 권한
실패 영향 보통 해당 process 종료 system 전체 안정성에 영향 가능
대표 작업 비즈니스 로직, 계산, 데이터 변환 file system, network, scheduler, memory manager

System call은 이 경계를 안전하게 넘는 통로입니다.

CPU는 special instruction이나 trap을 통해 Kernel mode로 진입하고, kernel은 요청을 확인한 뒤 작업을 수행하고 다시 User mode로 돌아옵니다.

이 과정은 일반 function call보다 비쌉니다.

권한 전환, argument 검증, kernel 자료구조 접근, scheduler 개입 가능성, cache/TLB 영향 같은 비용이 함께 따라올 수 있기 때문입니다.

 


3. 그림으로 이해하기

System call의 흐름을 단순화하면 다음처럼 볼 수 있습니다.

 

User mode의 application은 System call boundary를 통해 Kernel mode의 file system, network, scheduler 같은 기능을 요청합니다.

 

중요한 점은 System call이 단순히 library function을 호출하는 것과 다르다는 것입니다.

std::strlen 같은 함수는 같은 User mode 안에서 실행될 수 있지만, read나 write는 kernel boundary를 넘어갈 수 있습니다.

그래서 호출 횟수와 호출 위치가 성능에 영향을 줍니다.

 


4. Context switch와는 무엇이 다른가?

System call과 Context switch는 자주 함께 언급되지만 같은 말은 아닙니다.

System call은 User mode에서 Kernel mode로 들어가 운영체제 기능을 요청하는 경계 전환입니다.

Context switch는 CPU가 실행하던 thread나 process를 바꾸기 위해 register, stack pointer, program counter 같은 실행 문맥을 저장하고 복원하는 작업입니다.

구분 System call Context switch
핵심 의미 kernel 기능 요청 실행 주체 변경
전환 방향 User mode에서 Kernel mode로 진입 후 복귀 Thread A에서 Thread B로 CPU 실행 대상 변경
항상 함께 발생? 아님 아님
예시 write가 kernel buffer에 데이터를 복사하고 바로 반환 blocking read로 잠든 thread 대신 다른 thread 실행

예를 들어 write System call이 매우 빨리 끝나면 같은 thread가 다시 User mode로 돌아올 수 있습니다.

이 경우 mode switch는 있었지만 scheduler가 다른 thread로 바꾸는 Context switch는 없을 수 있습니다.

반대로 read가 network data를 기다리며 blocking되면 kernel은 현재 thread를 재우고 다른 thread를 실행할 수 있습니다.

이때는 System call 도중 Context switch가 함께 발생할 수 있습니다.

 


5. C++ 코드로 연결해보기

다음 예제는 Unix-like 환경에서 작은 write를 여러 번 호출하는 경우와, 데이터를 모아 한 번에 write하는 경우를 비교합니다.

실무 코드에서는 partial write, EINTR, error handling을 더 정교하게 처리해야 하지만, 여기서는 System call 횟수를 줄이는 감각을 보기 위한 예제입니다.

#include <fcntl.h>
#include <unistd.h>

#include <cstring>
#include <iostream>
#include <string>

int main()
{
    const char* path = "/tmp/syscall-demo.txt";
    int fd = ::open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        std::cerr << "open failed\n";
        return 1;
    }

    const char* line = "hello\n";
    int tiny_write_calls = 0;

    for (int i = 0; i < 5; ++i) {
        if (::write(fd, line, std::strlen(line)) == -1) {
            std::cerr << "write failed\n";
            ::close(fd);
            return 1;
        }
        ++tiny_write_calls;
    }

    std::string batch;
    for (int i = 0; i < 5; ++i) {
        batch += "hello\n";
    }

    int batched_write_calls = 0;
    if (::write(fd, batch.data(), batch.size()) == -1) {
        std::cerr << "write failed\n";
        ::close(fd);
        return 1;
    }
    ++batched_write_calls;

    ::close(fd);

    std::cout << "tiny write calls: " << tiny_write_calls << '\n';
    std::cout << "batched write calls: " << batched_write_calls << '\n';
    std::cout << "output file: " << path << '\n';
}

 

실행 결과

tiny write calls: 5
batched write calls: 1
output file: /tmp/syscall-demo.txt

 

이 예제의 핵심은 문자열을 출력하는 방법이 아니라 System call boundary를 넘는 횟수입니다.

작은 write를 반복하면 kernel에 들어가는 횟수가 늘어납니다.

반대로 데이터를 모아서 한 번에 넘기면 boundary crossing 비용을 줄일 수 있습니다.

실제 application에서는 standard library buffer, OS page cache, network buffer, storage 특성까지 함께 작동하므로 결과를 단순화해서 해석하면 안 됩니다.

 


6. 실무에서는 어디서 문제가 될까?

System call 비용은 하나하나가 항상 큰 병목이라는 뜻은 아닙니다.

문제는 높은 빈도와 작은 단위가 결합될 때 자주 나타납니다.

특히 server code에서는 다음 패턴을 조심해야 합니다.

  1. 작은 log를 매번 flush한다.
    요청마다 log를 즉시 disk로 밀어 넣으면 write, fsync, scheduler 비용이 커질 수 있습니다.
  2. network packet을 너무 작은 단위로 보낸다.
    작은 send가 반복되면 syscall overhead와 packet overhead가 함께 커질 수 있습니다.
  3. file을 반복해서 열고 닫는다.
    open과 close도 kernel resource 관리가 필요한 작업입니다. 자주 반복되면 cache나 pool을 검토해야 합니다.
  4. blocking IO가 많은 thread를 무작정 늘린다.
    thread 수가 늘면 scheduler와 Context switch 비용, stack memory 사용량이 함께 커질 수 있습니다.
  5. 측정 없이 API만 바꾼다.
    std::fstream을 write로 바꿔도 buffer 전략이 나쁘면 문제는 그대로 남을 수 있습니다.

실무에서는 System call 자체보다 호출 패턴을 먼저 봐야 합니다.

요청당 read/write 횟수, flush 횟수, blocking 지점, thread wake-up 횟수 같은 값이 실제 성능을 설명하는 경우가 많습니다.

DB client나 HTTP client를 사용할 때도 connection pool, batching, buffering, timeout 설정이 중요한 이유가 여기에 있습니다.

 


7. 면접 질문 예시

이 주제는 운영체제 기본기와 실무 성능 감각을 함께 묻기 좋습니다.

  1. System call과 일반 function call의 차이를 설명해보세요.
    일반 function call은 보통 같은 User mode 안에서 실행되지만, System call은 kernel 기능을 요청하기 위해 User mode와 Kernel mode 경계를 넘는다고 설명하면 좋습니다.
  2. System call이 발생하면 항상 Context switch가 일어나나요?
    항상 그렇지 않습니다. 빠르게 처리되고 같은 thread로 돌아올 수도 있고, blocking이 발생하면 scheduler가 다른 thread를 실행하면서 Context switch가 일어날 수 있습니다.
  3. 작은 write를 많이 호출하면 왜 성능 문제가 될 수 있나요?
    호출마다 kernel boundary를 넘고 kernel 자료구조에 접근해야 하므로 호출 횟수가 많아지면 overhead가 커집니다. batching과 buffering으로 호출 횟수를 줄일 수 있습니다.

좋은 답변은 “System call은 느리다”에서 끝나지 않습니다.

어떤 boundary를 넘는지, 어떤 경우 Context switch까지 이어지는지, 그리고 application code에서 호출 패턴을 어떻게 줄일 수 있는지까지 연결해야 합니다.

 


8. 자주 하는 오해

  1. System call은 항상 Context switch를 만든다.
    System call은 kernel mode 진입이고, Context switch는 실행 thread가 바뀌는 것입니다. 둘은 연결될 수 있지만 같은 개념은 아닙니다.
  2. System call은 무조건 나쁘다.
    운영체제 자원을 안전하게 쓰려면 System call은 필요합니다. 문제는 불필요하게 자주 호출하거나 작은 단위로 반복하는 패턴입니다.
  3. library function call은 전부 System call이다.
    std::strlen 같은 계산 함수는 User mode 안에서 끝날 수 있습니다. 반면 file, socket, process 관련 함수는 내부에서 System call을 사용할 수 있습니다.
  4. buffer를 쓰면 항상 빠르다.
    buffer는 호출 횟수를 줄일 수 있지만 latency 요구사항이 강한 logging, streaming, realtime path에서는 flush 시점과 메모리 사용량을 함께 봐야 합니다.

핵심은 System call을 피해야 할 대상으로만 보는 것이 아니라, 어느 지점에서 kernel boundary를 넘는지 의식하는 것입니다.

 


9. 정리

System call은 application이 운영체제 kernel 기능을 요청하는 공식 경계입니다.

User mode와 Kernel mode는 system resource를 안전하게 보호하기 위해 나뉩니다.

System call은 Context switch와 다르며, blocking 상황에서는 Context switch로 이어질 수 있습니다.

작은 IO를 반복하면 kernel boundary를 넘는 횟수가 늘어 성능 문제가 될 수 있습니다.

실무에서는 API 이름보다 read/write 횟수, flush 시점, batching, blocking 여부를 함께 봐야 합니다.

 

System call을 이해하면 IO 성능, blocking, thread scheduling 문제를 application code와 운영체제 경계에서 함께 해석할 수 있습니다.

 


728x90
반응형