Operating System Services
- 운영체제는 사용자 편의와 process 실행의 효율성을 위해 여러가지 서비스를 제공한다.
For Users
- User Interface
- Command Line Interface (CLI) : GUI와 상반되는 개념으로, windows의 명령 프롬프트, MAC OS의 terminal과 같은 것 들이다. 이들을 shell이라고 부르며, 입력한 command를 fetch한 후 해석해, 알맞은 동작을 한다.
- shell은 공간 효율성을 추구하기 위해 shell 자체에 명령어를 포함시키지 않고, directory에서 명령어들이 담긴 실행 파일을 찾아 실행하는 option 을 사용할 수 있다.
- Batch Interface : 일련의 command들이 모인 실행 파일이다. 여러 개의 compile option을 제공하고, 한 번에 여러 개의 파일을 compile 할 수 있다. 예시로는 MakeFile이 있다.
For Efficiency
- Recource Allocation
- CPU cycle, main memory, file storage 등 다양한 종류의 자원이 있다. 하지만 이 자원들은 한정되어 있고, 그에 비해 실행해야 할 process들은 무수히 많다. 운영체제는 한정된 자원들을 process에게 효율적으로 분배하기 위해 노력한다.
- Accounting
- CPU 사용의 통계 자료를 제공하는 것이다. 위의 자원들이 어디서 어떻게 사용되는지 표시한다.
- Protection and Security
- 허용되지 않은 접근을 막아 안정성을 더하는 것이다.
- 예시로는 mode bit의 사용이 있다. mode bit은 0과 1의 값을 가질 수 있고, 값에 따라 user mode와 kernel mode를 오간다. kernel로의 접근은 kernel mode에서만 가능하다. system call 등의 호출이 user mode 상태에서 일어난다면, 잘못된 접근으로 간주해 차단한다.
System Calls
- kernel mode에 들어가는 방법은 두 가지가 있다.
- H/W interrupt: program에서 의도하지 않은, 외부 장치에 의해 asynchronously 이루어지는 interrput이다.
- trap (S/W interrupt): 잘못된 코드 (0으로 나누기 등)으로 인해 발생하는 exeption과, system call로 구성된다.
- System call은 user level program에 의해 호출되는 kernel이 제공하는 API의 일종이다. OS에게 service를 요청하는 유일한 mechanism이다.
- 위 이유에 의해 여러 곳에서 자주 호출되고, 응용 프로그램이 H/W에 접근하거나 OS의 기능을 사용해야 할 때 호출된다.
- exception과 함께 trap의 일종이기도 하다.
- 예시로는 fork, read, write 등이 있다.
Indirect calling
![](https://velog.velcdn.com/images/jjdg148/post/5a504c13-e5dc-4b82-a509-9dbd4eb7cc48/image.png)
- system call에는 간접 호출이 사용된다. kernel에 직접 접근하는 것이 어렵기 때문에 system call API를 통해 library 함수를 호출하고, 그 library 함수가 kernel의 함수를 호출하는 것이다.
- library 함수 내에 system call과 관련된 함수들이 내포되어 있기 때문에 가능하다.
- 간접 호출에는 크게 두 가지 장점이 있다.
- Portability
기기마다 상이한 system call을 가지는 경우들이 있다. 기기를 바꿀 때 마다 새로운 system call을 호출하는 것은 번거로운 일이다. 하지만, 공통된 API를 통해 상황에 맞는 system call을 간접적으로 호출한다면, 기기 간의 호환성이 증가한다.
- Ease of programming
사용자는 system call을 직접 호출하지 않고, API를 통해 간접적으로 호출하기 때문에, 여러 가지 system call에 대한 자세한 내용을 알 필요가 없다.
System Call Interface
![](https://velog.velcdn.com/images/jjdg148/post/729e1f03-9b34-4f19-a52b-f8352dfd466b/image.png)
- system call은 system call interface의 형태로 구현되어 있다. system call의 종류는 아주 많기 때문에, 효율적인 관리를 위해 system call number라는 번호를 부여한다. system call interface는 system call number와, 그에 대응하는 kernel에서 실제로 호출되는 함수를 담은 table이다.
- system call을 담은 library 함수가 호출되면, 해당 함수의 system call number를 system call interface에서 lookup해 kernel 함수를 실행시킨다.
Passing parameters in system calls
- system call은 kernel 함수이기 때문에, kernel에게 자신이 사용하는 인자들을 전달해야 한다. 이에는 두 가지 방법이 있다.
- 인자들을 레지스터에 바로 저장하는 방식: movl외에는 별 다른 동작이 필요 없기 때문에 빠르고 간단하다. 하지만, 레지스터의 갯수가 한정되어 있다는 문제가 있다.
- block을 이용하는 방식: block은 main memory 상의 특정 공간을 뜻한다. 이 공간에 인자 값들을 저장하고, 레지스터에는 block의 주소값을 저장하는 방법이다. 앞 방법의 한계점을 해결한 방법이지만, 더 많은 과정이 필요하다.
- LINUX에서는 두 방법을 혼합해 사용한다. 인자가 6개 이하일 때는 1번을, 6개 이상일 때는 2번을 사용한다.
![](https://velog.velcdn.com/images/jjdg148/post/a648b6dd-6e6c-4437-bde9-e7d9f2c000f0/image.png)
위 사진은 LINUX에서 system call을 하는 과정을 보여준다.
- user level에서 kernel 접근을 요하는 함수 (fork 등)이 호출된다.
- 간접 호출을 위해 fork의 library 함수가 호출된다. library 함수에서는 2라는 인자를 전달하기 위해 eax register에 값을 넣는다. 이 인자의 의미는 추후에 알게 된다.
- system call을 호출하기 위해 interrupt가 일어난다. system call은 LINUX의 interrupt vector인 IDT의 0x80 위치에 존재하는데, 이를 lookup하기 위한 interrupt인 것이다.
- system call의 interrupt handler가 호출된다. SAVE_ALL은 interrupt 완료 후 kernel mode에서 user mode로 돌아갈 때, 돌아갈 주소 context(사용될 register 값)을 저장한다. call에서는 system call 의 interface를 lookup한다. call의 인자를 살펴보면, library 함수에서 전달 받은 eax의 값을 찾을 수 있다. 이는 fork()의 system call number 였던 것이다.
- 전달 받은 system call number를 system call interface에서 찾는다. 이에 해당하는 sys_fork()라는 kernel 함수가 호출된다.
System Program
- 응용 프로그램과 함께 user program의 한 종류로, 시스템을 효과적으로 사용할 수 있도록 지원한다.
- H/W 접근이 필요하므로, 매우 많은 system call들을 내포하고 있다.
OS Design & Implementation
OS를 설계하고 구현할 때 다음의 요소들이 고려되어야 한다.
- 사용자가 사용하고 배우기 편한가?
- 빠르고 안정적인 수행이 가능한가?
- 오류가 없고 유동적이며 효울적인가?
Monolithic Kernel & Microkernel
위 요소들을 충족하기 위해서는 다음의 전제를 지키면 좋다:
"policy가 바뀌어도 kernel의 내부 mechanism은 바뀌지 않아야 한다"
OS에 다양한 변경사항들이 적용돼도, kernel 내부의 변화가 적을수록 호환성과 user experience가 좋아진다.
kernel의 역할에는 process 관리, memory 관리, storage 관리, I/O device 관리가 있다. 이 역할들을 어떻게 분배하냐에 따라 위 전제를 지킬 수 있는 정도가 달라진다.
- monolithic kernel: 위에 4가지 기능을 kernel이 모두 수행하는 경우다.
- microkernel: monolithic kernel과 비교되는 개념으로, 가장 핵심적인 process 관리 기능만 kernel이 담당하고, 나머지는 user level에서 관여하도록 넘긴다.
OS에 기능을 추가할 일이 생기면, microkernel의 경우가 더 효율적이다. kernel이 아닌 user level의 구조를 바꾸면 되기 때문이다.
Operation System Structure
OS는 여러가지 구조로 구현될 수 있다.
Simple structure
- 말 그대로 간단한 구조의 OS로, 현재는 거의 사용되지 않는다.
- module화 되어있지 않고, layer들로 구분되는 계층 구조도 없다.
- 이로 인해 priviliged operation이 없고, user mode에서 바로 하드웨어에 접근할 수 있다. hardware protection을 제공하지 않는 것이다.
Layered approach
- OS를 여러 계층으로 나누어 구현하는 방식이다. 가장 하위 layer는 하드웨어이고, 가장 상위에는 user interface가 있다. 이 사이에는 응용 프로그램들과 kernel이 있다. 특징으로는
- 각 layer들은 하부 layer에서 제공되는 operation들로만 구현되어야 한다.
반대로, 하부 layer에서는 상부 layer의 operation을 사용할 수 없다.
상부 layer인 app에서 system call을 통해 kernel에 접근하고, 이를 통해 하드웨어에 접근한다는 것도 같은 맥락이다.
- 각 layer들은 독립적으로 구현된다. 이로 인해 별도로 debugging이 가능하고, 하부 layer들이 검증된 상태라면, 상부 layer에서는 자신의 debugging만 신경쓰면 된다.
Microkernels
앞서 설명했던, kernel의 특정 기능들을 user level에 넘겨준 간소된 kernel이다.
장점
1. 확장과 수정이 쉽다.
2. 디버깅이 쉽다.
3. kernel mode에서의 동작이 줄어, 안정적이다.
단점
1. user process간의 communication이 많이 필요해, 여기서 overhead가 발생한다.
Modules
- Layered approach와는 달리, OS를 계층이 아닌 기능 관점에서 모듈화 한 개념이다.
- 각각의 모듈들은 독립적으로 동작한다.
- 모듈들을 동적으로 추가, 제거할 수 있는 dynamic loading, unloading 기능을 제공한다. 이도 독립적으로 동작하기 때문에, 다른 모듈에게 영향을 주지 않고 쉽게 기능을 추가/제거할 수 있다.
- insmod는 모듈을 추가, rmmod는 모듈을 제거한다.
- 사용 예시로는 device driver 들이 있다. device driver는 I/O를 제어하기 때문에, kernel에 포함돼야 한다. 다른 기법에서 kernel에 접근하는 것은 쉽지 않기 때문에, module 기법을 이용한다.
- LINUX에서는 layered approach와 module 기법을 혼합해 사용한다.
Virtual Machine (VM)
![](https://velog.velcdn.com/images/jjdg148/post/38d45ced-d8e1-4881-9ae2-7eca0299a17d/image.png)
- 하나의 장치에서 여러 가지의 OS를 구동하기 위한 장치이다. 이를 구현하는 기본 아이디어는 각각의 프로세스들이 virtual hardware 위에서 동작하는 것처럼 보이게 illusion을 주는 것이다.
- 한 하드웨어 위의 vm들은 physical memory를 공유하지만, 각자의 메모리를 소유한 것 처럼 보인다. 하드웨어 위에서 가상 머신을 생성하고, 필요한 만큼 자원을 할당해주고, 가상 머신들의 요청을 처리해주는 등 가상화를 도와줄 매니저가 필요한데, 이 역할을 vm과 실제 하드웨어 사이에 위치하는 virtualization layer가 수행한다.
![](https://velog.velcdn.com/images/jjdg148/post/d508eeaf-2496-4800-af2a-dfb5161c16b7/image.png)
- user mode에 vm들을 만들어, 자기 자신만의 virtual user space와 virtaul kernel을 가지고 있다는 허상을 주는 것이다.
System Boot
![](https://velog.velcdn.com/images/jjdg148/post/e77eb8f4-660c-4e54-a648-18aa93e35b70/image.png)
- H/W initialization
- load OS to memory (RAM)
- OS execution (main 함수의 첫 줄을 실행하면서 OS에게 제어권을 넘기는 것)
- 이 과정을 수행하는 코드를 bootstrap loader라고 한다. 부팅 과정은 항상 일정하고, 가장 먼저 일어나는 일이기 때문에 ROM에 저장된다. ROM은 가장 먼저 조회되고, 비휘발성인 대신 읽기만 가능하기 때문이다.
- 하지만, 초기화 할 하드웨어가 많기 때문에, bootstrap loader의 코드가 길어져 ROM에서 많은 공간을 차지하게 된다. 이를 해결하기 위해 단계를 나누게 된다.
Two step approach
![](https://velog.velcdn.com/images/jjdg148/post/4427cbf9-8fa1-4da2-953c-9bc6eb78b854/image.png)
- bootstrap loader은 꼭 필요한 동작만 하고, 나머지는 boot block이라는 코드 덩어리에 넘기는 것이다. ROM에 올라간 간소화 된 bootstrap loader이 disk에 있는 boot block을 RAM에 올리고, 이후 동작을 boot block이 수행하는 것이다.