강좌 & 팁
커널의 초기 스택위치
커널은 처음 시작될때 OS 없이 구동하는 펌웨어와 동일한 방식으로 구동됩니다.
커널 진입점에서 이런 저런 설정을 하게 됩니다.
대표적으로는 MMU 활성화가 있고 그 외적으로는 일반적인 어플리케이션을 실행시키는 것과
유사한 준비를 하게 됩니다.
커널이 사용하게될 MMU table 의 설정, 스택의 설정, 커널의 bss 영역의 초기화와 data 복사등이 있습니다.
MMU table 의 설정은 swapper_pg_dir 이라고 불리는 특별한 영역이 사용됩니다.
이곳에는 시스템의 전체 메모리에 대한 1:1 매핑 테이블과 CPU 마다 사용되는 고유한 시스템 레지스터들
그리고 IO 에 대한 mapping table 이 생성됩니다.
커널의 bss 영역의 초기화와 data 의 복사는 일반적인 어플리케이션을 로딩시켜줄 때 하는 것과 동일합니다.
마지막으로 스택을 설정하게 되는데 여기에서 설정되는 커널 스택이 init_task 의 스택이 되게 됩니다.
보통 커널스택이라고 불리는 것이 바로 이것입니다.
커널은 모든 아키텍쳐에서 공통적으로 사용되는 init_thread_union 이라는 task 를 사용하게 됩니다.
arm 에서는 이것을 위해서 아래와 같이 합니다.
arch/ar/kernel/head-comm.S
13 .type __switch_data, %object
14 __switch_data:
15 .long __mmap_switched
16 .long __data_loc @ r4
17 .long __data_start @ r5
18 .long __bss_start @ r6
19 .long _end @ r7
20 .long processor_id @ r4
21 .long __machine_arch_type @ r5
22 .long cr_alignment @ r6
23 .long init_thread_union + THREAD_START_SP @ sp
24
25 /*
26 * The following fragment of code is executed with the MMU on in MMU mode,
27 * and uses absolute addresses; this is not position independent.
28 *
29 * r0 = cp#15 control register
30 * r1 = machine ID
31 * r9 = processor ID
32 */
33 .type __mmap_switched, %function
34 __mmap_switched:
35 adr r3, __switch_data + 4
36
37 ldmia r3!, {r4, r5, r6, r7}
38 cmp r4, r5 @ Copy data segment if needed
39 1: cmpne r5, r6
40 ldrne fp, [r4], #4
41 strne fp, [r5], #4
42 bne 1b
43
44 mov fp, #0 @ Clear BSS (and zero fp)
45 1: cmp r6, r7
46 strcc fp, [r6],#4
47 bcc 1b
48
49 ldmia r3, {r4, r5, r6, sp}
50 str r9, [r4] @ Save processor ID
51 str r1, [r5] @ Save machine type
52 bic r4, r0, #CR_A @ Clear 'A' bit
53 stmia r6, {r0, r4} @ Save control register values
54 b start_kernel
__mmap_switched 라는 레이블은 MMU 가 활성화되고 나서 수행되는 첫 주소입니다.
여기서 수행되는 것들은 __switch_data 레이블을 기본값으로 해서
data load, bss 클리어영역의 정보를 얻어와서 필요한 내용을 수행하고
init_thread_union + THREAD_START_SP @ sp
위의 값을 sp 에 세팅하게 됩니다.
init_thread_union 은 아키텍쳐마다 선언을 하게 되어 있는데 init thread 가 수행되는 공간입니다.
arch/arm/kernel/init_task.c 에는
33 union thread_union init_thread_union
34 __attribute__((__section__(".init.task"))) =
35 { INIT_THREAD_INFO(init_task) };
36
37 /*
38 * Initial task structure.
39 *
40 * All other task structs will be allocated on slabs in fork.c
41 */
42 struct task_struct init_task = INIT_TASK(init_task);
43
44 EXPORT_SYMBOL(init_task);
이렇게 선언이 되어 있고 일반적으로 커널쓰레드의 사이즈는 8K size 를 갖습니다.
크다면 크고 적다면 적기 때문에 커널에서 수행되는 함수들은 스택을 많이 쓰지 않도록 조심해야 합니다.
조용하게 커널이 망가질수 있으니까요.
그리고 초기 stack 의 위치는 아래와 같이 선언됩니다.
include/asm-arm/thread_info.h
18 #define THREAD_SIZE_ORDER 1
19 #define THREAD_SIZE 8192
20 #define THREAD_START_SP (THREAD_SIZE - 8)
init_thread_union 은 thread_union 이라는 구조인데
include/linux/sched.h 에는 아래와 같이 선언되어 있습니다.
1247 union thread_union {
1248 struct thread_info thread_info;
1249 unsigned long stack[THREAD_SIZE/sizeof(long)];
1250 };
결국 init_thread_union 이라는 union 이 하나 선언이 되는데 그 위치는 section 에 존재합니다.
그리고 거기서부터 8192K 뒤쪽 바로 아래위치에 스택이 위치합니다.
그리고 마지막 살펴볼 곳이 있습니다.
init_thread_union 을 하나 선언했는데 그 뒤로 8192 위치에 스택을 잡으면 그 사이 공간은 어떻게 확보될까요...
커널 이미지를 만드는 링크 스트립트에는 아래와 같은 내용이 있습니다.
arch/arm/kernel/vmlinux.lds.S
111 #ifdef CONFIG_XIP_KERNEL
112 __data_loc = ALIGN(4); /* location in binary */
113 . = PAGE_OFFSET + TEXT_OFFSET;
114 #else
115 . = ALIGN(THREAD_SIZE);
116 __data_loc = .;
117 #endif
118
119 .data : AT(__data_loc) {
120 __data_start = .; /* address in memory */
121
122 /*
123 * first, the init task union, aligned
124 * to an 8192 byte boundary.
125 */
126 *(.init.task)
127
128 #ifdef CONFIG_XIP_KERNEL
129 . = ALIGN(4096);
130 __init_begin = .;
131 *(.init.data)
132 . = ALIGN(4096);
133 __init_end = .;
134 #endif
135
136 . = ALIGN(4096);
137 __nosave_begin = .;
해당 변수는 섹션중에서 init.task 라는 위치의 init task union 을 위한 공간을 확보하게 되는데 8192 aligned 를 맞추어
시작위치에 대한 aling 을 맞추고 그 뒤로는 8192 만큼의 공간을 확보하도록 해 줍니다.
확인을 위해서 System.map 을 보고 확인합니다.
System.map
17384 c0319000 A _etext
17385 c031a000 A __data_loc
17386 c031a000 D __data_start
17387 c031a000 D init_thread_union
17388 c031c000 D __nosave_begin
17389 c031c000 D __nosave_end
17390 c031c000 d acct_globals
17391 c031c000 D tasklist_lock
보는 것처럼 init_thread_union 은 8192 만큼의 공간을 확보하고 있고
초기 스택의 위치는 0xc031c000 - 8 = 0xC031BFF8 이 될것입니다.