일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 컴퓨터공학 #Java #자바 #클래스 #객체 #인스턴스
- BOJ #컴퓨터공학 #C++ #알고리즘 #자료구조
- HTML #CSS
- 잔
- 컴퓨터공학 #자료구조 #스택 #c++ #알고리즘 #백준문제풀이
- 컴퓨터공학 #c #c언어 #문자열입력
- Today
- Total
영벨롭 개발 일지
[운영체제]Process API 공부: fork, wait, exec 본문
Process API란?
Prcess API란 OS가 Application에게 제공하는 interface입니다.
프로세스를 생성, 정지, 종료, 재개와 같은 기능을 제공해주는 것인데요! System call 이라고도 부릅니다.
Process API인 fork(), exec(), wait()을 하나씩 살펴보겠습니다.
fork() - child process 생성
fork()는 child process를 생성해줍니다.
child process는 fork()를 호출한 process 즉 부모 process로부터 분리된 메모리 공간을 할당받으며 부모와 동일한 메모리 contents를 갖습니다.
child process는 자신만의 register들과 PC(program counter register)를 갖습니다.
일단 생성되고 나면 부모와는 독립적인 process가 됩니다.
반환값은 부모 process는 child process의 PID(process ID)를 반환하고, child process는 0을 반환합니다.
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(int argc, char* argv[]) {
printf("hello world (pid: %d)\n", (int)getpid());
int rc = fork(); //child process 생성
if (rc < 0) { //fork 실패 -> exit
fprintf(stderr, "fork faild\n");
exit(1);
}
else if (rc == 0) { //child process
printf("hello, I am child (pid: %d)\n", (int)getpid());
}
else { //parent
printf("hello, I am parent of %d (pid: %d)\n", rc, (int)getpid());
}
return 0;
}
//출력
hello world (pid: 29146)
hello, I am parent of 29147 (pid: 29146)
hello, I am child (pid: 29147)
실행 순서는 다음과 같이 되는 것을 확인할 수 있습니다.
main() 시작 -> fork() -> 부모 -> 자식 -> main() 종료
wait() - process들 간의 dependency 생성
child process가 생성되면, 부모 process에서 wait() 시스템 콜은 child process가 실행이 끝날 때까지 return하지 않습니다.
앞서 fork() 시스템 콜에서 언급했듯이, 일단 child process가 생성되고 나면 부모와 자식 process는 독립적인 process가 됩니다. 때문에 부모와 자식 process는 어떠한 종속성도 가지고 있지 않습니다.
부모와 자식 process간의 순서를 정하고 dependency를 부여하기 위해 wait() 시스템 콜을 사용할 수 있습니다.
위 코드에서 부모 프로세스에서 wait()을 추가해보겠습니다.
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main(int argc, char* argv[]) {
printf("hello world (pid: %d)\n", (int)getpid());
int rc = fork(); //child process 생성
if (rc < 0) { //fork 실패 -> exit
fprintf(stderr, "fork faild\n");
exit(1);
}
else if (rc == 0) { //child process
printf("hello, I am child (pid: %d)\n", (int)getpid());
}
else { //parent
int wc = wait(NULL); //기다리고 있는 child process의 PID
printf("hello, I am parent of %d (wc: %d) (pid: %d)\n", rc, wc, (int)getpid());
}
return 0;
}
//출력
hello world (pid: 29146)
hello, I am child (pid: 29147)
hello, I am parent of 29147 (wc: 29147) (pid: 29146)
wait()을 추가했더니 fork -> 자식 -> 부모 순서로 실행 순서가 바뀐 것을 확인할 수 있습니다!
exec() - Running a new program
exec()는 fork() 에서 처럼 복사본이 아닌 다른 프로그램을 실행할 때 사용합니다.
프로그램을 실행하기 위해선 OS는 새로운 binary image를 load하고 새로운 stack과 heap을 초기화 해야합니다. 때문에 exec()는 binary file의 이름과 arguments, 두 개의 parameter를 받습니다.
exec()가 호출되고 나면, 기존의 메모리 contents를 인자로 받은 binary file로부터 가져온 새로운 메모리 contents들로 교체합니다.
char *argv[3];
argv[0] = "echo";
argv[1] = "hello";
argv[2] = 0;
exec("/bin/echo", argv);
Why separating fork() and exec()?
그렇다면 fork()와 exec() 모두 프로그램을 실행한다는 것인데 이 두개를 왜 구분할까요?
이 두개를 구분함으로써 우리는 새로운 프로그램을 실행하기 전 다양한 setting들을 조작할 수 있으며 IO redirection과 pipe를 가능하게 할 수 있습니다.
fork()와 exec()를 구분하는 것은 UNIX Shell 을 building 하는데 있어서 필수적입니다. fork()를 실행된 후, exec()를 실행하기 전에 shell이 코드를 실행할 수 있도록 해줍니다.
명령어를 실행할 새로운 child 프로세스를 생성하기 위해 fork()를 호출하고 나면, wait()을 호출함으로써 해당 명령어가 완료되기를 기다립니다.
child 프로세스가 완료되면, shell은 wait()으로부터 return하게 되고 prompt를 다시 출력하여 새로운 명령어를 기다리게 됩니다.
- I/O Redirection
예를 들어, 다음과 같은 명령어를 실행하면,
propmpt> wc p3.c > newfile.txt
wc 의 output은 newfile.txt로 redirection 됩니다.
exec() 가 호출되기 전, child 프로세스가 생성되면, shell은 STDOUT을 닫고 newfile.txt를 open 합니다. 이렇게함으로써 wc의 결과는 screen 대신 newfile.txt에 보내지게 되는거죠!
- Pipe
prompt> echo hello world | wc
UNIX pipe도 비슷하게 구현되지만, dup()과 pipe() 시스템 콜과 함께 구현됩니다.
한 프로세스의 output이 kernel pipe에 연결되어 작성되면 다른 프로세스는 이 pipe에서 data를 읽어와 input으로 연결합니다.
m = dup(n) 은 file descriptor table에서 n번째 이후에 empty slot의 file descriptor m을 반환하여 m 번째 slot은 n 번째 slot과 동일한 offset을 가리키게 됩니다.
int pipe(int fd[2]) 는 kernel buffer라고도 불리는 file이며 성공적으로 호출되었다면 0, 실패했을 경우 -1을 반환합니다. 파이프를 생성한 프로세스는 file descriptor만 갖고 있게 됩니다.
첫 번째 file descriptor fd[0]은 이 file을 읽기 위한 것으로, file의 시작 offset을 갖고 있습니다.
두 번째 file descriptor fd[1]은 이 file에 data를 쓰기 위한 것으로, file의 끝 offset을 갖고 있습니다.
만약 데이터를 fd[1]에 쓰게 되면 fd[0]으로 그 데이터를 읽을 수 있습니다.
'CS > 운영체제' 카테고리의 다른 글
[운영체제] Limited Direct Execution(LDE) 메커니즘 (0) | 2022.04.01 |
---|---|
[운영체제][OS]프로세스 Process란? (0) | 2022.03.04 |
[운영체제][OS]Operating Systems, 운영체제 들어가기 (0) | 2022.02.28 |