如图所示,内核的初始化过程由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。这样内核就会试图同步磁盘,确保其状态一致。如果超过了内核选项中定义的时间,它也可能会重新启动机器。

内核初始化-从start_kernel到init相关推荐

  1. 内核初始化流程start_kernel

     main.c中start_kernel的初始化流程,不同内核版本函数顺序会有所差别,但总体功能差异性不大. visio 图: start_kernel调用.vsd

  2. 从linux内核启动,学习Linux内核启动过程:从start_kernel到init

    一.实验步骤: 1:运行menuos: a)cd LinuxKernel/ b)qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd root ...

  3. linux初始化内存盘卡住,分析内核初始化时根内存盘的加载过程(init/main.c)-嵌入式系统-与非网...

    作者:opera 概述 ==== 1)当内核配置了内存盘时, 内核在初始化时可以将软盘加载到内存盘中作为根盘. 当同时配置了初始化内存盘(Initail RAM Disk)时, 内核在初始化时可以在安 ...

  4. linux内核启动过程3:内核初始化阶段

    上一篇<<linux内核启动过程2:保护模式执行流程>>分析了保护模式启动过程以及bzImage的解压入口函数,本篇继续分析内核启动过程,从保护模式到C代码初始化. start ...

  5. 3. Linux系统启动分析-从start_kernel到init进程的启动

    ##################################### 作者:张卓 原创作品转载请注明出处:<Linux操作系统分析>MOOC课程 http://www.xuetang ...

  6. Linux系统基础——内核初始化

    内核初始化 特此说明: 刘超的趣谈linux操作系统是比较重要的参考资料,本文大部分内容和所有图片来源于这个专栏. 1 背景知识 BootLoader阶段后,cpu从实模式转换成保护模式.有了更强的寻 ...

  7. Linux 内存管理篇(2)内核初始化与内存管理启用

    前言 继内存寻址之后, 本篇开始介绍Linux内核地址空间初始化过程. 通过内存寻址篇我们知道, Linux 系统运行过程中位于保护模式,系统必须要是用MMU来完成地址寻址, 这就依赖于段表跟页表. ...

  8. Linux内核初始化阶段内存管理的几种阶段

    本系列旨在讲述从引导到完全建立内存管理体系过程中,内核对内存管理所经历的几种状态.阅读本系列前,建议先阅读memblock的相关文章. 一些讲在前面的话 在很久很久以前,linux内核还是支持直接从磁 ...

  9. linux内核ufs设备树,Linux内核初始化流程笔记

    Linux内核初始化流程笔记 分类: LINUX 作者:gfree.wind@http://www.doczj.com/doc/fc580419c1c708a1294a4409.html 博客:htt ...

  10. kali2020.3 vm版本内核是多少_Zircon Fuchsia 内核分析 启动(内核初始化)

    相关阅读: Zircon - Fuchsia 内核分析 - 启动(平台初始化) 简介 前面已经介绍了 Zircon 内核启动的汇编代码部分,主要是一些 CPU 的初始化. 现在 prime CPU 已 ...

最新文章

  1. RMAN的rman: can't open target错误
  2. 连接阿里云和容器技术生态 - 阿里云开源容器项目汇总
  3. AlphaGo Zero又上《Science》封面!谷歌的人工智能又干翻人类了!
  4. dubbo配置参考手册
  5. 苹果AirPods有望在年末推出新款产品 或将支持防水功能
  6. 吴恩达《机器学习》第六章:逻辑回归
  7. 基于Hexo+Node.js+github+coding搭建个人博客——基础篇
  8. jquery选择器的介绍和使用
  9. 三维重建——相机几何参数标定
  10. lopatkin俄大神精简中文系统Windows 8.1 Pro 19599 x86-x64 ZH-CN SM
  11. 企查查如何在线查询失信企业?
  12. 梅开二度宋分题——Java实现登录 和 信息录入功能
  13. pygame-KidsCanCode系列jumpy-part14-背景音乐及音效
  14. 自控重点整理1.1 比例微分PD控制器的作用
  15. Oracle的表空间、用户和模式
  16. 【Windows脚本】打印机脚本1-添加打印机(无需物理打印机)
  17. 易经中最有智慧的20条人生哲理!
  18. Photoshop切图----学习笔记
  19. 基于Springboot外卖系统05:用户非登陆状态的页面拦截器实现
  20. mac的终端显示分支名称?mac的终端和idea中的terminal同时修改

热门文章

  1. POJ 2135 Farm Tour
  2. c语言统计字符个数回车字符,C语言统计一串字符中空格键、Tab键、回车键、字母、数字及其他字符的个数(Ctrl+Z终止输入)...
  3. Linux查看某个进程的磁盘IO读写情况
  4. 整合spring-boot-starter-data-redis报错解决
  5. Google Home其实是个错误
  6. openstack相关资料集结
  7. MySQL之用Mysql-Proxy实现读写分离
  8. [转载]github在线更改mysql表结构工具gh-ost
  9. Linux内核等待队列wait_queue学习
  10. 考前必练15道题_《系统集成项目管理工程师备考宝典》