들어가며
kernel 환경 구축은 처음이라서 일단 드림핵으로 basics 강의를 수강하였다.
[주버전][부버전][개정판 번호]
ex) 6.6.30
커널 버전은 위와 같이 구분한다. 사실 이러한 구분보다는 LTS/Stable 구분이 더 중요하다.
LTS는 장기간의 지원을 제공하는 커널 버전이다. 이보다는 더 빠르게 기능이 추가되는 커널이 Stable이다. 대신 지원 기간은 더 짧다.
또한, 리눅스 커널은 시스템 아키텍처마다 동작 방식이 약간씩 다르다. 소스 안 arch 디렉토리를 들어가면 각 아키텍처의 코드가 포함되어 있다. x86부터 arm, MIPS 등 hexagon 같은 이외 아키텍처도 많다.
Background
Kernel Space & User Space
리눅스에서 메모리는 위 두 가지 스페이스로 구분하여 관리한다. 시스템의 안전성과 보안을 위함이다. 각 영역은 접근 권한이 다르며 user가 kernel space에 있는 리소스를 사용하기 위해서는 kernel에 요청을 해야하는데, 이때 이 요청을 system cal 이라고 한다.
x86 기준, syscall 어셈블리 명령어로 요청을 수행한다.
- 시스템 콜 호출
- CPU는 커널 모드로 전환
- 시스템 콜의 진입점,
entry_SYSCALL_64()함수 진입 - 시스템 콜 테이블인
sys_call_table로부터 유저가 요청한 시스템 콜 호출
task
리눅스의 응용 프로그램은 fork()를 호출해서 프로세스를 생성하고, pthread_create()를 호출해서 스레드를 생성한다.
- 프로세스는 독립된 메모리 영역(코드, 데이터, 힙, 스택)을 가짐
- 스레드는 스택 제외 메모리 영역 공유
다만, 커널 입장에서는 이 프로세스, 스레드를 구분하지 않고 모두 태스크로 정의한다. 응용 프로그램에서는 fork, pthread_create로 구분되지만, 커널 단으로 들어오면 모두 copy_process() 함수를 호출한다.
그리고 이 태스크가 생성되면 task_struct 구조체가 할당된다.
struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
/*
* For reasons of header soup (see current_thread_info()), this
* must be the first element of task_struct.
*/
struct thread_info thread_info;
#endif
unsigned int __state;
/* saved state for "spinlock sleepers" */
unsigned int saved_state;
/*
* This begins the randomizable portion of task_struct. Only
* scheduling-critical items should be added above here.
*/
randomized_struct_fields_start
void *stack;
refcount_t usage;
/* Per task flags (PF_*), defined further below: */
unsigned int flags;
unsigned int ptrace;
#ifdef CONFIG_SMP
int on_cpu;
struct __call_single_node wake_entry;
unsigned int wakee_flips;
unsigned long wakee_flip_decay_ts;
struct task_struct *last_wakee;
...
구조체 구성 요소
Memory Architecture
task_struct의 멤버 mm은 메모리 아키텍처의 주소들을 담고 있다.
struct mm_struct {
struct {
/*
* Fields which are often written to are placed in a separate
* cache line.
*/
...
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;
...
} __randomize_layout;
/*
* The mm_cpumask needs to be at the end of mm_struct, because it
* is dynamically sized based on nr_cpu_ids.
*/
unsigned long cpu_bitmap[];
};
위 코드를 보면 struct에 start, end로 코드 세그먼트, 데이터 세그먼트, 힙 세그먼트, 스택 세그먼트 등의 시작 주소와 끝 주소를 담고 있다.
user 영역의 메모리 구조는 프로세스마다 독립적이지만, kernel 영역은 모든 프로세스가 공유하는 영역이다.
| 주소 영역 | 설명 |
|---|---|
| 0x000000000000000000 ~ 0x00007ffffffffffff | 유저 영역이 위치함, 코드 세그먼트, 스택 세그먼트 등이 존재 |
| 0xffff88800000000000 ~ 0xffffc87ffffffffff | 물리적인 RAM을 나타내는 영역 |
| 0xffffc9000000000000 ~ 0x0000e8fffffffffff | vmalloc 영역 |
| 0xffffffff80000000 ~ 0xffffffff9ffffffff | 커널 코드가 위치한 .text 영역, 커널 이미지 |
| 0xffffffffa0000000 ~ 0xffffffffefffffff | 커널 모듈이 위치한 영역 |
위 메모리 맵은 x86-64 아키텍처의 4-level page table에 해당한다. 커널 CONFIG_X86_5LEVEL 옵션에 따라 구조가 달라질 수는 있다.
마지막으로 Addr에 대해 짚고 넘어가자. 알다시피 시스템에서 주소란, 가상 주소(VA)와 물리 주소(PA)로 나뉜다.
가상 주소는 커널이 프로세스에게 제공하는 주소 공간이다. 각 프로세스의 메모리 독립성을 보장하기 위해 해당 주소 개념을 사용한다.
물리 주소의 경우, 실제 메모리 하드웨어의 주소를 나타낸다. 물리 주소 공간은 RAM의 실제 메모리에 직접 매핑이 된다.
- 커널과 하드웨어는 물리 주소로 메모리에 접근
- 그리고 데이터 읽고 쓰기 수행
때문에 앞서 설명했던 page table중에 RAM 영역에 위치한다.
가상 주소의 경우 MMU에 의해 물리 주소로 변환된다.
Slab Allocator
슬랩 할당자는 커널의 동적 메모리 할당 관리를 위해 고안된 개념이다. kmalloc()을 호출하면 슬랩 할당자로부터 메모리를 할당 받을 수 있고, kfree()를 호출하여 할당받은 메모리를 해제할 수 있다.
과거에는 SLAB, SLUB, SLOB으로 나뉘었으나 지금은 특수한 경우를 제외하곤 SLUB만 사용한다.
슬랩 할당자 주요 구성 요소
슬랩 캐시 : 슬랩 페이지의 집합 슬랩 페이지 : 동일한 크기의 슬랩 객체로 구성
# cat /proc/slabinfo | grep "kmalloc"
...
kmalloc-8k 450 502 16384 2 8 : tunables 0 0 0 : slabdata 251 251 0
kmalloc-4k 2597 3068 8192 4 8 : tunables 0 0 0 : slabdata 767 767 0
kmalloc-2k 2224 3968 4096 8 8 : tunables 0 0 0 : slabdata 496 496 0
kmalloc-1k 4921 8608 2048 16 8 : tunables 0 0 0 : slabdata 538 538 0
kmalloc-512 28209 44128 1024 32 8 : tunables 0 0 0 : slabdata 1379 1379 0
kmalloc-256 37684 87040 512 32 4 : tunables 0 0 0 : slabdata 2720 2720 0
kmalloc-192 150168 209824 256 32 2 : tunables 0 0 0 : slabdata 6557 6557 0
kmalloc-128 21693 23968 256 32 2 : tunables 0 0 0 : slabdata 749 749 0
kmalloc-96 60864 60864 128 32 1 : tunables 0 0 0 : slabdata 1902 1902 0
kmalloc-64 199168 199168 128 32 1 : tunables 0 0 0 : slabdata 6224 6224 0
kmalloc-32 17173 28544 64 64 1 : tunables 0 0 0 : slabdata 446 446 0
kmalloc-16 27226 46464 32 128 1 : tunables 0 0 0 : slabdata 363 363 0
kmalloc-8 31277 47022 40 102 1 : tunables 0 0 0 : slabdata 461 461 0
리눅스의 /proc/slabinfo를 확인해보면, 현재 시스템에서 사용 중인 슬랩 캐시 목록을 확인할 수 있다.