리눅스 커널 부팅과정(Linux Kernel Booting Sequence) 분석
윈도우에 이어 리눅스도 부팅과정을 분석하고 있는데요, 소스코드가 있으니 윈도우보다는 그나마 조금 낫네요. ^^;;;; 여기저기서 자료를 좀 모았는데, 나름 정리해봅니다.
1.GRUB
윈도우에는 BOOTMGR이 있듯이 리눅스에는 GRUB이 있습니다. 이 GRUB의 역할은 Disk의 MBR 영역에 있다가 PC 부팅할 때 리눅스 커널(Linux Kernel)을 메모리에 올려주는 것입니다. GRUB은 Stage 1과 Stage 2로 구성되어 있는데요, Stage 1은 우리가 이야기하는 MBR 영역에 있는 1 Sector를 말하고 Stage 2는 Disk에 접근해서 커널을 로딩하는 파트를 말합니다.
- Stage 1에서는 간단히 몇가지 BIOS 관련 Parameter를 설정하고 Stage 2로 넘어감
- Stage 2에서는 리눅스 커널 파일(vmlinuz)을 분석해서 리눅스 커널의 부트로더(Bootloader)를 0x90000 ~ 에 로딩하고, 압축된 커널과 커널의 압축을 푸는 부분을 0x100000(1MB) ~ 에 로딩
- Stage 2 후에는 커널 부트로더를 뛰어 넘어 0x90200 위치로 Jump(?)
사실 마지막 부분에 대한 내용은 제가 제대로 확인을 못해서 좀 애매하긴한데, 한빛미디어에서 나온 리눅스 커널의 이해를 보면 LILO 시절부터 0x90200 어드레스로 Jump를 하고 있습니다. 사실 GRUB 역시 크게 다르지 않을 것이라 생각합니다만... 좀 더 시간을 갖고 확인할 필요가 있을 것 같네요. ^^;;;;
GRUB에 대한 자세한 내용은 The GRUB MBR을 참고하세요 ;)
2. 리눅스 커널 부트로더
리눅스 커널 부트로더는 arch/x86/boot/header.S에 있는데요, 크기가 2 Sector입니다. ^^;;; 따라서 앞부분의 512Byte는 MBR 역할을 하고 그 뒤에 512Byte는 커널 부트로더와 커널을 이어주는 역할을 합니다.
그런데... 예전에는 바로 부팅할 수 있도록 했나 본데, 지금은 LILO나 GRUB의 등장으로 지원하지 않는 것 같습니다. 아래는 header.S에 있는 코드의 일부분인데요, 보시면 플로피를 통한 직접부팅을 지원 안하니까 다시 디스켓 빼라는 메시지와 함께 재부팅하는 코드만 달랑 있습니다. ^^;;;; 사실 이 부분의 코드가 GRUB에 의해 0x90000에 로딩되는 부분입니다.
18 #include <asm/segment.h>
19 #include <linux/utsrelease.h>
20 #include <asm/boot.h>
21 #include <asm/e820.h>
22 #include <asm/page_types.h>
23 #include <asm/setup.h>
24 #include "boot.h"
25 #include "offsets.h"
26
27 BOOTSEG = 0x07C0 /* original address of boot-sector */
28 SYSSEG = 0x1000 /* historical load address >> 4 */
29
30 #ifndef SVGA_MODE
31 #define SVGA_MODE ASK_VGA
32 #endif
33
34 #ifndef RAMDISK
35 #define RAMDISK 0
36 #endif
37
38 #ifndef ROOT_RDONLY
39 #define ROOT_RDONLY 1
40 #endif
41
42 .code16
43 .section ".bstext", "ax"
44
45 .global bootsect_start
46 bootsect_start:
47
48 # Normalize the start address
49 ljmp $BOOTSEG, $start2
50
51 start2:
52 movw %cs, %ax
53 movw %ax, %ds
54 movw %ax, %es
55 movw %ax, %ss
56 xorw %sp, %sp
57 sti
58 cld
59
60 movw $bugger_off_msg, %si
61
62 msg_loop:
63 lodsb
64 andb %al, %al
65 jz bs_die // <<= 무조건 Jump하면 메시지 출력후 bs_die로 이동~!!
66 movb $0xe, %ah
67 movw $7, %bx
68 int $0x10
69 jmp msg_loop
70
71 bs_die:
72 # Allow the user to press a key, then reboot
73 xorw %ax, %ax
74 int $0x16
75 int $0x19
76
77 # int 0x19 should never return. In case it does anyway,
78 # invoke the BIOS reset code...
79 ljmp $0xf000,$0xfff0
80
81 .section ".bsdata", "a"
82 bugger_off_msg:
83 .ascii "Direct booting from floppy is no longer supported.\r\n"
84 .ascii "Please use a boot loader program instead.\r\n"
85 .ascii "\n"
86 .ascii "Remove disk and press any key to reboot . . .\r\n"
87 .byte 0
88
89
90 # Kernel attributes; used by setup. This is part 1 of the
91 # header, from the old boot sector.
92
93 .section ".header", "a"
94 .globl hdr
이 바로 뒤는 당연히 0x90200에 위치하는데요, _start로 시작하며 커널의 bss 영역을 초기화하고 main 함수로 Jump하는 부분이 있습니다. 해당 부분의 코드는 arch/x86/boot/header.S를 참고하세요 ;)
104 # offset 512, entry point
105
106 .globl _start
107 _start:
108 # Explicitly enter this as bytes, or the assembler
109 # tries to generate a 3-byte jump here, which causes
110 # everything else to push off to the wrong offset.
111 .byte 0xeb # short (2-byte) jump
112 .byte start_of_setup-1f
113 1:
114
115 # Part 2 of the header, from the old setup.S
... 생략 ...
224 .section ".inittext", "ax"
225 start_of_setup:
226 #ifdef SAFE_RESET_DISK_CONTROLLER
227 # Reset the disk controller.
228 movw $0x0000, %ax # Reset disk controller
229 movb $0x80, %dl # All disks
230 int $0x13
231 #endif
232
... 생략 ...
271
272 # Check signature at end of setup
273 cmpl $0x5a5aaa55, setup_sig
274 jne setup_bad
275
276 # Zero the bss
277 movw $__bss_start, %di
278 movw $_end+3, %cx
279 xorl %eax, %eax
280 subw %di, %cx
281 shrw $2, %cx
282 rep; stosl
283
284 # Jump to C code (should not return)
285 calll main // <== 여기가 다음 스텝인 리눅스 커널 로더로 jump하는 부분
3. 리눅스 커널 로더
리눅스 커널 로더는 arch/x86/boot/main.c에 있습니다. 제가 이 코드에 리눅스 "리눅스 커널로더"라는 명칭을 사용해도 될지 모르겠지만, 일단 이 시점에서 32bit 모드로 변환하는 코드가 들어 있어서 커널로더라고 했습니다. ^^;;; 이 코드의 역할은 32bit 커널이 실행될 준비를 하는 건데요, BIOS를 통해 메모리 크기를 확인하고 키보드를 초기화한다음 32bit 커널로 Jump합니다. 이때 Jump하는 어드레스는 32bit 커널이 로딩되어 있는 0x100000(1MB)입니다. ^^;;; 해당 위치에 GRUB이 이미 올려놓았거든요 ;)
아래는 arch/x86/boot/main.c 파일의 일부분입니다. 좀더 자세한 코드는 arch/x86/boot/main.c를 통해서 확인할 수 있습니다. ^^;;;
125 void main(void)
126 {
127 /* First, copy the boot header into the "zeropage" */
128 copy_boot_params();
129
130 /* End of heap check */
131 init_heap();
132
133 /* Make sure we have all the proper CPU support */
134 if (validate_cpu()) {
135 puts("Unable to boot - please use a kernel appropriate "
136 "for your CPU.\n");
137 die();
138 }
139
140 /* Tell the BIOS what CPU mode we intend to run in. */
141 set_bios_mode();
142
143 /* Detect memory layout */
144 detect_memory();
145
146 /* Set keyboard repeat rate (why?) */
147 keyboard_set_repeat();
148
149 /* Query MCA information */
150 query_mca();
151
152 /* Query Intel SpeedStep (IST) information */
153 query_ist();
154
155 /* Query APM information */
156 #if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
157 query_apm_bios();
158 #endif
159
160 /* Query EDD information */
161 #if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
162 query_edd();
163 #endif
164
165 /* Set the video mode */
166 set_video();
167
168 /* Parse command line for 'quiet' and pass it to decompressor. */
169 if (cmdline_find_option_bool("quiet"))
170 boot_params.hdr.loadflags |= QUIET_FLAG;
171
172 /* Do the last things and invoke protected mode */
173 go_to_protected_mode(); // <== 여기가 32bit 커널로 Jump하는 부분이에요 :)
174 }
4. 리눅스 커널 - 압축된 커널
커널 로더에 의해서 32bit 커널로 jmp(0x100000)하면 이제 본격적으로 커널이 시작되는데요, 이때 수행되는 함수는 arch/x86/boot/compressed/head_32.S에 있는 가장 첫 번째 함수, startup_32입니다. 파일의 가장 앞부분에 있는 함수기 때문에 0x100000 어드레스로 Jump하는 것만으로 바로 저 함수에 도달하는 것이지요. ^^;;;
startup_32() 함수는 여러가지 일을 하는데요, 가장 중요한 역할은 자신에게 붙어있는 압축된 커널을 안전한 버퍼로 옮긴다음 압축을 푸는 겁니다. >ㅁ<)-b 그리고 압축을 풀고나면 이를 다시 0x400000 어드레스로 옮기고 jump하는 것이지요. 안전한 버퍼라는 말이 조금 애매하긴한데 대략 0x400000 어드레스 부근인 것 같습니다(정확하게는 _end 오프셋 뒷쪽인데요, 이 값은 빌드 타임에 정해지는 값이라 compressed 디렉터리의 코드량에 따라서 좀 달라질 수도 있을 것 같습니다. ^^;;; (정확하지는 않아요 ㅠㅠ)
다음은 압축된 커널을 안전한 곳으로 복사하고 압축을 푼 뒤에 압축이 해제된 곳으로 jump하는 코드입니다. 전체 내용은 arch/x86/boot/compressed/head_32.S를 참조하세요.
26 #include <linux/linkage.h>
27 #include <asm/segment.h>
28 #include <asm/page_types.h>
29 #include <asm/boot.h>
30 #include <asm/asm-offsets.h>
31
32 .section ".text.head","ax",@progbits
33 ENTRY(startup_32)
34 cld
35 /* test KEEP_SEGMENTS flag to see if the bootloader is asking
36 * us to not reload segments */
37 testb $(1<<6), BP_loadflags(%esi)
38 jnz 1f
39
40 cli
41 movl $(__BOOT_DS),%eax
42 movl %eax,%ds
43 movl %eax,%es
44 movl %eax,%fs
45 movl %eax,%gs
46 movl %eax,%ss
47 1:
... 생략 ...
87 /* Copy the compressed kernel to the end of our buffer
88 * where decompression in place becomes safe.
89 */
90 pushl %esi
91 leal _end(%ebp), %esi
92 leal _end(%ebx), %edi
93 movl $(_end - startup_32), %ecx
94 std
95 rep
96 movsb
97 cld
98 popl %esi
99
100 /* Compute the kernel start address.
101 */
102 #ifdef CONFIG_RELOCATABLE
103 addl $(CONFIG_PHYSICAL_ALIGN - 1), %ebp
104 andl $(~(CONFIG_PHYSICAL_ALIGN - 1)), %ebp
105 #else
106 movl $LOAD_PHYSICAL_ADDR, %ebp
107 #endif
108
109 /*
110 * Jump to the relocated address.
111 */
112 leal relocated(%ebx), %eax
113 jmp *%eax
114 ENDPROC(startup_32)
115
116 .section ".text"
117 relocated:
118
119 /*
120 * Clear BSS
121 */
122 xorl %eax,%eax
123 leal _edata(%ebx),%edi
124 leal _end(%ebx), %ecx
125 subl %edi,%ecx
126 cld
127 rep
128 stosb
129
130 /*
131 * Setup the stack for the decompressor
132 */
133 leal boot_stack_end(%ebx), %esp
134
135 /*
136 * Do the decompression, and jump to the new kernel..
137 */
138 movl output_len(%ebx), %eax
139 pushl %eax
140 # push arguments for decompress_kernel:
141 pushl %ebp # output address
142 movl input_len(%ebx), %eax
143 pushl %eax # input_len
144 leal input_data(%ebx), %eax
145 pushl %eax # input_data
146 leal boot_heap(%ebx), %eax
147 pushl %eax # heap area
148 pushl %esi # real mode pointer
149 call decompress_kernel <<= 요기가 실제로 압축 해제를 담당하는 부분
150 addl $20, %esp
151 popl %ecx
152
153 #if CONFIG_RELOCATABLE
154 /* Find the address of the relocations.
155 */
156 movl %ebp, %edi
157 addl %ecx, %edi
158
159 /* Calculate the delta between where vmlinux was compiled to run
160 * and where it was actually loaded.
161 */
162 movl %ebp, %ebx
163 subl $LOAD_PHYSICAL_ADDR, %ebx
164 jz 2f /* Nothing to be done if loaded at compiled addr. */
165 /*
166 * Process relocations.
167 */
168
169 1: subl $4, %edi
170 movl 0(%edi), %ecx
171 testl %ecx, %ecx
172 jz 2f
173 addl %ebx, -__PAGE_OFFSET(%ebx, %ecx)
174 jmp 1b
175 2:
176 #endif
177
178 /*
179 * Jump to the decompressed kernel.
180 */
181 xorl %ebx,%ebx
182 jmp *%ebp // <<= 압축을 푼 어드레스로 jump하는 부분
실제로 압축 해제는 decompress_kernel() 함수를 호출해서 처리하는데, 이 함수는 arch/x86/boot/compressed/misc.c 파일에 있습니다. decompress_kernel() 함수의 원형은 아래와 같습니다.
304 asmlinkage void decompress_kernel(void *rmode, memptr heap,
305 unsigned char *input_data,
306 unsigned long input_len,
307 unsigned char *output)
위의 함수 원형을 보면 결국 우리가 넘겨주는 output buffer에 차곡차곡 압축을 풀어줌을 알 수 있는데요, 저 output 버퍼의 위치는 decompress_kernel()을 호출하는 곳에서 LOAD_PHYSICAL_ADDR로 대략 정의하고 있는데요, 정확하게 말하기는 힘들지만 대략 0x400000(4MB) 어드레스 부근이라고 생각됩니다.
리눅스 커널의 이해를 보면 압축을 해제한 뒤에 다시 1MB 영역의 어드레스로 덮어쓴다는 이야기가 있던데, 실제 코드에서는 바로 jump하는 부분만 확인했을 뿐 데이터를 이동하는 부분은 찾지 못했습니다. ㅠㅠ 그렇다는 이야기는 이제는 더이상 안옮기거나 제가 못찾았다는 이야기겠지요. 혹시 찾으신 분 있으시면 덧글로 제보 부탁드릴께요 ㅠㅠ
5. 리눅스 커널 - 실제 커널
이제 압축까지 다 해제된 실제 커널로 왔습니다. 커널의 실제 엔트리 포인트는 startup_32() 함수이구요, arch/x86/kernel/head_32.S에 있습니다. 이 함수를 보면 페이지 테이블을 임시로 설정하고 start_kernel() 함수를 호출하는 것을 볼 수 있습니다. start_kernel()은 32bit 커널에서 C 코드가 시작되는 부분이지요. 여기까지만 오면 사실 부팅은 거의 끝난 겁니다. ^^;;;;
다음은 startup_32() 함수의 코드입니다. 전체 코드는 arch/x86/kernel/head_32.S 파일을 참조하세요.
269 #ifdef CONFIG_SMP
270 ENTRY(startup_32_smp)
271 cld
272 movl $(__BOOT_DS),%eax
273 movl %eax,%ds
274 movl %eax,%es
275 movl %eax,%fs
276 movl %eax,%gs
277 #endif /* CONFIG_SMP */
... 생략 ...
451 movl $(__KERNEL_STACK_CANARY),%eax
452 movl %eax,%gs
453
454 xorl %eax,%eax # Clear LDT
455 lldt %ax
456
457 cld # gcc2 wants the direction flag cleared at all times
458 pushl $0 # fake return address for unwinder
459 #ifdef CONFIG_SMP
460 movb ready, %cl
461 movb $1, ready
462 cmpb $0,%cl # the first CPU calls start_kernel
463 je 1f
464 movl (stack_start), %esp
465 1:
466 #endif /* CONFIG_SMP */
467 jmp *(initial_code) // <<= start_kernel() 함수의 어드레스가 들어 있음
468
6. 리눅스 커널 - C 함수 시작
start_kernel() 함수는 init/main.c 파일에 있으며, 각종 커널 관련 자료구조(페이지 테이블 포함)를 다시 한번 초기화하고 init 프로세스를 생성하는 역할을 합니다. start_kernel() 함수의 가장 마지막은 rest_init() 함수인데요, 이 함수는 kernel_thread() 함수를 호출해서 별도의 스레드를 만든 다음 kernel_init 함수를 수행하게 만듭니다. 그리고 자기 자신은 계속 코드를 수행하면서 cpu_idle() 함수를 수행하여 idle process가 됩니다.
다음은 start_kernel() 함수의 일부분과 rest_init() 함수의 일부분을 나타낸 것입니다.
535
536 asmlinkage void __init start_kernel(void)
537 {
538 char * command_line;
539 extern struct kernel_param __start___param[], __stop___param[];
540
541 smp_setup_processor_id();
542
543 /*
544 * Need to run as early as possible, to initialize the
545 * lockdep hash:
546 */
547 lockdep_init();
548 debug_objects_early_init();
549
550 /*
551 * Set up the the initial canary ASAP:
552 */
553 boot_init_stack_canary();
554
555 cgroup_init_early();
556
557 local_irq_disable();
558 early_boot_irqs_off();
559 early_init_irq_lock_class();
560
561 /*
562 * Interrupts are still disabled. Do necessary setups, then
563 * enable them
564 */
565 lock_kernel();
566 tick_init();
567 boot_cpu_init();
568 page_address_init();
569 printk(KERN_NOTICE "%s", linux_banner);
570 setup_arch(&command_line);
571 mm_init_owner(&init_mm, &init_task);
572 setup_command_line(command_line);
573 setup_per_cpu_areas();
574 setup_nr_cpu_ids();
575 smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
576
577 /*
578 * Set up the scheduler prior starting any interrupts (such as the
579 * timer interrupt). Full topology setup happens at smp_init()
580 * time - but meanwhile we still have a functioning scheduler.
581 */
582 sched_init();
583 /*
584 * Disable preemption - early bootup scheduling is extremely
585 * fragile until we cpu_idle() for the first time.
586 */
587 preempt_disable();
588 build_all_zonelists();
589 page_alloc_init();
590 printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
591 parse_early_param();
592 parse_args("Booting kernel", static_command_line, __start___param,
593 __stop___param - __start___param,
594 &unknown_bootoption);
595 if (!irqs_disabled()) {
596 printk(KERN_WARNING "start_kernel(): bug: interrupts were "
597 "enabled *very* early, fixing it\n");
598 local_irq_disable();
599 }
600 sort_main_extable();
601 trap_init();
602 rcu_init();
603 /* init some links before init_ISA_irqs() */
604 early_irq_init();
605 init_IRQ();
606 pidhash_init();
607 init_timers();
608 hrtimers_init();
609 softirq_init();
610 timekeeping_init();
611 time_init();
612 sched_clock_init();
613 profile_init();
614 if (!irqs_disabled())
615 printk(KERN_CRIT "start_kernel(): bug: interrupts were "
616 "enabled early\n");
617 early_boot_irqs_on();
618 local_irq_enable();
619
620 /*
621 * HACK ALERT! This is early. We're enabling the console before
622 * we've done PCI setups etc, and console_init() must be aware of
623 * this. But we do want output early, in case something goes wrong.
624 */
625 console_init();
626 if (panic_later)
627 panic(panic_later, panic_param);
628
629 lockdep_info();
630
631 /*
632 * Need to run this when irqs are enabled, because it wants
633 * to self-test [hard/soft]-irqs on/off lock inversion bugs
634 * too:
635 */
636 locking_selftest();
637
638 #ifdef CONFIG_BLK_DEV_INITRD
639 if (initrd_start && !initrd_below_start_ok &&
640 page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
641 printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
642 "disabling it.\n",
643 page_to_pfn(virt_to_page((void *)initrd_start)),
644 min_low_pfn);
645 initrd_start = 0;
646 }
647 #endif
648 vmalloc_init();
649 vfs_caches_init_early();
650 cpuset_init_early();
651 page_cgroup_init();
652 mem_init();
653 enable_debug_pagealloc();
654 cpu_hotplug_init();
655 kmem_cache_init();
656 kmemtrace_init();
657 debug_objects_mem_init();
658 idr_init_cache();
659 setup_per_cpu_pageset();
660 numa_policy_init();
661 if (late_time_init)
662 late_time_init();
663 calibrate_delay();
664 pidmap_init();
665 pgtable_cache_init();
666 prio_tree_init();
667 anon_vma_init();
668 #ifdef CONFIG_X86
669 if (efi_enabled)
670 efi_enter_virtual_mode();
671 #endif
672 thread_info_cache_init();
673 cred_init();
674 fork_init(num_physpages);
675 proc_caches_init();
676 buffer_init();
677 key_init();
678 security_init();
679 vfs_caches_init(num_physpages);
680 radix_tree_init();
681 signals_init();
682 /* rootfs populating might need page-writeback */
683 page_writeback_init();
684 #ifdef CONFIG_PROC_FS
685 proc_root_init();
686 #endif
687 cgroup_init();
688 cpuset_init();
689 taskstats_init_early();
690 delayacct_init();
691
692 check_bugs();
693
694 acpi_early_init(); /* before LAPIC and SMP init */
695
696 ftrace_init();
697
698 /* Do the rest non-__init'ed, we're now alive */
699 rest_init(); // <<= 요기가 실제 init 프로세스 생성으로 가는 부분
700 }
아래는 rest_init() 함수의 내용입니다.
451 static noinline void __init_refok rest_init(void)
452 __releases(kernel_lock)
453 {
454 int pid;
455
// init 프로세스를 생성하는 kernel_init 스레드를 만드는 부분
456 kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
457 numa_default_policy();
458 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
459 kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
460 unlock_kernel();
461
462 /*
463 * The boot idle thread must execute schedule()
464 * at least once to get things moving:
465 */
466 init_idle_bootup_task(current);
467 rcu_scheduler_starting();
468 preempt_enable_no_resched();
469 schedule();
470 preempt_disable();
471
472 /* Call into cpu_idle with preempt disabled */
473 cpu_idle(); // <<= Idle Process가 되는 부분
474 }
kernel_thread()는 아래 코드에서 볼 수 있듯이 마지막에 init_post() 함수를 호출하는데 이 함수가 최종적으로 init process를 생성합니다. 다음은 kernel_thread() 함수와 init_post() 함수의 코드입니다. 리눅스가 실행될 때 일반적으로 initrd와 같은 RAMDISK 이미지를 사용하기 때문에, init_post() 함수 안의 ramdisk_execute_command 변수에는 "/init" 이 들어가 있으며, 따라서 initrd의 루트 디렉터리의 init 프로세스를 실행합니다.
initrd에 있는 init은 사실 스크립트로 기본적인 드라이버를 로딩하고 루트 디스크를 마운트한 뒤, 실제 init process로 제어를 넘깁니다. 그러면 본격적으로 커널이 실행되는 것이죠. ^^;;;;
847 static int __init kernel_init(void * unused)
848 {
849 lock_kernel();
850 /*
851 * init can run on any cpu.
852 */
853 set_cpus_allowed_ptr(current, cpu_all_mask);
854 /*
855 * Tell the world that we're going to be the grim
856 * reaper of innocent orphaned children.
857 *
858 * We don't want people to have to make incorrect
859 * assumptions about where in the task array this
860 * can be found.
861 */
862 init_pid_ns.child_reaper = current;
863
864 cad_pid = task_pid(current);
865
866 smp_prepare_cpus(setup_max_cpus);
867
868 do_pre_smp_initcalls();
869 start_boot_trace();
870
871 smp_init();
872 sched_init_smp();
873
874 do_basic_setup();
875
876 /*
877 * check if there is an early userspace init. If yes, let it do all
878 * the work
879 */
880
881 if (!ramdisk_execute_command)
882 ramdisk_execute_command = "/init";
883
884 if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
885 ramdisk_execute_command = NULL;
886 prepare_namespace();
887 }
888
889 /*
890 * Ok, we have completed the initial bootup, and
891 * we're essentially up and running. Get rid of the
892 * initmem segments and start the user-mode stuff..
893 */
894
895 init_post(); // <<= init process 생성으로 가는 부분
896 return 0;
897 }
803 static noinline int init_post(void)
804 __releases(kernel_lock)
805 {
806 /* need to finish all async __init code before freeing the memory */
807 async_synchronize_full();
808 free_initmem();
809 unlock_kernel();
810 mark_rodata_ro();
811 system_state = SYSTEM_RUNNING;
812 numa_default_policy();
813
814 if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
815 printk(KERN_WARNING "Warning: unable to open an initial console.\n");
816
817 (void) sys_dup(0);
818 (void) sys_dup(0);
819
820 current->signal->flags |= SIGNAL_UNKILLABLE;
821
822 if (ramdisk_execute_command) { // <<= initrd와 같이 RAMDISK 이미지를 사용하는 경우 이 코드로 진입하고, 이때 ramdisk_execute_command는 /init이 들어 있음
823 run_init_process(ramdisk_execute_command);
824 printk(KERN_WARNING "Failed to execute %s\n",
825 ramdisk_execute_command);
826 }
827
828 /*
829 * We try each of these until one succeeds.
830 *
831 * The Bourne shell can be used instead of init if we are
832 * trying to recover a really broken machine.
833 */
834 if (execute_command) {
835 run_init_process(execute_command);
836 printk(KERN_WARNING "Failed to execute %s. Attempting "
837 "defaults...\n", execute_command);
838 }
839 run_init_process("/sbin/init");
840 run_init_process("/etc/init");
841 run_init_process("/bin/init");
842 run_init_process("/bin/sh");
843
844 panic("No init found. Try passing init= option to kernel.");
845 }
start_kernel() 함수 이하에 있는 함수들의 전체 코드는 init/main.c를 참고하기 바랍니다. ^^
약간 보기 불편한 점은 있지만 그래도 한번 정리해놓으니 좋군요.
그럼 다들 좋은 밤 되세요 ;)
참고 자료
리눅스 부팅 과정에 대해서 자세히 설명되어 있는 PPT 자료 : http://mie.pcu.ac.kr/Lectures%20Data/LinuxEmbeddedSystem/10/Ch03_%EB%A6%AC%EB%88%85%EC%8A%A4%20%EB%B6%80%ED%8C%85%20%EA%B3%BC%EC%A0%95.pdf
부팅 과정에 대한 내용이 정리되어 있는 웹페이지 : http://webcache.googleusercontent.com/search?q=cache:bjlV3hSwlUsJ:www.ibm.com/developerworks/library/l-linuxboot/+&cd=3&hl=ko&ct=clnk&gl=kr
'OS Kernel' 카테고리의 다른 글
64비트 멀티코어 OS 구조와 원리가 3쇄까지 왔습니다. @0@)/~ (37) | 2017.09.01 |
---|---|
가상화 기술 기반 보안 기술을 만들면서 겪었던 황당한 문제... (0) | 2016.07.13 |
"아키텍트를 꿈꾸는 사람들" 스터디에서 발표할 자료~!! (14) | 2012.01.07 |
OS 개발 시 참고하면 유용한 책과 사이트 리스트 (0) | 2011.11.20 |
[ARM] ARM GCC가 만들어낸 어셈 코드 훓어보다보니... (0) | 2007.11.05 |