x86_64 Linux 시스템 콜 전체 목록을 13개 카테고리(파일 I/O, 프로세스(Process), 메모리, 네트워크, 시그널, IPC, 시간, 보안, 비동기 I/O 등)로 정리합니다. 각 시스콜은 레지스터(Register) 기반 호출 규약(Calling Convention)으로 전달되며, 어셈블리(Assembly)에서 직접 호출하거나 glibc 래퍼로 접근할 수 있고 실시간(Real-time) 관련 제어 시스콜도 포함됩니다. 각 항목마다 syscall 번호, 이름, C 함수 시그니처, 한국어 설명을 제공하여 빠른 조회가 가능합니다. 동작 원리와 진입 경로는 시스템 콜 (System Call) 문서를 참고하세요.
전제 조건:시스템 콜 (System Call) 문서를 먼저 읽으세요.
SYSCALL/SYSRET 명령어, 특권 링(Privilege Ring), 진입 경로(entry_SYSCALL_64), vDSO, seccomp 등 동작 원리가 상세히 설명되어 있습니다. 이 페이지는 "무엇이 있는가"를 빠르게 조회하는 레퍼런스이며, "어떻게 동작하는가"는 해당 문서를 참고하세요.
일상 비유: 시스템 콜은 도서관 사서에게 책을 요청하는 것과 같습니다.
독자(사용자 프로그램)는 서가(하드웨어·커널 자원)에 직접 손을 댈 수 없고, 반드시 정해진 창구(syscall 인터페이스)를 통해 사서(커널)에게 부탁해야 합니다. 사서는 이용 규정(권한·유효성 검사)을 확인한 뒤 허가된 작업만 수행하고 결과를 돌려줍니다. 창구 번호(syscall 번호)가 어떤 서비스를 요청하는지 결정하고, 이름(직접 책 이름 전달)이나 주소(포인터)는 인자로 전달합니다.
핵심 요약
파일 디스크립터(File Descriptor, fd) — 열린 파일·소켓·파이프를 가리키는 정수 번호. 0(stdin), 1(stdout), 2(stderr)는 기본 할당되며, 프로세스마다 독립적인 테이블로 관리됩니다.
errno — syscall 실패 시 커널이 rax에 넣는 음수 오류 코드. glibc 래퍼가 -errno를 errno 전역 변수에 양수로 변환하고 -1을 반환합니다. 성공 시 rax ≥ 0입니다.
syscall 번호 — rax 레지스터에 넣는 정수로 어떤 syscall을 호출할지 결정합니다. arch/x86/entry/syscalls/syscall_64.tbl(Linux 6.11 이전) 또는 scripts/syscall.tbl(6.11 이후)에 정의됩니다.
블로킹(Blocking) vs 비블로킹(Non-blocking) I/O — 블로킹은 데이터가 준비될 때까지 프로세스가 슬립합니다. 비블로킹(O_NONBLOCK)은 즉시 EAGAIN을 반환하며, epoll/poll과 함께 이벤트 루프에서 활용합니다.
vDSO(virtual Dynamic Shared Object) — clock_gettime 등 자주 호출되는 읽기 전용 syscall을 커널 진입 없이 사용자 공간에서 직접 실행하는 최적화 기법. 일반 syscall 대비 3~10배 빠릅니다.
단계별 이해 — write() 한 번이 일어나는 일
프로그램이 출력을 요청합니다 printf("hello\n")를 호출하면 glibc 내부 버퍼에 데이터가 쌓입니다. 버퍼가 가득 차거나 명시적으로 플러시(Flush)하면 write(1, buf, n) syscall로 이어집니다.
CPU가 커널 모드로 전환됩니다 SYSCALL 명령어가 링 3(사용자) → 링 0(커널)으로 특권을 상승시키고 entry_SYSCALL_64로 점프합니다. 커널은 sys_call_table[1]에서 sys_write() 함수 포인터를 찾아 호출합니다.
커널이 작업을 수행합니다 sys_write()가 사용자 버퍼를 검증(copy_from_user)하고, VFS를 거쳐 페이지 캐시(Page Cache)에 데이터를 기록합니다. 실제 스토리지 쓰기는 보통 비동기로 수행됩니다(writeback 스레드).
커널이 결과를 반환합니다
실제로 쓴 바이트 수를 rax에 넣고 SYSRET으로 사용자 공간에 복귀합니다. 반환값이 요청보다 적으면(부분 쓰기) 루프로 재시도해야 합니다. rax가 음수이면 glibc가 errno = -rax로 설정합니다.
참고: 이 페이지(Page)는 "무엇이 있는가"를 다루는 레퍼런스입니다. "어떻게 동작하는가"(SYSCALL/SYSRET 진입 경로, SYSCALL_DEFINE, vDSO, seccomp 등)는 시스템 콜 (System Call)을 참고하세요.
syscall 번호는 arch/x86/entry/syscalls/syscall_64.tbl 기준이며 커널 버전에 따라 달라질 수 있습니다.
개요
사용자 프로그램은 CPU의 특권 링 3(링 3, Ring 3)에서 실행되며 하드웨어·메모리·파일 등 운영체제 자원에 직접 접근할 수 없습니다. 커널은 이러한 자원을 보호하고 여러 프로세스 간 안전한 공유를 위해 특권 링 0(링 0, Ring 0)에서 실행됩니다. 시스템 콜(System Call)은 사용자 공간에서 커널 서비스를 요청하는 유일하게 허가된 경계 통과 방법입니다 — 사용자 프로그램이 링 3에서 링 0 기능이 필요할 때마다 SYSCALL 명령어를 통해 커널에 요청하고, 커널이 검증 후 처리한 뒤 SYSRET으로 복귀합니다. 동작 원리의 상세 설명은 시스템 콜 (System Call)을 참고하세요.
x86_64 Linux 커널에는 471개 이상의 시스템 콜이 정의되어 있습니다. 2026년 4월 21일 기준 최신 mainline인 Linux 7.0에서는 번호 471까지 할당되어 있으며, rseq_slice_yield도 이미 테이블에 포함되어 있습니다. 6.11부터 일반 syscall 번호는 scripts/syscall.tbl 단일 파일로 관리되고, 각 아키텍처 arch/*/kernel/Makefile.syscalls가 이 표의 abis 필드를 읽어 테이블·헤더를 생성합니다. 아래 카테고리별 분포와 호출 규약을 참고하세요.
최근 추가 (2023-2026):futex_wait/futex_requeue(6.7, 455~456), statmount/listmount(6.8, 457~458), lsm_get_self_attr/lsm_set_self_attr/lsm_list_modules(6.8, 459~461), mseal(6.10, 462), setxattrat/getxattrat/listxattrat/removexattrat(6.13, 463~466), open_tree_attr(6.15, 467), file_getattr/file_setattr(6.17, 468~469), listns(6.19, 470), rseq_slice_yield(7.0, 471)가 각 카테고리에 정리되어 있습니다.
syscall이 실패하면 커널은 -errno를 rax로 반환하고, glibc 래퍼가 errno 전역 변수에 양수 값을 설정한 뒤 -1을 반환합니다.
errno 이름
번호
의미
자주 발생하는 syscall
EPERM
1
권한 없음 (Operation not permitted)
setuid, capset, seccomp
ENOENT
2
파일/디렉터리 없음
open, stat, unlink
ESRCH
3
프로세스 없음
kill, waitpid
EINTR
4
시그널에 의해 중단됨
read, write, nanosleep
EIO
5
I/O 오류
read, write, fsync
ENXIO
6
장치 없음
open (디바이스)
EACCES
13
접근 거부
open, chmod, execve
EFAULT
14
잘못된 주소 (user space 포인터 문제)
read, write, 대부분
EBUSY
16
장치/리소스 사용 중
umount, rmdir
EEXIST
17
파일이 이미 존재함
open(O_CREAT|O_EXCL), mkdir
ENOTDIR
20
디렉터리가 아님
chdir, mkdir, openat
EISDIR
21
디렉터리임
open(쓰기 모드), unlink
EINVAL
22
잘못된 인자
ioctl, fcntl, mmap
EMFILE
24
프로세스가 열 수 있는 fd 한계 초과
open, socket, pipe
ENFILE
23
시스템 전체 fd 한계 초과
open, socket
ENOTTY
25
터미널 장치가 아님
ioctl (tty 전용 명령)
EPIPE
32
파이프 끊김 (읽는 쪽이 닫힘)
write (파이프/소켓)
ERANGE
34
결과가 범위 초과
getxattr, readlink
EDEADLK
35
잠금(Lock) 데드락 발생
fcntl(F_SETLKW)
ENAMETOOLONG
36
경로명이 너무 김 (PATH_MAX=4096)
open, stat, rename
ENOSYS
38
syscall이 구현되지 않음
미구현/아키텍처 불일치
ENOTEMPTY
39
디렉터리가 비어 있지 않음
rmdir
ELOOP
40
심볼릭 링크 순환
open, stat
EAGAIN / EWOULDBLOCK
11
잠시 후 재시도 (비블로킹 I/O)
read, accept, recv
ENOMEM
12
메모리 부족
mmap, fork, malloc 내부
EADDRINUSE
98
주소(포트)가 이미 사용 중
bind
ECONNREFUSED
111
연결 거부됨
connect
ETIMEDOUT
110
연결 타임아웃
connect, sendto
ENOBUFS / ENOMEM
105/12
버퍼(Buffer)/메모리 부족
socket, sendmsg
syscall 기초 용어 정리
용어
한 줄 설명
syscall 번호 (NR)
rax에 넣는 정수. 어떤 서비스를 요청할지 결정합니다. <sys/syscall.h>의 SYS_write 등 매크로로 참조합니다.
파일 디스크립터 (fd)
커널 내 struct file에 대한 프로세스별 인덱스. open()이 반환하고 close()로 해제합니다.
errno
실패 시 커널이 설정하는 오류 코드. perror()나 strerror(errno)로 사람이 읽을 수 있는 메시지로 변환합니다.
블로킹 (Blocking)
조건(데이터 수신, 잠금 해제 등)이 충족될 때까지 프로세스가 슬립합니다. 기본 동작입니다.
비블로킹 (Non-blocking)
O_NONBLOCK 설정 시 준비되지 않으면 즉시 EAGAIN을 반환합니다. 이벤트 루프 구현에 사용합니다.
원자적 (Atomic)
중간 상태 없이 완전히 성공하거나 실패합니다. O_CREAT|O_EXCL, rename() 등이 원자적 보장을 제공합니다.
부분 읽기/쓰기 (Partial I/O)
요청한 바이트보다 적게 읽거나 쓸 수 있습니다. 실제 처리 바이트 수를 반환값으로 확인하고 루프로 재시도해야 합니다.
vDSO
커널 진입 없이 사용자 공간에서 직접 실행되는 가상 공유 라이브러리(Shared Library). clock_gettime, gettimeofday 등이 이 경로로 가속됩니다.
AT_FDCWD
openat 등 *at() 계열 syscall에서 현재 작업 디렉터리(cwd)를 기준점으로 사용하라는 특수 상수입니다.
TOCTOU
시간차 공격 (Time-Of-Check Time-Of-Use) 취약점. stat() 후 open() 사이에 파일이 교체될 수 있으므로 openat()으로 한 번에 처리합니다.
파일 I/O
모든 사용자 데이터는 결국 파일을 통해 저장·전송됩니다. Linux는 모든 것이 파일(Everything is a file) 철학을 따르므로 일반 파일뿐 아니라 소켓·파이프·디바이스·/proc 가상 파일 모두 동일한 read()/write() 인터페이스로 접근합니다. read()/write() 호출은 커널 내부에서 VFS → 페이지 캐시(Page Cache) → 파일시스템 드라이버 순으로 처리됩니다. 자세한 VFS 경로는 VFS 계층을 참고하세요.
파일 오프셋을 변경하지 않고 지정 위치에 씁니다. O_APPEND 플래그와 함께 사용 시 offset이 무시됩니다.
19
readv
ssize_t readv(int fd, const struct iovec *iov, int iovcnt)
분산된 버퍼 배열(scatter)로 읽기를 수행합니다. 헤더+바디 분리 처리 시 syscall 횟수를 줄일 수 있습니다.
20
writev
ssize_t writev(int fd, const struct iovec *iov, int iovcnt)
분산된 버퍼 배열(gather)로 쓰기를 수행합니다. 소켓에서 헤더+페이로드(Payload)를 하나의 syscall로 전송할 때 유용합니다.
40
sendfile
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
파일을 소켓으로 zero-copy 전송합니다. 커널이 직접 파일 → 소켓 버퍼로 복사하여 유저 공간 왕복을 생략합니다. nginx/Apache의 정적 파일 전송에 사용합니다.
76
truncate
int truncate(const char *path, off_t length)
파일 크기를 length로 자릅니다. 크게 하면 스파스 파일(hole)이 됩니다.
77
ftruncate
int ftruncate(int fd, off_t length)
이미 열린 fd의 파일 크기를 변경합니다. 쓰기 권한으로 열려 있어야 합니다.
257
openat
int openat(int dirfd, const char *path, int flags, mode_t mode)
dirfd를 기준으로 상대 경로의 파일을 엽니다. AT_FDCWD를 넣으면 cwd 기준. TOCTOU 취약점(Vulnerability) 방지에 권장됩니다 (POSIX.1-2008).
285
fallocate
int fallocate(int fd, int mode, off_t offset, off_t len)
파일에 디스크 공간을 미리 할당하거나 구멍(hole)을 만듭니다. FALLOC_FL_KEEP_SIZE로 파일 크기 유지, FALLOC_FL_PUNCH_HOLE로 스파스 홀을 만듭니다.
326
copy_file_range
ssize_t copy_file_range(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags)
두 파일 사이에 데이터를 커널 내에서 복사합니다. Btrfs/XFS의 reflink를 활용하면 실제 복사 없이 메타데이터만 변경합니다.
275
splice
ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags)
파이프를 경유하여 두 fd 간 zero-copy 데이터 전송. sendfile보다 범용적이며 SPLICE_F_MOVE|SPLICE_F_NONBLOCK 플래그 사용.
276
tee
ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags)
파이프 간 데이터를 복제합니다 (원본 유지). 로그 분기(tee 명령어)의 기반.
278
vmsplice
ssize_t vmsplice(int fd, const struct iovec *iov, unsigned long nr_segs, unsigned int flags)
사용자 공간(User Space) 메모리 → 파이프로 zero-copy 전송. SPLICE_F_GIFT로 페이지 소유권 이전.
463
setxattrat
int setxattrat(int dirfd, const char *pathname, int at_flags, const char *name, const struct xattr_args *args, size_t size)
dirfd 기준 상대 경로 또는 FD-only(AT_EMPTY_PATH)로 확장 속성(Extended Attribute)을 설정합니다 (Linux 6.13+). struct xattr_args로 값·크기·XATTR_CREATE/REPLACE를 한 번에 전달하여 syscall 6-인자 한계를 우회합니다.
int removexattrat(int dirfd, const char *pathname, int at_flags, const char *name)
dirfd 기준으로 확장 속성을 제거합니다 (Linux 6.13+).
Linux 6.17 fallocate 확장:FALLOC_FL_WRITE_ZEROES 플래그가 추가되어 SCSI WRITE SAME·NVMe Write Zeroes 명령을 커널이 표준 경로로 발행합니다. 기존 FALLOC_FL_ZERO_RANGE가 파일시스템별 구현에 의존한 반면, 새 플래그는 디바이스가 지원하면 실제 쓰기 없이 "영역을 0으로 채운다"는 의도를 그대로 전달합니다.
open() / openat() 플래그 (flags 인자)
플래그
값(8진수)
설명
O_RDONLY
0
읽기 전용(Read-Only)으로 엽니다.
O_WRONLY
01
쓰기 전용으로 엽니다.
O_RDWR
02
읽기/쓰기 모두 가능하게 엽니다.
O_CREAT
0100
파일이 없으면 생성합니다. mode 인자로 권한을 지정합니다.
O_EXCL
0200
O_CREAT와 함께 사용 시 파일이 이미 있으면 EEXIST로 실패합니다 (원자적 생성).
O_TRUNC
01000
파일을 열면서 길이를 0으로 자릅니다.
O_APPEND
02000
쓸 때마다 파일 끝으로 이동합니다 (원자적, 로그 파일에 적합).
O_NONBLOCK
04000
비블로킹 모드로 엽니다. 데이터 없을 때 EAGAIN 반환.
O_SYNC
04010000
write마다 스토리지에 동기적으로 플러시(Flush)합니다 (데이터+메타데이터).
O_DSYNC
010000
데이터만 동기적으로 플러시합니다 (메타데이터 제외, O_SYNC보다 빠름).
O_CLOEXEC
02000000
execve() 시 fd를 자동으로 닫습니다 (fd 누수 방지, 권장).
O_DIRECTORY
0200000
디렉터리만 열고, 파일이면 ENOTDIR 반환합니다.
O_NOFOLLOW
0400000
심볼릭 링크를 따라가지 않습니다 (보안 목적).
O_TMPFILE
020200000
이름 없는 임시 파일을 생성합니다. linkat()로 나중에 이름을 붙일 수 있습니다.
O_PATH
010000000
파일을 열지 않고 경로 참조용 fd만 얻습니다 (fstat, openat 등에만 사용 가능).
O_DIRECT
040000
페이지 캐시를 우회하여 직접 I/O를 수행합니다. 정렬 제약 있음.
기본 파일 I/O 패턴
/* open → read → write → close 기본 패턴 */int fd = openat(AT_FDCWD, "file.txt", O_RDONLY | O_CLOEXEC);
if (fd < 0) { perror("openat"); exit(1); }
char buf[4096];
ssize_t n;
while ((n = read(fd, buf, sizeof(buf))) > 0) {
/* 부분 쓰기 처리: write는 요청보다 적게 쓸 수 있음 */ssize_t written = 0;
while (written < n) {
ssize_t w = write(STDOUT_FILENO, buf + written, n - written);
if (w < 0) { perror("write"); break; }
written += w;
}
}
if (n < 0) perror("read");
close(fd); /* EINTR에도 fd는 닫혀있으므로 재시도 금지 *//* sendfile: 파일 → 소켓 zero-copy */off_t off = 0;
sendfile(sock_fd, file_fd, &off, file_size);
/* pread/pwrite: 멀티스레드에서 안전한 위치 지정 I/O */ssize_t r = pread64(fd, buf, 512, offset); /* lseek 불필요 */
write() 커널 내부 경로
흔한 실수:
부분 읽기/쓰기 미처리 — read()/write()는 요청보다 적게 처리할 수 있습니다. 반드시 루프로 전체 바이트를 처리하세요.
close() 후 EINTR 재시도 — close()가 EINTR을 반환해도 fd는 이미 닫혀있습니다. 재시도하면 다른 스레드(Thread)가 열어둔 fd를 닫을 위험이 있습니다.
O_APPEND 없이 다중 프로세스 로그 쓰기 — 여러 프로세스가 같은 파일에 쓸 때 O_APPEND 없으면 데이터가 뒤섞입니다.
sendfile의 in_fd 제약 — in_fd는 반드시 mmap() 가능한 파일이어야 합니다 (파이프/소켓 불가).
파일 디스크립터 관리
파일 디스크립터(fd)는 단순한 정수이지만, 그 뒤에는 커널 내부의 복잡한 자료구조 계층이 있습니다: fd → struct file(파일 오프셋·플래그·참조 카운트) → struct inode(메타데이터·실제 데이터 위치) → 블록 장치(Block Device). 프로세스마다 고유한 fd 테이블을 가지며 0(stdin), 1(stdout), 2(stderr)는 기본 할당됩니다. fd 관리 syscall을 이해하면 셸 리다이렉션(2>&1은 내부에서 dup2(2, 1)), 파이프라인(Pipeline)(pipe() 후 자식에서 dup2()), fd 누수(Leak) 방지(O_CLOEXEC로 exec 시 자동 닫기), 멀티스레드 안전 복제(dup3·F_DUPFD_CLOEXEC의 원자적 처리)를 올바르게 구현할 수 있습니다. fork() 시 fd 테이블이 복사되고, execve() 시 O_CLOEXEC 설정된 fd는 자동으로 닫힙니다.
번호
이름
시그니처
설명
16
ioctl
int ioctl(int fd, unsigned long request, ...)
디바이스 드라이버와 특수 파일에 대한 제어 명령을 전달합니다. 파일시스템(FIOCLEX, FIONREAD), 터미널(TIOCGWINSZ), 네트워크(SIOCGIFADDR), 블록장치(BLKGETSIZE64) 등에 광범위하게 사용됩니다.
32
dup
int dup(int oldfd)
fd를 복제하여 사용 가능한 가장 낮은 번호의 새 fd를 반환합니다. 두 fd는 같은 struct file을 참조하므로 f_pos와 플래그를 공유합니다.
33
dup2
int dup2(int oldfd, int newfd)
oldfd를 지정한 newfd로 복제합니다. newfd가 이미 열려있으면 원자적으로 닫고 덮어씁니다. 셸 리다이렉션(2>&1)에 사용합니다.
72
fcntl
int fcntl(int fd, int cmd, ...)
fd의 속성 조회/변경(F_GETFL/F_SETFL), fd 플래그(F_GETFD/F_SETFD), fd 복제(F_DUPFD), POSIX 파일 잠금(File Lock)(F_SETLK/F_GETLK), 시그널(F_SETOWN/F_SETSIG) 등 다용도 제어 syscall입니다.
73
flock
int flock(int fd, int operation)
파일 전체에 advisory 잠금(LOCK_SH=공유/LOCK_EX=배타/LOCK_UN=해제)을 설정합니다. NFS에서는 동작하지 않습니다. LOCK_NB로 비블로킹 시도 가능.
int memfd_create(const char *name, unsigned int flags)
이름 없는 익명 파일을 생성합니다. tmpfs 기반이며 ftruncate+mmap으로 공유 메모리로 활용합니다 (MFD_CLOEXEC, MFD_ALLOW_SEALING).
fcntl() 주요 명령 (cmd 인자)
cmd
설명
F_DUPFD
arg 이상의 가장 낮은 번호로 fd를 복제합니다.
F_DUPFD_CLOEXEC
F_DUPFD + O_CLOEXEC 플래그를 원자적으로 설정합니다.
F_GETFD
fd 플래그(FD_CLOEXEC) 를 가져옵니다.
F_SETFD
fd 플래그를 설정합니다. arg에 FD_CLOEXEC=1 또는 0.
F_GETFL
파일 상태 플래그(O_NONBLOCK, O_APPEND 등)를 가져옵니다.
F_SETFL
파일 상태 플래그를 변경합니다. O_NONBLOCK, O_ASYNC만 변경 가능.
F_GETLK
파일 잠금 상태를 확인합니다 (POSIX advisory lock).
F_SETLK
파일 잠금을 설정/해제합니다. 즉시 실패 가능 (비블로킹).
F_SETLKW
파일 잠금을 설정합니다. 잠금 가능할 때까지 대기합니다 (블로킹).
F_OFD_SETLK
Open File Description 잠금 설정 (Linux 3.15+, 스레드(Thread) 간 안전).
F_SETOWN
SIGIO/SIGURG를 받을 프로세스/그룹을 설정합니다.
F_SETSIG
비동기 I/O 알림에 사용할 시그널 번호를 변경합니다.
F_ADD_SEALS
memfd에 sealing을 추가합니다 (F_SEAL_WRITE 등, 불변성 보장).
디렉터리 & 파일시스템
파일시스템(Filesystem)은 파일과 디렉터리의 계층 구조를 디스크에 저장하고 조회하는 방법을 정의합니다. Linux는 VFS(Virtual File System) 추상화 계층을 통해 ext4·Btrfs·XFS·NFS 등 다양한 파일시스템을 단일 인터페이스로 지원합니다. 디렉터리 항목(dentry)은 파일 이름과 inode 번호의 매핑이며, 하드 링크(Hard Link)는 같은 inode를 가리키는 여러 이름, 심볼릭 링크(Symbolic Link)는 다른 경로를 가리키는 파일입니다. 경로 기반 syscall보다 *at() 변형(openat, linkat, mkdirat, renameat2 등)이 TOCTOU 경쟁 조건(Race Condition)을 방지하고 O_CLOEXEC를 원자적으로 설정할 수 있어 보안이 중요한 코드에서 권장됩니다. 마운트(Mount)는 특정 디렉터리(마운트 포인트)에 파일시스템을 연결하는 작업으로, CAP_SYS_ADMIN 권한 또는 사용자 네임스페이스가 필요합니다.
심볼릭 링크가 가리키는 경로 문자열을 읽습니다. NUL 종료 문자가 없으므로 직접 추가해야 합니다.
90
chmod
int chmod(const char *path, mode_t mode)
파일 권한 비트를 변경합니다. 파일 소유자 또는 CAP_FOWNER만 가능합니다.
91
fchmod
int fchmod(int fd, mode_t mode)
열린 파일 디스크립터의 권한을 변경합니다.
92
chown
int chown(const char *path, uid_t owner, gid_t group)
파일의 소유자와 그룹을 변경합니다. 심볼릭 링크를 따라갑니다 (링크 자체 변경은 lchown).
258
mkdirat
int mkdirat(int dirfd, const char *path, mode_t mode)
dirfd 기준으로 디렉터리를 생성합니다. dirfd=AT_FDCWD이면 mkdir과 동일합니다.
263
unlinkat
int unlinkat(int dirfd, const char *path, int flags)
AT_REMOVEDIR 플래그로 디렉터리 삭제도 가능한 통합 unlink/rmdir입니다.
265
linkat
int linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags)
dirfd 기준으로 하드 링크를 생성합니다. AT_EMPTY_PATH로 fd를 직접 링크할 수 있습니다.
266
symlinkat
int symlinkat(const char *target, int newdirfd, const char *linkpath)
newdirfd 기준으로 심볼릭 링크를 생성합니다.
316
renameat2
int renameat2(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, unsigned int flags)
RENAME_EXCHANGE로 두 파일을 원자적으로 교환하거나, RENAME_NOREPLACE로 대상이 있으면 실패합니다.
165
mount
int mount(const char *src, const char *tgt, const char *fstype, unsigned long flags, const void *data)
파일시스템을 마운트합니다. CAP_SYS_ADMIN 또는 user namespace mount 권한이 필요합니다.
166
umount2
int umount2(const char *target, int flags)
마운트를 해제합니다. MNT_FORCE로 강제 해제, MNT_DETACH로 지연(Latency) 해제가 가능합니다.
137
statfs
int statfs(const char *path, struct statfs *buf)
파일시스템의 타입, 블록 크기, 총 블록/여유 블록, inode 수 등 통계를 가져옵니다.
78
getdents64
int getdents64(int fd, struct linux_dirent64 *dirp, int count)
디렉터리 fd에서 항목들을 읽습니다. ls, find, opendir()/readdir() glibc 함수의 기반입니다.
262
newfstatat
int newfstatat(int dirfd, const char *path, struct stat *st, int flags)
dirfd 기준 경로의 파일 메타데이터를 가져옵니다. AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW로 fd 자체 또는 링크 자체 정보 조회.
332
statx
int statx(int dirfd, const char *path, int flags, unsigned int mask, struct statx *statxbuf)
확장된 파일 메타데이터 조회 (Linux 4.11+). 생성 시각(stx_btime), 마운트 ID, 속성 플래그 등 기존 stat에 없는 정보를 제공합니다.
457
statmount
int statmount(const struct mnt_id_req *req, struct statmount *buf, size_t bufsize, unsigned int flags)
64비트 마운트 ID로 특정 마운트의 상세 속성을 조회합니다 (Linux 6.8+). listmount()로 얻은 마운트 ID와 함께 사용합니다. 기존 /proc/<pid>/mountinfo 파싱 없이 커널 직접 조회가 가능하며, idmapping 정보 조회도 지원합니다 (6.15 확장).
특정 마운트의 자식 마운트 ID 배열을 반환합니다 (Linux 6.8+). 루트 마운트에서 시작하여 마운트 트리를 순회할 수 있습니다. 컨테이너 런타임, lsblk 등 마운트 토폴로지(Topology) 분석 도구에서 사용합니다.
467
open_tree_attr
int open_tree_attr(int dirfd, const char *path, unsigned int flags, struct mount_attr *attr, size_t attr_size)
open_tree()의 확장 버전으로 struct mount_attr를 선택적으로 전달합니다 (Linux 6.15+). 이미 idmapped된 마운트에서 새 idmapped 마운트를 생성하는 중첩 idmapping 시나리오에서 사용합니다.
468
file_getattr
int file_getattr(int dirfd, const char *pathname, struct file_attr *fa, size_t size, unsigned int at_flags)
chattr/lsattr류 속성(FS_XFLAG_IMMUTABLE, APPEND, project ID, extent 크기 등)을 race-free로 조회합니다 (Linux 6.17+). 기존 ioctl(FS_IOC_GETFLAGS)의 파일시스템별 상이한 동작을 표준화합니다.
469
file_setattr
int file_setattr(int dirfd, const char *pathname, const struct file_attr *fa, size_t size, unsigned int at_flags)
file_attr 구조체 기반으로 확장 파일 속성을 설정합니다 (Linux 6.17+). 구조체 크기 인자로 향후 필드 추가 시 전·후방 호환을 보장합니다.
470
listns
ssize_t listns(unsigned int nstype, int fd, uint64_t *ns_ids, size_t nr_ns_ids, unsigned int flags)
시스템 내 네임스페이스를 직접 열거합니다 (Linux 6.19+). 기존에는 모든 프로세스의 /proc/<pid>/ns/를 스캔해야 했으나 이 시스콜로 커널이 직접 네임스페이스 목록을 반환합니다. listmount()와 유사한 인터페이스이며 컨테이너 런타임, nsenter 도구에서 활용됩니다.
Linux 6.8부터: statmount()와 listmount()가 추가되어 /proc/<pid>/mountinfo 파싱 없이 마운트 토폴로지를 직접 조회할 수 있습니다. Linux 6.15에서 statmount()에 idmapping 조회 기능과 마운트 토폴로지 변경 알림 API가 추가되었으며, open_tree_attr()로 중첩 idmapped 마운트 생성이 가능해졌습니다.
파일 변경 감시 (inotify)
inotify는 파일시스템 이벤트를 fd 기반으로 모니터링하는 메커니즘입니다. 파일 생성, 삭제, 수정, 이동 등을 감시하며, IDE의 자동 새로고침, 로그 모니터링, 빌드 도구의 파일 감시 등에 사용됩니다.
int inotify_add_watch(int fd, const char *pathname, uint32_t mask)
감시할 경로와 이벤트 마스크를 등록합니다. IN_CREATE|IN_DELETE|IN_MODIFY|IN_MOVED_FROM|IN_MOVED_TO 등의 이벤트를 조합합니다.
255
inotify_rm_watch
int inotify_rm_watch(int fd, int wd)
감시 대상을 제거합니다. 제거 시 IN_IGNORED 이벤트가 발생합니다.
프로세스 관리
프로세스(Process)는 실행 중인 프로그램의 인스턴스로, 커널이 각 프로세스에게 독립된 가상 주소 공간·파일 디스크립터 테이블·자격증명(Credentials)을 부여하여 서로 격리합니다. 프로그램을 실행하거나 종료하는 행위 자체가 커널 자원을 생성·해제하는 과정이므로 반드시 syscall을 통해 이루어집니다. Linux에서는 프로세스와 스레드(Thread)가 clone()이라는 하나의 syscall로 통합 생성되며, 어떤 자원(메모리 공간, fd 테이블, 시그널 핸들러 등)을 공유할지 플래그로 결정합니다. fork()는 내부적으로 clone(SIGCHLD, ...)와 동일하고, pthread_create()는 clone(CLONE_VM|CLONE_FILES|CLONE_THREAD|...)를 사용합니다.
프로세스 내 모든 스레드를 종료합니다. glibc의 exit()가 실제로 이 syscall을 호출합니다.
247
waitid
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options, struct rusage *ru)
wait4보다 세밀한 옵션을 제공합니다. idtype으로 P_PID/P_PGID/P_ALL 선택, WNOWAIT으로 상태를 소비하지 않고 조회만 가능합니다.
322
execveat
int execveat(int dirfd, const char *path, char *const argv[], char *const envp[], int flags)
dirfd 기준 상대 경로로 execve를 수행합니다. AT_EMPTY_PATH 플래그로 fd를 직접 실행할 수 있습니다. Linux 6.14+:AT_EXECVE_CHECK 플래그 추가 — 실제 실행 없이 해당 파일의 실행 허용 여부(커널 보안 정책 기준)만 확인합니다. 스크립트 인터프리터나 dlopen()이 파일 형식과 무관하게 보안 정책을 사전 확인할 때 사용합니다.
435
clone3
long clone3(struct clone_args *args, size_t size)
구조체(Struct)로 인자를 전달하는 clone의 확장 버전입니다 (Linux 5.3+). CLONE_INTO_CGROUP으로 생성 즉시 cgroup에 넣거나, set_tid로 PID를 지정할 수 있습니다.
434
pidfd_open
int pidfd_open(pid_t pid, unsigned int flags)
프로세스를 fd로 참조합니다 (Linux 5.3+). PID 재사용 경쟁 조건을 완전히 제거하며, waitid(P_PIDFD)로 안전한 대기, pidfd_send_signal로 안전한 시그널 전송이 가능합니다.
424
pidfd_send_signal
int pidfd_send_signal(int pidfd, int sig, siginfo_t *info, unsigned int flags)
pidfd를 통해 시그널을 전송합니다. PID 재사용 경쟁 없이 안전하며, sig=0으로 프로세스 존재 확인에 사용합니다.
438
pidfd_getfd
int pidfd_getfd(int pidfd, int targetfd, unsigned int flags)
다른 프로세스의 fd를 복제합니다 (Linux 5.6+). ptrace 없이 fd를 가져올 수 있으며, 컨테이너 디버깅(Debugging)에 유용합니다.
clone() 주요 플래그 (flags 인자)
플래그
설명
사용 예
CLONE_VM
가상 주소 공간 공유 (스레드 핵심)
pthread_create 내부
CLONE_FS
cwd/root/umask 공유
스레드
CLONE_FILES
파일 디스크립터 테이블 공유
스레드 (fork는 미사용)
CLONE_SIGHAND
시그널 핸들러 공유
스레드
CLONE_THREAD
같은 스레드 그룹(TGID)에 추가 — CLONE_VM|CLONE_SIGHAND 필요
pthread_create
CLONE_NEWPID
새 PID 네임스페이스 생성
컨테이너
CLONE_NEWNET
새 네트워크 네임스페이스 생성
컨테이너
CLONE_NEWNS
새 마운트 네임스페이스 생성
컨테이너, unshare
CLONE_NEWUSER
새 사용자 네임스페이스 생성 (UID/GID 매핑(Mapping))
rootless 컨테이너
CLONE_NEWUTS
새 UTS(hostname/domainname) 네임스페이스
컨테이너
CLONE_NEWIPC
새 IPC(SysV, POSIX MQ) 네임스페이스
컨테이너
CLONE_NEWCGROUP
새 cgroup 네임스페이스
컨테이너
CLONE_VFORK
vfork처럼 부모 실행 정지
vfork 구현
CLONE_PTRACE
ptrace 연결 유지 (디버거)
strace, gdb
CLONE_SETTLS
스레드 로컬 스토리지(TLS) 포인터 설정
pthread_create (fs/gs 기반)
CLONE_CHILD_CLEARTID
자식 종료 시 ctid 주소를 0으로 클리어 후 futex 깨움
pthread_join 구현
fork/exec 기본 패턴
/* fork → exec → wait 기본 패턴 */pid_t pid = fork();
if (pid < 0) { perror("fork"); exit(1); }
if (pid == 0) {
/* 자식: 새 프로그램 실행 */execve("/usr/bin/ls", (char*[]){"ls", "-la", NULL},
(char*[]){"PATH=/usr/bin", NULL});
_exit(127); /* execve 실패 시 — exit() 아닌 _exit() 사용 */
}
/* 부모: 자식 종료 대기 */int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status))
printf("종료 코드: %d\n", WEXITSTATUS(status));
/* 데몬 프로세스 생성 패턴 (POSIX) */if (fork() > 0) _exit(0); /* 부모 종료 → init 입양 */setsid(); /* 새 세션, 제어 터미널 분리 */if (fork() > 0) _exit(0); /* 세션 리더 아닌 프로세스로 */chdir("/"); /* 마운트 해제 방지 */close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
흔한 실수:
좀비 프로세스 방치 — wait()/waitpid()를 호출하지 않으면 자식이 ZOMBIE 상태로 남습니다. SIGCHLD를 SIG_IGN으로 설정하면 자동 수거됩니다.
멀티스레드 프로세스에서 fork() — fork()는 호출한 스레드만 복제합니다. 자식에서 다른 스레드가 잡고 있던 뮤텍스(Mutex)는 영원히 잠긴 채 남습니다. fork 후 즉시 exec만 하세요.
execve 실패 후 exit() 사용 — exit()는 atexit 핸들러와 stdio 버퍼(Buffer)를 플러시합니다. fork 자식에서는 _exit()를 사용해야 부모 stdio 버퍼가 이중 플러시되는 것을 방지합니다.
PID 재사용 경쟁 — kill(pid)와 waitpid(pid) 사이에 PID가 재사용될 수 있습니다. Linux 5.3+에서는 pidfd_open()을 사용하세요.
메모리 관리
현대 운영체제는 각 프로세스에게 독립된 가상 주소 공간(Virtual Address Space)을 제공합니다. 프로세스는 물리 메모리(Physical Memory) 주소를 알 필요 없이 가상 주소(Virtual Address)만 사용하고, 커널의 메모리 관리 유닛(MMU)이 페이지 테이블(Page Table)을 통해 가상 → 물리 주소(Physical Address)를 변환합니다. 이 가상 주소 공간을 할당·해제·보호하는 작업이 커널의 핵심 책임이므로 반드시 syscall을 통해 이루어집니다. glibc의 malloc()은 내부적으로 작은 할당(≤128KB)에는 brk()를, 큰 할당에는 mmap(MAP_ANONYMOUS)를 사용하며, 직접 syscall을 호출하지 않고 glibc 메모리 풀을 경유합니다. 파일을 메모리에 매핑(mmap)하면 파일 I/O 없이 포인터 접근만으로 파일 내용을 읽고 쓸 수 있어 데이터베이스·인터프리터 등에서 광범위하게 활용됩니다.
관련 문서:메모리 관리 — Buddy, SLUB, OOM |
메모리 — mmap, VMA, 페이지 폴트(Page Fault) |
VMA/mmap — MAP_SHARED vs MAP_PRIVATE, userfaultfd
번호
이름
시그니처
설명
9
mmap
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset)
파일이나 익명 메모리를 가상 주소 공간에 매핑합니다. 성공 시 매핑된 주소 반환, 실패 시 MAP_FAILED((void*)-1). 페이지 경계(4KB) 정렬 필요.
10
mprotect
int mprotect(void *addr, size_t len, int prot)
메모리 영역의 접근 보호를 변경합니다. PROT_READ|PROT_WRITE|PROT_EXEC 조합. JIT 컴파일러에서 PROT_WRITE → PROT_EXEC 전환에 사용합니다.
11
munmap
int munmap(void *addr, size_t len)
mmap으로 생성된 매핑을 해제합니다. 중간 영역만 해제하면 원래 영역이 분할됩니다.
12
brk
int brk(void *addr)
힙 세그먼트 끝 주소(program break)를 변경합니다. addr=NULL이면 현재 끝 주소를 반환합니다. glibc malloc이 내부에서 사용합니다.
25
mremap
void *mremap(void *old_addr, size_t old_len, size_t new_len, int flags, ...)
기존 매핑의 크기나 위치를 변경합니다. MREMAP_MAYMOVE로 이동 허용, MREMAP_FIXED로 목적지 지정. realloc의 큰 블록 처리에 활용됩니다.
26
msync
int msync(void *addr, size_t len, int flags)
mmap 매핑의 변경 내용을 파일에 반영합니다. MS_SYNC(동기), MS_ASYNC(비동기), MS_INVALIDATE(다른 매핑 무효화(Invalidation)).
27
mincore
int mincore(void *addr, size_t len, unsigned char *vec)
각 페이지의 RAM 상주 여부를 확인합니다. vec[i]의 최하위 비트가 1이면 상주. 프리패치 판단이나 메모리 pressure 분석에 사용합니다.
28
madvise
int madvise(void *addr, size_t len, int advice)
메모리 사용 패턴을 커널에 힌트로 전달합니다. 아래 표 참조.
149
mlock
int mlock(const void *addr, size_t len)
지정 영역을 RAM에 고정합니다 (swap 불가). 실시간 처리, 암호화(Encryption) 키 보호에 사용합니다. RLIMIT_MEMLOCK 제한 있음.
150
munlock
int munlock(const void *addr, size_t len)
mlock 잠금을 해제합니다. 해당 영역은 다시 swap 대상이 됩니다.
151
mlockall
int mlockall(int flags)
현재 및 미래의 모든 매핑을 잠급니다. MCL_CURRENT(현재)+MCL_FUTURE(미래). 실시간 프로세스에 사용합니다.
237
mbind
long mbind(void *addr, unsigned long len, int mode, const unsigned long *nodemask, ...)
NUMA 메모리 정책(Memory Policy)을 설정합니다. MPOL_BIND(특정 노드 강제), MPOL_PREFERRED, MPOL_INTERLEAVE(노드 간 분산).
323
userfaultfd
int userfaultfd(int flags)
사용자 공간에서 페이지 폴트(Page Fault)를 처리하는 fd를 생성합니다 (Linux 4.3+). 라이브 마이그레이션(Live Migration), 체크포인팅, CRIU에 핵심적입니다. UFFD_USER_MODE_ONLY 플래그 권장.
310
process_vm_readv
ssize_t process_vm_readv(pid_t pid, const struct iovec *local, unsigned long liovcnt, const struct iovec *remote, unsigned long riovcnt, unsigned long flags)
다른 프로세스의 가상 메모리(Virtual Memory)를 직접 읽습니다. ptrace보다 빠르며 strace, 디버거, CRIU에 사용됩니다.
311
process_vm_writev
ssize_t process_vm_writev(pid_t pid, const struct iovec *local, unsigned long liovcnt, const struct iovec *remote, unsigned long riovcnt, unsigned long flags)
다른 프로세스의 가상 메모리에 직접 씁니다.
440
process_madvise
int process_madvise(int pidfd, const struct iovec *iovec, size_t vlen, int advice, unsigned int flags)
다른 프로세스의 메모리에 madvise 힌트를 적용합니다 (Linux 5.10+). Android의 메모리 관리자가 백그라운드 앱의 페이지를 cold/pageout 처리할 때 사용합니다.
462
mseal
int mseal(void *addr, size_t len, unsigned long flags)
VMA 범위를 봉인하여 이후 mprotect/munmap/mmap/mremap 변경을 -EPERM으로 차단합니다 (Linux 6.10+). 동적 링커(Linker)가 R-X 세그먼트를 로드한 직후 적용하여 메모리 손상 공격 경로를 봉쇄합니다. flags는 모두 예약되어 0이어야 합니다. x86-64/arm64/loongarch/s390 지원(6.12+).
mmap() prot / flags 인자
인자
값/상수
설명
prot (보호)
PROT_NONE
접근 불가 (가드 페이지, NULL 역참조(Dereference) 감지)
PROT_READ
읽기 가능
PROT_WRITE
쓰기 가능
PROT_EXEC
실행 가능 (W^X 정책: PROT_WRITE와 동시 사용 지양)
flags (매핑 유형)
MAP_SHARED
파일 변경이 다른 프로세스에게도 보임 (msync로 플러시)
MAP_PRIVATE
COW 사본 — 변경이 파일에 반영되지 않음
MAP_ANONYMOUS
파일과 무관한 익명 메모리 (fd=-1 필요)
MAP_FIXED
addr을 정확히 사용 (기존 매핑 덮어씀 — 위험)
MAP_FIXED_NOREPLACE
addr이 이미 사용 중이면 EEXIST 반환 (Linux 4.17+)
MAP_HUGETLB
HugePage(2MB/1GB) 사용. 대량 데이터 성능 향상.
MAP_POPULATE
매핑 즉시 페이지 테이블을 채움 (나중의 페이지 폴트 방지)
MAP_LOCKED
mmap+mlock 효과 (mlockall 대신)
MAP_NORESERVE
swap 예약 없이 오버커밋 허용
madvise() 힌트 (advice 인자)
advice
설명
MADV_NORMAL
기본 동작 (보통 readahead 적용)
MADV_SEQUENTIAL
순차 접근 예상 — readahead 크게 증가
MADV_RANDOM
임의 접근 예상 — readahead 끄기
MADV_WILLNEED
곧 접근 예정 — 미리 읽기(prefault)
MADV_DONTNEED
더 이상 필요 없음 — 페이지 캐시 해제 가능 (메모리 즉시 반납)
MADV_FREE
lazy 해제 — 메모리 압박 시에만 반납 (zero 초기화 보장 안 됨)
MADV_HUGEPAGE
THP(Transparent Huge Page) 사용 요청
MADV_NOHUGEPAGE
THP 사용 금지
MADV_DONTFORK
fork 시 자식에게 매핑 상속하지 않음
MADV_DONTDUMP
코어 덤프에서 이 영역 제외 (민감한 데이터 보호)
MADV_COLD
LRU 리스트에서 cold 위치로 이동 (Linux 5.4+)
MADV_PAGEOUT
즉시 swap out 요청 (Linux 5.4+)
MADV_POPULATE_READ
읽기 권한으로 페이지 미리 채움 — 폴트 없이 접근 가능 (Linux 5.14+)
mmap 페이지 폴트(Page Fault) 처리 흐름
mmap 활용 패턴
/* 파일 전체를 메모리 매핑하여 읽기 */int fd = open("data.bin", O_RDONLY);
struct stat st;
fstat(fd, &st);
void *map = mmap(NULL, st.st_size, PROT_READ,
MAP_PRIVATE, fd, 0);
close(fd); /* fd는 즉시 닫아도 매핑은 유지됨 */madvise(map, st.st_size, MADV_SEQUENTIAL);
/* map[0] ~ map[st.st_size-1] 직접 접근 가능 */munmap(map, st.st_size);
/* 익명 mmap: 큰 메모리 할당 (malloc 내부 방식) */void *big = mmap(NULL, 1 << 20, /* 1MB */PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
/* 공유 메모리: 프로세스 간 통신 */int mfd = memfd_create("shm", MFD_CLOEXEC);
ftruncate(mfd, 4096);
void *shared = mmap(NULL, 4096,
PROT_READ | PROT_WRITE, MAP_SHARED, mfd, 0);
/* fork 후 자식과 공유됨, 또는 Unix socket으로 mfd 전달 */
흔한 실수:
MAP_FIXED로 기존 매핑 덮어쓰기 — MAP_FIXED는 지정 주소의 기존 매핑을 경고 없이 덮어씁니다. Linux 4.17+에서는 MAP_FIXED_NOREPLACE를 사용하세요.
munmap 크기 불일치 — munmap() 크기가 원래 매핑과 다르면 일부만 해제되어 VMA가 분할됩니다. 정확한 크기를 전달하세요.
mmap 후 close 안함 — fd를 닫아도 매핑은 유지되므로 즉시 close()해야 fd 누수를 방지합니다.
PROT_WRITE|PROT_EXEC 동시 사용 — W^X 보안 정책 위반입니다. JIT 컴파일러는 먼저 PROT_WRITE로 코드를 쓰고, mprotect()로 PROT_EXEC로 전환해야 합니다.
네트워크 소켓
네트워크 통신도 파일 디스크립터(fd) 기반으로 추상화됩니다. socket()이 반환한 fd는 read()/write()로 데이터를 보내고 받을 수 있어 파일 I/O와 같은 인터페이스로 다룰 수 있습니다. 소켓의 핵심 특성인 프로토콜 패밀리(AF_INET=IPv4, AF_INET6=IPv6, AF_UNIX=같은 머신 내 IPC), 타입(SOCK_STREAM=TCP 연결형, SOCK_DGRAM=UDP 비연결형), 프로토콜은 socket()을 생성할 때 결정됩니다. 이후 TCP 서버는 bind → listen → accept, 클라이언트는 connect 순서로 연결을 수립하고, UDP는 connect 없이 sendto/recvfrom을 직접 사용합니다. 소켓 옵션(setsockopt)으로 재사용 허가(SO_REUSEADDR), 버퍼 크기, keep-alive 등을 세밀하게 제어합니다.
connect() EINPROGRESS 미처리 — 비블로킹 connect()는 즉시 EINPROGRESS를 반환합니다. epoll/poll로 쓰기 가능 상태를 확인한 후 getsockopt(SO_ERROR)로 결과를 확인하세요.
accept()에서 EMFILE — fd 한도 초과 시 accept가 영원히 EMFILE을 반환하며 CPU를 100% 사용합니다. 여분의 fd를 하나 확보해두고 EMFILE 시 닫았습니다 열어 소켓을 수거하세요.
시그널
시그널(Signal)은 커널 또는 다른 프로세스가 목표 프로세스에게 특정 이벤트가 발생했음을 알리는 비동기 알림 메커니즘입니다. 예를 들어 Ctrl+C를 누르면 터미널 드라이버가 전경(Foreground) 프로세스 그룹에 SIGINT(2)를 보내고, 자식 프로세스가 종료되면 커널이 부모에게 SIGCHLD(17)를 보냅니다. 각 프로세스는 시그널마다 기본 동작(Default: 종료·무시·중지), 무시(Ignore), 또는 사용자 정의 핸들러(Handler) 세 가지 중 하나를 선택할 수 있습니다. 시그널 전달은 커널이 해당 프로세스를 스케줄링하는 시점(syscall 복귀 직전 또는 인터럽트 복귀 시)에 처리됩니다 — 코드 실행 중 즉각 인터럽트하지 않습니다. SIGKILL(9)과 SIGSTOP(19)은 프로세스가 블록하거나 무시할 수 없으며, 커널이 강제로 처리합니다.
관련 문서:IPC — 시그널 내부 동작, signalfd, eventfd |
시스템 콜 — seccomp/ptrace 훅
set에 포함된 시그널 중 하나가 도달할 때까지 대기합니다. timeout으로 최대 대기 시간(Latency) 지정.
130
rt_sigsuspend
int rt_sigsuspend(const sigset_t *mask, size_t sigsetsize)
시그널 마스크를 mask로 원자적으로 교체하고 시그널을 기다립니다. EINTR로 반환 후 마스크가 복원됩니다. 경쟁 조건 없는 대기에 사용합니다.
131
sigaltstack
int sigaltstack(const stack_t *ss, stack_t *oss)
시그널 처리용 대체 스택을 설정합니다. 스택 오버플로(Stack Overflow)우(SIGSEGV) 처리에 필수입니다.
234
tgkill
int tgkill(pid_t tgid, pid_t tid, int sig)
멀티스레드 프로세스에서 특정 스레드에 시그널을 전송합니다. kill(tid)와 달리 tid 재사용에 의한 경쟁 없이 안전합니다.
289
signalfd4
int signalfd4(int fd, const sigset_t *mask, size_t sizemask, int flags)
mask의 시그널을 fd에서 read()로 동기 수신합니다. epoll과 통합하여 이벤트 루프(Event Loop)로 처리 가능합니다 (SFD_NONBLOCK, SFD_CLOEXEC).
표준 시그널 참조 (x86_64)
번호
이름
기본 동작
설명 / 주요 발생 원인
1
SIGHUP
종료
제어 터미널 연결 해제, 데몬의 설정 재로드 관례로 사용
2
SIGINT
종료
Ctrl+C — 포그라운드 프로세스 그룹에 전달
3
SIGQUIT
코어덤프+종료
Ctrl+\ — SIGINT와 유사하나 코어 생성
4
SIGILL
코어덤프+종료
잘못된 CPU 명령어 실행
5
SIGTRAP
코어덤프+종료
디버거 breakpoint, ptrace 트랩
6
SIGABRT
코어덤프+종료
abort() 호출, assert() 실패
7
SIGBUS
코어덤프+종료
버스(Bus) 오류 — 정렬되지 않은 메모리 접근, mmap 영역 접근 오류
8
SIGFPE
코어덤프+종료
부동 소수점/정수 나누기 0, 오버플로우
9
SIGKILL
종료 (강제)
즉시 종료 — 블록/무시 불가. OOM killer가 사용
10
SIGUSR1
종료
사용자 정의 — Nginx worker 재로드, 로그 로테이션 등
11
SIGSEGV
코어덤프+종료
잘못된 메모리 접근 (NULL 역참조, 스택 오버플로우 등)
12
SIGUSR2
종료
사용자 정의 — 애플리케이션별 커스텀 용도
13
SIGPIPE
종료
읽는 쪽이 닫힌 파이프/소켓에 쓰기 — SIG_IGN 처리 관례
14
SIGALRM
종료
alarm() 타이머 만료
15
SIGTERM
종료
정상 종료 요청 — systemd stop, kill 기본값
17
SIGCHLD
무시
자식 프로세스 종료/중지 알림 — waitpid() 연계
18
SIGCONT
계속
SIGSTOP/SIGTSTP로 중지된 프로세스 재개
19
SIGSTOP
중지 (강제)
프로세스 일시 정지 — 블록/무시 불가
20
SIGTSTP
중지
Ctrl+Z — 블록/무시 가능
29
SIGIO
종료
비동기 I/O 완료 알림 (F_SETOWN + O_ASYNC)
30
SIGPWR
종료
전원 이상 알림 (UPS 등)
31
SIGSYS
코어덤프+종료
잘못된 syscall 호출 — seccomp 필터가 활용
34~64
SIGRTMIN~SIGRTMAX
종료
실시간 시그널 — 큐잉 보장, 순서 보장(Ordering), 추가 데이터 전달 가능
시그널 핸들러 등록 패턴
/* 안전한 시그널 핸들러 패턴 */staticvolatilesig_atomic_t got_signal = 0;
staticvoidhandler(int sig) {
got_signal = 1; /* sig_atomic_t만 안전하게 쓸 수 있음 */
}
structsigaction sa = {
.sa_handler = handler,
.sa_flags = SA_RESTART, /* 중단된 syscall 자동 재시작 */
};
sigemptyset(&sa.sa_mask);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
/* 이벤트 루프에서 시그널 처리 (signalfd 방식) */sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGTERM);
sigprocmask(SIG_BLOCK, &mask, NULL);
int sigfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
/* sigfd를 epoll에 등록하면 이벤트 루프에서 시그널 처리 가능 */
시그널 핸들러 안전 규칙:
async-signal-safe 함수만 호출 — 핸들러 내에서 printf(), malloc(), exit() 등은 데드락(Deadlock)을 유발할 수 있습니다. 안전한 함수: write(), _exit(), signal(), sigaction(), read() 등 (man 7 signal-safety 참조).
volatile sig_atomic_t만 공유 — 핸들러에서 전역 변수를 수정하려면 volatile sig_atomic_t 타입만 안전합니다.
SA_RESTART 설정 — 이 플래그가 없으면 시그널이 read(), write(), accept() 등을 EINTR로 중단시킵니다.
signalfd 권장 — 시그널을 fd로 변환하면 이벤트 루프에서 안전하게 처리할 수 있으며, 비동기 핸들러의 복잡성을 제거합니다.
IPC (프로세스 간 통신)
IPC(Inter-Process Communication)는 서로 다른 프로세스 간에 데이터를 교환하거나 실행을 동기화하는 메커니즘입니다. 프로세스는 기본적으로 독립된 가상 주소 공간을 사용하므로 포인터를 직접 공유할 수 없고, 커널이 중개하는 공유 채널이 필요합니다. Linux의 IPC 메커니즘은 특성에 따라 선택합니다: 파이프(pipe)는 부모-자식 간 단방향 스트림, Unix 도메인 소켓은 양방향·권한 전달·fd 공유가 가능한 범용 IPC, 공유 메모리(shared memory)는 대용량 데이터를 복사 없이 교환하는 최고 성능 방법, futex는 잠금 경합(Lock Contention)이 없는 경우 syscall을 생략하는 빠른 동기화 기본 요소입니다. 현대적 설계에서는 futex(잠금), Unix socket(메시지), memfd+mmap(대용량 공유 메모리)을 조합하여 사용합니다.
관련 문서:IPC — pipe/Unix socket, futex, System V IPC |
동기화 기법
번호
이름
시그니처
설명
22
pipe
int pipe(int pipefd[2])
단방향 통신용 파이프를 생성합니다. fd[0]은 읽기 전용, fd[1]은 쓰기 전용이며 최대 64KiB(Linux 3.11+)의 커널 버퍼를 사용합니다.
293
pipe2
int pipe2(int pipefd[2], int flags)
O_CLOEXEC(exec 시 자동 닫기), O_NONBLOCK(비블로킹) 플래그를 원자적으로 지정하여 파이프를 생성합니다. 모던 코드에서 pipe 대신 권장됩니다.
29
shmget
int shmget(key_t key, size_t size, int shmflg)
System V 공유 메모리 세그먼트를 생성하거나 기존 것을 가져옵니다. IPC_CREAT|0666 플래그로 생성하며, 반환된 shmid로 shmat에서 연결합니다.
30
shmat
void *shmat(int shmid, const void *shmaddr, int shmflg)
공유 메모리 세그먼트를 프로세스 주소 공간에 매핑합니다. shmaddr=NULL이면 커널이 적절한 주소를 선택합니다. SHM_RDONLY로 읽기 전용 연결 가능.
67
shmdt
int shmdt(const void *shmaddr)
프로세스 주소 공간에서 공유 메모리 세그먼트의 연결을 해제합니다. 세그먼트 자체는 삭제되지 않습니다.
31
shmctl
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
공유 메모리 세그먼트를 제어합니다. IPC_RMID로 삭제, IPC_STAT으로 상태 조회, IPC_SET으로 권한 변경.
64
semget
int semget(key_t key, int nsems, int semflg)
System V 세마포어(Semaphore) 집합을 생성하거나 가져옵니다. nsems개의 세마포어가 하나의 집합을 이룹니다.
65
semop
int semop(int semid, struct sembuf *sops, size_t nsops)
세마포어 집합에 P(sem_op 음수)/V(sem_op 양수) 연산을 수행합니다. SEM_UNDO 플래그로 프로세스 종료 시 자동 롤백(Rollback)됩니다.
64비트 카운터를 fd로 추상화한 이벤트 알림 메커니즘입니다. write(fd, &1, 8)로 신호, read(fd, &v, 8)로 수신합니다. epoll과 조합하여 사용합니다.
202
futex
long futex(uint32_t *uaddr, int futex_op, uint32_t val, ...)
사용자 공간(User Space) 원자 연산과 커널 대기 큐(Wait Queue)를 결합한 빠른 잠금 원시 연산입니다. pthread_mutex, pthread_cond, Go runtime의 기반. FUTEX_WAIT/FUTEX_WAKE가 핵심 오퍼레이션입니다.
449
futex_waitv
int futex_waitv(struct futex_waitv *waiters, unsigned int nr_futexes, unsigned int flags, struct timespec *timeout, clockid_t clockid)
FUTEX2 계열: 여러 futex를 벡터로 한 번에 대기합니다 (Linux 5.16+). Wine/Proton의 멀티 오브젝트 대기 구현에 사용됩니다.
455
futex_wait
int futex_wait(void *uaddr, unsigned long val, unsigned long mask, unsigned int flags, struct timespec *timeout, clockid_t clockid)
FUTEX2 계열: 단일 futex 대기 (Linux 6.7+). 기존 FUTEX_WAIT_BITSET 오퍼레이션을 독립 syscall로 분리하여 타입 검사와 확장성을 개선합니다. FUTEX_PRIVATE_FLAG는 flags 인자로 전달합니다.
456
futex_requeue
int futex_requeue(struct futex_waitv *waiters, unsigned int flags, int nr_wake, int nr_requeue)
FUTEX2 계열: 대기 중인 waiter를 다른 futex로 재큐잉합니다 (Linux 6.7+). 기존 FUTEX_CMP_REQUEUE를 개선한 버전으로, struct futex_waitv로 소스·목적지 futex를 지정합니다.
Linux 6.7부터: futex2 시스템 콜 패밀리(futex_wait, futex_wake(별도 미추가), futex_requeue)가 완성되었습니다. 기존 다중화(Multiplexing)된 futex() 인터페이스의 각 오퍼레이션을 독립 syscall로 분리하여 타입 안전성과 유지보수성을 높입니다. glibc가 래퍼를 제공하기 전까지는 syscall(SYS_futex_wait, ...) 형태로 직접 호출해야 합니다.
IPC 활용 패턴
/* pipe2: 부모-자식 간 스트림 통신 */int pfd[2];
pipe2(pfd, O_CLOEXEC);
if (fork() == 0) {
close(pfd[0]); /* 자식: 읽기 끝 닫기 */write(pfd[1], "hello", 5);
_exit(0);
}
close(pfd[1]); /* 부모: 쓰기 끝 닫기 */char buf[64];
read(pfd[0], buf, sizeof(buf));
/* eventfd: 스레드/프로세스 간 이벤트 알림 */int efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
/* 생산자: 카운터 증가 */uint64_t val = 1;
write(efd, &val, sizeof(val));
/* 소비자: 카운터 읽기 (읽으면 0으로 초기화) */read(efd, &val, sizeof(val));
/* epoll에 efd 등록하면 생산자 알림을 이벤트 루프에서 처리 */
시간 & 타이머
정확한 시간 측정과 타이머 기반 이벤트 처리는 네트워크 프로토콜(타임아웃·재전송(Retransmission)), 실시간 제어(주기 작업), 성능 분석(latency 측정) 등 광범위한 영역에서 필요합니다. Linux는 여러 종류의 시계(Clock)를 제공합니다: CLOCK_REALTIME은 벽시계(Wall Clock) 시간으로 NTP 조정의 영향을 받으며, CLOCK_MONOTONIC은 항상 단조 증가하여 경과 시간 측정에 안전하고, CLOCK_BOOTTIME은 슬립(Sleep) 중에도 계속 흐르며, CLOCK_TAI는 윤초(Leap Second)를 포함한 국제 원자시입니다. clock_gettime과 gettimeofday는 vDSO(virtual Dynamic Shared Object)를 통해 커널 진입 없이 사용자 공간에서 직접 실행되므로 일반 syscall 대비 3~10배 빠릅니다. 고해상도 타이머(hrtimer)가 필요할 때는 CLOCK_MONOTONIC을 우선 사용하세요.
int timer_create(clockid_t clkid, struct sigevent *sevp, timer_t *timerid)
POSIX 인터벌 타이머를 생성합니다. 만료 시 시그널 전달(SIGEV_SIGNAL) 또는 스레드 함수 호출(SIGEV_THREAD) 방식 선택 가능.
223
timer_settime
int timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value, struct itimerspec *old_value)
POSIX 타이머의 만료 시각과 인터벌을 설정합니다. it_interval을 0이 아니면 반복 타이머로 동작합니다.
224
timer_gettime
int timer_gettime(timer_t timerid, struct itimerspec *curr_value)
POSIX 타이머의 현재 만료까지 남은 시간과 인터벌을 조회합니다.
283
timerfd_create
int timerfd_create(clockid_t clkid, int flags)
타이머 만료를 fd로 읽을 수 있는 타이머 fd를 생성합니다. epoll/select와 조합하여 이벤트 루프에 타이머를 통합할 때 이상적입니다. TFD_NONBLOCK|TFD_CLOEXEC 권장.
286
timerfd_settime
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value)
timerfd의 만료 시각과 인터벌을 설정합니다. TFD_TIMER_ABSTIME으로 절대 시각 설정도 가능합니다.
287
timerfd_gettime
int timerfd_gettime(int fd, struct itimerspec *curr_value)
timerfd의 현재 설정(남은 시간, 인터벌)을 조회합니다.
159
adjtimex
int adjtimex(struct timex *buf)
NTP 데몬이 사용하는 커널 시계 조정 인터페이스입니다. 주파수 오차, 시간 오프셋, PPS 신호 등을 설정합니다. (CAP_SYS_TIME 필요)
보안 & 권한
Linux 보안 모델은 세 가지 기본 질문으로 구성됩니다: "누구인가"(신원/UID·GID), "무엇을 할 수 있는가"(권한/Capabilities), "무엇에 접근할 수 있는가"(정책/LSM). 전통적인 DAC(Discretionary Access Control)는 파일 소유자·그룹·others 3단계 권한 비트로 접근을 제어합니다. 현대 Linux는 여기에 더해 Capabilities(root 권한을 37개 세분화된 능력으로 분리), LSM(SELinux/AppArmor — 강제적 접근 통제), seccomp(syscall 화이트리스트·BPF 필터로 공격 표면 최소화), Namespaces(PID/user/mount/network 등 자원 격리)를 다층으로 결합합니다. 컨테이너(Docker/Kubernetes)의 격리는 이 네 가지 메커니즘의 조합으로 구현됩니다. 관련 syscall은 프로세스가 자신의 자격증명(Credentials)을 변경하거나 보안 정책을 설정하는 데 사용됩니다.
Landlock 규칙 집합에 허용 규칙을 추가합니다. LANDLOCK_RULE_PATH_BENEATH로 특정 디렉터리 이하만 허용.
446
landlock_restrict_self
int landlock_restrict_self(int ruleset_fd, __u32 flags)
현재 스레드에 Landlock 규칙 집합을 적용하여 파일시스템 접근을 제한합니다. 적용 후 철회 불가.
459
lsm_get_self_attr
int lsm_get_self_attr(unsigned int attr, struct lsm_ctx *ctx, __u32 *size, __u32 flags)
현재 프로세스의 LSM(Linux Security Module) 보안 속성을 조회합니다 (Linux 6.8+). 기존 /proc/self/attr/ 인터페이스를 대체하며, 다중 LSM(SELinux + AppArmor 동시 활성) 환경에서 정확한 속성 분리를 지원합니다.
460
lsm_set_self_attr
int lsm_set_self_attr(unsigned int attr, struct lsm_ctx *ctx, __u32 size, __u32 flags)
현재 프로세스의 LSM 보안 속성을 설정합니다 (Linux 6.8+). SELinux의 도메인 전환이나 AppArmor 프로필 전환에 활용됩니다. LSM_FLAG_SINGLE 플래그로 특정 LSM에만 적용할 수 있습니다.
461
lsm_list_modules
int lsm_list_modules(__u64 *ids, __u32 *size, __u32 flags)
시스템에서 활성화된 LSM 모듈 목록을 LSM ID 배열로 반환합니다 (Linux 6.8+). 다중 LSM 스태킹 환경에서 어떤 모듈이 활성화됐는지 확인하는 데 사용합니다.
Linux 6.8부터: lsm_get_self_attr(), lsm_set_self_attr(), lsm_list_modules() 세 가지 LSM syscall이 추가되었습니다. 기존 /proc/self/attr/ 인터페이스는 단일 LSM 시대에 설계되어 다중 LSM 스태킹 환경에서 정보가 누락되는 문제가 있었습니다. 새 syscall은 LSM ID 기반으로 각 모듈의 속성을 명확히 분리합니다.
Landlock 샌드박싱 패턴 (Linux 5.13+)
/* 파일시스템 접근을 /tmp 아래로만 제한 */structlandlock_ruleset_attr attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE |
LANDLOCK_ACCESS_FS_WRITE_FILE,
};
int rfd = landlock_create_ruleset(&attr, sizeof(attr), 0);
/* /tmp 아래만 읽기/쓰기 허용 */int dir = open("/tmp", O_PATH | O_CLOEXEC);
structlandlock_path_beneath_attr rule = {
.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE |
LANDLOCK_ACCESS_FS_WRITE_FILE,
.parent_fd = dir,
};
landlock_add_rule(rfd, LANDLOCK_RULE_PATH_BENEATH,
&rule, 0);
close(dir);
/* 규칙 적용 (되돌릴 수 없음!) */prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
landlock_restrict_self(rfd, 0);
close(rfd);
/* 이후 /tmp 이외의 파일 접근 시 EACCES */
비동기 I/O & 이벤트
수천~수만 개의 동시 연결을 처리해야 하는 서버(웹 서버, 데이터베이스, 게임 서버)는 각 연결마다 스레드를 만드는 방식으로는 메모리와 문맥 전환(Context Switch) 오버헤드 때문에 확장하기 어렵습니다. 대신 이벤트 기반(Event-Driven) 방식으로 하나의 스레드가 다수의 fd를 감시하며 준비된 fd만 처리합니다. Linux에서 이 역할을 하는 syscall이 epoll입니다 — O(1) 시간에 준비된 fd를 반환하여 select/poll보다 대규모 연결에 효율적입니다. 한 단계 더 나아가 io_uring(Linux 5.1+)은 제출 큐(SQ Ring)와 완료 큐(CQ Ring)를 공유 메모리로 구현하여 I/O 요청과 응답을 syscall 없이 배치로 처리합니다. nginx·libuv·tokio·Redis는 epoll, io_uring이 지원되는 최신 환경에서는 Tokio·Monoio가 io_uring을 활용합니다.
관련 문서:IPC — eventfd, epoll 통합 패턴 |
성능 최적화 — io_uring 배치 처리, 오버헤드(Overhead) 분석
새 epoll 인스턴스를 생성합니다. EPOLL_CLOEXEC 플래그를 항상 지정하는 것을 권장합니다. O(1) 이벤트 통지, 백만 이상의 fd를 효율적으로 처리합니다.
233
epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epoll 관심 목록에 fd를 추가(EPOLL_CTL_ADD), 수정(EPOLL_CTL_MOD), 삭제(EPOLL_CTL_DEL)합니다. EPOLLET으로 엣지 트리거 모드 설정.
232
epoll_wait
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
준비된 이벤트가 있을 때까지 대기합니다. 반환값은 준비된 fd 수이며, events[]에만 결과가 채워집니다.
206
io_setup
int io_setup(unsigned nr_events, aio_context_t *ctx_idp)
Linux AIO 비동기 I/O 컨텍스트를 생성합니다. O_DIRECT 파일에서만 진정한 비동기가 보장됩니다 (일반 파일은 실제로 블로킹).
209
io_submit
long io_submit(aio_context_t ctx_id, long nr, struct iocb **iocbpp)
Linux AIO 요청을 제출합니다. 실제 커널 비동기 I/O 제출.
208
io_getevents
long io_getevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout)
완료된 AIO 이벤트를 수집합니다. min_nr개 이상 완료될 때까지 대기합니다.
425
io_uring_setup
int io_uring_setup(u32 entries, struct io_uring_params *params)
고성능 비동기 I/O를 위한 io_uring 인스턴스를 설정합니다 (Linux 5.1+). SQ/CQ 링을 mmap으로 매핑하여 syscall 없이 요청/완료 처리가 가능합니다.
426
io_uring_enter
int io_uring_enter(unsigned int fd, u32 to_submit, u32 min_complete, u32 flags, const sigset_t *sig, size_t sigsz)
SQ에 추가된 요청을 커널에 제출하고 완료를 대기합니다. SQPOLL 모드에서는 이 syscall도 불필요합니다.
427
io_uring_register
int io_uring_register(unsigned int fd, unsigned int opcode, void *arg, unsigned int nr_args)
io_uring 인스턴스에 파일/버퍼를 미리 등록하여 매 요청 시 참조 카운팅 오버헤드를 제거합니다 (IORING_REGISTER_BUFFERS, IORING_REGISTER_FILES 등).
epoll 레벨 트리거(LT) vs 엣지 트리거(ET) 비교
epoll 이벤트 루프 패턴
int epfd = epoll_create1(EPOLL_CLOEXEC);
/* 서버 소켓을 epoll에 등록 */structepoll_event ev = {
.events = EPOLLIN,
.data.fd = server_fd,
};
epoll_ctl(epfd, EPOLL_CTL_ADD, server_fd, &ev);
structepoll_event events[1024];
for (;;) {
int nfds = epoll_wait(epfd, events, 1024, -1);
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == server_fd) {
/* 새 연결 수락 후 epoll에 등록 */int cfd = accept4(server_fd, NULL, NULL,
SOCK_NONBLOCK | SOCK_CLOEXEC);
ev.events = EPOLLIN | EPOLLET; /* 엣지 트리거 */
ev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
} else {
/* ET 모드: EAGAIN까지 모든 데이터 읽기 필수 */for (;;) {
ssize_t n = read(events[i].data.fd, buf, sizeof(buf));
if (n <= 0) break; /* EAGAIN 또는 EOF */process_data(buf, n);
}
}
}
}
흔한 실수:
EPOLLET에서 부분 읽기 — 엣지 트리거(Edge Trigger) 모드에서 데이터를 EAGAIN까지 모두 읽지 않으면 남은 데이터에 대한 알림이 오지 않습니다. 반드시 루프로 전부 읽으세요.
EPOLLONESHOT 미사용 (멀티스레드) — 여러 스레드가 같은 fd 이벤트를 동시에 처리할 수 있습니다. EPOLLONESHOT으로 한 번 알림 후 비활성화하고, 처리 완료 후 EPOLL_CTL_MOD로 재활성화하세요.
dup된 fd와 epoll — close(fd)해도 같은 struct file을 참조하는 다른 fd가 남아있으면 epoll에서 제거되지 않습니다. EPOLL_CTL_DEL을 명시적으로 호출하세요.
기타 시스템 콜
앞서 분류된 카테고리에 속하지 않는 중요한 syscall 목록입니다. 시스템 정보 조회(uname, sysinfo), 리소스 제한(prlimit64: 프로세스별 파일 수·메모리·CPU 시간 한도), 커널 모듈 로딩(finit_module), eBPF(bpf(): 커널에 안전하게 검증된 프로그램을 주입하는 혁신적 인터페이스), 성능 분석(perf_event_open(): PMU 하드웨어 카운터·tracepoint 접근)이 여기에 해당합니다. 특히 bpf()는 클래식 BPF(패킷 필터)에서 출발해 현재는 네트워킹(XDP), 보안(seccomp-BPF, BPF-LSM), 관측성(bpftrace, BCC), 스케줄러(sched_ext) 등 커널 전반을 사용자 프로그램이 안전하게 확장하는 플랫폼으로 발전했습니다. prlimit64는 데몬/서버 프로세스의 열린 파일 수(RLIMIT_NOFILE)·잠금 가능 메모리(RLIMIT_MEMLOCK)·코어 덤프 크기(RLIMIT_CORE) 등을 운영 환경에 맞게 튜닝하는 데 필수입니다.
RLIMIT 리소스 종류
리소스
값
단위
설명
RLIMIT_CPU
0
초
CPU 시간 제한. 초과 시 SIGXCPU 전달
RLIMIT_FSIZE
1
바이트
파일 크기 최대값. 초과 시 SIGXFSZ
RLIMIT_DATA
2
바이트
데이터 세그먼트(힙+BSS) 크기 한도
RLIMIT_STACK
3
바이트
스택 크기 한도 (기본 8MiB)
RLIMIT_CORE
4
바이트
코어 덤프 파일 크기 한도 (0이면 생성 안함)
RLIMIT_NOFILE
7
개수
열 수 있는 최대 파일 디스크립터 수 (기본 1024)
RLIMIT_AS
9
바이트
가상 주소 공간 최대 크기
RLIMIT_NPROC
6
개수
사용자가 생성할 수 있는 최대 프로세스/스레드 수
RLIMIT_MEMLOCK
8
바이트
메모리에 잠글 수 있는 최대 바이트 수 (mlock)
RLIMIT_LOCKS
10
개수
파일 잠금/lease 최대 수
RLIMIT_SIGPENDING
11
개수
대기 시그널 최대 수
RLIMIT_MSGQUEUE
12
바이트
POSIX 메시지 큐 총 바이트 한도
RLIMIT_NICE
13
우선순위
nice 값의 최대 상향 한도
RLIMIT_RTPRIO
14
우선순위
실시간 스케줄링 최대 우선순위
RLIMIT_RTTIME
15
마이크로초
실시간 프로세스 연속 CPU 시간 한도
eBPF 프로그램 타입 (bpf() 주요 용도)
프로그램 타입
연결점
주요 사용 시나리오
BPF_PROG_TYPE_SOCKET_FILTER
소켓
패킷 필터링, tcpdump 대체
BPF_PROG_TYPE_KPROBE
커널 함수 진입/반환
함수 추적, 인자/반환값 감시
BPF_PROG_TYPE_TRACEPOINT
커널 트레이스포인트
syscall 진입/반환, 스케줄러(Scheduler) 이벤트
BPF_PROG_TYPE_XDP
NIC 드라이버 레벨
초고속 패킷 처리, DDoS 완화
BPF_PROG_TYPE_TC
TC (traffic control)
네트워크 QoS, 패킷 리다이렉트
BPF_PROG_TYPE_PERF_EVENT
perf 이벤트
PMU 카운터, CPU 프로파일링
BPF_PROG_TYPE_CGROUP_SKB
cgroup 소켓
컨테이너별 네트워크 정책
BPF_PROG_TYPE_LSM
LSM 훅
커널 보안 정책 (BPF-LSM, Linux 5.7+)
BPF_PROG_TYPE_SK_LOOKUP
소켓 룩업
소켓 선택 정책 커스터마이즈
BPF_PROG_TYPE_SYSCALL
syscall 추적
syscall 인자 검사, seccomp 보완
번호
이름
시그니처
설명
63
uname
int uname(struct utsname *buf)
커널 이름(sysname), 호스트명(nodename), 릴리즈(release), 버전(version), 아키텍처(machine) 정보를 가져옵니다.
99
sysinfo
int sysinfo(struct sysinfo *info)
메모리 총량/여유, 스왑(Swap) 총량/여유, 1/5/15분 부하 평균, 업타임, 프로세스 수를 한 번에 가져옵니다.
97
getrlimit
int getrlimit(int resource, struct rlimit *rlim)
프로세스의 리소스 소프트/하드 제한값을 조회합니다. prlimit64 사용을 권장합니다.
160
setrlimit
int setrlimit(int resource, const struct rlimit *rlim)
프로세스의 리소스 제한값을 설정합니다. 소프트 한도는 하드 한도 이하, 하드 한도는 CAP_SYS_RESOURCE가 있어야만 상향 가능합니다.
302
prlimit64
int prlimit64(pid_t pid, int resource, const struct rlimit64 *new, struct rlimit64 *old)
64비트 리소스 제한을 조회/설정합니다. pid=0이면 자신에게, 타 PID에는 CAP_SYS_RESOURCE가 필요합니다. 서비스 소켓 수 등 튜닝에 사용합니다.
98
getrusage
int getrusage(int who, struct rusage *usage)
사용자/시스템 CPU 시간, 메이저/마이너 페이지 폴트 수, 최대 RSS, I/O 횟수 등 리소스 사용 통계를 가져옵니다. RUSAGE_SELF/RUSAGE_CHILDREN/RUSAGE_THREAD로 범위 선택.
int finit_module(int fd, const char *param_values, int flags)
파일 디스크립터에서 직접 커널 모듈을 로드합니다. init_module보다 보안적으로 우월합니다 (fd 기반이므로 경로 기반 TOCTOU 없음).
176
delete_module
int delete_module(const char *name, unsigned int flags)
커널 모듈을 언로드합니다. 사용 중인 모듈은 EBUSY를 반환합니다.
169
reboot
int reboot(int magic1, int magic2, int cmd, void *arg)
시스템 재시작, 전원 끄기, halt, kexec 로드/실행 등을 수행합니다. 실수 방지를 위해 magic1=0xfee1dead, magic2=0x28121969 등 매직 넘버가 필요합니다.
334
rseq
int rseq(struct rseq *rseq, uint32_t rseq_len, int flags, uint32_t sig)
Restartable Sequence를 등록합니다. 시그널/선점(Preemption)으로 중단된 임계 구간을 재시작하는 lock-free 패턴 구현에 사용합니다 (Linux 4.18+).
435
clone3
long clone3(struct clone_args *cl_args, size_t size)
확장된 clone syscall입니다. pidfd 직접 생성, cgroup 지정, 스택 자동 설정 등의 새 기능을 구조체로 전달합니다 (Linux 5.3+).
462
mseal
int mseal(unsigned long start, size_t len, unsigned long flags)
VMA 범위를 봉인하여 mprotect/munmap/mmap/mremap으로 수정할 수 없게 합니다 (Linux 6.10+). VM_SEALED 플래그를 설정하며, 이후 수정 시도는 -EPERM을 반환합니다. Chrome, Firefox 등 브라우저 JIT 영역 보호에 활용됩니다.
471
rseq_slice_yield
int rseq_slice_yield(unsigned int flags)
Restartable Sequence의 타임 슬라이스 연장 메커니즘을 위한 syscall입니다. Linux 7.0 기준으로 이미 번호 471이 할당되어 있으며, rseq 임계 구간에서 타임 슬라이스 연장을 요청한 뒤 작업 완료 시 CPU를 자발적으로 양보(Yield)하는 흐름을 제공합니다. SYSCALL_WORK_RSEQ_SLICE와 함께 사용됩니다.