01. 운영체제(OS)란?
운영체제란, User와 컴퓨터 하드웨어 사이에서 동작하는 프로그램이다. 하드웨어 자원들(CPU, Memory, Disk, tty)을 관리하고 프로그램들을 지원해준다. 조금 더 쉽게 표현하면, 하드웨어 자원들을 감싸고(감추고) 다른 프로그램들이 원할하게 동작하도록 도와주는 시스템이라고 할 수 있다.
-> "하드웨어 자원들을 감싼다"는 문장의 의미는, 사용자가 하드웨어 자원들의 동작을 이해할 필요 없이 운영체제가 이것을 알아서 처리해준다는 의미...정도로 이해하면 될 것 같다.
아래 그림과 같이 이해해보자. 위 그림에서 운영체제는 프로그램과 하드웨어 사이에 위치하고 있다. OS는 하드웨어를 감싸고 다른 프로그램들을 지원해주는 역할을 수행한다. 이제 아래 그림을 보자. 위의 그림과 유사하다. OS는 밑으로는 하드웨어 자원들을 지원하고, 위로는 소프트웨어 자원들(즉 프로그램)을 지원한다.
OS는 하드웨어와 소프트웨어(프로그램) 중간에 위치하며, 밑으로는 하드웨어 자원들을 위로는 소프트웨어 자원들을 지원해준다.

02. Program이란?
c언어로 코딩을 할 때, main문을 만들고 컴파일과 어셈블리 과정을 거치면 오브젝트 파일이 나온다. 오브젝트 파일은 0과 1로 구성된 이진(Binary) 데이터로 이루어져있다. 여기서 이 바이너리가 바로 프로그램이다.

우리가 일반적으로 사용하는 Power Point, Word 이런것도 모두 프로그램이다. 그런데 왜 파워포인트와 워드는 하나의 Offce Program이 아닌 각각의 프로그램으로 만들었을까? 많은 이유가 있겠지만 가장 중요한 이유는 바로 자원 낭비를 줄이기 위해서이다.
예를 들어 Office 프로그램 하나에 모든 기능이 있는 경우를 생각해보자. 나는 파워포인트만 사용하면 되는데 파워포인트를 사용하기 위해서는 Office 프로그램을 실행시켜야한다. 하지만 Office 프로그램에는 파워포인트 외에도 워드, 엑셀 등이 포함되어 있다. 따라서 Office 프로그램을 실행시키면 워드, 엑셀과 같은 당장 필요하지 않은 프로그램도 함께 실행되어 의미없는 메모리 사용 문제가 발생한다. 이로 인해 컴퓨터 성능 저하 문제가 발생할 수 있다.
이러한 이유로 Office는 전체의 프로그램이 아닌, 기능에 맞는 프로그램들로 분할하여 출시되었다. 이와 마찬가지로 Linux도 OS를 하나의 커다란 프로그램이 아닌 Kernel, Shell, Utility 등의 프로그램으로 분할해 두었다.

03. Kernel이란?
03.1. Kernel / Shell / Utility
커널(Kernel)은 항상 메모리에 상주하는 memory resident한 OS의 핵심 부분으로, 하드웨어와 (응용)프로그램 사이에서 인터페이스를 제공하며 컴퓨터 자원들을 관리하는 역할을 한다. 즉, 커널은 인터페이스로써 프로그램 수행에 필요한 여러가지 서비스를 제공하고 여러 하드웨어 자원(CPU, Memory..)을 관리하는 역할을 한다. 메모리에 상주하는 특징을 빼면 평범한 C 프로그램이다.
쉘(Shell)이란, Utility의 한 종류로 주된 임무는 job 컨트롤을 하는 것이다. 유틸리티는 여러개의 disk에 있는데, 이것들이 언제 올라오고 내려가는지를 컨트롤한다(교통정리). 커널은 항상 컴퓨터 자원들을 담당하기 때문에 사용자와의 상호작용은 지원하지 않는다. 따라서 사용자와의 직접적인 상호작용을 위한 프로그램이 따로 제공되는데, 이 중 대표적인 것이 명령어 해석기인 쉘(Shell)이다.
유틸리티(Utility) 또한 OS의 부분 중 하나이다. 디스크에 상주하며 사용자가 요청을 할때 메모리에 올라온다. utility = command = job라고 생각하면 편하다.

03.2. Kernel의 자원 관리
다시 커널에 대해 살펴보도록 하겠다. 커널의 가장 큰 목표는 컴퓨터의 하드웨어 자원(물리적 자원)과 추상화 자원을 관리하는 것이다. 추상화란, 물리적으로 하나뿐인 하드웨어를 여러 사용자들이 번갈아 사용할 수 있도록 마치 하드웨어가 여러개인 것처럼 보이게 하는 기술이다. 즉 커널에 의해 유저들은 하나의 하드웨어를 독점하여 사용하는 듯한 느낌을 받는다. 물리적 자원들과 이를 추상화한 자원들을 칭하는 용어는 대표적으로 아래와 같은 것들이 있다.
- CPU(물리적 자원) - Task or Process(추상화 자원): CPU는 실행 단위를 제공하며, 커널은 물리적 자원인 CPU를 추상적 자원인 Process 또는 Task로 관리한다
- Memory - Page or Segment: 메모리는 커널에 의해 Page 또는 Segment 단위로 관리된다
- Disk - File: 물리적 자원인 디스크를 추상적 자원인 File로 제공한다
- Network - Socket: 물리적 자원인 네트워크 자원은 Socket을 통해 프로그램 데이터 송수신이 가능하도록 추상화된다
위의 작업들 외에 커널은 Device Driver Management(각종 외부 장치에 대한 접근), Interrupt Handling(인터럽트 핸들러), I/O Communication(입출력 통신 관리) 등의 작업 또한 수행한다.

▼ 앞서 말한 커널 구성 요소들이 존재하는 공간을 Kernel Space라고 할 수 있다. Kernel Space 위에는 사용자로 여겨지는 User Space가 있으며 여기에 Task, Process들이 존재한다. 그리고 이 User Space와 Kernel Space 사이에는 보이지 않는 System Call Interface가 존재한다(뒤에서 다룬다). 유저 스페이스의 Task 또는 Process들이 커널이 관리하는 자원에 접근해야할 필요가 있을 때 -> System Call Interface를 통해 -> 커널 스페이스의 자원관리자에게 요청이 전달되는 방식이다. 이 과정이 이루어지면 커널은 사용자 요청에 맞는 하드웨어에게 명령을 전달하고 작업을 수행하게된다.

Kernel은 사용자가 System Call을 통해 컴퓨터 자원을 사용할 수 있게 해주는 자원 관리자라고 할 수 있다.
03.3. Kernel의 유형
커널은 크게 모놀리식 커널(Monolithic Kernel)과 마이크로 커널(Micro Kernel)로 나눌 수 있다.
<<모놀리식 커널>>
모놀리식 커널은 우리가 개념적으로 알고 있는 커널로써, 애플리케이션을 제외한 모든 System 서비스들, 예를 들어 VFS(Virtual File System), IPC(InterProcess Communication), File System 등을 커널이 직접 처리하는 방식이다. 각 서비스들은 커널 내 부의 여러 계층에서 관리되며, 모놀리식 커널은 사용자가 운영체제 서비스들을 System Call을 통해 사용할 수 있게 해준다.
- 장점: 커널 내부에서 서비스들이 서로 시스템 자원을 공유하며 효율적으로 관리할 수 있고, 내부 서비스를 커널이 직접 수행하므로 처리속도가 빠르다.
- 단점: 모놀리식 커널은 커널이 많은 것을 관리하기 때문에 커널의 크기가 크고, 하나의 오류가 전체 시스템에 영향을 미칠 수 있다
초기 모놀리식 커널은 단일 모듈이었기 때문에, 내부 서비스 추가/수정 과정을 수행할 경우 커널 전체를 다시 컴파일하고 로딩해야했다. 하지만 최신 모놀리식 커널은 여러개의 모듈(Module)로 구성되어있어 커널의 추가/수정이 수훨해졌다.
<<마이크로 커널>>
마이크로 커널은 기존의 모놀리식 커널에서 핵심 서비스(Process/ Memory/Network Management)만을 남겨두고 나머지를 제외하여 가볍게 만든 커널이다. 기존의 모놀리식 커널이 가지고 있던 시스템 서비스들(VFS, IPC...)이 마이크로 커널에서는 개별적인 서버 형태로 존재한다.
- 장점: 서버를 추가하는 방식이기에 커널을 변경하지 않고 간단하게 기능 추가/수정이 가능하고, 프로세스가 각각의 서버 영역에서 수행되기 때문에 하나의 서비스가 다운되어도 다른 서비스에 영향을 미치지 않는다.
- 단점: 커널 최소화를 위해 핵심적인 서비스만을 모아두고 이외의 서비스들은 서버를 추가하는 방식의 구조여서, 프로세스간 통신을 통해 대부분의 서비스가 수행된다. 따라서 메시지 전송에 따른 컨텍스트 스위칭이 많이 발생하며 시스템 복잡도가 증가될수록 시스템 부하가 증가되는 단점이 있다.
모놀리식 커널: Unix, Embedded Linux, OSEK, WinMoblie
마이크로 커널: MacOS, Windows NT
아래 그림을 보면 확실히 마이크로 커널의 커널 모드가 작은 것을 볼 수 있다.

04. 어떻게 Kernel - Shell - Utilities가 연결될까?
사용자가 컴퓨터 전원을 키면, 컴퓨터가 부팅되면서 디스크에 있던 Kernel Program이 제일 먼저 메모리에 올라간다.

컴퓨터가 부팅되고 사용자가 터미널을 실행하면, 터미널 위에 Shell이라는 프로그램이 메모리에 올라온다. Shell은 컴퓨터가 사용자의 커맨드를 입력받을 때 까지 대기 상태로 존재한다. 사용자가 커맨드를 입력하면 해당 커맨드에 해당하는 Utility Program을 디스크로부터 가져와 실행한다. 예를 들어 사용자 B가 Shell에 ppt를 실행시키라는 커맨드를 내리면, Shell은 child process를 생성해 ppt를 실행시켜준다.
1. 커널은 항상 메모리에 올라와 있고
2. 커널 이외의 것들은 전부 Utility이다. Utility는 항상 Disk에 있다가 필요할 때만 메모리에 올라오고 내려간다. 이를 Command라고도 한다
3. 메모리에 언제 올라오고 언제 내려가는지를 핸들링 하는 것이 바로 Shell이다

05. Linux Protection
리눅스는 Windows와 달리 멀티 유저 시스템이기 때문에 여러 사용자가 동시에 메모리에 접근할 수 있다. 만약 한 프로세스가 다른 프로세스의 정보를 read/write하면 어떻게 될까? 악의적인 사용자가 내 개인정보를 read하거나, 저장되어 있는 정보를 다른 정보로 write해 덮어버릴 수도 있다. 이런 중요한 정보들은 주로 Memory 또는 Disk에 위치하기 때문에 다른 사람이 해당 위치에 있는 내 정보에 접근 하는 것을 막아야한다. 문제가 발생한 후에 악의적인 사용자의 접근을 막는 사후 처리는 아무런 의미가 없기 때문에, 최대한 사전에 악의적인 사용자의 접근을 막아야한다.

사전에 악의적 사용자의 접근을 방지할 수 있는 방법을 아래 그림과 함께 살펴보자.
컴퓨터는 1코어 CPU를 탑재하고 있으며, 현재 Bob과 Dan의 프로그램이 메모리 위에서 실행되고 있다. CPU는 연산에 필요한 로직이 수행될때 무조건 Bob 또는 Dan의 프로그램을 위해 사용되어진다. Bob의 프로그램이 CPU를 차지했다고 가정해보자. Shell 또한 프로그램이기 때문에 의도치 않은 오류가 발생할 수 있으며, 이러한 오류로 인해 Bob이 read/write 해야하는 영역이 아닌 허용되지 않은 디스크나 메모리 영역(ex. Dan의 영역)에서 read/write가 수행될 수 있다.

이런 불상사가 발생하지 않도록 고안된 해결책이 있다. 바로 특정 사용자의 Shell에게 CPU를 양도해도, 연산시 CPU가 I/O instruction을 아예 수행하지 못하게 막아버리는 방법이다. 하지만 I/O instruction이 불가능 하다는 소리는 아니다. 사용자가 I/O 작업을 할 때는 반드시 kernel에게 요청을 하는 방식으로 진행된다는 의미이다. 커널이 (I/O)요청을 받게 되면, 해당 I/O가 정상적인지 검사를 진행한 뒤, 커널이 가지고 있는 function으로 I/O를 처리해준다.
아래 사진을 보자. 현재 Bob의 쉘에 CPU가 양도되었고, 메모리에 올라온 프로그램은 I/O 연산이 진행되어야 하는 상황이다. 하지만 위에서 사용자는 I/O를 할 수 있는 권한이 없다고 했다. 따라서 Bob은 해당 작업을 커널에게 요청하고 -> 커널은 커널 내의 function을 이용해 I/O을 진행해준다. 이렇게 사용자가 커널에게 I/O를 요청하는 것을 System Call이라고 한다.

06. System Call
이러한 system call 과정을 도와주기 위해 하드웨어에 Binary bit mode라는 개념이 도입됐다.
CPU의 mode bit가 어떻게 사용되는지 살펴보자. 우선 mode bit이 1로 세팅되어 있으면 Kernel mode, 0으로 세팅되어 있으면 User mode라고 가정해보자. mode bit가 kernel mode로 세팅되어 있는 상황에서는 CPU는 모든 메모리 영역에 접근 가능하며 모든 명령어 수행이 가능하다. 반대로 mode bit가 user mode로 세팅되어 있으면, 나의 주소(사용자의) 영역에만 접근이 가능하며 I/O 명령, 파일에 영향을 미칠 수 있는 특수 명령 수행이 불가능하다. 즉 제한적인 명령어 수행만 가능하다.

그렇다면 설정된 mode bit는 어떤 영역에서 검사되어질까? CPU 명령어 사이클을 살펴보면, PC 레지스터에서 메모리로 수행할 명령어의 주소를 보낼 때 mode bit가 user mode라면 CPU와 메모리 사이에 존재하는 MMU(Memory Management Unit) 하드웨어가 CPU에서 전송되는 주소를 체킹한다. 만약 사용자가 요청한 명령어의 주소가 사용자 로컬 범위 밖에 위치하는 주소하면 그 즉시 CPU를 뺏겨버린다. 만약 범위 문제 없이 정상적으로 명령어를 가져왔다면, 해독 단계에서 opcode를 보고 명령어가 어떠한 역할을 하는지 파악한다. 그리고 수행되기 전에 한 번 더 op-code를 보고 이것이 권한이 있는(priviledged) op-code인지 아닌지를 체크한다.

그렇다면 내가 파일을 읽고 쓰는 로직의 코드를 짠 상황을 생각해보자. 현재 나의 프로세스는 user mode일 것인데, user mode에서는 I/O를 수행하지 못한다. 커널이 아니기 때문이다. 이런 경우 우리는 어떻게 I/O를 수행해야할까? 아래 그림과 함께 이해해보자.
내가 작성한 a.out 프로그램을 보면, read()와 write()가 사용되고있다. 나는 현재 read로 어떤 파일을 읽어오라고 코딩을 해놓았다. 하지만 이러한 read 즉 I/O가 있는 것처럼 보이는건 소스코드에 한정된다. 컴파일 과정을 거친 소스코드(바이너리)를 보면 그 안에는 I/O statement가 없다. 소스코드에 존재하는 read 같은 I/O가 필요한 부분들은 컴파일 과정에서 컴파일러에 의해 전부 chmodk(change mode protection kernel) 명령을 수행하도록 변경된다.

그렇다면 바이너리 내부의 I/O와 관련된 로직이 수행되려고 하면 chmodk가 어떻게 수행되는지 살펴보자. chmodk는 privileged(권한이 있는) 명령어이다. 따라서 커널이 아닌 user program은 이것을 수행하지 못하기 때문에 여기서부터 CPU 사용 권한이 뻇기며 trap에 걸린다. 이때 System Call을 통해 커널에게 이 명령이 read인지 write인지 알려줄 파라미터가 (커널에게 전송되는)요청과 함께 커널 trap handler로 들어간다. 이후 user mode가 kerner mode로 변경되며, trap handler 루틴이 수행된다. 커널은 요청받은 정보를 확인한 뒤, 자신이 가지고 있는 커널 내 함수를 호출하여 요청을 처리해준다.
좀 복잡하다. 정리를 해보면 다음과 같다.
1.내가 작성한 코드에 read, write 같은 I/O 관련 statement가 있는 상태이다
2.이 코드를 컴파일 하면 read, write 같은 I/O statement는 사라지고 chmodk가 남게된다
3.현재 CPU의 mode bit는 user mode이기 때문에, 런타임 실행이 되면 chmodk가 실행되며 trap에 걸리게 된다
4.이때 하드웨어가 mode bit를 user mode -> kernel mode로 변경해준다
5.따라서 현재 trap은 커널 프로그램 안에 존재하게 되며, trap handler에서 왜 요청이 왔는지 확인하고, 요청에 해당하는 함수, 즉 I/O 관련 함수를 호출한다
6.호출하면서 권한 체크를 한뒤에야 실행이된다
7.실행이 완료되면 mode bit는 다시 kernel mode -> user mode로 변경된다.

'Cyber_Sec > Linux Kernel' 카테고리의 다른 글
| SSR 3주차 - Kernel Security (0) | 2025.03.31 |
|---|