우리가 사용하는 시스템 콜의 예시를 들어주세요.
개발자가 일상적으로 사용하는 거의 모든 I/O, 프로세스, 네트워크 작업은 내부적으로 시스템 콜을 통해 커널에 요청됩니다.
일상적인 작업과 시스템 콜 매핑
| 일상 작업 | 시스템 콜 | 설명 |
|---|---|---|
| 파일 열기 | open() | 파일 디스크립터 반환 |
| 파일 읽기 | read() | 버퍼에 데이터 읽기 |
| 파일 쓰기 | write() | 버퍼의 데이터 쓰기 |
| 파일 닫기 | close() | 파일 디스크립터 해제 |
| 프로세스 생성 | fork() | 현재 프로세스 복제 |
| 프로그램 실행 | execve() | 새 프로그램으로 교체 |
| 프로세스 종료 | exit() | 프로세스 종료 |
| 메모리 할당 | mmap() / brk() | 힙 메모리 확장 |
| 네트워크 연결 | socket() / connect() | 소켓 생성 및 연결 |
| 시간 조회 | gettimeofday() | 현재 시간 반환 |
| 프로세스 대기 | wait() | 자식 프로세스 종료 대기 |
| 디렉토리 조회 | getdents() | 디렉토리 내 파일 목록 |
1. 파일 읽기/쓰기
// Java
FileInputStream fis = new FileInputStream("data.txt"); // open()
byte[] buf = new byte[1024];
int n = fis.read(buf); // read()
fis.close(); // close()
FileOutputStream fos = new FileOutputStream("out.txt"); // open()
fos.write("hello".getBytes()); // write()
fos.close(); // close()
내부 시스템 콜 흐름:
open("data.txt", O_RDONLY) → fd = 3
read(3, buf, 1024) → n = 읽은 바이트 수
close(3) → 파일 디스크립터 해제
2. 콘솔 출력
System.out.println("hello world");
내부 시스템 콜:
write(1, "hello world\n", 12)
│
└── fd=1 은 stdout (표준 출력)
표준 파일 디스크립터:
0 = stdin (표준 입력)
1 = stdout (표준 출력)
2 = stderr (표준 에러)
3. 프로세스 생성
// Java에서 외부 프로그램 실행
Process p = Runtime.getRuntime().exec("ls -al");
내부 시스템 콜:
fork() → 현재 프로세스 복제 (자식 프로세스 생성)
execve("/bin/ls", ["ls", "-al"], envp) → 자식을 ls 프로그램으로 교체
wait4() → 부모가 자식 종료 대기
┌────────────┐ fork() ┌────────────┐
│ 부모(JVM) │ ────────→ │ 자식(JVM) │
└────────────┘ └─────┬──────┘
│ │ execve()
│ wait4() ▼
│ ┌────────────┐
│ │ ls -al │
│ └─────┬──────┘
│ │ exit()
▼ ▼
자식 종료 감지 프로세스 종료
4. 네트워크 통신
// Java 소켓 통신
Socket socket = new Socket("example.com", 80);
OutputStream out = socket.getOutputStream();
out.write("GET / HTTP/1.1\r\n".getBytes());
InputStream in = socket.getInputStream();
int data = in.read();
socket.close();
내부 시스템 콜 순서:
socket(AF_INET, SOCK_STREAM, 0) → 소켓 생성 (fd = 4)
connect(4, {ip, port}, ...) → 서버에 TCP 연결
write(4, "GET / HTTP/1.1\r\n", 16) → 요청 전송
read(4, buf, size) → 응답 수신
close(4) → 소켓 닫기
5. 스레드 생성
new Thread(() -> {
System.out.println("새 스레드");
}).start();
내부 시스템 콜:
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_THREAD, ...)
│
├── CLONE_VM : 메모리 공간 공유
├── CLONE_FS : 파일 시스템 정보 공유
├── CLONE_FILES : 파일 디스크립터 공유
└── CLONE_THREAD : 같은 스레드 그룹
※ Linux에서 스레드는 clone()으로 생성
fork()와 clone()의 차이는 공유 범위
6. 메모리 할당
byte[] arr = new byte[10 * 1024 * 1024]; // 10MB 배열
JVM이 힙 메모리 부족 시:
mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)
→ 커널이 가상 메모리 영역 할당
→ 실제 물리 메모리는 접근 시 Page Fault로 할당
또는:
brk(new_address)
→ 힙 영역의 끝 주소를 확장
7. 시간 조회
long now = System.currentTimeMillis();
long nano = System.nanoTime();
내부 시스템 콜:
clock_gettime(CLOCK_REALTIME, &ts) → currentTimeMillis()
clock_gettime(CLOCK_MONOTONIC, &ts) → nanoTime()
8. Sleep
Thread.sleep(1000); // 1초 대기
내부 시스템 콜:
nanosleep({tv_sec=1, tv_nsec=0}, NULL)
→ 커널이 프로세스를 Sleep 상태로 전환
→ 타이머 인터럽트로 시간 경과 감지
→ 1초 후 프로세스를 다시 Ready 상태로 전환
9. 파일 목록 조회
File dir = new File("/home/user");
String[] files = dir.list();
내부 시스템 콜:
open("/home/user", O_RDONLY|O_DIRECTORY) → fd = 3
getdents64(3, buf, size) → 디렉토리 엔트리 읽기
close(3)
전체 요약
Java 코드 시스템 콜 커널 작업
─────────────────────────────────────────────────────────────────
System.out.println() → write() → 콘솔 장치 출력
new FileInputStream() → open() → 파일 시스템 접근
fis.read() → read() → 디스크에서 데이터 읽기
Runtime.exec() → fork() + execve() → 프로세스 생성
new Socket() → socket()+connect()→ 네트워크 연결
new Thread().start() → clone() → 스레드 생성
new byte[10MB] → mmap() / brk() → 메모리 할당
System.currentTimeMillis() → clock_gettime() → 시간 조회
Thread.sleep() → nanosleep() → 프로세스 슬립