如图所示,内核的初始化过程由start_kernel函数开始,至第一个用户进程init结束,调用了一系列的初始化函数对所有的内核组件进行初始化。其中,start_kernel、rest_init、kernel_init、init_post等4个函数构成了整个初始化过程的主线。
图 内核初始化
本节接下来的内容会结合内核代码,对内核初始化过程主线上的几个函数进行分析,使读者对该过程有个整体上的认识,以此为基础,读者可以根据自己的兴趣或需要,选择与某些组件相关的初始化函数,进行更进一步的研究分析。
u start_kernel函数
从start_kernel函数开始,内核即进入了C语言部分,它完成了内核的大部分初始化工作。实际上,可以将start_kernel函数看做内核的main函数。
代码清单1 start_kernel函数
513 asmlinkage void __init start_kernel(void)
514 {
515 char * command_line;
516 extern struct kernel_param __start___param[], __stop___param[];
517
/*
* 当只有一个CPU的时候这个函数就什么都不做,
* 但是如果有多个CPU的时候那么它就
* 返回在启动的时候的那个CPU的号
*/
518 smp_setup_processor_id();
519
520 /*
521 * Need to run as early as possible, to initialize the
522 * lockdep hash:
523 */
524 unwind_init();
525 lockdep_init();
526
/* 关闭当前CPU的中断 */
527 local_irq_disable();
528 early_boot_irqs_off();
/*
* 每一个中断都有一个中断描述符(struct irq_desc)来进行描述,这个函数的
* 作用就是设置所有中断描述符的锁
*/
529 early_init_irq_lock_class();
530
531 /*
532 * Interrupts are still disabled. Do necessary setups, then
533 * enable them
534 */
/* 获取大内核锁,锁定整个内核。 */
535 lock_kernel();
/* 如果定义了CONFIG_GENERIC_CLOCKEVENTS,则注册clockevents框架 */
536 tick_init();
537 boot_cpu_init();
/* 初始化页地址,使用链表将其链接起来 */
538 page_address_init();
539 printk(KERN_NOTICE);
/* 显示内核的版本信息 */
540 printk(linux_banner);
/*
* 每种体系结构都有自己的setup_arch()函数,是体系结构相关的,具体编译哪个
* 体系结构的setup_arch()函数,由源码树顶层目录下的Makefile中的ARCH变量
* 决定
*/
541 setup_arch(&command_line);
542 setup_command_line(command_line);
543 unwind_setup();
/* 每个CPU分配pre-cpu结构内存, 并复制.data.percpu段的数据 */
544 setup_per_cpu_areas();
545 smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
546
547 /*
548 * Set up the scheduler prior starting any interrupts (such as the
549 * timer interrupt). Full topology setup happens at smp_init()
550 * time - but meanwhile we still have a functioning scheduler.
551 */
/* 进程调度器初始化 */
552 sched_init();
553 /*
554 * Disable preemption - early bootup scheduling is extremely
555 * fragile until we cpu_idle() for the first time.
556 */
/* 禁止内核抢占 */
557 preempt_disable();
558 build_all_zonelists();
559 page_alloc_init();
/* 打印Linux启动命令行参数 */
560 printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
/* 对内核选项的两次解析 */
561 parse_early_param();
562 parse_args("Booting kernel", static_command_line, __start___param,
563 __stop___param - __start___param,
564 &unknown_bootoption);
/* 检查中断是否已经打开,如果已经打开,则关闭中断 */
565 if (!irqs_disabled()) {
566 printk(KERN_WARNING "start_kernel(): bug: interrupts were "
567 "enabled *very* early, fixing it\n");
568 local_irq_disable();
569 }
570 sort_main_extable();
/*
* trap_init函数完成对系统保留中断向量(异常、非屏蔽中断以及系统调用)
* 的初始化,init_IRQ函数则完成其余中断向量的初始化
*/
571 trap_init();
/* 初始化RCU(Read-Copy Update)机制 */
572 rcu_init();
573 init_IRQ();
/* 初始化hash表,便于从进程的PID获得对应的进程描述符指针 */
574 pidhash_init();
/* 初始化定时器相关的数据结构 */
575 init_timers();
/* 对高精度时钟进行初始化 */
576 hrtimers_init();
/* 初始化tasklet_softirq和hi_softirq */
577 softirq_init();
578 timekeeping_init();
/* 初始化系统时钟源 */
579 time_init();
/* 对内核的profile(一个内核性能调式工具)功能进行初始化 */
580 profile_init();
581 if (!irqs_disabled())
582 printk("start_kernel(): bug: interrupts were enabled early\n");
583 early_boot_irqs_on();
584 local_irq_enable();
585
586 /*
587 * HACK ALERT! This is early. We're enabling the console before
588 * we've done PCI setups etc, and console_init() must be aware of
589 * this. But we do want output early, in case something goes wrong.
590 */
/*
* 初始化控制台以显示printk的内容,在此之前调用的printk
* 只是把数据存到缓冲区里
*/
591 console_init();
592 if (panic_later)
593 panic(panic_later, panic_param);
594
/* 如果定义了CONFIG_LOCKDEP宏,则打印锁依赖信息,否则什么也不做 */
595 lockdep_info();
596
597 /*
598 * Need to run this when irqs are enabled, because it wants
599 * to self-test [hard/soft]-irqs on/off lock inversion bugs
600 * too:
601 */
602 locking_selftest();
603
604 #ifdef CONFIG_
605 if (initrd_start && !initrd_below_start_ok &&
606 initrd_start < min_low_pfn << PAGE_SHIFT) {
607 printk(KERN_CRIT "initrd overwritten
(0x%08lx < 0x%08lx) - "
608 "disabling it.\n",initrd_start,
min_low_pfn << PAGE_SHIFT);
609 initrd_start = 0;
610 }
611 #endif
/* 虚拟文件系统的初始化 */
612 vfs_caches_init_early();
613 cpuset_init_early();
614 mem_init();
/* slab初始化 */
615 kmem_cache_init();
616 setup_per_cpu_pageset();
617 numa_policy_init();
618 if (late_time_init)
619 late_time_init();
/*
* 一个非常有趣的CPU性能测试函数,可以计算出CPU在1s内执行了多少次一个
* 极短的循环,计算出来的值经过处理后得到BogoMIPS值(Bogo是Bogus的意思),
*/
620 calibrate_delay();
621 pidmap_init();
/* 接下来的函数中,大多数都是为有关的管理机制建立专用的slab缓存 */
622 pgtable_cache_init();
/* 初始化优先级树index_bits_to_maxindex数组 */
623 prio_tree_init();
624 anon_vma_init();
625 #ifdef CONFIG_X86
626 if (efi_enabled)
627 efi_enter_virtual_mode();
628 #endif
/* 根据物理内存大小计算允许创建进程的数量 */
629 fork_init(num_physpages);
/*
* proc_caches_init(),buffer_init(),
unnamed_dev_init(), key_init()
*
*/
630 proc_caches_init();
631 buffer_init();
632 unnamed_dev_init();
633 key_init();
634 security_init();
635 vfs_caches_init(num_physpages);
636 radix_tree_init();
637 signals_init();
638 /* rootfs populating might need page-writeback */
639 page_writeback_init();
640 #ifdef CONFIG_PROC_FS
641 proc_root_init();
642 #endif
643 cpuset_init();
644 taskstats_init_early();
645 delayacct_init();
646
/*
* 测试该CPU的各种缺陷,记录检测到的缺陷,以便于内核的其他部分以后可以
* 使用它们的工作。
*/
647 check_bugs();
648
649 acpi_early_init(); /* before LAPIC and
650
651 /* Do the rest non-__init'ed, we're now alive */
/* 创建init进程 */
652 rest_init();
653 }
2 reset_init函数
在start_kernel函数的最后调用了reset_init函数进行后续的初始化。
代码清单2 reset_init函数
438 static void noinline __init_refok rest_init(void)
439 __releases(kernel_lock)
440 {
441 int pid;
442
/* reset_init()函数最主要的历史使命就是启动内核线程kernel_init */
443 kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
444 numa_default_policy();
/* 启动内核线程kthreadd,运行kthread_create_list全局链表中的kthread */
445 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
446 kthreadd_task = find_task_by_pid(pid);
447 unlock_kernel();
448
449 /*
450 * The boot idle thread must execute schedule()
451 * at least once to get things moving:
452 */
/*
* 增加idle进程的need_resched标志, 并且调用schedule释放CPU,
* 将其赋给更应该获取CPU的进程。
*/
453 init_idle_bootup_task(current);
454 preempt_enable_no_resched();
455 schedule();
456 preempt_disable();
457
458 /* Call into cpu_idle with preempt disabled */
/*
* 进入idle循环以消耗空闲的CPU时间片, 该函数从不返回。然而,当有实际工作
* 要处理时,该函数就会被抢占。
*/
459 cpu_idle();
460 }
3 kernel_init函数
kernel_init函数将完成设备驱动程序的初始化,并调用init_post函数启动用户空间的init进程。
代码清单3 kernel_init函数
813 static int __init kernel_init(void * unused)
814 {
815 lock_kernel();
816 /*
817 * init can run on any cpu.
818 */
/* 修改进程的CPU亲和力 */
819 set_cpus_allowed(current, CPU_MASK_
820 /*
821 * Tell the world that we're going to be the grim
822 * reaper of innocent orphaned children.
823 *
824 * We don't want people to have to make incorrect
825 * assumptions about where in the task array this
826 * can be found.
827 */
/* 把当前进程设为接受其他孤儿进程的进程 */
828 init_pid_ns.child_reaper = current;
829
830 __set_special_pids(1, 1);
831 cad_pid = task_pid(current);
832
833 smp_prepare_cpus(max_cpus);
834
835 do_pre_smp_initcalls();
836
/* 激活
837 smp_init();
838 sched_init_smp();
839
840 cpuset_init_smp();
841
/*
* 此时与体系结构相关的部分已经初始化完成,现在开始调用do_basic_setup函数
* 初始化设备,完成外设及其驱动程序(直接编译进内核的模块)的加载和初始化
*/
842 do_basic_setup();
843
844 /*
845 * check if there is an early userspace init. If yes, let it do all
846 * the work
847 */
848
849 if (!ramdisk_execute_command)
850 ramdisk_execute_command = "/init";
851
852 if (sys_access((const char __user *)ramdisk_execute_command, 0) != 0) {
853 ramdisk_execute_command = NULL;
854 prepare_namespace();
855 }
856
857 /*
858 * Ok, we have completed the initial bootup, and
859 * we're essentially up and running. Get rid of the
860 * initmem segments and start the user-mode stuff.
861 */
862 init_post();
863 return 0;
864 }
4 init_post函数
到init_post函数为止,内核的初始化已经进入尾声,第一个用户空间进程init将姗姗来迟。
代码清单4 init_post函数
774 static int noinline init_post(void)
775 {
776 free_initmem();
777 unlock_kernel();
778 mark_rodata_ro();
779 system_state = SYSTEM_RUNNING;
780 numa_default_policy();
781
782 if (sys_open((const char __user *) "/dev/console", O_
783 printk(KERN_WARNING "Warning: unable to open an initial console.\n");
784
785 (void) sys_dup(0);
786 (void) sys_dup(0);
787
788 if (ramdisk_execute_command) {
789 run_init_process(ramdisk_execute_command);
790 printk(KERN_WARNING "Failed to execute %s\n",
791 ramdisk_execute_command);
792 }
793
794 /*
795 * We try each of these until one succeeds.
796 *
797 * The Bourne shell can be used instead of init if we are
798 * trying to recover a really broken machine.
799 */
800 if (execute_command) {
801 run_init_process(execute_command);
802 printk(KERN_WARNING "Failed to execute %s. Attempting "
803 "defaults...\n", execute_command);
804 }
805 run_init_process("/sbin/init");
806 run_init_process("/etc/init");
807 run_init_process("/bin/init");
808 run_init_process("/bin/sh");
809
810 panic("No init found. Try passing init= option to kernel.");
811 }
第776行,到此,内核初始化已经接近尾声了,所有的初始化函数都已经被调用,因此free_initmem函数可以舍弃内存的__init_begin至__init_end(包括.init.setup、.initcall.init等节)之间的数据。
所有使用__init标记过的函数和使用__initdata标记过的数据,在free_initmem函数执行后,都不能使用,它们曾经获得的内存现在可以重新用于其他目的。
第782行,如果可能,打开控制台设备,这样init进程就拥有一个控制台,并可以从中读取输入信息,也可以向其中写入信息。
实际上init进程除了打印错误信息以外,并不使用控制台,但是如果调用的是shell或者其他需要交互的进程,而不是init,那么就需要一个可以交互的输入源。如果成功执行open,/dev/console即成为init的标准输入源(文件描述符0)。
第785~786行,调用dup打开/dev/console文件描述符两次。这样,该控制台设备就也可以供标准输出和标准错误使用(文件描述符1和2)。假设第782行的open成功执行(正常情况),init进程现在就拥有3个文件描述符--标准输入、标准输出以及标准错误。
第788~804行,如果内核命令行中给出了到init进程的直接路径(或者别的可替代的程序),这里就试图执行init。
因为当kernel_execve函数成功执行目标程序时并不返回,只有失败时,才能执行相关的表达式。接下来的几行会在几个地方查找init,按照可能性由高到低的顺序依次是: /sbin/init,这是init标准的位置;/etc/init和/bin/init,两个可能的位置。
第805~807行,这些是init可能出现的所有地方。如果在这3个地方都没有发现init,也就无法找到它的同名者了,系统可能就此崩溃。因此,第808行会试图建立一个交互的shell(/bin/sh)来代替,希望root用户可以修复这种错误并重新启动机器。
第810行,由于某些原因,init甚至不能创建shell。当前面的所有情况都失败时,调用panic。这样内核就会试图同步磁盘,确保其状态一致。如果超过了内核选项中定义的时间,它也可能会重新启动机器。