Linux Kernel 관련 온라인 강의 요약본입니다.
https://olc.kr/course/course_online_view.jsp?id=35&s_keyword=Kernel&x=0&y=0
What is an Operating System?
운영체제(OS, Operating System)는 사용자의 프로그램과 하드웨어 사이에 존재하며, 사용자가 사용하는 하드웨어 자원(CPU, Memory, disk, tty)을 관리하여 사용자 프로그램이 작동할 수 있도록 관리하는 추상화된 인터페이스를 제공하는 프로그램입니다.
왜 프로그램을 잘게 쪼개는 가?
실제로 우리가 word, excel, ppt와 같은 프로그램을 1개의 실행 파일로 만들지 않고 여러 개의 실행 파일로 분리하는 이유는 간단합니다.
하나의 프로그램이 너무 크면 유지 보수 측면에서 어렵고, 더 많은 하드웨어 자원과 overhead를 동반하기 때문입니다. 따라서 사용할 기능이 존재하는 프로그램만 실행하여 사용하는 것이 대부분의 환경에서 좋을 것 입니다.
OS의 경우도 동일합니다. 모든 기능이 존재하는 하나의 큰 프로그램이 아니라 기능에 맞게 프로그램을 kernel, shell, utility로 분할하여 사용합니다.
OS를 구성하는 Program
OS를 구성하는 프로그램에 대해서 알아보면 아래와 같습니다.
•
Kernel
메모리에 상주하며, 사용 프로그램과 달리 커널 모드에서 실행되는 특권 프로그램입니다.
•
Utility
커맨드라고도 부르며, 디스크에 ㅅ에 상주합니다. 즉 항상 disk에 존재하고 필요하면 disk에서 불러와 실행합니다.
•
Shell
utility중 하나로, 유저와 상호작용하여 다른 utility를 실행하거나 관리합니다.
•
File
sequence of bytes라고 부르며, 리눅스에서는 I/O device들도 파일로 취급하여 관리합니다.
실제로는 I/O도 File로 볼 수 있도록 커널에서 추상화하여 제공합니다.
How Kernel-Shell-Utilities are Related
커널이라는 프로그램은 컴퓨터가 부팅되면 가장 먼저 실행되는 프로그램입니다.
커널이 memory에 적재된 후, user a가 접근하면 커널에서 user a의 권한을 가지고 sh을 실행하여 반환합니다.
user a는 반환받은 sh을 이용하여 다른 프로그램을 실행하거나 명령어를 실행하는 등 컴퓨터의 hardware를 사용하게 됩니다.
또한 Linux의 경우 multi-user System이기 때문에 여러 사용자가 동시에 접근할 수 있습니다. user a와 user b가 각 각 sh을 실행하고 sh을 실행하기 위한 자원(Memory, CPU, Disk, etc…)을 사용합니다.
Why protection is necessary in Linux
Linux와 Window의 차이와 특징에 대해서 말해보겠습니다.
•
Linux
Linux는 기본적으로 여러 사용자가 접근하여 hardware 자원을 사용하도록 개발된 운영체제입니다.
1.
Protection
여러 사용자가 접근하여 CPU와 Memory, Disk등을 사용하기 때문에 Protection이 중요합니다. 다른 사용자의 memory 값을 읽어올 수 있거나, 다른 사용자의 권한이 없는 파일을 읽어오는 것을 통제하고 관리합니다.
2.
Resource
큰 틀로 봤을 때 PC(CPU, Memory, etc…)를 공유하여 사용하기 때문에 최소한의 자원을 이용하여 프로그램을 실행하고 결과를 출력합니다.
•
Window
Window는 단일 사용자가 사용하는 환경에서 사용자 친화적으로 컴퓨터를 사용하도록 개발된 운영체제입니다.
1.
Protection
단일 사용자이기 때문에 크게 조심하거나, 고려할 부분이 존재하지 않습니다.
2.
Resource
PC(CPU, Memory, etc…)를 혼자서 사용하기 때문에 사용자가 편하도록 자원의 효율성을 꼭 따질 필요는 없습니다.
초기 운영체제를 개발할 때 이야기지 지금은 Window와 Linux 모두 Multi-user를 고려하여 개발되었고, 각 운영체제마다 잘 어울리는 시스템과 환경이 존재하여 고려해 사용하면 됩니다.
Multi-user system -- Protection
만약 P1 프로세스가 P2 프로세스의 데이터를 read, write 할 수 있다면, 이는 명백한 보안 결함입니다.
예를 들어 카카오톡과 롤을 켰는데 롤에서 카카오톡 메시지를 읽고 보낼 수 있다면 큰 문제인 것과 유사합니다.
이런 데이터들은 memory와 disk에 존재하고, 다른 프로세스와의 Protection을 위해 memory와 Disk와 같은 장치에 접근할 때 권한이 있는 영역인지 아닌지 확인하는 방식으로 작동합니다.
자세한 내용은 이후에 다루겠지만, Linux Kernel은 MMU 기반의 페이지 테이블과 커널 내부의 프로세스 관리 구조(PCB)를 활용하여 각 프로세스의 메모리 접근 권한을 관리합니다.
No I/O Instructions
프로세스 예제를 통해 설명하겠습니다. Bob이 P1(a.out)을 실행하고 Dan이 P2(a.out)을 실행한다고 가정하겠습니다.
이러한 사용자 프로세스는 CPU의 사용자 모드(User Mode)에서 실행되며, CPU는 현재 설정된 권한 수준(Privilege Level)을 보고 사용자 모드(User Mode)인지 커널 모드(Kernel Mode)인지 구분합니다.
모든 사용자 프로세스는 CPU 안에서 명령어(Instruction)가 실행될 때, I/O가 필요한 명령어와 같은 특권 명령어(privileged instruction)를 실행할 수 없도록 CPU가 fault 또는 exception을 발생시킵니다.
System Call
실제 프로그램을 작성하고 실행하면 사용자 모드에서 작동하지만, 파일에 I/O 등 여러 I/O가 가능한데 이것을 가능하게 하는 System Call에 대해서 알아보겠습니다.
사용자는 I/O 명령이 불가능하고, I/O 수행하기 위해 고안된 방식이 System Call입니다.
System Call이란 커널에게 대신 요청해서 I/O를 수행해 달라고 전달합니다.
요청 받은 커널은 사용자의 요청이 정상적인 요청인지 확인한 뒤, 커널 내부에 존재하는 함수를 통해서 I/O를 수행하고 결과를 반환합니다.
이 과정에서 CPU는 사용자 모드에서 커널 모드로 전환되며, 커널에서 작업이 완료되면 다시 사용자 모드로 복귀합니다.
사용자 모드(User Mode) 커널 모드(Kernel Mode)
System Call을 호출하면 사용자 모드에서 커널 모드로 전환된다고 위에서 설명했습니다. 이러한 모드 전환을 위해서 CPU에서 권한 수준(Privilege Level)을 나타내는 상태 비트(Status Bit)을 사용합니다.
먼저 CPU 명령 사이클이 돌면서 명령어(Instruction)이 실행되고, 프로그램이 동작하는지 알아보겠습니다.
1.
PC(Program Counter)에서 실행될 메모리 주소를 가져와 MAR(Memory Address Register)에 저장
2.
MAR에 저장된 메모리 주소에 접근해 저장된 내용(Instruction)을 MBR(Memory Buffer Register)로 저장
3.
MBR에 있는 명령어를 IR(Instruction Register)로 저장
4.
IR에 있는 명령어를 해석
5.
해석한 명령어는 opcode와 operand로 분리되어, 명령어 종류에 따라 ALU 연산, 메모리 접근, 분기 처리 등이 수행
Mode Bit
CPU의 Mode Bit가 어떻게 사용되는지 보겠습니다. Mode Bit가 1이면 커널 모드, 0이면 사용자 모드라고 가정하겠습니다.
커널 모드가 설정되어 있다면 모든 특권 명령어(privileged instruction)를 실행할 수 있지만, 사용자 모드의 경우 I/O 접근이나 시스템 제어와 관련된 특권 명령어(privileged instruction)는 실행이 허용되지 않습니다.
이러한 명령어를 사용자 모드에서 실행하는 경우 CPU는 fault 또는 exception을 발생시킵니다.
Mode Bit를 통해서 사용자 모드와 커널 모드를 관리하고 커널 모드인 경우 모든 메모리 영역에 접근이 가능하고, 모든 명령어가 실행 가능합니다.
반면 유저 모드의 경우는 제한된 메모리에 접근하고 제한된 명령어만 사용이 가능합니다.
초기 Linux 시스템에는 커널 모드에서 모든 메모리 영역에 접근할 수 있었고, 실행 가능한 명령어에도 별다른 제한이 존재하지 않았습니다.
현대 Linux 시스템에는 보안 강화를 위해 SEMP(Supervisor Execution Prevention)와 SMAP(Supervisor Mode Access Prevention)과 같은 보호 기법이 도입되었습니다.
이로 인해 커널 모드라고 해도 사용자 메모리에 있는 코드를 직접 실행하는 것이 불가능하며, 사용자에 메모리에 접근 또한 제한되거나 접근하기 위한 과정이 필요합니다.
Mode bit Check
이런 Mode bit를 언제 검사하는지 알아보겠습니다.
CPU 명령 사이클이 진행되면서 PC(Program Counter)를 통해 실행할 메모리 주소를 가져올 때, 사용자 모드라면 CPU는 MMU(Memory Management Unit)를 통해서 해당 메모리 주소가 접근 가능한 영역인지 검사합니다.
•
검사한 메모리의 주소가 사용자가 할당받은 메모리 범위를 벗어나는 경우
CPU는 Page Fault를 발생시키고 커널로 제어를 넘깁니다. 이후 커널은 상황에 따라 해당 프로세스를 종료하거나 적절한 처리를 수행합니다.
•
검사한 메모리의 주소가 사용자가 할당받은 메모리 범위인 경우
메모리 주소로부터 실행할 명령어를 가져와 Decode 한 뒤,opcode를 보고 특권 명령어(privileged instruction)인지 아닌지 검사하고 실행하게 됩니다.
File I/O
우리가 C로 코드를 짜면 사용자 모드로 실행되는 프로그램인데도 read, write와 같은 파일 I/O를 할 수 있습니다.
실제로는 read, write와 같은 libc 함수들은 디스크에 직접 접근해서 파일을 읽고 쓰는 것이 아니라 내부로 들어가보면 system call을 호출합니다.
그리고 실제 I/O는 커널이 system call을 받아 전달된 인자를 확인하여 수행한 뒤 사용자 모드로 전환하며 결과 값을 반환합니다.
chmodk
수업에서는 chmodk라는 것을 사용하지만, 찾아봐도 Linux나 Unix 계열에서 chmodk를 설명하는 관련 문서는 찾을 수 없었습니다. 따라서 이 글에서는 chmodk를 syscall로 보고 정리해보겠습니다.
우리가 I/O와 같은 작업을 요청하면, 컴파일러는 이를 read, write와 같은 libc 함수를 호출합니다.
이런 파일 입출력 함수 경우 libc 소스코드를 보면 함수 내부에서 system call을 발생시키는 명령어인 syscall을 호출하고 있습니다.
syscall은 사용자 모드에서 커널 모드로 변경하는 명령어입니다.
syscall 명령어가 실행되면 CPU는 Mode Bit를 커널 모드로 변경하고, 미리 등록된 trap handler로 이동하여 커널 코드가 실행됩니다. 이후 커널은 요청된 작업을 처리한 뒤 다시 사용자 모드로 복귀하여 결과를 반환합니다.
여기서 trap handler로 이동한 뒤, 커널은 요청된 작업을 검사합니다.
예를 들어 접근 가능한 파일인지, 접근 가능한 메모리 영역인지, 명령을 수행할 수 있는 권한을 가지고 있는지 등등
여기서 커널이 요청된 작업에 대해 권한이 문제가 없다면 커널 모드에서 I/O를 수행합니다.
작업이 완료되면 system call 복귀 과정에서 CPU가 사용자 모드로 Mode Bit를 변경하고, 다시 syscall 호출 이후의 사용자 코드를 실행하게 됩니다.
여기서 system call을 복귀하는 것은, 커널 모드에서 사용자 모드로 복귀하는 iretq 또는 sysret와 같은 복귀용 명령어를 실행합니다.
User Mode Stack & Kernel Mode Stack
하나의 프로세스가 실행되는 동안, CPU는 사용자 모드와 커널 모드를 번갈아 가면서 실행합니다.
이 과정에서 유저가 작성한 함수와 커널에서 호출되는 함수를 각각 함수 호출과 지역 변수를 관리해야 하므로, 서로 분리된 스택(Stack)도 필요하게 됩니다.
따라서 각 프로세스는 사용자 스택(User Stack)과 커널 스택(Kernel Stack)을 각각 하나씩 총 2개를 사용합니다.


















