fork, exec, wait 와 I/O redirection, pipe
fork()
- 자식 process 생성
- 부모의 address space copy

wait() or waitpid()
- child와 parent 간의 dependency 생성(deterministic)
-reaping a child process
- 자식 process가 종료되면, exit(status)을 report 하도록 한다.
- 자식의 exit status는 process table에 저장되고, process table의 entry는 parent process 가 wait call을 invoke 한 뒤 해제된다.
- wait()가 호출되면 부모 프로세스는 자식의 종료 상태를 받아들이고, 그 후 자식 프로세스의 프로세스 테이블 항목(PCB)은 운영체제에 의해 할당 해제됩니다.
이를 통해 좀비 프로세스(종료 후 아직 reaping 되지 않은 자식)가 정리되고,
프로세스 테이블에 남은 항목이 제거됩니다. (자식 프로세스에 대한 모든 참조가 삭제됨을 의미합니다.)
Zombie Process
- process 실행은 다 완수했지만, process table에 남아있는 process
- 부모는 executing, while child is dead.
Orphan Process
- parent가 wait 하지 않은 상태로 죽었는데, 실행중인 child process
- init process가 새로운 부모가 되고, wait call을 호출한다.
- 본래 부모는 dead, while child is executing
Copy-On-Write 방식 사용
- 기본 동작:
- fork()를 호출하면 부모 프로세스의 메모리 공간(코드, 데이터, 힙 등)을 자식 프로세스와 공유합니다.
- 하지만 실제로는 메모리를 복사하지 않고, 두 프로세스가 같은 메모리 페이지를 참조합니다.
- 메모리 페이지 수정 시점:
- 부모 프로세스나 자식 프로세스 중 하나가 메모리를 수정하려고 할 때, 해당 페이지가 복사됩니다.
- 최초로 수정하려는 시점에만 복사가 이루어지며, 이때 각 프로세스는 독립적인 메모리 공간을 갖게 됩니다.
exec()
: running a new program
- parent 코드 말고 다른 코드 실행하고 싶어!
- 운영체제는 새로운 프로그램(바이너리 이미지)을 메모리에 로드하고, 새 프로그램을 위한 스택 및 힙을 초기화합니다.
- 인자: (실행할 바이너리 파일의 경로, 해당 프로그램에 전달할 인자 배열 argv)
- 1번) 현재 프로세스의 내용을 지우고 2번)새로운 프로그램의 내용을 메모리에 로드하여 새로운 프로그램을 실행시키는 역할을 합니다.(pid는 유지)(현재 실행중인 process 덮어씌우기)
- return 값 없다.

- ex)

- kernel에서 제공하는 API를 가지고 process 실행
- fork 와 exec 구분하는 이유?
-> 실행 전에 I/O redirection 또는 pipe를 중간에 사용하려고
- I/O redirection : 앞의 outcome이 stdout이 아니라 뒤에 있는 파일로 가도록해준다.
- pipe: 앞의 outcome이 뒤의 입력으로 가도록 해준다.
서로 address 공간이 달라서 원래는 접근이 불가능하지만, OS가 Inter Process Communication을 제공한다. (I/O redirection, pipe)
I/O redirection
: 전자의 output을 capture하여 후자의 input으로 send한다.



File Descriptor and File Descriptor Table
- process가 생성되면, process의 file descriptor array를 만든다.
- STDIN, STDOUT, STDERR는 초기화 되어있다.
- File Descriptor -> File Struct(각 struct마다 offset 존재)
- ex) read 시스템 콜 하게되면, kernel이 해당 fd가 가리키는 struct의 offset을 읽고 buffer에 복사한다.
- ex) write 시스템 콜 하게되면, kernel이 buffer에 있는 내용을 해당 fd가 가리키는 struct의 offset에 이어서 쓴다.


File Descriptor and System Calls
- open():
새로운 파일을 열 때, 운영체제는 새로운 파일 객체를 할당하고, 이를 가리키는 FD를 생성합니다. 이 파일 디스크립터는 파일 디스크립터 테이블에서 사용 가능한 가장 작은 번호를 할당받습니다.
- close():
- 파일 디스크립터 fd가 더 이상 사용되지 않도록 해제합니다.
- 해당 파일 디스크립터와 연결된 파일 객체가 더 이상 어떤 파일 디스크립터에서도 참조되지 않으면, 그 파일 객체도 메모리에서 해제됩니다.
- fork():
- 부모의 fd table을 자식에게 copy합니다.(file struct 공유)
- exec():
- 프로세스가 파일 디스크립터 테이블을 유지한다.


Pipe: '|'
: 한 process의 output이 STDOUT이 다른 process의 STDIN으로 들어간다.
Duplication of FD using dup()
fd = dup(n) system call : file descriptor table의 빈 곳을 찾아 n을 복사한다.
pipe()
: 커널에서 관리되는 특수한 파일(kernel buffer을 읽고 쓰는 fd)로, 두 개의 파일 디스크립터를 통해 프로세스 간에 데이터를 전달할 수 있습니다. 이 파일 디스크립터들은 읽기와 쓰기를 위한 각각의 끝을 나타냅니다.
- 파이프(pipe)는 단방향(unidirectional) 통신(FIFO queue)을 위해 사용됩니다.
- 한 쪽에서는 데이터를 쓰고, 다른 쪽에서는 그 데이터를 읽을 수 있습니다.

- No structured communication: 데이터의 크기나 송신자/수신자 정보를 알 수 없습니다.
- 파이프의 접근 방식은 파일 디스크립터(file descriptor)를 통해 이루어집니다.
- 파이프는 두 개의 파일 디스크립터로 구성됩니다:
p[0]: 읽기 끝 (read end)
- 다른 프로세스는 p[0]을 통해 데이터를 읽습니다.
- 만약 읽을 데이터가 없으면, 읽기 프로세스는 차단(block)되어 대기 상태가 됩니다.
- 즉, 버퍼에 데이터가 쌓일 때까지 읽는 프로세스는 기다리게 됩니다.
p[1]: 쓰기 끝 (write end)
- 한 프로세스가 p[1]을 통해 데이터를 쓰면, 그 데이터는 커널에서 관리하는 버퍼에 저장됩니다.
Pipe Used by Commands
e.g., > ps-aux | grep root | tail 
Anonymous Pipe
int pipe(int filesdes[2])
: 프로세스가 파이프를 만들지만 실제로 누가 쓰고 읽는지 알 수 없다.

부모 process 는 fd[0]을 통해 읽고, fd[1]에다가 쓴다.
만약 fork를 하는 경우, 동일한 fd table 공유, 즉 동일한 pipe 공유
child process는 fd[0]을 통해 읽고, fd]1]에다가 쓴다.
따라서 parent의 close(fd[0]), child의 close(fd[1]) 하면 메모리 누수를 막는다.
한계
두 개 프로세스가 커뮤니케이트 할 땐 좋지만,
- 한개의 process가 communicate 끝난 다음에 다른 process가 communicate할 수 있다.
- 그리고 process가 여러개인 경우 누가 읽고 쓰는지 모른다.
Named Pipe(FIFO)
- child-parent가 아닌 여러 process가 공유할 수 있게 해주는 pipe
- mkfifo or mknod 명령어를 통해 생성된다.
- int mkfif (const char *paht, mode_T, mode);
- file이라는 path를 통해서 kernel buffer을 연결 할 수 있게 만들어준다.
- file path가 곧 kernel buffer에 대한 이름이다.
ex)
- Write-Only without O_NONBLOCK(O_WRONLY): 파일을 쓰기 전용으로 열겠다.
- 읽기 프로세스가 대기 중인 경우: 쓰기 작업이 즉시 완료됩니다.
- 읽기 프로세스가 없는 경우: 쓰기 작업은 블로킹 상태가 되어 대기하게 됩니다.

- nmyfifo라는 kernel buffer가 할당됨
- unlink() file-system call: 실제 file object delete하는 역할