2012.07.08 04:58
     

리눅스 커널 부팅과정(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를 참고하기 바랍니다. ^^

약간 보기 불편한 점은 있지만 그래도 한번 정리해놓으니 좋군요.
그럼 다들 좋은 밤 되세요 ;)

참고 자료

Mittm

Android App

Posted by kkamagui

댓글을 달아 주세요

  1. Favicon of http://lscpjyoon.tistory.com BlogIcon 철이 2012.07.08 15:01 신고  댓글주소  수정/삭제  댓글쓰기

    ㅠㅠ 존경 대단~ ㅋㅋ

  2. Favicon of http://deniallog.tistory.com BlogIcon Denial 2012.07.09 10:29 신고  댓글주소  수정/삭제  댓글쓰기

    와우~! 까마귀님 좋은 정보감사합니다. 리눅스 부팅부터 시작해서 MMU 초기화와 페이지테이블을 셋팅하는 부분의 분석도 부탁드려도 될까요? 요즘 커널 공부하려하는데 너무 골머리가 아프네요 ㅠ_ㅠ

    • Favicon of http://www.mint64os.pe.kr BlogIcon kkamagui 2012.07.11 08:11 신고  댓글주소  수정/삭제

      안녕하세요 ^^
      어떻하죠? ㅠㅠ 말씀하신 부분은 아직 제 관심사가 아니라서 언제가능할지 말씀드리기가 어렵네요 ㅠㅠ

      제 글의 참고자료와 리눅스 커널의 이해를 보시면 조금 도움이 되지 않을까 생각합니다 ^^

      그럼 좋은 하루 되세요 ^^

  3. Favicon of http://honeyperl.tistory.com BlogIcon h0ney 2012.07.13 15:58 신고  댓글주소  수정/삭제  댓글쓰기

    좋은자료 감사합니다.. 댓글 단 분들이 다 같이 공부하는 동생들이네요 ㅋㅋ

    • Favicon of http://kkamagui.tistory.com BlogIcon kkamagui 2012.07.14 01:52 신고  댓글주소  수정/삭제

      오우, 안녕하세요 >ㅁ<)-b

      3월 이후로 오래간만이네요 ^^

      h0ney님의 블로그도 잘 보고 있습니다. 우리 모두 화이팅이에요 >ㅁ<)/~~

  4. Favicon of http://osx86.org BlogIcon 2012.07.23 11:14 신고  댓글주소  수정/삭제  댓글쓰기

    옛날보다 더 길어졌구만 ㅋ
    요샌 또 리눅스 보나 보구만 나도 너처럼 좀 열심히 해야 될텐데, 오늘도 반성중

  5. Favicon of http://asda.com/ BlogIcon asda 2012.07.29 20:46 신고  댓글주소  수정/삭제  댓글쓰기

    빠른 답변을 기대하며 블로그 댓글에 한번 글을 써봅니다.
    근데 혹시 MintOS는 32비트 C kernerl->64비트 C kernerl로 가지 않습니까?
    근데 이걸 바로 32비트 C kernerl을 안 지나고 그냥 바로 64비트로 가는 방법이 없을까요?.. 제가 구현하기가 조금 어렵네요..ㅠㅠ
    중간에 보호모드도 적용하고해서 꼭 32비트 C kernerl 꼭 거쳐야 하는건가요?..

    • Favicon of http://kkamagui.tistory.com BlogIcon kkamagui 2012.07.30 03:08 신고  댓글주소  수정/삭제

      사실... 방법이 있긴 합니다. ^^;;;

      16비트 모드에서 64비트 모드로 바로 가는 방법인데요... 인텔 메뉴얼에는 나와있지 않은 방법이기 때문에...
      그냥 "이렇게도 할 수 있구나" 정도로 생각하시면 될 것 같습니다. ^^;;;;

      인텔 메뉴얼에서 가이드하는 방법은 16비트 모드 -> 32비트 모드 -> 64비트 모드이기 때문에 별로 권해드리고 싶지는 않네요.

      16비트 모드에서 64비트 모드로 바로 이동하는 예제는 제 블로그에 있는 Real Mode -> IA-32e Mode(Long Mode)로 바로 전환하는 예제( http://kkamagui.tistory.com/756 ) 글을 참고하시면 될 것 같습니다. ^^

      그럼 즐거운 OS 프로그래밍하세요 ;)

    • gamja9e 2012.08.02 11:14 신고  댓글주소  수정/삭제

      허접한 코드지만 아래 링크를 참조 하셔도 될 것 같습니다.
      https://github.com/gamja9e/ganaeos64/blob/master/booting/first.S

    • Favicon of http://kkamagui.tistory.com BlogIcon kkamagui 2012.10.21 10:59 신고  댓글주소  수정/삭제

      옷~ gamja9e님도 OS를 만들고 계시는 군요 ;)

      소스 코드가 아주 정리가 잘 되어있네요 >ㅁ<)-b

  6. Hyncheol 2012.10.17 21:42 신고  댓글주소  수정/삭제  댓글쓰기

    와우 리눅스 커널도 분석하는 구나.. 난 아직 start_kernel 덜봤는데.
    여전히 대단하구만.. release a baby하면.. 바쁠 법도 한데..ㅎㅎ

    • Favicon of http://kkamagui.tistory.com BlogIcon kkamagui 2012.10.21 10:49 신고  댓글주소  수정/삭제

      오우 횽~ >ㅁ<)-b

      release a baby하면 완전 정신 없어요 ㅎㅎ

      그래도 애기가 자는 틈틈이 책도 보고 와이프 눈치도 보고(?) 그리고 있어요 ㅎㅎ

      횽도 화이팅~!!