fork()

새로운 process를 생성하는 system call이다.
OS는 fork()를 호출한 프로세스의 address space를 copy해서 동일한 address space를 가진 process를 생성한다.
새로 만들어진 process는 same address space, same code segment를 가지고 있다.

프로세스를 그대로 복사한다고 보면 됨.
different PCB를 가지고 있지만 내용물은 완전히 동일함.
register state, stack state, memory state 전부 동일.

exec()

exec()을 호출하면 현재 process의 memory space에 function(혹은 file, program)을 reload해서 전체 memory space를 rewrite한다.

마치.. 다시 태어나는 것임.
마치 새로운 소프트웨어처럼 메모리를 싹 갈아치워버린다. 그리고 새로운 프로그램의 main function을 가리키게 된다.

exec() 함수 이후의 모든 instruction은 실행되지 않는다.


OS는 time sharing을 통해 CPU virtualization을 수행한다.
그 때 고려해야할 사항에는 다음과 같은 것들이 있다.

Performance : efficiency와 관련되어 있음. 시스템에 큰 과부하를 주지 않고 virtualization을 어떻게 구현할지에 대한 것임.
Control : correctness와 관련되어 있음. CPU control을 OS가 regain할 수 있어야 함.

만약 OS가 control을 lose하면 그 누구도 software를 interrupt할 수 없게 된다.
만약 무한루프처럼 절대 return되지 않는 함수를 user software가 돌려벼린다면 OS는 영원히 CPU의 control을 가져오지 못할 것이다.
이러한 상황에서 OS는 어떻게 프로그램을 멈추고 control을 regain할 수 있을까?

OS는 언제든 software의 execution을 컨트롤할 수 있어야 한다. 그렇지 않으면 CPU virtualization을 수행할 수 없다. 왜냐면 user program이 OS나 다른 프로그램에게 CPU를 스스로 yield하지 않는 이상 아무도 그 실행에 개입할 수 없기 때문이다. 극히 수동적인 상태가 되어버림... 완전 갑을관계

Direct Execution

user software와 OS의 영역을 신경쓰지 않은 채 모든 실행을 user software에게 전적으로 맡긴다.
direct execution은 user program이 자신이 원하는 실행을 마치고 CPU control을 알아서 돌려줄 것이라는 전제가 깔려 있다.
메모리에 프로그램을 올려서 main()을 통해 user program에게 control이 넘어가는 순간부터 user program이 return되기 전까지 OS는 control을 regain하지 못한다.

만약 user program이 아주 긴 시간동안 돈다면??? 이를테면 프로그램이 무한루프 같은걸로 영원히 돌아버린다면????
혹은 disk I/O나 system resource를 더 얻는 제한된 operation을 수행해버린다면(protection violation)???
user program이 실행되는 동안 개입하지 않는 OS는 아무것도 하지 못한 채로 손을 놓고 있을 수 밖에 없다.

system resource는 전부 OS에 의해 가상화되기 때문에 이에 접근하는 것은 모두 restricted operation이다.

따라서 user program이 OS의 영역을 침범하게 된다면, 혹은 user program이 영원히 돈다면 OS가 개입해서 running software을 빼내고 control를 가져올 수 있어야 한다.

Solution

이를 해결하기 위해 mode를 사용해 각 mode에 따라 실행할 수 있는 operation의 범위를 나눈다.
user program이 system resource를 원한다면 mode를 바꿔버린다.
xv6에는 ring 0부터 ring 3까지 총 4개의 단계가 있는데 ring 0kernel mode이고 ring 3user mode이다.

user mode는 user software가 running되는 mode로, 하드웨어 resource의 일정 부분만 접근할 수 있다.
kernel mode는 OS가 running되는 mode로, kernel mode에서 도는 모든 mode는 어떤 곳이든, 어느 resource이든 접근할 수 있다.

OS가 user modekernel mode를 가지고 있는데, kernel mode일 때 기존의 user software가 사용하고 있던 user stack을 이용하는 것은 안전하지 않으므로 kernel stack을 따로 사용하게 된다.
즉, OS는 user stackkernel stack의 2가지 스택을 가지게 된다.
리눅스에서는 kernel stack이 보통 2 page, 8KB로 PCB(xv6에서는 proc)바로 다음에 저장된다. 그니까 prockernel stack을 가지고 있고 OS가 running할 때 이를 사용하는 것.

user software가 running하다가 system hardware같은 resource에 접근하고 싶다면 mode를 바꿔야 한다.
CPU control을 OS에게 넘긴다는 뜻임.. 그럼 OS가 user software가 하드웨어 resource에 접근할 수 있도록 도와준다. 이 control을 넘기는 과정을 system call을 통해 한다.

system calltrap이기도 하다.
trap은 system resource에 접근하고 싶어서 자기가 자발적으로 control을 OS에게 넘기는 것이다.
interrupt도 비슷한 기능을 수행하지만 trap과는 조금 다르다. interrupt는 비동기적으로 예기치 못하게 발생해서 CPU에게 signal을 보내 control을 넘기게 된다.

system call이 발생하면 CPU에게 trap signal을 보낸다. CPU가 그 신호를 받으면 user software의 실행을 멈춘다. 그럼 실행을 멈췄으니까 그 user software의 CPU state(레지스터)를 proc에 저장해야한다 (memory state는 memory에 그냥 존재하고 있는거니까 따로 저장할 필요가 없다). 그래서 그 state정보를 kernel stack에 저장하는 것이다.
이 과정이 끝나면 CPU는 stack pointer를 user stack에서 kernel stack으로 옮긴다. 하드웨어 circuit이 적절한 OS code를 찾아서 CPU에게 주면 CPU가 그 code를 실행하면서 kernel stack으로 OS code를 실행하게 된다.

System Call

system call은 user program이 system resource에 접근할 수 있도록 도와준다.

system call은 trap instruction이다. kernel로 jump해서 kernel mode로 privilege level을 올린다.

system call은 return-trom-trap instruction이다. system call이 끝나면 system call을 호출했던 user program에게 return되면서 privilege level을 user mode로 내린다.

IDTR

IDT는 interrupt descriptor table이다.
OS가 boot되면 trap table을 initialize하게 되는데 그 trap table이 IDT이고 IDT의 base address가 CPU 레지스터인 IDTR에 저장된다.

trap table에는 많은 entry가 있고 그 entry는 OS handler의 address이다.
이 table이 제대로 initialize되지 않으면 interrupt가 제대로 동작하지 않아서 마우스나 키보드 같은 것들도 절대 respond하지 않는다. 해당 interrupt에 대한 제대로 된 handler를 찾을 수 없기 때문이다.
CPU는 IDTR이 set되기 전까지 모든 신호를 무시하게 된다. 컴퓨터를 막 켰을 때 마우스나 키보드가 제대로 동작하지 않는 이유가 바로 이것이다. IDTR이 제대로 initialize되지 않은 것이다.
CPU에서 trap이나 interrupt가 발생하면 physical signal을 발생시키는데, loop to itself인 신호다. 자기가 자기 자신에게 보내는 신호이기 때문이다. 외부로 나가거나 외부에서 들어오는 신호가 아닌 것이다.
만약 system call이라면 interrupt number가 0x80으로, PIC가 이 번호를 저장한다.

PIC가 CPU에게 INTR signal을 보내면 CPU가 멈추고 interrupt number(여기서는 0x80)를 fetch해서 IDTR을 통해 IDT0x80*(size of IDTR)을 해서 proper interrupt service routine(ISR)을 찾는다.
system call의 종류에 따라 해당하는 unique number를 갖고 이를 통해 어떻게 handle할지가 정해지는 것이다.

Limited Direct Execution

제한된 level에서만 user software가 동작하도록 한다.
user software가 system resource를 원하는 등의 special instruction을 run하려고 할 때마다 CPU가 자체적으로 code의 privilige level을 check한다. 만약 code가 user software이면 privilige violation error를 뱉고 user software를 kill한다.

  1. [OS] trap table을 initialize한다.
  2. [Hardware] syscall handler의 address를 기억한다(IDTR remember).
  3. [OS] process list(PCB)의 entry를 생성하고 프로그램을 위한 메모리를 allocate한다. memory에 프로그램을 load하고 user stack을 setup한다. kernel stack을 register와 PC로 채우고 trap에서 return한다(fork()exec()도 trap이다).
    이 과정을 끝내면 OS의 초기설정을 마치고 user program을 실행할 준비가 됐으니까 user program에게 control을 넘기는 것이다.
  4. [Hardware] kernel stack으로부터 register를 restore한다. 그리고 kernel mode에서 user mode로 mode switchmain()으로 jump한다 (exec()을 call한 것임).
    stack pointer esp를 kernel stack에서 user stack으로 옮기고 ring 0에서 ring 3로 올리고 CPU의 eip에게 main()의 first address를 준 것.
  5. [Program] main()을 실행한다. 그러다가 system resource가 필요해지면 system trap을 call해서 OS에게 서비스를 요구한다.
  6. [Hardware] 현재 CPU state인 레지스터를 kernel stack에 저장하고 kernel mode로 privilige level을 올린다. 그리고 trap handler로 jump한다.
    syscall이 발생하면 IDTR을 통해 proper syscall handler를 찾아낸다. system call number은 user stack에서 읽어온다.
    system call handler를 실행하기 직전에 CPU가 자동으로 user mode에서 kernel mode로 mode switch를 한다. mode를 바꾸지 않으면 handler code를 실행할 수 없다.
  7. [OS] trap을 handle하고 system call에 따른 명령을 수행한다. 그리고 trap으로부터 return한다.
    이 때에는 현재 program(user)의 proc structure 바로 아래에 붙어있는 kernel stack을 사용한다.
  8. [Hardward] 저장해두었던 register를 kernel stack으로부터 restore하고 stack pointer를 옮긴 뒤에 mode switch를 수행해 user mode로 바꾼다. 그리고 user program의 trap이후의 코드를 실행한다.
  9. [Program] 나머지 code를 실행하다가 main()으로부터 return한다. 이 경우에는 exit() trap이 발생한다.
  10. [OS] control이 user에서 OS로 다시 넘어오고, exit() system call이 호출되면 OS는 그 프로세스에게 할당되었던 모든 것을 없애버린다. 그 프로세스가 terminate되었다고 간주하기 때문이다.

0개의 댓글