어떤 문장으로도 OS 를 명쾌하게 정의할 수는 없지만
우선은 "하드웨어를 관리하는 프로그램" 정도로 생각하고 글을 이어나간다.
OS 가 하는일은 실로 다양한데 기술적인 용어는 잠깐 뒤로 미루고, 보편적인 OS 의 핵심 목표는
조금 더 디테일하게 표현하면 각각의 기능은
아래는 보편적인 컴퓨터 시스템의 동작 구조이다.
핵심 객체를 기준으로 추상화를 한 그림인데, 핵심 객체로는
Users, Applications, Operating System, Hardware 가 있다.
크게 User 와 System 의 관점으로 컴퓨터 동작 구조를 바라볼 수도 있는데 각각의 객체가 요구하는 기능은 사뭇 다르다.
우선 User 의 입장은 보편적으로 컴퓨터를 사용하는 일반인의 마음과 비슷하다고 보면 된다.
System 의 입장은 자원을 사용하는 관점에서 봐야한다.
컴퓨터 시스템에서 핵심 Hardware 는 CPU, Memory, I/O devices, device controller 로 나눌 수 있다.
각 Hadrware 별 자세한 작동방식은 OS 주제로 다루기에는 좀 무거워서 넘어가겠다.
CPU 와 device controller 는 자체 연산 처리를 한다
처음 공부할 때 헷갈렸던 것이 이 부분인데, cpu 만이 연산처리를 담당하는 객체라고 생각하고 I/O device 들은 동작을 할 때 필요한 연산을 cpu 에게 위임한다고 이해했을 때, 그 구조가 굉장히 부자연스럽게 다가왔던 기억이 난다.
실제로는 device controller 가 device 종류 별 연산을 각자 담당하고, 연산 결과가 나오면 그제서야 cpu 에게 interrupt 를 통해 결과만 전달하면 된다.
이런 구조를 짜놓았기 때문에 cpu 는 내부 시스템의 연산을 처리하는 객체, I/O controller 는 외부 device 의 연산을 처리하는 객체로써 유기적으로 연결될 수 있다.
그렇다면 위와같은 동작 프로세스에서 OS 가 해결해야하는 문제는 무엇일까?
위에서 언급한 OS 핵심 목표와 연관지어 정리하자면
위와 같은 문제를 해결하기 위해서 Linux 는 Kernel 프로그램 을 제시한다.
Kerenl 도 어떻게 한 문장으로 정의할 수는 없지만 우선은 "실질적인 OS 동작 주체" 라고 하겠다.
실제로 Kernel 은 컴퓨터를 부팅하고 ROM 에서 불러오는 Bootstrap program 이 register, controller, memory 를 초기화하고 가장 먼저 실행시키는 프로그램 인 만큼 중요한 프로그램이다.
...
기본적으로 컴퓨터에서 프로그램이 동작한다는 것은 프로그램과 관련된 데이터가 메모리에 올라와 있다는 것을 의미하고 우리는 이런 상태를 프로세스가 동작하고 있다고 이야기 하곤 한다.
이 메모리 또한 Hardware 이기 때문에 OS 의 관리대상이 된다.
Kernel 은 프로세스를 Process Control Block 으로 추상화 시켜 관리하고 있고, 정해진 스케쥴링 규칙에 맞게 Disk 에서 프로그램 정보를 얻어와서 메모리에 패치하거나, 자주 쓸 프로세스는 메모리 어딘가에 짱박아 뒀다가 다시 꺼내와서 쓰기도 한다.
Disk 에서 PCB 대기열은 wait queue
Memory 에서 PCB 대기열은 ready queue
에 저장된다고도 한다. 실제로 queue 자료구조로 이루어져 있다고 함
Instructuion(Program Counter) 와 프로그램 데이터를 같은 메모리에 올려서(우리가 아는 프로세스) CPU 가 연산할 수 있도록 하는 방식이 Von Neumann 의 초기 아키텍쳐 설계라고 한다.
...
User 는 여러가지 프로그램을 실행하는데 그렇다면 User 가 메모리에 직접 프로그램을 로드하는 것일까?
만약 User 가 임의로 Hardware 에 접근해서 데이터를 조작할 수 있다면 여러가지 문제가 발생할 것이다.
대표적인으로 데이터 무결성에 문제가 생기고, 인간이 조작함으로써 오는 실수들이 시스템 자원을 다루는데 비효율적인 결과를 초래할 것이다.
쉽게 말해서 Hardware 를 통제하는 규칙이 없기 때문에 손상될 우려가 생긴다는 것이다.
이를 위해서 Kernel 은 User 에게 systemcall 을 제공하는데, systemcall 을 통해서만 시스템 자원에 접근가능한 구조를 구축한다.
이를 통해서 시스템 자원은 User 로부터 보호받을 수 있고, 사전에 합의된 방식으로만 조작됨을 보장받을 수 있다.
User mode 와 Kernel mode 를 나누어 놓고 설명하는 이유도 systemcall 을 설명하기 위함인데, 하나의 프로그램을 메소드 단위로 생각한다면, 특정 시점에서 이 mode 를 바꾸는 법은 systemcall 메소드 내부에서만 유효하다는 것을 보장한다면 Hardware 에 접근하는 시점을 예측할 수 있다.
사실 우리가 사용하는 systemcall 메소드는 일종의 인터페이스인데, 실제로 systemcall 이 동작하는 방식은 적절한 값을 레지스터에 세팅해놓고 cpu 가 이를 연산에 사용하는 식인데, 매 systemcall 마다 사용자가 레지스터 값을 설정하는건 여간 번거로운일이 아니다.
따라서 순수 systemcall 의 동작을 메소드로 추상화해서 제공함으로써 사용자는 구현방식에 대해서는 더이상 궁금해하지않고 사용가능하다는 장점이 있다.
하지만... 이렇게 추상화된 systemcall 도 여전히 사용하기가 불편하기 때문에 사용자를 위해서 고안된 것이 libc, Windows API, Java API 등 기본 라이브러리이다.
기존보다 한 단계 더 추상화되었기 때문에 c 의 printf, java 의 System 객체 메소드 등등 우리가 편리하게 시스템 자원을 사용할 수 있게 되었다.
high-level -> low-level 코드 변경이 최근에는 굉장히 최적화가 잘 돼있어서 예전에는 성능 이슈가 있었지만 요즘에는 굳이 성능향상을 위해 일부러 systemcall 을 직접 호출해서 코드를 짤 필요는 없다고 한다. 그 시간이 더 든다
동작방식 (간략히)
사용자의 library 에 systemcall "호출" 코드가 있다. 해당 코드 내부를 살펴보면 레지스터에 적절한 값을 넣은 뒤 인터럽트(트랩) 을 발생시키는데 이 때 cpu 가 트랩을 감지하고 커널에게 방금 들어온 systemcall 을 처리하게 하도록 한다
커널과 함께 동작하는 OS 가 사용자에게 대표적으로 제공하는 서비스는 아래와 같다.
Program Execution - 프로그램 실행
I/O Operation - 디바이스 관리
File System Manipulation - 하나의 문서 저장소 역할
Communication - 다른 유저(프로세스)와 정보 교환
Error Detection - 오류 감지
시스템을 위해서는
Resource allocation : 메모리 스케쥴링, 자원 할당
Accounting : 얼마나 자원을 사용중인지 감시
Protection and Security : 프로세스 상태와 Hardware 보호
을 수행한다.
모든 OS 시스템은 일종의 가이드라인을 따라야 하는데
그 중 하나가 Mechanism 과 Policy 의 분할이다.
시스템 로직은 정책에 독립적이어야 한다는 것인데, 정책에 의존적인 로직은 굉장히 가변적인 정책 앞에서는 무용할 수 있기 때문에 사전에 이를 대비해서 OS 시스템 디자인을 해야한다.
OS 시스템의 구조들 중에서 대표적으로 비교되는 것이 Monolithic kernel 과 Micro kernel 이다.
사용자에게 제공되는 시스템 라이브러리 또는 일부 application(compiler, interpreter) 을 제외하고 모든 시스템관련 처리가 커널에서 이루어지도록 만든 구조이다.
전통적인 방식의 설계 구조이지만 여전히 강력한 구조라고도 할 수 있는데, 그 이유는 이 모놀리식 구조의 장점에 있다.
우선 같은 모드(여기서는 커널 모드) 에 있다는 의미 자체가 자원을 나눠쓰기에 용이하다는 것을 의미하므로 구현이 비교적 단순하고, 자원 재활용성이 높다.
=> 시스템 자원 자체를 커널 프로그램이 다 소유하고 있기 때문에 사용자는 커널에게 요청만 하면 된다. 집약적인 구조이기 때문에 속도가 빠르다.
커널 프로그램 단 하나에서 시스템 전반적인 작업을 모두 처리하다 보니 어느 하나라도 오류가 발생한다면 커널 전체가 다운되는 문제가 생긴다.
기존에 커널에서 처리해야하는 작업을 떼어서 따로 프로그램(component)으로 작성한 방식을 이야기한다.
이제부터 커널 프로세스와 component 가 독립되어있기 때문에 component 가 오류를 발생시켜도 커널이 다운되는 일이 없어졌다.
처음에 안전을 위해서 커널로 격리시킨 기능들을 어떻게 커널에서 부터 떼어 올까 했는데 커널에서 떼어 온다기 보다는 커널이 프로세스와 프로세스 사이의 메세지 통로 역할을 함으로써 우리가 우려하는 의도치 않은 작업이 수행될 일이 없도록 보장한다.
다만 기존의 커널 프로세스에서 모든 작업이 수행되는 것과는 반대로 프로세스 자체가 분리되어 있기 때문에 그 과정에서 오는 메세징 오버헤드(네트워크를 사용한다면 네트워크 요청 오버헤드) 가 발생할 수 있다.