[ostep]5. Process API(프로세스 API)

hankkim·2020년 12월 27일
0

ostep

목록 보기
2/2
post-thumbnail

5.1 fork() 시스템 콜

프로세스 생성에 fork() 시스템 콜 사용

pid_t getpid();

header : unistd.h, sys/types.h
return : 호출 프로세스의 ID

pid_t fork();

header : unistd.h,sys/types.h
return : 실패하면 -1, 부모에게는 새로 생성된 자식 프로세스 PID가 반환, 자식 프로세스에는 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();
    if (rc < 0) {
    	fprintf(stderr, "fork failed\n");
        exit(1);
    }  else if (rc == 0) { // 자식 (새 프로세스)
    	printf("hello, I am child (pid:%d)\n", (int)getpid());
    } else {
    	printf("hello. I am parent of %d (pid:%d)\n", rc, (int)getpid());
    }
	return 0;
}

자식 프로세스

  • fork()를 호출하면서부터 시작
  • 부모 프로세스와 완전히 동일하지는 않다.
  • 자신의 주소 공간, 레지스터, PC 값을 갖는다.
  • fork() 시스템 콜의 반환값이 다르다.

단일 CPU 시스템에서 이 프로그램을 실행하면, 프로세스가 생성되는 시점에는 2개(부모와 자식)프로세스 중 하나가 실행된다. (순서가 다를 수 있다)

CPU 스케줄러(scheduler) 는 실행할 프로세스를 선택한다. 어느 프로세스를 먼저 실행된다라고 단정하는 것은 매우 어렵다. 이 비결정성(nondeterminism) 으로 인해 멀티 쓰레드 프로그램 실행 시 다양한 문제가 발생한다.

5.2 wait() 시스템 콜

pid_t wait(int *wstatus);

header : sys/types.h, sys/wait.h
return : 성공하면 자식 프로세스 ID, 오류 시 -1
자식 프로세스가 종료되면 return

#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();
    if (rc < 0) {
    	fprintf(stderr, "fork failed\n");
        exit(1);
    }  else if (rc == 0) { // 자식 (새 프로세스)
    	printf("hello, I am child (pid:%d)\n", (int)getpid());
    } else {
    	int wc = wait(NULL);
    	printf("hello. I am parent of %d (wc:%d) (pid:%d)\n", wc, rc, (int)getpid());
    }
	return 0;
}

5.3 exec() 시스템 콜

exec()

자기 자신이 아닌 다른 프로그램을 실행해야 할 때 사용

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
	printf("hello world (pid:%d)\n", (int)getpid());
    int rc = fork();
    if (rc < 0) {
    	fprintf(stderr, "fork failed\n");
        exit(1);
    }  else if (rc == 0) { // 자식 (새 프로세스)
    	printf("hello, I am child (pid:%d)\n", (int)getpid());
        char *myargs[3];
        myargs[0] = strdup("wc");
        myargs[1] = strdup("p3.c");
        myargs[2] = NULL;
        execvp(myargs[0], myargs);
        printf("this shouldn't, print out");
    } else {
    	int wc = wait(NULL);
    	printf("hello. I am parent of %d (wc:%d) (pid:%d)\n", wc, rc, (int)getpid());
    }
	return 0;
}

실행 파일 이름(예 wc)와 인자(예 p3.c)가 주어지면 해당 실행 파일의 코드와 정적 데이터를 읽어들여 현재 실행 중인 프로세스의 코드 세그멘트와 정적 데이터 부분을 덮어쓴다. 힙과 스택 및 프로그램 다른 주소 공간들로 새로운 프로그램의 실행을 위해 다시 초기화된다.
자식 프로세스가 exec()을 호출한 후에는 p3.c는 전혀 실행되지 않은 것처럼 보인다. exec() 시스템 콜이 성공하게 되면 p3.c는 절대로 리턴하지 않는다.

5.4 왜, 이런 API를?

UNIX의 쉘을 구현하기 위해서는 fork()와 exec()를 분리해야 한다.
쉘이 fork()를 호출하고 exec()를 호출하기 전에 코드를 실행할 수 있다.
이 때 실행하는 코드에서 프로그램의 환경을 설정하고, 다양한 기능을 준비한다.
fork()와 exec()를 분리해서 쉘은 많은 유용한 일을 쉽게 할 수 있다.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    int rc = fork();
    if (rc < 0) {
    	fprintf(stderr, "fork failed\n");
        exit(1);
    }  else if (rc == 0) { // 자식: 표준 출력 파일로 재지정
		close(STDOUT_FILENO);
        open("./p4.output", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);
        char *myargs[3];
        myargs[0] = strdup("wc");
        myargs[1] = strdup("p3.c");
        myargs[2] = NULL;
        execvp(myargs[0], myargs);
    } else {
    	int wc = wait(NULL);
    }
	return 0;
}

wc p3.c > newfile.txt

자식이 생성되고 exec()이 호출되기 전에 표준 출력(standard output) 파일을 닫고 newfile.txt 파일을 연다.

5.5 여타 API들

kill()

프로세스에게 시그널(signal) 을 보내는데 사용

시그널이라는 운영체제의 메커니즘은 외부 사건을 프로세스에게 전달하는 토대

0개의 댓글