운영체제 강의 4강

신승준·2022년 5월 21일
0

운영체제

목록 보기
4/12

4-1강, Process Management 1

프로세스 생성(Process Creation)

  • 부모 프로세스가 자식 프로세스를 생성한다. 보통 복제 생성을 하게 된다.
  • 프로세스의 트리 형성(계층 구조)
  • 프로세스는 자원을 필요로 한다.
    • 운영체제로부터 받는다.
    • 부모와 자원을 공유할 수도 있고 공유하지 않을 수도 있다. 원칙적으로는 별도의 프로세스가 되고 경쟁하는 사이(CPU를 얻을려고, 메모리를 더 많이 얻으려고)가 되기에 공유하지 않는다고 말할 수 있다.
  • 자원의 공유
    • 부모와 자식이 모든 자원을 공유하는 모델
    • 일부를 공유하는 모델
    • 전혀 공유하지 않는 모델
  • 수행(프로세스가 실행될 때, Execution)
    • 부모와 자식은 공존하며 수행되는 모델
    • 자식이 종료(terminate)될 때까지 부모가 기다리는(wait) 모델
  • 프로세스 생성
    • 주소 공간(Address space)
      • 자식은 부모의 공간(주소 공간, address space)을 복사한다.(binary, OS data(PCB 혹은 자원들)도 다 복사한다.)
      • 그렇게 복제된 곳에 새로운 프로그램을 올린다.
        • 따라서 서로 다른 프로그램들이 컴퓨터 내에 존재하게 된다.
    • Unix의 예
        1. fork() 시스템 콜이 새로운 프로세스를 생성
        • 부모를 그대로 복사(OS data except PID(process ID) + binary)
        • 주소 공간 할당
        1. fork() 다음에 이어지는 exec() 시스템 콜을 통해 새로운 프로그램을 메모리에 올린다.
      • 시스템 콜이기에 이러한 일은 운영체제(OS)가 수행한다.
      • 복제한 다음에 필요한 부분만 덮어씌우는 형식이다.
  • copy on write(COW)
    • write가 발생했을 때, 즉 원래 있던 내용을 바꿀 때 copy해서 새로운 것을 만들고, 그 전까지는 부모의 자원을 공유하는 것이다. 내용이 수정되면 부모의 code, data, stack을 복제한다. 이 때 모두 복제하는 것이 아니라 잘게 쪼개진 필요한 부분만 카피한다. 가능하면 이렇게 하는 것이 효율적이나 원칙적으로는 독립적인 것이 원칙이다.
  • 자식 프로세스가 종료된 다음에 부모 프로세스가 종료되어야 한다.
  • 프로세스 종료
    • 프로세스가 마지막 명령을 수행한 후 exit() 시스템 콜을 통해 운영체제에게 알려준다.(자발적)
      • 자식이 부모에게 output data를 보내게 된다.(via wait() 시스템 콜) ex. wait(NULL)
      • 프로세스의 각종 자원들이 운영체제에게 반납된다.
    • 부모 프로세스가 abort() 시스템 콜을 통해 자식의 수행을 종료시킨다.(비자발적)
      • 자식이 할당 자원의 한계치를 넘어설 때(자식을 낳아놨더니 너무 펑펑 쓰는 경우)
      • 자식에게 할당된 태스크가 더 이상 필요하지 않음.(일 시킬려고 만들었고, 더 이상 시킬 일이 없을 때)
      • 부모가 종료(exit)되는 경우
        • 어쩌다 보니 부모가 종료되어야 하는 상황이 생겼다.
        • 그러면 자식들을 모두 종료시킨 다음에 종료되어야 한다.
        • 운영체제는 부모 프로세스가 종료되는 경우, 자식이 더 이상 수행되도록 두지 않는다.
        • 자식이 자식을 낳아놓았을 수도 있다. 자식의 자식이 먼저 종료되고, 자식이 종료되고 부모가 종료되는 단계적인 종료를 걸쳐 종료된다.

4-2강, Process Management2

fork()

int main() {
    int pid;
    printf("\n Hello!\n");
    pid = fork();	// 자식 프로세스는 fork를 실행한 이후부터 실행된다.
    
    if (pid == 0) {
        printf("\n Hello, I am child!\n");		// 자식, pid가 0이라서
    } else if (pid > 0) {
        printf("\n Hello, I am parent!\n");		// 부모, pid가 양수라서
    }
}

  • 부모 프로세스는 pid = fork()일 때, 즉 PC가 pid = fork()를 가리킬 때 자식 프로세스를 복제하게 된다. 복제 시 PC도 똑같이 복제되므로 자식 프로세스의 PC도 pid = fork()를 가리키고 있다.(복제된 그 당시에) main 함수의 시작 부분을 가리키고 있는 것이 아니다. 그리고 그 밑의 코드를 수행하게 된다.
  • 부모 프로세스는 fork()의 반환 값(process id)으로 양수를 얻게 된다. 자식 프로세스는 0을 얻게 된다. 이를 통해 뭐가 부모이고 뭐가 자식인지 구별할 수 있게 된다.
  • 부모는 Hello가 찍히지만, 자식은 그걸 출력한 기억은 있지만 출력되지는 않는다.

exec()

#include <stdio.h>

int main() {
    int pid;
    pid = fork();
    
    if (pid == 0) {
        printf("\n Hello, I am child! Now I'll run date\n");
        execlp("/bin/date", "/bin/date", (char *)0);
        printf("Can I?");
    } else if (pid > 0) {
        printf("\n Hello, I am parent!\n");
    }
}
  • execlp를 통해 다른 프로그램으로 기억이 덮어씌워지게 된다.
  • execlp("/bin/date", "/bin/date", (char *)0);
    • 리눅스에서의 커맨드 혹은 프로그램이다. date는 현재 날짜와 시각을 출력해주는 프로그램이다. 마지막은 NULL 포인터이다.
    • 자식 프로세스는 Hello, I am child! Now I'll run date를 출력시키고 나서 date 프로그램을 실행하게 된다.
    • exec()을 하고 나면 돌아갈 수 없다. 따라서 Can I?는 출력되지 않는다.

* execlp()

int main() {
printf("1");
execlp("echo", "echo", "hello", "3", (char *)0);
printf("2");
  • 1이 출력되고 hello 3이 출력되고 종료된다.
execlp("echo", "echo", "hello", "3", (char *)0);
  • echo라는 명령어를 실행해라. 그 인자는 hello와 3이다. 그리고 마지막에 NULL 포인터를 적어주는 것이 형식이다.
// shell
echo hello 3	// hello 3가 출력된다.

wait()

  • 프로세스를 잠들게 하는 것(block상태, sleep)이다.
  • 오래 걸리는 event를 기다려야 할 때, 결과가 나오면 다시 깨어나게 된다.
  • 프로세스가 A가 wait() 시스템 콜을 호출하면,
    • 커널은 자식 프로세스가 종료될 때까지 기다리다가 프로세스 A를 sleep시킨다.
    • 자식 프로세스가 종료되면 프로세스 A는 Block에서 ready 상태로 바뀌어 CPU를 얻을 수 있는 상태가 된다. (ready queue에 들어가게 된다)

  • 부모 프로세스는 else문의 wait를 만나 자식 프로세스가 끝날 때까지 기다리게 된다. 즉 block 상태가 되어 CPU를 얻지 못하고 기다리게 된다.

exit()

int main() {
    int pid;
    pid = fork();
 	exit();
    
    if (pid == 0) {
        printf("\n Hello, I am child! Now I'll run date\n");
        execlp("/bin/date", "/bin/date", (char *)0);
        printf("Can I?");
    } else if (pid > 0) {
        printf("\n Hello, I am parent!\n");
    }
}
  • exit()을 만나 부모, 자식 모두 종료된다.

  • 자발적 종료
    • 마지막 statement 수행 후, 프로그램 코드에 적힌 exit() 시스템 콜을 통해
    • 프로그램에 명시적으로 적어주지 않아도 main 함수가 리턴되는 위치에 컴파일러가 넣어준다.
  • 비자발적 종료
    • 막 실행 중 인데 밖에서 해당 프로세스를 죽이는 경우이다.
    • 부모 프로세스가 자식 프로세스를 죽이는 경우(대표적)
      • 자식 프로세스가 한계치를 넘어서는 자원을 요청했을 때
      • 자식에게 할당된 업무가 더 이상 필요하지 않을 때
    • 키보드로 kill, break 등을 친 경우(터미널에 ctrl + c를 눌렀을 경우)
    • 부모가 종료하는 경우(그냥 부모의 일이 다 끝나서)
      • 부모 프로세스가 종료하기 전에 자식들이 먼저 종료되어야 한다.

프로세스와 관련한 시스템 콜

  • fork() : create a child(copy)
  • exec() : overlay new image
  • wait() : sleep until child is done
  • exit() : frees all the resources, notify parent

프로세스 간 협력

  • 독립적 프로세스(Independent process)

  • 협력 프로세스(Cooperating process)

  • 프로세스 간 협력 메커니즘(IPC : Interprocess Communication)

    • 메세지를 전달하는 방법

      • message passing : 커널을 통해 메세지 전달
        • 사용자 프로세스끼리는 전달할 수 없다. 커널을 통해 전달해야 한다.
        • 커널의 한 구석에 이 메세지를 저장하기 위한 공간이 필요해진다.
        • 전달하고 받을 때마다, 시스템 콜을 통해 커널 모드로 전환되어야 하므로(커널 영역의 메모리를 읽어야 하므로) 오버헤드가 발생하게 된다.
        • 속도 또한 느려질 수 있다.
        • Direct Communication
          • 통신하려는 프로세스의 이름을 명시적으로 표시
        • Indirect Communication
          • mailbox(혹은 port)를 통해 메세지를 간접 전달한다.
          • mailbox의 내용을 꺼내볼 대상은 정해져 있지 않다.
    • 주소 공간을 공유하는 방법(shared memory)

      • 각 프로세스는 각자의 주소 공간에서만 활동하는데, 그럼에도 불구하고 일부 주소 공간을 공유하게 하는 메커니즘이다. 즉 다른 프로세스가 일부 주소 공간을 공유하는 것이다.
      • 기존 프로세스에서 shared memory 영역을 읽으면 되기 때문에 시스템 콜을 통하여 커널 모드로 전환할 필요가 없다.
      • 따라서 상대적으로 속도가 빠르다.
      • Producer-Consumer 문제가 발생할 수 있다.
      • 서로 신뢰할 수 있는 관계여야만 공유가 가능하다.
      • 쓰레드
        • 쓰레드는 사실상 하나의 프로세스이므로 프로세스 간 협력으로 보기는 어려우나, 동일한 process를 구성하는 쓰레드들간에는 주소 공간을 공유하므로, 협력이 가능하다.
profile
메타몽 닮음 :) email: alohajune22@gmail.com

0개의 댓글