본문 바로가기

Operating System/Ch3) process

Process

그럼, Ch1,2에서 봤던 Process는 어떻게 구현하고, 어떻게 관리할까?

What is a Process?

Process란,

  • An instance of program in excecution
  • Protection의 기본 단위
  • PID로 구분되며, CPU context / OS resources / Other information을 보유하는 객체
  •  

 

 

  • Code와 Static data를 process화 시키면 시행에 필요한 부가적인 resource가 형성된다
  • Proces를 동작시키기 위해, CPU는 다음곽 같은 5가지 동작을 무한루프에서 반복한다(Instruction cycle = Fetch - Decode - Execute cycle)
    • Fetch IP
    • MEM[PC] ( Process의 메모리 할당 )
    • Decode IP
    • Execute IP
    • Update PC

 

 

그런데, CPU가 여러개면 여러가지의 Process를 동시에 동작할 수도 있다. 하지만, 만약 1개의 CPU만 있다고 가정하면 Starvation ( Process가 CPU를 할당 못받는 현상 ) 이 발생할 수 있기에, Ch.4에서 다루겠지만 CPU를 virtualizing 시킨다.

즉, Os는 각 process로 하여금 혼자서 CPU 전체를 쓰고 있다는 착각을 하도록 만들어야 한다.(사실은 여러 프로세스가 나눠씀)

 

 

  • 이를 도식화하면 다음과 같다.
  • Preeemtive Multitasking만 나타내었고, Code B와 A 사이에 switch()로 context switch를 발생시켜 실행 Process를 변경시킨다.

 

Implementing Processes

  • PCB ( Process Control Block ) or Process Desriptor 이라고 부르는 struct를 통해 구현한다
  • 각각의 PCB가 process를 represents하며, process에 대한 모든 정보를 담는다
    • ex) CPU reigsters / Pid,Ppid, priority, Memory managing information …….

 

 

 

Process API

  • 그렇다면, 이러한 Process는 구체적으로 어떻게 만드는가

Unix Process management API

  • fork()함수와 exec()함수를 통해서 새로운 process를 생성한다.
  • 맨 처음 컴퓨터가 부팅될 때, 하나의 initialprocess가 생성되고, 모든 Process는 initial Process로부터 fork()된 자식 Process이다.

exec는 현재 PC의 정보를 지우고, argument로 받은 program으로 정보를 뒤집어씌우는 함수이다.

 

Process Creation

  • fork()
    • Parent process를 cloning하는 새로운 process를 생성함
      • 이때 Child는 parents의 대부분의 resource와 privilege를 상속받는다 (open files, UID, etc)
      • 또한 parent의 adress space( or memory)도 상속받음
    • Parent는 wiat()을 이용해 child process가 끝나길 기다리거나, parallel하게 실행을 계속할 수도 있음.
    • Shell , GUI가 내부적으로 이런 syscall 사용함
  • exec()
    • 현재 Process의 image를 새로운 program으로 덮어씌우는 함수
    • Windows : CreateProcess() = fork() + exec() : Window는 이런 계층구조 없음

Process Termination

  • Normal exit (voluntary)
    • return 0 from main or exit(0)
  • Error exit ( voluntary )
    • return non-zero or exit(non-zero)
  • Fatal error ( nonvoluntary )
    • Segmentation fault - illegal memory access
    • Protection fault etc
  • Killed by another process ( nonvoluntary )
    • By receiving a signal
  • 여기서, Zombie process는 종료되었으나 제거되지 않은 상태임 : parent가 wait하지 않아 ptable에서 child를 수거하지 않은 상태

Process termination API

  • exit()
    • Caller process가 실행을 끝내게 되며, return값으로 0(normal) 또는 1(error)을 반환한다.
    • main()에서 Return하는 것이 identical하다
  • kill()
    • Process에게 signal을 보낸다
    • 보내는 Parameter의 종류에 따라, sleep할지 die할 동작을 지정한다
  • wait()
    • Calling process의 child의 status가 변화하는 것을 기다린다
      • 보통 child의 termintion임
    • terminate된 child의 resources들을 Release한다
    • waitpid()로 특정 child를 지정할수도 있다

Implementation

  • fork()와 exec()을 이용해서 구현한 Simplified shell의 예시이다.
  • 변수로는 cmdline과 명령어를 파싱하는 배열이 있고, pid와 종료상태를 저장하는 status 변수가 존재한다.
  • getcmd로 사용자로부터 명령어를 받고, parsing한 후 입력된 명령어가 내장된 명령인지 확인한다. 내장된 명령어가 아니면, if문을 실행한다.
  • fork()를 이용해서 새로운 자식 process를 만들고, execv 명령어를 통해서 error 메시지를 출력한 후 exit하는 로직이다.
  •  

 

 

→그렇다면, 이제 Context switch의 자세한 과정을 알아보자

Performing context switch

 

 

  • 앞서 Protection에서 컴퓨터가 부팅하면 kernel이 timer hardware을 시작한다고 한 바 있다.
  • Process A가 시행중에, Timer interrupt가 들어와 A의 context를 k-stack에 저장해놓는다
  • 이후, kernel mode로 진입해 trap handler을 진행하도록 요청한다.
  • Kernel은 요청한 trap을 처리하고, switch()를 call한다
  • 저장해놓았던 A의 context를 PCB(A)에 옮겨놓고, PCB(B)의 context를 register에 복원한다.
  • 이후 kernel stack에서 process B를 pointing하고, trap으로부터 return한다
  • 이후 Hardware에서 B의 kernel stack으로부터 register값을 복원하고, usermode로 돌아가 B의 IP로부터 명령어를 실행한다.
  • 다음은, XV6에서 context swith를 하는 사례이다.

 

다음은, XV6에서 context swith를 하는 사례이다.

 

 

- 제일 중요 ****8 sched() → scheduler() → swtch()를 호출하니 자세한 건 XV6 파일에 있는 코드를 참고해라

 

 

이것은 실제 xv6에서 swtch()가 구현되어있는 어셈블리 파일이다.

 

  • eax는 old stack의 context를 가리키는 포인터를 담고 있고, edx는 new stack의 context를 가리키는 포인터를 담고 있다.
  • 각각의 context 주소값을 먼저 pointer을 kernel stack에 담는다. 현재 esp가 가리키고 있는 것은, 실행중이었던 old의 context이기에, %eax라고 생각한다.
  • 현재 %esp에서 16바이트, 32바이트 떨어진 공간에 old와 new의 context 정보가 담긴 register을 저장한다. ( context에는 저장할 register가 4개임. eip는 CPU가 직접 관리해서 예외)
  • 이후 현재 가리키고 있는 %esp는 old이므로, 이 정보를 pushl로 stack에 보관한다.
  • 3070~3071에서 현재 esp가 가리키는(원래 esp값에서 4X4byte만큼 떨어진 공간)값을 %eax의 주소공간에 넣어놓고, esp값은 new context가 가리키는 값으로 바꾼다.
  • 이후 pop을 통해서 new context의 register 정보를 Load한 후 , ret을 통해 %eip가 가리키는 다음 명령어를 실행한다.

 

(-> 근데 과제할거면 ebp(base pointer), esp(stack pointer), eip (instruciton pointer) 이거 3개는 시바 무조건 원리 이해해라 멀티스레딩 과제할때 또나온다,........... 죽는 줄)

 

 

 

⇒ 그렇다면 OS는 Context switch할 process를 어떻게 찾는가?

Process State Queues

  • 각각의 PCB는 현재 status에 따라 적절한 Queue 들어가게 된다.
  • Ready Queue : Runnable process
  • Wait Queue : process의 이벤트나 resource의 type에 따라 여러 종류가 있다.
  •  

  • 처음 생성된 Process는, ready queue에 들어가게 된다. 이유 CPU가 할당되어 실행되면
    • I/O device 사용 요청이 있을 수 있고,
    • mutex 사용을 위한 대기를 할 수도 있고,
    • fork로 child를 만들수도 있으며
    • timer interrupt가 발생할 수도 있다.1
  • 이와 같은 다양한 경우의 요구하는 resource에 따라 각 resource들의 대기 큐로 진입하게 된다.
  • 이후 CPU scheduling 조건에 따라 다시 ready queue로 들어가게 되며, 다시 CPU를 할당받고 exit(자원을 반납) 하게 된다. 종료되면 모든 queue에서 삭제된다.
  • proc.c의 sleep과 ide.c파일에 i/O 대기 queue에 process를 넣는 과정이 구현되어 있으니 참고하자.

fork()

  • fork()를 구현할 때에 생각해봐야할 점
    • 자식 process를 위해 새로운 PCB를 생성한다.
    • 자식 process의 독립적인 실행을 위해 새로운 주소 공간을 할당해준다
      • 이 과정은 보통 copy-on-write로 최적화함.
    • 자식 process의 주소공간은 정확히 parent의 entire content를 copy한다.
    • 자식 process는 부모 process가 사용하던 kernel resource에 대한 pointer를 공유한다.
      • 두 프로세스가 동일한 파일을 열고 닫을 수 있음
    • 생성된 PCB는 ready queue에 넣는다
    • 만약 child면 0을 리턴, 부모면 child의 pid를 return한다

exec()

  • exec()을 구현할 때에 생각해봐야할 점 : int execv( char *prog, char *argv[])
  • caller의 address space를 지우고, process에게 할당되었던 resource들을 release한다.
  • 새로운 프로그램 “prog”를 주소공간에 Load한다.
  • 새로운 프로그램의 실행을 위해 CPU의 레지스터와 하드웨어 컨텍스트가 초기화된다. 프로그램 카운터와 스택 포인터 등이 새 프로그램의 시작점과 스택 메모리에 맞게 설정된다.
  • exec()은 새로운 process를 만들지 않으므로, exec()을 했는데 return값이 있으면 error임.

⇒ 그렇다면, proces간의 소통은 어떻게 이루어질까?

Inter-Process Communication

  • 다음과 같은 일들을 위해, process들끼리 협력하는 일이 필요할 수 있다.
  • Information share / Computation speedup / Modularity / Convinience
  • IPC model에는 2가지가 있다.
    • Message passing
    • memory를 공유하지 않는 상황에서도 통신이 가능하며, send()와 recieve()의 syscall로 구현한다.
    • pipe, socke, message queue를 이용해 통신한다.
    • 느리다
    •  

 

  • Shared memory
    • 두 개 이상의 process가 공유 memory에 직접 접근하는 방식이다.
    • 속도가 빠르며, 뮤텍스나 세마포어 등을 통해 관리해야한다.(이게 뭔지는 강의듣다보면 안다... 지금 알려하지 마라 강의 수강철회할수도 있다)
    •