您的位置:首页 > 其它

读核笔记-内核初始化-从start_kernel到init

2013-08-16 22:28 337 查看
如图所示,内核的初始化过程由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_BLK_DEV_INITRD

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
SMP init */

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_ALL);

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

/*
激活SMP系统中其他CPU */

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_RDWR, 0) < 0)

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。这样内核就会试图同步磁盘,确保其状态一致。如果超过了内核选项中定义的时间,它也可能会重新启动机器。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: