3、分析Linux内核的启动过程

来源:互联网 发布:数据透视表里求和项 编辑:程序博客网 时间:2024/06/05 19:27

姓名:周毅

原创作品转载请注明出处 

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000


一、基础知识

1、计算机通电后,CPU开始从一个固定的地址加载代码并开始执行,这个地址就是BIOS的驱动程序所在的位置,于是BIOS的驱动开始执行,BIOS会初始化启动许多硬件(硬盘、网卡等等);

2、BIOS加载硬盘 MBR 中的 GRUB 后,启动过程就被GRUB2接管,GRUB2加载内核和initrd image,并启动内核;

3、内核接管整个系统后,加载/sbin/init并创建第一个用户态的进程,init进程开始调用一系列的脚本来创建很多子进程,这些子进程负责初始化整个系统;

4、start_kernel( )是linux内核启动的主函数,在init/main.c中定义,本实验主要也是分析start_kernel()函数的执行过程,从内核启动到第一个进程创建;

二、实验过程

  • 使用实验楼的虚拟机打开shell

  1. cd LinuxKernel/
  2. qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img

  • 使用gdb跟踪调试内核

  1. qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S # 关于-s和-S选项的说明:
  2. # -S freeze CPU at startup (use ’c’ to start execution)
  3. # -s shorthand for -gdb tcp::1234 若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项


另开一个shell窗口

  1. gdb
  2. (gdb)file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
  3. (gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
  4. (gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后

执行start_kernel时,内核启动界面停在此处:


三、代码分析

下面分析start_kernel,主要初始化了一些必要工作,如0号进程init_task,锁,内存,时钟,中断,进程管理等等:

asmlinkage __visible void __init start_kernel(void){char *command_line;//命令行,用来存放bootloader传递过来的参数char *after_dashes;//这两个变量为地址指针,指向内核启动参数处理相关结构体在内存的位置,/* * Need to run as early as possible, to initialize the * lockdep hash: */lockdep_init();//建立一个哈希表(hash tables),就是一个前后指向的指针结构体数组.函数的主要作用是初始化锁的状态跟踪模块。set_task_stack_end_magic(&init_task);//设置第一个进程init_tasksmp_setup_processor_id();//针对SMP处理器,用于获取当前CPU的硬件ID,如果不是多核,函数为空debug_objects_early_init();//初始化哈希桶(hash buckets)并将static object和pool object放入poll列表,这样堆栈就可以完全操作了/* * Set up the the initial canary ASAP: */boot_init_stack_canary();//初始化堆栈保护,防止栈溢出攻击的堆栈保护关键字cgroup_init_early();//在系统启动时初始化cgroups,同时初始化需要early_init的子系统local_irq_disable();//关闭当前CPU的所有中断响应,操作CPSR寄存器。early_boot_irqs_disabled = true;//系统中断关闭标志,当early_init完毕后,会恢复中断设置标志为false。/* * Interrupts are still disabled. Do necessary setups, then * enable them */boot_cpu_init();//设置当前引导系统的CPU在物理上存在,在逻辑上可以使用,并且初始化准备好,即激活当前CPUpage_address_init();//初始化高端内存的映射表pr_notice("%s", linux_banner);//输出各种信息//★很重要的一个函数★ arch/arm/kernel/setup.c内核架构相关初始化函数,是非常重要的一个初始化步骤。其中包含了处理器相关参数的初始化、内核启动参数(tagged list)的获取和前期处理、内存子系统的早期初始化setup_arch(&command_line);mm_init_cpumask(&init_mm);//初始化代表内核本身内存使用的管理结构体init_mm,init_mm定义了整个kernel的内存空间。setup_command_line(command_line);//对cmdline进行备份和保存setup_nr_cpu_ids();//设置最多有多少个nr_cpu_ids结构setup_per_cpu_areas();//为系统中每个CPU的per_cpu变量申请空间,同时拷贝初始化段里数据(.data.percpu)smp_prepare_boot_cpu();/* arch-specific boot-cpu hooks为SMP系统里引导CPU(boot-cpu)进行准备工作。在ARM系统单核里是空函数 */build_all_zonelists(NULL, NULL);//设置内存管理相关的node(节点,每个CPU一个内存节点)和其中的zone(内存域,包含于节点中,如)数据结构,以完成内存管理子系统的初始化,并设置bootmem分配器page_alloc_init();//设置内存页分配通知器pr_notice("Kernel command line: %s\n", boot_command_line);//输出命令参数到显示终端parse_early_param();//解析cmdline中的启动参数after_dashes = parse_args("Booting kernel",  static_command_line, __start___param,  __stop___param - __start___param,  -1, -1, &unknown_bootoption);if (!IS_ERR_OR_NULL(after_dashes))parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,   set_init_arg);jump_label_init();/* * These use large bootmem allocations and must precede * kmem_cache_init() */setup_log_buf(0);//使用bootmeme分配一个记录启动信息的缓冲区pidhash_init();//进程ID的HASH表初始化,用bootmem分配并初始化PID散列表,由PID分配器管理空闲和已指派的PID,这样可以提供通PID进行高效访问进程结构的信息。LINUX里共有四种类型的PID,因此就有四种HASH表相对应。vfs_caches_init_early();//前期虚拟文件系统(vfs)的缓存初始化sort_main_extable();//对内核异常表(exception table)按照异常向量号大小进行排序,以便加速访问trap_init();//对内核陷阱异常进行初始化,在ARM系统里是空函数,没有任何的初始化mm_init();//标记哪些内存可以使用,并且告诉系统有多少内存可以使用,当然是除了内核使用的内存以外/* * Set up the scheduler prior starting any interrupts (such as the * timer interrupt). Full topology setup happens at smp_init() * time - but meanwhile we still have a functioning scheduler. */sched_init();//对进程调度器的数据结构进行初始化,创建运行队列,设置当前任务的空线程,当前任务的调度策略为CFS调度器/* * Disable preemption - early bootup scheduling is extremely * fragile until we cpu_idle() for the first time. */preempt_disable();//关闭优先级调度。由于每个进程任务都有优先级,目前系统还没有完全初始化,还不能打开优先级调度。if (WARN(!irqs_disabled(), "Interrupts were enabled *very* early, fixing it\n"))local_irq_disable();idr_init_cache();// 为IDR机制分配缓存,主要是为 struct idr_layer结构体分配空间rcu_init();//初始化直接读拷贝更新的锁机制。context_tracking_init();radix_tree_init();//内核radis 树算法初始化/* init some links before init_ISA_irqs() */early_irq_init();//前期外部中断描述符初始化,主要初始化数据结构init_IRQ();//对应架构特定的中断初始化函数tick_init();rcu_init_nohz();init_timers();//初始化引导CPU的时钟相关的数据结构,注册时钟的回调函数,当时钟到达时可以回调时钟处理函数,最后初始化时钟软件中断处理hrtimers_init();//初始化高精度的定时器,并设置回调函数。softirq_init();//初始化软件中断,软件中断与硬件中断区别就是中断发生时,软件中断是使用线程来监视中断信号,而硬件中断是使用CPU硬件来监视中断。timekeeping_init();//初始化系统时钟计时,并且初始化内核里与时钟计时相关的变量。time_init();//初始化系统时钟。开启一个硬件定时器,开始产生系统时钟就是system_timer的初始化,arch/arm/mach-msm/board-*.csched_clock_postinit();perf_event_init();//CPU性能监视机制初始化,此机制包括CPU同一时间执行指令数,cache miss数,分支预测失败次数等性能参数profile_init();//分配内核性能统计保存的内存,以便统计的性能变量可以保存到这里call_function_init();//初始化所有CPU的call_single_queue,同时注册CPU热插拔通知函数到CPU通知链中WARN(!irqs_disabled(), "Interrupts were enabled early\n");early_boot_irqs_disabled = false;local_irq_enable();kmem_cache_init_late();//这是内核内存缓存(slab分配器)的后期初始化,当初始化完成之后,就可以使用通用内存缓存了/* * HACK ALERT! This is early. We're enabling the console before * we've done PCI setups etc, and console_init() must be aware of * this. But we do want output early, in case something goes wrong. */console_init();//初始化控制台,从这个函数之后就可以输出内容到控制台了。if (panic_later)//判断分析输入的参数是否出错,如果有出错,就启动控制台输出之后,立即打印出错的参数,以便用户立即看到出错的地方。panic("Too many boot %s vars at `%s'", panic_later,      panic_param);lockdep_info();//打印锁的依赖信息,用来调试锁/* * Need to run this when irqs are enabled, because it wants * to self-test [hard/soft]-irqs on/off lock inversion bugs * too: */locking_selftest();//测试锁的API是否使用正常,进行自我测试。比如测试自旋锁、读写锁、一般信号量和读写信号量。#ifdef CONFIG_BLK_DEV_INITRD  //检查initrd的位置是否符合要求if (initrd_start && !initrd_below_start_ok &&    page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",    page_to_pfn(virt_to_page((void *)initrd_start)),    min_low_pfn);initrd_start = 0;}#endifpage_cgroup_init();//初始化容器组的页面内存分配,mem_cgroup是cgroup体系中提供的用于memory隔离的功能,debug_objects_mem_init();//这个函数是创建调试对象内存分配初始化,所以紧跟内存缓存初始化后面kmemleak_init();//内核内存泄漏检测机制初始化setup_per_cpu_pageset();//创建每个CPU的高速缓存集合数组并初始化,此前只有启动页组。因为每个CPU都不定时需要使用一些页面内存和释放页面内存,为了提高效率,就预先创建一些内存页面作为每个CPU的页面集合。numa_policy_init();//初始化NUMA的内存访问策略。if (late_time_init)//主要运行时钟相关后期的初始化功能。late_time_init();sched_clock_init();//对每个CPU进行系统进程调度时钟初始化calibrate_delay();//主要计算CPU需要校准的时间,这里说的时间是CPU执行时间。pidmap_init();//进程位图初始化,一般情况下使用一页来表示所有进程占用情况。anon_vma_init();//初始化反向映射的匿名内存,提供反向查找内存的结构指针位置,快速地回收内存。acpi_early_init();#ifdef CONFIG_X86if (efi_enabled(EFI_RUNTIME_SERVICES))efi_enter_virtual_mode();#endif#ifdef CONFIG_X86_ESPFIX64/* Should be run before the first non-init thread is created */init_espfix_bsp();#endifthread_info_cache_init();//线程信息(thread_info)的缓存初始化。cred_init();//任务信用系统初始化fork_init(totalram_pages);//根据当前物理内存计算出来可以创建进程(线程)的最大数量,并进行进程环境初始化,为task_struct分配空间。proc_caches_init();//进程缓存初始化,为进程初始化创建机制所需的其他数据结构申请空间buffer_init();//初始化文件系统的缓冲区,并计算最大可以使用的文件缓存。key_init();//初始化内核安全键管理列表和结构,内核密钥管理系统security_init();//初始化内核安全管理框架,以便提供访问文件/登录等权限。dbg_late_init();//内核调试系统后期初始化vfs_caches_init(totalram_pages);//虚拟文件系统进行缓存初始化,提高虚拟文件系统的访问速度signals_init();//初始化信号队列缓存。信号管理系统/* rootfs populating might need page-writeback */page_writeback_init();//页面写机制初始化proc_root_init();//初始化系统进程文件系统,主要提供内核与用户进行交互的平台,方便用户实时查看进程的信息。cgroup_init();//进程控制组正式初始化,主要用来为进程和其子程提供性能控制。比如限定这组进程的CPU使用率为20%cpuset_init();//初始化CPUSET,CPUSET主要为控制组提供CPU和内存节点的管理的结构taskstats_init_early();//任务状态早期初始化,为结构体获取高速缓存,并初始化互斥机制。任务状态主要向用户提供任务的状态信息delayacct_init();//任务延迟机制初始化,初始化每个任务延时计数。当一个任务等CPU运行,或者等IO同步时,都需要计算等待时间。check_bugs();//检查CPU配置、FPU等是否非法使用不具备的功能,检查CPU BUG,软件规避BUGsfi_init_late();// SFI 初始程序晚期设置函数if (efi_enabled(EFI_RUNTIME_SERVICES)) {efi_late_init();efi_free_boot_services();}ftrace_init();//功能跟踪调试机制初始化,初始化内核跟踪模块,ftrace的作用是帮助开发人员了解Linux 内核的运行时行为,以便进行故障调试或性能分析 function trace./* Do the rest non-__init'ed, we're now alive */rest_init();//剩余的初始化,至此,内核已经开始工作了}

init/init_task.c中静态初始化init_task进程:

#include <linux/init_task.h>#include <linux/export.h>#include <linux/mqueue.h>#include <linux/sched.h>#include <linux/sched/sysctl.h>#include <linux/sched/rt.h>#include <linux/init.h>#include <linux/fs.h>#include <linux/mm.h>#include <asm/pgtable.h>#include <asm/uaccess.h>static struct signal_struct init_signals = INIT_SIGNALS(init_signals);static struct sighand_struct init_sighand = INIT_SIGHAND(init_sighand);/* Initial task structure */struct task_struct init_task = INIT_TASK(init_task);//init_task在此初始化EXPORT_SYMBOL(init_task);/* * Initial thread structure. Alignment of this is handled by a special * linker map entry. */union thread_union init_thread_union __init_task_data ={ INIT_THREAD_INFO(init_task) };

rest_init()调用kernel_init()初始化1号进程init,然后init_task变为dle进程:

static noinline void __init_refok rest_init(void){int pid;rcu_scheduler_starting();/* * We need to spawn init first so that it obtains pid 1, however * the init task will end up wanting to create kthreads, which, if * we schedule it before we create kthreadd, will OOPS. */kernel_thread(kernel_init, NULL, CLONE_FS);//创建一个内核线程,实际上就是内核进程,调用kernel_init()numa_default_policy();pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);rcu_read_lock();kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);rcu_read_unlock();complete(&kthreadd_done);/* * The boot idle thread must execute schedule() * at least once to get things moving: */init_idle_bootup_task(current);//init_task变为idle进程schedule_preempt_disabled();/* Call into cpu_idle with preempt disabled */cpu_startup_entry(CPUHP_ONLINE);}

kernel_init()通过run_init_process()执行init:

static int __ref kernel_init(void *unused){int ret;kernel_init_freeable();/* need to finish all async __init code before freeing the memory */async_synchronize_full();free_initmem();mark_rodata_ro();system_state = SYSTEM_RUNNING;numa_default_policy();flush_delayed_fput();if (ramdisk_execute_command) {ret = run_init_process(ramdisk_execute_command);if (!ret)return 0;pr_err("Failed to execute %s (error %d)\n",       ramdisk_execute_command, ret);}/* * We try each of these until one succeeds. * * The Bourne shell can be used instead of init if we are * trying to recover a really broken machine. */if (execute_command) {ret = run_init_process(execute_command);if (!ret)return 0;pr_err("Failed to execute %s (error %d).  Attempting defaults...\n",execute_command, ret);}/*这里的 run_init_process 就是通过 execve()来运行 init 程序。这里首先运行“/sbin/init”,如果失败再运行“/etc/init”,然后是“/bin/init”,然后是“/bin/sh”(也就是说,init 可执行文件可以放在上面代码中寻找的 4 个目录中都可以),如果都失败,则可以通过在系统启动时在添加的启动参数来指定 init,比如 init=/home/wzhou/init。 */if (!try_to_run_init_process("/sbin/init") ||    !try_to_run_init_process("/etc/init") ||    !try_to_run_init_process("/bin/init") ||    !try_to_run_init_process("/bin/sh"))return 0;panic("No working init found.  Try passing init= option to kernel. "      "See Linux Documentation/init.txt for guidance.");}

run_init_process调用do_execve函数通过系统调用执行init:

static int run_init_process(const char *init_filename){argv_init[0] = init_filename;return do_execve(getname_kernel(init_filename),//构造 sys_execve 系统调用(const char __user *const __user *)argv_init,(const char __user *const __user *)envp_init);}
四、总结init_task、idle、init进程:

     task 0 的进程结构(task_struct init_task)由INIT_TASK宏静态定义。该结构体(init_task)在linux启动时被设置为current_task。当初始化到rest_init函数中时,通过kernel_thread函数启动第一个内核线程kernel_init。kernel_init再通过do_execve启动/sbin/init。这就是我们看到的init进程,进程号为1。初始化的最后linux调用scheule()整个系统就运行起来了,init_task则变为idle进程。




0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 快递被买家签收后调包了怎么办 快递买家签收了东西坏了怎么办 快递没签收到买家评价了怎么办 快递买家签收后现在要退货怎么办 支付宝充话费充错对方关机怎么办 闲鱼同意退款了买家不退东西怎么办 被骗了说给存q币怎么办 方正说我的淘宝字体侵权怎么办 买家说收到衣服没有吊牌该怎么办 淘宝下完订单店主不发货怎么办 工商局不给查被告企业的信息怎么办 被职业打假人起诉到法院怎么办 京东购物如果换货不给你发货怎么办 淘宝上发的快递没有了怎么办 天猫购物半个月不发货怎么办 京东网同一产品购买多规格的怎么办 天猫商城购买的家具要退换货怎么办 亚马逊美国站会员日前没销量怎么办 淘宝买的鞋子把脚磨坏了怎么办 拼多多下单 没货 没法发货怎么办 闲鱼退货物流弄坏了卖家拒收怎么办 客户说物流太慢了 要退货怎么办 京东退货物流系统不更新怎么办 把货交给物流但是物流丢货了怎么办 货还在物流就申请退款怎么办 荣耀4x返回键不管用怎么办 华为手机关不了机也开不了机怎么办 荣耀畅玩5x手机密码忘了怎么办 华为短信验证码失败其他错误怎么办 红米3x手机卡顿反应慢怎么办 华为手机不停的自动重启怎么办 华为荣耀4x卡在开机界面怎么办 华为荣耀8青春版密码忘了怎么办 华为手机内存满了开不起来怎么办 华为荣耀畅玩平板2比较卡怎么办 红米4x太卡了怎么办 魅蓝e移动卡只有2g网络怎么办 联通关闭2g网络双卡手机怎么办 小米6x手机插耳机音量大怎么办 音量键和亮度键盘拆了怎么办 荣耀v10锁屏密码忘了怎么办