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

本文详细介绍了Linux内核从start_kernel函数开始直至第一个用户进程init结束的整个初始化过程。通过分析start_kernel、rest_init、kernel_init及init_post等关键函数,帮助读者理解内核初始化的各个阶段。
摘要由CSDN通过智能技术生成

如图所示,内核的初始化过程由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_softirqhi_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性能测试函数,可以计算出CPU1s内执行了多少次一个 

         * 极短的循环,计算出来的值经过处理后得到BogoMIPS值(BogoBogus的意思), 

         */ 

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。这样内核就会试图同步磁盘,确保其状态一致。如果超过了内核选项中定义的时间,它也可能会重新启动机器。
目录 1 引子 2 1.1 上电 2 1.2 BIOS时代 3 1.3 内核引导程序 5 2 内核映像的形成 8 2.1 MakeFile预备知识 9 2.1.1 Makefile书写规则 9 2.1.2 Makefile变量 10 2.1.3 条件判断 14 2.1.4 函数 17 2.1.5 隐含规则 17 2.1.6 定义模式规则 19 2.1 KBuild体系 23 2.1.1 内核目标 24 2.1.2 主机程序 26 2.1.3 编译标志 27 2.2 内核编译分析 28 2.2.1 编译配置 29 2.2.2 寻找第一个目标 32 2.2.3 prepare和scripts目标 38 2.2.4 递归编译各对象 41 2.2.5 链接vmlinux 44 2.2.6 制作bzImage 50 3 实模式下的内核代码 57 3.1 内核映像内存布局 58 3.2 实模式汇编代码header.S 60 3.2.1 无用的bootsect代码 60 3.2.2 初始化头变量hdr 63 3.2.3 准备实模式下C语言环境 64 3.3 实模式代码main函数 69 3.3.1 复制初始化头变量 71 3.3.2 初始化堆 74 3.3.3 确保支持当前运行的CPU 75 3.3.4 设置BIOS的x86模式 76 3.3.5 内存的检测 78 3.3.6 设置键盘属性 81 3.3.7 填充系统环境配置表 82 3.3.8 填充IST信息 83 3.3.9 设置Video模式 83 3.4 实模式代码go_to_proteced_mode函数 91 3.4.1 禁止可屏蔽和不可屏蔽中断 92 3.4.2 打开A20地址线 93 3.4.3 安装临时全局描述符表 99 3.4.4 第一次启动保护模式 101 4 保护模式下的内核代码 107 4.1 32位x86保护模式代码 107 4.1.1 内核解压缩的前期工作 108 4.1.2 解压缩内核 111 4.1.3 第二次启动保护模式 121 4.1.4 第一次启动分页管理 124 4.1.5 初始化0号进程 128 4.2 向start_kernel进发 131 4.2.1 初始化中断描述符表 132 4.2.2 第三次启动保护模式 137 4.2.3 启动x86虚拟机 141 5 走向现代:start_kernel函数 144 5.1 初始化同步与互斥环境 148 5.1.1 屏蔽中断 148 5.1.2 启动大内核锁 152 5.1.3 注册时钟通知链 153 5.1.4 激活第一个CPU 155 5.1.5 初始化地址散列表 160 5.1.6 打印版本信息 161 5.2 执行setup_arch()函数 166 5.2.1 拷贝可用内存区信息 171 5.2.2 获得总页面数 175 5.2.3 着手建立永久内核页表 177 5.2.4 第二次启动分页管理 181 5.2.5 建立内存管理架构 186 5.2.6 添砖加瓦 192 5.3 设置每CPU环境 206 5.4 初始化内存管理区列表 211 5.5 利用early_res分配内存 214 5.6 触碰虚拟文件系统 223 5.7 初始化异常服务 224 5.8 初始化内存管理 230 5.8.1 启用伙伴算法 230 5.8.2 初始化slab分配器 241 5.8.3 初始化非连续内存区 250 5.9 初始化调度程序 251 5.10 初始化中断处理系统 256 5.10.1 设置APIC中断服务 256 5.10.2 初始化本地软时钟 264 5.10.3 软中断初始化 268 5.10.4 初始化定时器中断 271 5.11 走进start_kernel尾声 273 5.11.1 初始化slab的后续工作 273 5.11.2 启动console 275 5.11.3 一些简单的函数 276 5.11.4 校准CPU时钟速度 279 5.11.5 创建一些slab缓存 282 5.12 安装根文件系统 287 5.12.1 创建VFS相关slab缓存 288 5.12.2 安装rootfs 291 5.12.3 安装proc文件系统 296 6 后start_kernel时代 298 6.1 创建1号进程 298 6.2 子系统的初始化 306 6.3 启动shell环境 309
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值