본문 바로가기
CS/운영 체제🖥️

[OS] CPU 가상화 메커니즘 (Limited Direct Execution) - 2부

by Mintta 2023. 10. 31.

이전글

https://codingmon.tistory.com/68

 

[OS] CPU 가상화 메커니즘 (Limited Direct Execution) - 1부

오늘 다룰 주제는 CPU의 가상화입니다. 운영체제는 여러 프로세스들이 동시에 실행되는 것처럼 보이도록 하기 위해 물리적인 CPU를 공유하도록 지원합니다. 바로 Time Sharing(시분할) 방법을 통해서

codingmon.tistory.com


2부에서 이야기할 주제는 저번 1부에서 말했듯이, 아직 해결되지 않은 프로세스 간 전환 방법과 그 해결책입니다 !

 

힘내서 한번 가보겠습니다 !

 

시작하기에 앞서 지금까지 과정을 살짝 요약해보자면

 

우선 CPU를 가상화하기 위해서는 Performance와 Control을 신경써야만 했습니다.

그리고 우린 지난번에 Performance를 위해 CPU가 그냥 처음부터 끝까지 쭉 CPU에서 바로바로 작업을 수행하는 basic한 기술을 기반으로 된 Limit가 없는 Directed Execution를 살펴봤습니다.

하지만 limit가 없는 Directed Execution의 문제점은 User program에서 모든 권한이 열려있기 때문에 I/O 요청 등 프로세스에서 필요한 작업 외에 필요 이상의, 혹은 크리티컬한 이슈를 불러일으킬 수 있는 부분까지 모두 접근할 수 있다는 거였고, 심지어는 수정까지 할 수 있었습니다.

 

이 문제를 막기 위해 나온 것이 바로 CPU의 두가지 mode, 바로 User mode와 Kernel mode(특권 모드)가 등장하게 됩니다.

그리고 이 모드에 따라서 노출되는 명령어 셋, 즉 interface가 다릅니다.

 

그리고 이렇게 모드를 나누고 나니 User Program에서 진짜 kernel의 기능이 필요한 순간이 있는데, 이런 상황일 때 아예 접근을 하지 못하는 문제가 생기게 됩니다.

 

그래서 나온 방법이 바로 부분적으로 운영체제의 관리하에 허용해주는 kernel 기능들을 쓸 수 있게끔 systemcall - trap을 통해서 허가된 필요한 기능만을 User program이 요청을 보낼 수 있게 된 것이죠.

 

6.3 Problem #2: Switching Between Processes

이제 남은 문제인 프로세스간의 switching을 살펴봅시다 !

 

일단 지금까지의 상태로는 프로세스간의 context switching이 불가능합니다.

 

왜 ?!

 

왜냐하면 운영체제에게 지금 권한이 없기 때문입니다. 프로세스가 CPU에서 실행 중이라는 것은 이로 인해 운영체제가 실행되지 않는다는 것을 의미합니다. 운영체제가 실행되지 않고 있다면 운영체제가 뭘 할 수 있을까요..? 아무것도 할 수가 없습니다.

 

따라서 프로세스가 CPU에서 실행 중이라면 운영 체제는 어떤 조치도 취할 방법이 없다는 것 입니다.

그렇다면 CPU의 Control을 운영체제가 가져와야만 합니다..!

 

어떻게 이루어내는지 한번 살펴보겠습니다.

 

A Cooperative Approach: Wait For System Calls

프로세스를 믿어보자..!

Cooperative, 협력적이라는 말이죠. 말에서도 뜻을 유추할 수 있듯이 CPU를 가져간 프로세스가 오랜 시간 동안 실행된다면 주기적으로 CPU를 양보하여 운영 체제가 다른 작업을 실행할 수 있겠다고 결정을 내릴 수 있게끔 합니다.

 

한마디로 운영체제가 프로세스가 스스로 자발적으로 CPU의 Control을 줄 것이다하고 프로세스를 믿는 겁니다.

 

사실 대부분의 프로세스는 파일을 열고 읽거나, 다른 기계에 메시지를 보내거나, 새로운 프로세스를 생성하는 등의 systemcall 호출을 통해서 CPU의 Control을 생각보다 자주 운영체제에 양도합니다.

(명시적인 yield(양보, 양도) systemcall을 포함하고 있습니다.)

 

또한, 뭔가 illegal한 일을 수행했을 때에도 프로세스는 운영체제에게 CPU의 Control을 양도합니다.

예를 들어 divide by zero를 수행한다던가, 접근해서는 안되는 메모리 주소에 접근했다던가하면 운영 체제에 Trap을 발생시킵니다.

그러면 운영 체제가 Trap을 받아서 Trap handler를 통해서 아마 불법을 저지른 프로세스를 kill하던가 하겠죠..!

사이버펑크의 경찰을 떠올리게 하는 OS의 대처..

 

결론적으로 Cooperative scheduling system에서는 운영 체제가 CPU의 Control을 되찾기 위해 프로세스의 systemcall이나 어떤 종류의 불법 작업을 수행하기를 기다립니다(wait). 

 

하지만 프로세스가 악의적인 마음을 가져버리고 교묘하게 불법작업은 안 일으키는 선에서 작업을 계속해서 수행하면 어떻게 하죠..? 🤔

운영체제는 방법이 없겠죠. 따라서 이렇게 수동적이여서는 안된다고 생각합니다.

 

A Non-Cooperative Approach: The OS Takes Control

프로세스가 악의적인 마음을 가지고 계속해서 독점하고 있다면 OS로써는 그냥 컴퓨터를 재부팅하는 것 외에는 선택지가 없게 됩니다. 

 

그리고 이에 대한 대처 방법으로 Time Interrupt가 등장하게 됩니다.

악의적인 프로세스에 대한 대처 방법: Time Interrupt

일정 시간 지나면 프로세스한테서 CPU Control 강제로 뺏어버리기

일진 운영체제 (원본그림 출처: https://bbs.ruliweb.com/etcs/board/300780/read/52121813)

Timer Device는 매 milisecond마다 interrupt를 raise를 할 수 있고, interrupt이 발생하면 현재 진행중인 Process는 일시 중지(halted)되고, 운영 체제의 미리 구성된 Interrupt handler가 실행됩니다.

 

이 시점에서 운영 체제는 CPU의 Control을 다시 얻게 되고, 다른 프로세스를 시작할 수도 있습니다.

systemcall과 마찬가지로 운영 체제는 Timer Interrupt가 발생할 때 어떤 코드를 실행해야하는지 부팅시에 하드웨어에게 알려야합니다 (초기화 과정). 그리고 부팅 중에 운영 체제는 이제 Timer를 키면 됩니다. 당연히 이때 kernel mode여야하겠죠.

 

앞서서 Timer Interrupt가 발생하면 Interrupt handler에서 작업이 실행되는 것으로 보아 이를 Trap 작업으로 이해할 수 있습니다.

Trap으로 kernel code가 불렸다면 kernel code에서 다시 원래 프로세스로 돌아가기 위해서는 기존의 레지스터 값들을 kernel stack에 저장해두었다가 return-from-trap에서 복원시켜줄 필요가 있습니다. 

 

그렇기 때문에 Trap시에 레지스터를 저장해두었다가 return-from-trap이 수행될 때 다시 context를 복구할 수 있는 기능 또한 있어야합니다 !

 

Saving and Restoring Context (문맥 보존 / 복원)

프로세스가 CPU의 Control을 포기하면 운영체제의 Scheduler가 현재 이 프로세스에게 다시 권한을 주고 계속해서 실행시킬 것인지, 아니면 다른 프로세스에게 Control을 넘겨줄지 결정하게 됩니다.

(Scheduler의 scheduling 정책에 대해서는 추후에 알아봅시다!)

 

만약 다른 프로세스를 실행시키기로 결정했다면, Context Switch의 등장입니다.

 

처음에는 A의 프로세스 Context를 kernel stack에 저장을 해두었는데 왜 또 PCB에 저장하지 ? 같은 일을 두번하는 듯한 느낌이 들어서 이해가 안가 헷갈려서 애를 먹었던 것 같습니다..!

 

그래서 깨달은 것이 trap <-> return from trap, switch 이렇게 나눠서 생각해야한다는 점이었습니다 !

 

Trap이 걸렸을 때 ! 현재 진행 중이었던 (곧 일시중단 될) 프로세스의 레지스터 값들이 kernel에 있는 해당 프로세스 전용 kernel stack에 저장됩니다.

return-from-trap이 호출되면 kernel stack에 저장해놨던 Context를 통해서 원래의 프로세스로 돌아오게 되는 것 입니다.

 

그래서 Trap과 return-from-trap의 쌍은 user program <-> kernel 간에 전환을 위한 명령어라고 생각하시면 됩니다 !

 

그렇다면 이제 프로세스간의 전환을 담당하는 게 바로 switch() 입니다 !

그리고 switch할 때에도 곧 일시정지 시킬 기존의 프로세스의 PCB(Process Control Block)에 현재 Context를 저장합니다. (이 때 PCB는 프로세스 마다 커널 코드안에 존재합니다). 그리고 곧 실행시킬 프로세스의 PCB에서 Context를 읽어와서 현재 CPU의 레지스터에 덮어씌웁니다.

그 후에 return-from-trap을 하게되면 현재 PCB의 상태를 기준으로 돌아가기 때문에 switch된 새로운 프로세스의 Return address를 통해 돌아갈 수 있게 되는 것 입니다.

 


마무리

프로세스 간 전환 방법과 그 해결방법을 위한 접근 방법들에 대해 알아보았습니다.

한가지는 협력적인 프로세스를 믿어보는 방법 그리고 한가지는 Timer를 두고 일정 시간이 지나면 프로세스의 권한을 운영 체제가 뺏어오는 방법인데, 첫번째 방법은 너무 수동적이라 후자를 선택하고 있습니다.

 

context switching부분이 좀 헷갈렸지만 이제는 이해가 되서 뿌듯하네요.!

 

댓글