Linux System Call 이름 읽는 법: execve·openat·fsync·epoll 의미와 경계

반응형

Linux system call 이름에는 get, set, at, fd, pid, uid, ctl, stat, sync처럼 반복되는 조각이 많다. 이름을 분해하면 첫 방향은 잡을 수 있지만, 철자를 임의의 약어로 풀어 기능 전체를 단정하면 오해가 생긴다. 가장 안전한 기준은 현재 architecture와 libc의 manual page다.

먼저 구분할 것: System Call과 libc Wrapper

application이 호출하는 C function 이름과 kernel system-call entry가 항상 일대일은 아니다.

  • fork() 같은 libc wrapper가 내부에서 다른 syscall을 사용할 수 있다.
  • exit()는 cleanup을 수행하는 libc function이고, _exit()/_Exit()는 더 직접적인 종료 primitive다. Linux thread group 전체 종료에는 exit_group syscall이 관여할 수 있다.
  • Python의 open()read()는 buffering·encoding을 포함한 runtime API다. source 한 줄이 syscall 한 번과 같지 않다.
  • available syscall과 number, calling convention은 architecture와 kernel version에 따라 다를 수 있다.

strace에서 보인 실제 호출과 language API 문서를 함께 봐야 하는 이유다.

자주 보이는 이름 조각

조각 흔한 의미 예시
get / set 상태 조회 / 변경 getpid, setuid
pid, ppid process ID / parent process ID waitpid, getppid
uid, gid user ID / group ID getuid, setgid
fd file descriptor eventfd, signalfd
at dirfd를 기준으로 pathname 해석 openat, fstatat
stat file metadata fstat, newfstatat
ctl operation·configuration control ioctl, fcntl, shmctl
sync dirty state의 persistence 요청 fsync, fdatasync
map / unmap virtual address mapping 생성 / 해제 mmap, munmap

이 표는 이름을 읽는 단서다. 예를 들어 epoll을 공식적으로 ‘efficient poll’의 약어라고 단정하거나 lseekl을 현재 동작의 계약처럼 해석할 필요는 없다.

Process와 Identity

API 실제 핵심 의미 주의할 경계
fork() caller와 별도 child process를 만든다 Linux의 copy-on-write는 구현 최적화이며 parent·child 실행 순서는 정해지지 않는다
execve() 현재 process image를 새 program으로 바꾼다 성공하면 새 process를 하나 더 만들지 않고 같은 PID에서 원래 호출로 돌아오지 않는다
waitpid() 지정 조건에 맞는 child state change를 기다리거나 회수한다 종료 child를 회수하지 않으면 zombie가 남을 수 있다
getpid() / getppid() process·parent process ID를 조회한다 namespace에 따라 보이는 PID가 다를 수 있다
getuid() / getgid() real user·group ID를 조회한다 effective·saved·filesystem ID와 구분한다
setuid() / setgid() credential을 변경한다 privilege와 현재 credential에 따라 바뀌는 ID와 복구 가능성이 달라 보안 검토가 필요하다

execvev는 argument vector, e는 environment vector를 받는 exec-family 형태를 읽는 단서다. execve(path, argv, envp)의 세 번째 인자는 단순 ‘환경 변수를 전달한다’보다 새 program의 environment를 명시하는 배열이다.

File과 Path

API 의미 자주 놓치는 점
openat() absolute path 또는 dirfd 기준 relative path를 연다 AT_FDCWD와 pathname 규칙, symlink·race 방어는 flag와 openat2 정책까지 봐야 한다
lseek() open file description의 file offset을 바꾼다 pipe처럼 seek할 수 없는 fd가 있고 sparse file hole을 만들 수도 있다
fstat() fd가 가리키는 file metadata를 읽는다 pathname lookup이 아니라 이미 연 fd 기준이다
chmod() / chown() mode bit / owner·group을 바꾼다 ACL, capability, mount option, privilege가 최종 접근을 함께 결정한다
truncate() / ftruncate() file size를 지정 길이로 바꾼다 줄일 때 data가 사라지고 늘릴 때 hole과 zero-read semantics가 생길 수 있다
fsync() file data와 retrieval에 필요한 metadata의 storage flush를 요청한다 새 file 이름·rename의 directory entry durability에는 directory fsync가 별도로 필요할 수 있다

fsync() 성공을 모든 storage stack에서 영구 보존이 절대 보장됐다는 문장으로 넓히지 않는다. filesystem, device write cache, power-loss protection의 보증 범위도 확인한다.

Virtual Memory와 System V Shared Memory

  • mmap()은 file 또는 anonymous object를 process virtual address space에 mapping한다. 호출 성공 시 모든 page가 즉시 physical memory에 resident한다는 뜻은 아니다.
  • munmap()은 지정 address range의 mapping을 제거한다. 이후 접근은 fault가 된다.
  • shmget()은 System V shared-memory segment identifier를 얻거나 조건에 따라 새 segment를 만든다.
  • shmat()은 segment를 caller address space에 attach한다.
  • shmdt()는 attach를 해제한다. segment 자체 삭제와는 다르다.
  • shmctl()은 metadata 조회·설정이나 IPC_RMID 같은 control operation을 수행한다.

POSIX shared memory는 shm_open()mmap()을 사용하는 다른 API family다. 이름이 비슷해도 System V IPC와 lifecycle을 섞지 않는다.

File Descriptor Control과 I/O Readiness

API 의미 경계
ioctl() device·fd별 operation을 request code로 제어 argument ABI가 device마다 다르고 untrusted input boundary가 될 수 있다
fcntl() fd flag, status flag, lock 등 다양한 operation FD_CLOEXECO_NONBLOCK은 서로 다른 층의 flag다
poll() 여러 fd의 readiness event를 기다린다 readiness는 I/O 완료나 다음 operation의 무조건 성공을 뜻하지 않는다
epoll Linux의 interest list 기반 I/O event facility fd 수만으로 항상 poll보다 빠르다고 단정할 수 없고 level·edge-trigger semantics가 중요하다

edge-triggered epoll에서는 nonblocking fd를 사용하고 EAGAIN까지 drain하는 pattern이 일반적이다. event를 한 번 받았다는 이유로 일부만 읽고 멈추면 다음 wakeup을 놓칠 수 있다.

Socket 이름은 endpoint 관점으로 읽는다

  • socket()은 communication endpoint를 만들고 fd를 반환한다.
  • getsockname()은 local address를 조회한다.
  • getpeername()은 connected peer address를 조회한다.
  • setsockopt()getsockopt()은 protocol level별 option을 설정·조회한다.

option 이름이 같아 보여도 level, value type, inheritance, 적용 시점이 다를 수 있다. buffer size 요청값과 kernel이 보고하는 실제값이 그대로 일치한다고 가정하지 않는다.

Time API

  • gettimeofday()는 wall-clock time을 읽지만 discontinuous하게 조정될 수 있다.
  • settimeofday()는 system wall clock을 바꾸는 privileged operation이다.
  • clock_gettime()CLOCK_REALTIME, CLOCK_MONOTONIC 등 목적별 clock을 선택한다.
  • nanosleep()의 인자는 nanosecond resolution을 표현하지만 scheduler가 정확히 그 시각에 task를 다시 실행해 준다는 보장은 아니다.

duration 측정과 timeout에는 보통 monotonic clock을 사용한다. wall clock은 NTP·관리자 변경에 따라 앞뒤로 움직일 수 있다.

실제 process 생성 흐름은 OSTEP fork·exec·wait 학습노트, system call 진입과 mode 전환은 Limited Direct Execution 학습노트로 이어갈 수 있다.

자주 묻는 질문

System call 이름의 약어를 알면 동작을 다 알 수 있나

아니다. 이름은 힌트일 뿐이다. argument, return value, error, thread·process scope, version 차이는 manual page에서 확인해야 한다.

fsync 뒤에는 data가 무조건 안전한가

file data와 필요한 metadata flush를 요청하는 핵심 primitive지만 directory entry와 device cache·filesystem의 보증을 함께 봐야 한다. application의 crash-consistency protocol은 호출 한 번보다 넓다.

epoll은 언제나 poll보다 빠른가

아니다. workload, ready fd 비율, update pattern, wakeup과 implementation에 따라 다르다. 성능보다 먼저 level/edge-trigger의 correctness와 backpressure를 검증한다.

참고 자료

반응형
KEEP READING
카테고리 전체 보기 →

댓글