System Call - Process

Hyungseop Lee·2023년 11월 20일
0

Process

  • Process : is an instance of a running program.
    (Not the same as "program" or "processor")

  • Process는 Two key abstractions을 제공 함.

    1. Logical control flow
      여러 process들이 매우 빠르게 switching되어 수행되어지기 때문에 마치 혼자 CPU를 쓰고 있는 것처럼 보여짐
      \to context switching을 통해 Logical control flow 제공
    2. Private virtual address space
      4GB, 16G,... 용량을 갖는 RAM에 무거운 process를 모두 올릴 수 없기 때문에
      virtual memory space (disk와 같은)에 process 내용을 저장하고, 새로운 process의 내용을 읽어옴.
      \to paging을 통해 virtual address space 제공

Context Switching

  • 프로세스A, B가 동시 수행되고 있다고 가정,
    CPU의 core가 1개만 있다고 가정.
    어떻게 여러 프로세스가 동시에 수행될 때
    혼자 CPU를 쓰는 것 같은 효과가 생길까?
    • process A가 수행되다가 process B로 context switching이 일어난다.
      • process A가 사용하고 있는 register값(program counter), 메모리 상태들을 저장(paging)한다.
        (이제 process A의 state는 running \to sleeping)
      • process B의 프로세스 정보들을 복원한다.
        (이제 process B의 state는 sleeping \to running)
  • 이러한 context switching으로 혼자 CPU를 쓰는 것 같은 효과가 생기고,
    process 정보를 memory에 저장, 복원하는 것에 의해서 memory를 혼자 쓰는 것 같은 효과가 생긴다.

Process States

  • Zombified: process의 자원이 아직 반환되지 않아 종료되지 않은 상태.

ps

  • ps : 현재 실행중인 process 출력

  • ps -elf : 현재 실행중인 모든 process 출력
    init process의 PID는 항상 1

    • "ps -elf" process의 PPID = 2994
      PID=2994는 "bash"이다.
      ➡️ bash process가 "ps -elf" process를 fork()함.

top

  • top : process에 대한 자세한 정보(PID, 우선순위, CPU 점유율, memory 차지)
    • 전체 211개의 task들이 있는데,
      running state process는 1개.
      sleeping state process는 210개.
      ➡️ context switching을 통해 process들이 실행된다

man proc

  • proc : kernel data interface를 위한 가상의 file system

  • 2994/ -> bash process directory

    • fd/ : bash process가 열어놓은 file descriptor.
      0, 1, 2은 stdin, stdout, stderr이므로 항상 열어놓음
    • sched : scheduler

Process Composition

32 bit OS

  • 각각의 process는 virtual memory address에서 여러 부분으로 나뉘어져 있음.
    아래 memory map을 참고
    • 32-bit OS에서, 한 Process 당 4GB의 Memory space를 가짐
    • 1GB는 Kernel이 씀
    • 3GB는 User가 씀
      1. Text (program code) area:
        process가 수행하는 코드
      2. Data area
        • Initialized data: 값을 할당해준 data
        • Uninitialized data (bss):
          선언만 되어있고, 값을 아직 할당해주지 않은 data
          Block Started by Symbol(BSS). \to 초기값 없는 변수는 실행 파일에 기록할 필요가 없으므로, 파일 크기를 절약하기 위해 실행 시 0으로 자동 초기화되는 영역을 만듦.
      3. Heap area:
        malloc과 같이 동적으로 할당받는 메모리 공간.
      4. Stack area:
        함수를 호출할 때마다 stack이 쌓임.

64 bit OS

(https://commons.wikimedia.org/wiki/File:Linux_Virtual_Memory_Layout_64bit.svg)

  • 각 process마다 64bit를 다 쓰지 않고, 실제로 48bit(256TB)만 활용함.
    그 중에 128TB는 kernel, 나머지 128TB를 user가 씀.
    32 bit OS보다 할당받는 공간 크기가 커질뿐, process의 memory layout은 기본적으로 동일함.

Process Table

  • Process Table :
    kernel은 어떤 프로세스가 현재 시스템에서 수행되고 있는지에 대한 정보가 필요. 그 정보를 갖고 있는 곳.

  • Each entry contains the following information about each process :

    • PID, PPID
    • UID, GID
    • state

Process Management

  • UNIX가 처음에 부팅될 때, 생기는 프로세스는 딱 하나이다. —> init process (PID = 1)

  • UNIX에서 프로세스를 만들 수 있는 유일한 방법은 기존의 프로세스를 복제(fork(2))하는 것.
    fork()하면, PID, PPID를 제외하고 모든 것이 똑같다. (code, data, stack이 모두 똑같음)

  • 우리가 원하는 것은 만들어진 process가 어떠한 program을 수행하기 원한다.
    그래서 fork()로 생성된 child process의 code를
    우리가 원하는 code로 바꿔주는 system call이
    exec()계열의 system call들이다.

  • init는 getty라고 하는 process를 만듦.
    getty process는 log-in promt를 동작시킴. (passwd 입력하라고 깜빡깜빡...)

  • 이제 부팅, 로그인이 완료되었고 getty를 fork()/exec()를 통해 shell(bash)을 띄움.
    bash(shell process)는 terminal의 console 입력부분에 커서를 깜빡깜빡..
    실행파일의 이름을 입력하면, shell process는 자기 자신을 fork()한다.
    fork()하여 만들어진 child process의 code 부분에
    exec() system call을 사용하여
    우리가 지정한 program의 code로 재할당.
    shell process는 wait() system call을 통해
    우리가 생성한 child process가 끝나기를 기다린다.
    child process가 종료되면,
    shell process가 wait하고 있다가
    깨어나서 종료된 process에 대해서 뒷처리를 해주고,
    그 다음 명령어를 입력할 수 있도록 shell prompt창에 다시 깜빡깜빡...


Process Management System Calls

  • getpid : Get a process's ID
  • getppid : Get a parent process's ID
  • fork : Duplicate a process
  • exec : Replaces the code, data, and stack of a process
  • exit : Terminate a process, parent process에 status code 전달
  • wait : Waits for a child process

getpid(2), getppid(2)

fork(2)

  • fork(2)가 성공했다면,
    parent process에는 child process의 PID가 반환됨.
    child process에는 0이 반환됨.

fork(2) example

exec(3) family

  • execve(2) : system call
    execve(2)를 약간 수정해서 library로 만든게 아래 exec family들.
    execve에서 vvector 형태로 arguments를 넘겨준다는 의미.
    execve에서 eenvironment variable을 넘겨주겠다는 의미.
    execl에서 llist 형태로 arguments를 넘겨준다는 의미.
    execlp에서 p는 실행파일의 절대경로를 전달하는게 아니라, filename만 전달하고,
    경로는 환경변수 PATH environment에서 찾도록 한다.
    • execl(3)
    • execv(3)
    • execle(3)
    • execlp(3)
    • execvp(3)

execve(2) example

execl(3) example

  • execve(2)에서 'v'는 vector형태로 arguments를 넘겨주는 방식인데,
    execl(3)에서 'l'은 list형태로 arguments를 넘겨주는 방식이므로 아래와 같이 써야 함.

  • 위에서는 -l 옵션(argv[2])이 execl의 argument 부분에 작성되지 않았으므로 동작되지 않음.
    따라서 코드에서 execl()부분에 argv[2]까지 전달되도록 수정하면 될 것임.

exit(3), atexit(3)

exit(3), atexit(3) example

wait(2), waitpid(2)

wait(2), waitpid(2) status value

wait(2) example


Orphan process

  • parent와 child가 수행이 되고 있는데,
    parent가 child가 exit할 때까지 wait해야 하는데,
    wait하지 않고 먼저 종료되면,
    child process는 parent process가 없는 process가 된다.
    ➡️ 이러한 process를 Orphan process라고 한다.
    리눅스에서는 Orphan process의 PPID를 1로 세팅한다.
    Orphan process의 parent process는 init process가 된다.
    Orphan process가 exit하여 status를 return하면 init process에 전달이 된다.

Example


Zombie process

  • parent와 child가 수행되고 있는데,
    child가 exit()했을 때, 코드를 없애고 메모리 자원도 해제했지만 완전히 종료된 것은 아니다.
    완전히 종료되기 위해서 parent process에서 wait()를 해줘야 한다.
    하지만 parent process에서 wait()를 하지 않고 다른 일을 하고 있으면,
    child process 입장에서는 exit()를 하긴 했는데 완벽하게 종료가 되지 않은 상태가 된다.
    ➡️ 이러한 process를 Zombie process라고 한다.

Example


Example

fork(2)

  • fork()의 리턴값을 확인하여 자식과 부모 프로세스를 구분하도록 자신의 pid와 ppid를 출력하자

위 경우에서, Child Process의 PPID가 1이 되는 상황이 발생한다...
왜 그럴까?
이는 parent process가 child process의 종료를 기다리지 않고 바로 종료했기 때문이다.
kernel의 process scheduling에 의해 child process가 먼저 실행되면 이러한 상황이 발생하지 않는데, parent process가 먼저 실행되어 종료된다면 위와 같은 상황이 발생한다.
즉, child process는 orphan process가 되었기 때문에 자동으로 PPID가 1로 할당된 것이다.
\to wait(2)로 parent process가 child process의 종료를 기다리도록 해결할 수 있다.

wait(2), exit(3)

parent process가 child process의 상태가 바뀌기를(끝나기를) 기다림
parent process는 child process의 status return 0을 받음.
앞서, child process가 orphan process가 되었던 문제를 해결할 수 있다.

WEXITSTATUS() macro

  • 하지만 아래와 같이,
    wait()는 종료 상태에 따라 다른 status code를 받게 되는데,
    이를 해석하기 위해 bit masking을 해야 한다.
    이를 편하게 해주는 macro가 WEXITSTATUS() 이다.

  • 아래 예시와 같이,
    child process에서 exit(1)을 하면,
    Normal termination의 경우이므로 총 16bit 중 상위 8bit 자리에 1이 써지고,
    이는 10진수로 256을 의미한다.
    따라서 parent process에서 받은 status code를 bit masking 없이 그대로 출력하면, 의도와 달리 256이 출력된다.

  • 따라서 원하는 1이라는 status code를 받기 위해서는
    WEXITSTATUS() macro를 사용하여 상위 8bit 값을 masking할 수 있다.

WIFEXITED() macro

zomebie process

  • parent process에서 wait()하기 직전에 무한 Loop를 돌려서
    child process에 대한 사후처리를 못하게 만들어서
    child process를 zombie process로 만들기

    Z: Zombie 상태라는 의미
    [07_zombie_process] <defunct>: 07_zombie_process가 zombie process이다.

race condition

  • race condition :
    parent process와 child process는 동시에 printf()를 실행하면서,
    같은 자원 stdout(file descriptor 1)에 접근하려고 한다.
    해당 OS의 scheduler는 이러한 경쟁을 어떻게 처리할까?
    Ubuntu에서는
    parent process 먼저 실행,
    child process 나중 실행.
    \to scheduler 차이도 있지만, memory buffer도 이유가 될 수 있다(fflush).

fflush

  • fflush :
    printf()를 하면, memory buffer에 저장되었다가
    어느정도 모이면 한꺼번에 출력한다.
    하지만 fflush를 사용하여 곧바로 출력되게 하도록 할 수 있다.
    • 아래 예제는 fflush로 인해 출력 순서가 바뀐 경우이다.
      \to 애초에 scheduler는 번갈아 가며 출력할 수 있도록 scheduling을 했던 것이다.
    • 아래 예제는 fflush를 하더라도 출력 순서가 바뀌지 않은 경우이다.
      \to 애초에 scheduler는 parent process에게 CPU를 계속 주고 나서, parent process가 끝난 후에 child process가 실행되도록 scheduling을 했던 것이다.

fork(2) & execve(2)

  • 현재 process에서 execve()를 사용하여 ls 명령어를 구현하라
    • execve()가 성공하면, 그 뒤에 있는 printf(), exit() 코드들은 실행되지 않는다.
      왜냐면 execve()는 현재 process 전체를 완전히 새로운 program으로 덮어쓰기 때문이다.
      즉, code, data, stack, heap area가 모두 삭제되고 새로운 process로 만들어진다.
    • execve()가 실패하면, 그 뒤에 있는 printf(), exit() 코드들은 실행된다.

fork(2) execl(3)

  • execl()은 프로그래머가 인자 목록을 직접 수동으로 나열해야 하기 때문에
    인자 개수가 많거나 가변적일 때 일반적으로 쓰지 않는다.
  • fork(2), execl(3)을 사용하여
    system("ls -l | wc -l"); 처럼 ""안의 명령어들을 실행시켜주는
    mysystem("ls -l | wc -l")이라는 함수를 작성하라.
    • system(3) 함수 사용 예시
    • mysystem() 함수 작성시, 참고할 system(3) 함수의 manual page
profile
Efficient Deep Learning

0개의 댓글