본문 바로가기
OS

3. Process API

by 녕인뉸 2022. 10. 9.

1. Process ID

(1) 프로세스는 pid(process identifier)를 통해 관리되고 식별됨

(2) 부모 프로세스는 자식 프로세스를 만들고 이 자식 프로세스가 다시 부모 프로세스가 돼서 자식 프로세스를 만들면서 프로세스 트리를 생성

pid = 1 -> 조상 프로세스

 

2. Create a process

(1) fork()

  : 부모 프로세스는 자식 프로세스를 생성하기 위해 fork() 시스템 콜을 사용

  : 자식 프로세스는 부모의 address space의 복사본을 가짐

  : 두 프로세스 모두 fork 이후에 한 가지 차이점으로 계속 실행됨 -> 차이점 : 리턴 값 (부모는 child pid, 자식은 0을 리턴)

pid = 12345 를 가진 부모 프로세스가 fork를 통해 pid = 12346을 가진 자식 프로세스를 생성

이때 자식 프로세스는 부모의 address space를 카피하고 리턴 값은 0

부모 프로세스는 자식 프로세스의 pid인 12346을 리턴

 

#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) {	// fork failed; exit 
    	fprintf(stderr, "fork failed\n"); 
        exit(1);
    } else if (rc == 0) { // child (new process) 
    	printf("hello, I am child (pid:%d)\n", (int) getpid());
    } else {	// parent goes down this path (main) 
    	printf("hello, I am parent of %d (pid:%d)\n", rc, (int) getpid());
    }
    return 0;
}

getpid로 부모 프로세스의 pid를 출력

fork로 자식 프로세스를 생성하고 rc == 0 이면 자식 프로세스를 생성한 것이므로 이 자식 프로세스의 pid를 가져와서 출력

rc != 0이면 부모 프로세스이므로 부모 pid를 출력

 

(2) wait() / waitpid()

- wait(&status) 시스템 콜은 자식 프로세스가 종료될 때 부모 프로세스에게 exti status를 알려줌

- exit status는 프로세스 테이블에 저장되고 프로세스 테이블 엔트리는 부모 프로세스가 wait()을 호출한 후에 할당 해제됨

- 만약 부모 프로세스가 wait() 호출을 실패하면, 자식 프로세스에 대한 프로세스 테이블 엔트리는 테이블에 남아있고 좀비 프로세스를 생성

    -> 자식은 부모가 책임져야함, 그래서 자식이 죽고나서 PCB 회수를 부모가 하게 되는데 이때 부모가 회수하지 않으면 좀비 프로세스가 됨

- Reaping : 부모에게 exit status를 주고, OS가 좀비 자식 프로세스를 프로세스 테이블로부터 지우는 wait() / waitpid() 시스템 콜을 사용하는 부모에 의해 수행

#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) {	// fork failed; exit 
    	fprintf(stderr, "fork failed\n"); 
        exit(1);
    } else if (rc == 0) {	// child (new process) 
    	printf("hello, I am child (pid:%d)\n", (int) getpid());
    } else {	// parent goes down this path (main) 
    	int wc = wait(NULL); // Reaping child process 
        printf("hello, I am parent of %d (wc:%d) (pid:%d)\n", rc, wc, (int) getpid());
    }
    return 0;
}

rc가 부모 프로세스일 경우, wait으로 자식 프로세스를 reaping하고

부모 프로세스의 pid와 wc를 출력

 

 

3. Zombie vs. Orphan Processes

(1) 좀비 프로세스나 없어진 프로세스는 실행을 완료했지만 여전히 프로세스 테이블에 entry를 갖고 있는 프로세스

  : 종료되었지만 부모가 여전히 wait을 통해 status를 수집하는 프로세스 -> 보통 버그나 코딩 에러가 원인

  : 모든 프로세스는 일반적으로 잠시 동안만 좀비로 있을 수 있음

  : 부모가 wait을 호출할 때, 자식은 일반적으로 종료됨 // wait(pid) - pid를 가진 자식 프로세스를 wait

  : 자식이 죽는 동안, 부모는 실행 중

(2) 부모는 wait을 호출하지 않고 종료될 때, 자식 프로세스는 고아가 됨 (ex. 여전히 실행 중)

// 부모의 부모 (조상) => init이 책임짐

  : init 프로세스는 프로세스들의 부모로 할당됨

  : init 프로세스는 주시적으로 고아 프로세스의 exit status를 수집하기 위해 wait을 호출

  : 자식이 죽는 동안, 부모는 실행 중

 

 

4. Running a new program - exec()

(1) caller는 caller 자체로부터 다른 프로그램을 실행시키려 함

  : editor를 시작

  : ls -l

(2) OS는 새로운 binary image를 load, 새로운 스택을 초기화, 새로운 프로그램에 대한 새로운 힙을 초기화하는 것이 필요함

(3) 2개의 파라미터 : binary file의 이름, arguments의 배열

(4) 메모리의 기존 내용을 새 이진 파일의 새 메모리 내용으로 바꿈

(5) exec()은 리턴하지 않고 새로운 프로그램을 실행

 

//p3.c
#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) { // fork failed; exit
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (rc == 0) { // child (new process)
    	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 { // parent goes down this path (main) 
    	int wc = wait(NULL);
    	printf("hello, I am parent of %d (wc:%d) (pid:%d)\n", rc, wc, (int) getpid());
    }
    return 0;
}

fork를 해서 rc가 자식 프로세스이면 자식 프로세스의 pid를 출력하고

myargs 이중 배열에 wc와 p3.c를 복사하고 execvp로 wc p3.c 프로세스를 수행

// wc p3.c -> p3.c의 바이트, 단어, 행 수를 출력

rc가 부모 프로세스면 wait을 실행하여 자식 프로세스를 reaping하고

부모 프로세스와 자식 프로세스의 pid를 출력

 

 

5. Why separating fork() and exec()?

(1) fork와 exec을 분리시킴으로써, 새로운 프로그램을 실행하기 전에 다양한 세팅을 조절할 수 있고 IO redirection과 pipe를 가능하게 함

** cat w3.c > newfile.txt // w3.c의 내용을 newfile.txt로 입력

** echo hello world | wc // hello world를 출력하고 wc를 수행

 

 

6. What is IO redirection?

(1) IO redirection은 파일, 명령어, 프로그램, 스크립트에서 출력을 캡처하여 다른 파일, 명령어, 프로그램, 스크립트의 입력으로 보내는 것

(2) 쉘은 명령어를 fork하고 argument와 함께 exec

  : 쉘은 fork()와 exec("wc", "wc w3.c") 호출

  : exec("wc", "wc w3.c")를 호출하기 전에, 쉘은 STDOUT을 닫고, newfile.txt를 open

 

#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("p4.c"); 
        myargs[2] = NULL; 
        execvp(myargs[0], myargs);
    } else {
    	int wc = wait(NULL);
    }
    return 0;
}

rc가 자식 프로세스이면 STDOUT을 닫고,

./p4~를 open

myargs 배열에 wc p4.c를 복사하고 execvp로 "wc p4.c"를 수행

 

 

 

7.File descriptor and File descriptor table

(1) File descriptor

- File descriptor는 파일, 디렉토리, 디바이스를 대표하는 정수

- 프로세스는 파일, 디렉토리, 디바이스를 열기 위해 file descriptor를 사용

- 각 프로세스는 자신의 file desriptor table을 가짐

- 기본적으로 열리는 Standard Streams : 0 (STDIN), 1(STDOUT), 2(STDERR)

 

8. File descriptor and System calls

(1) open()

- 새로운 file object를 할당하고, 새로운 file descriptor를 할당하고 새롭게 할당된 파일을 세팅

- 새로운 file descriptor를 할당할 때, file descriptor table로부터 가장 작은 free file descriptor를 사용함

 

(2) close()

- fd (file descriptor)를 할당해제

- 만약 file object와 결합된 file descriptor가 없으면 file object를 할당 해제

 

(3) fork()

- 부모에서 자식 프로세스로 file descriptor table을 카피

 

(4) exec()

- file desciptor table을 유지

 

 

** fork() and File descriptor

fork로 자식 프로세스를 생성하면 부모 프로세스가 갖고 있던 file descriptor table을 자식 프로세스가 카피함

 

**Input Redirection

close(0)으로 fd 0을 할당 해제

open("input.txt")로 새로운 file descriptor를 할당해서 input.txt를 세팅

가장 작은 fd인 0에 할당

fd 0의 데이터를 최대 1024 바이트만큼 읽어서 buf에 저장

 

 

**Output redirection

fd 1을 할당해제 하고

새로운 file descriptor를 할당해서 output.txt를 세ㅌㅇ한 후

가장 작은 free fd에 할당

fd 1의 데이터를 최대 1024바이트 만큼 읽어서 buf에 write

 

** cat and IO redirection

fd 0을 할당 해제하고

새로운 fd를 할당해서 input.txt를 read only로 세팅

가장 작은 free fd에 할당

exec으로 argv안에 넣은 "cat"이라는 명령어를 수행

 

fd 0의 데이터를 buf 사이즈 만큼 읽어서 buf에 데이터를 저장

만약 read함수의 리턴 값이 0보다 크면, 즉 올바르게 읽어들인 데이터의 길이를 반환했을 경우

1번 fd에 n만큼 읽어서 buf에 write

 

 

** Duplication of File descriptor using dup()

 

dup(n) : file descriptor table의 시작으로부터 빈 슬롯을 찾음

fd = dup(1) // fd 1을 fd로 copy

write(fd, "world\n", 6); // fd에 world\n를 write

 

 

9. What is pipe?

(1) 파이프는 한 프로세스를 다른 프로세스와 연결하는 단방향 바이트 스트림

  : 채널의 끝에 쓰인 데이터는 다른 채널의 끝에서 읽힘

  : 논리적인 관점에서, 파이프는 FIFO 큐의 특성과 비교될 수 있음

  : No structured communication - 파이프에 포함된 데이터의 sender/receiver, 사이즈에 대해 알 수 없음

  : 파이프는 파일 디스크립터를 통해 프로세스에 노출되는 커널 버퍼인 파일의 특정한 타입

  : 파이프에 대한 접근은 파일 디스크립터로부터 읽거나 파일 디스크립터에 쓰기를 통해 이뤄짐

 

 

10. Pipe used by commands

(1) 파이프 메카니즘의 최신 사용은 명령어가 링크될 때 명령어 라인의 interpreter를 통해 수행

ex. > ps -aux | grep root | tail

(2) Anonymous pipe

- 프로세스에 의해 생성되고 관련 디스크립터에 대한 전송은 해당 자손에 의한 상속에 의해 수행됨

 ex) fork를 사용하여 자식 프로세스를 생성

- 파이프의 생성자인 공통 조상과 프로세스 간의 커뮤니케이션만 허용되므로 제한적

- anonymous pipe의 생성

  : int pipe(int filedes[2]);

  -> read descriptor: filedes[0], write descriptor: filedes[1]

 

**Usage of pipe

#include <stdio.h>
#include <unistd.h>

int main(void) {
    inr n, fd[2], pid;
    char line[100];
    if (pipe(fd) < 0) exit(-1);
    if ((pid = fork()) < 0) exit(-1);
    else if (pid > 0) { /*parent */
        close(fd[0]); 
        write(fd[1], “Hello World\n”, 12);
    	wait(NULL); 
    }
    else { /* child */
        close(fd[1]); 
        n = read(fd[0], line, MAXLINE); 
        write(STDOUT_FILENO, line, n); 
    }
}

pipe(fd)로 파이프를 생성

fork를 수행해서 부모 프로세스일 경우, fd[0]을 닫고 fd[1]에 Hello World를 write하고 자식 프로세스가 죽을 때까지 wait

자식 프로세스일 경우, fd[1]을 닫고 fd[0]을 읽어서 line 배열에 읽은 데이터를 저장하고, write

(3) Named Pipe (FIFO)

- 커뮤니케이션은 양방향이며 부모-자식 관계 없음

- 파이프와 관련된 엔트리는 파일 시스템에 의해 관리됨

- named pipe는 여러 writer를 가짐

- named pipe는 프로세스간의 커뮤니케이션이 끝난 후에도 계속 존재

- 이름을 가지며 file operation과 관련된 파일과 똑같이 처리됨 (ex. open, close, read, write)

- mkfifo와 mknod 명령어에 의해 생성

- C 함수에 의해 생성되고 지워질 수 있음

  : int mkfifo(const char *path, mode_t mode);

  : int unlink(const char *path);

- named pipe로 부터 읽고 쓰는 것은 standard read(), write() 시스템 콜을 통해 수행될 수 있음

 

**Usage of mkfifo()

//writer.c
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main() 
{ 
    int fd; 
    char * myfifo = "/tmp/myfifo";
    
    /* create the FIFO (named pipe) */ 
    mkfifo(myfifo, 0666); // named pipe 생성
    
    /* write "Hi" to the FIFO */ 
    fd = open(myfifo, O_WRONLY); //write only로 myfifo을 open하여 fd로 리턴
    write(fd, "Hi", sizeof("Hi"));  // fd에 Hi write
    close(fd);
    
    /* remove the FIFO */ 
    unlink(myfifo); //named pipe를 remove
    return 0; 
}

 

//reader.c
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#define MAX_BUF 1024

int main() 
{ 
    int fd; 
    char * myfifo = "/tmp/myfifo"; 
    char buf[MAX_BUF]; 
    
    /* open, read, and display the message from the FIFO */ 
    fd = open(myfifo, O_RDONLY); 
    read(fd, buf, MAX_BUF); // fd를 읽어서 buf에 저장
    printf("Received: %s\n", buf); 
    close(fd);
    
    return 0; 
}

 

11. Open Write-Only with and without O_NONBLOCK

(1) O_RONLY / O_WRONLY 세트를 가진 FIFO를 열 때

  : O_NONBLOCK이 세팅되면, O_RONLY를 가진 open()이 지연 없이 리턴,

   프로세스가 읽기 위해 open한 파일을 갖고 있지 않으면 O_WRONLY를 가진 open()은 에러를 리턴

  : O_NONBLOCK이 지워지면, O_RONLY / O_WRONLY 를 가진 open()은 쓰레드가 쓰거나 읽기 위한 파일을 열 때까지 호출한 쓰레드를 블럭

**thread - 프로세스 내에서 실행되는 흐름의 단위, 실제로 작업을 수행하는 주체

 

 

12. Inter-Process Communication (IPC)

(1) 다른 프로세스는 다른 address space에서 실행됨

(2) 다양한 프로세스가 서로 통신할 수 있는 메카니즘이 필요 -> IPC

(3) 메세지를 전달하거나 공유된 메모리를 통해 수행될 수 있음

(4) 유닉스에서의 IPC의 유형

  : 전통적인 유닉스 IPC - signal, pipe, socket

  : System V IPCs - 메세지 큐, 세마포어, 공유된 메모리

'OS' 카테고리의 다른 글

6. Scheduling : The Multi-level feedback Queue  (0) 2022.10.14
5. Scheduling : Introduction  (0) 2022.10.14
4. Mechanism: Limited Direct Execution  (0) 2022.10.13
2. The Abstraction : The Process  (0) 2022.10.09
1. Introduction to Operating Systems  (0) 2022.10.06