1. vmlinux.lds

首先分析 Linux 内核的连接脚本文件 arch/arm/kernel/vmlinux.lds,通过链接脚本可以找到 Linux 内核的第一行程序是从哪里执行的:



第 493 行的 ENTRY 指明了了 Linux 内核入口,入口为 stext,stext 定义在文件arch/arm/kernel/head.S 中 , 因 此 要 分 析 Linux 内核的启动流程,就得先从文件arch/arm/kernel/head.S 的 stext 处开始分析

2. 内核启动流程分析

2.1 内核入口stext

stext 是 Linux 内核的入口地址,在文件 arch/arm/kernel/head.S 中有如下所示提示内容



如图可知:如果要启动Linux,启动要求如下:

  • 关闭 MMU。
  • 关闭 D-cache。
  • I-Cache 无所谓。
  • r0=0。
  • r1=machine nr(也就是机器 ID)。
  • r2=atags 或者设备树(dtb)首地址。

    Linux 内核的入口点 stext 其实相当于内核的入口函数,stext 函数内容如下:



    proc_info_list 在文件arch/arm/include/asm/procinfo.h 中的定义如下:
struct proc_info_list {
unsigned int cpu_val;
unsigned int cpu_mask;
unsigned long __cpu_mm_mmu_flags; /* used by head.S */
unsigned long __cpu_io_mmu_flags; /* used by head.S */
unsigned long __cpu_flush; /* used by head.S */
const char *arch_name;
const char *elf_name;
unsigned int elf_hwcap;
const char *cpu_name;
struct processor *proc;
struct cpu_tlb_fns *tlb;
struct cpu_user_fns *user;
struct cpu_cache_fns *cache;
};

Linux 内核将每种处理器都抽象为一个 proc_info_list 结构体,每种处理器都对应一个procinfo。因此可以通过处理器 ID 来找到对应的 procinfo 结构__lookup_processor_type 函数找到对应处理器的 procinfo 以后会将其保存到 r5 寄存器中

继续回到源代码:



2.2 __mmap_switched函数

2.3 start_kernel函数

start_kernel 通过调用众多的子函数来完成 Linux 启动之前的一些初始化工作,由于start_kernel 函数里面调用的子函数太多,而这些子函数又很复杂,因此我们简单的来看一些重要的子函数。精简并添加注释后的 start_kernel 函数内容如下:

asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes; /*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init(); //死锁检测模块,两个Hash表,此函数要尽早执行
set_task_stack_end_magic(&init_task); //任务栈结束魔术数,用于检测栈溢出
smp_setup_processor_id();//跟 SMP 有关(多核处理器),设置处理器 ID
debug_objects_early_init(); //debug初始化 /*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary(); //栈溢出检测初始化 cgroup_init_early(); // cgroup初始化,cgroup用于检测linux系统资源 local_irq_disable(); // 关闭当前cpu中断
early_boot_irqs_disabled = true; // /*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
boot_cpu_init(); // CPU有关初始化
page_address_init(); // 页地址初始化
pr_notice("%s", linux_banner); // 打印linux版本号和编译时间等信息
setup_arch(&command_line); /* 架构相关的初始化,此函数会解析传递进来的
* ATAGS 或者设备树(DTB)文件。会根据设备树里面
* 的 model 和 compatible 这两个属性值来查找
* Linux 是否支持这个单板。此函数也会获取设备树
* 中 chosen 节点下的 bootargs 属性值来得到命令
* 行参数,也就是 uboot 中的 bootargs 环境变量的
* 值,获取到的命令行参数会保存到
*command_line 中。
*/
mm_init_cpumask(&init_mm); // 内存相关初始化
setup_command_line(command_line); // 存储命令行参数
setup_nr_cpu_ids(); // 如果是多核,此函数用于获取CPU核心数量
setup_per_cpu_areas(); // 设置每个CPU的pre-cpu数据
smp_prepare_boot_cpu(); // build_all_zonelists(NULL, NULL); // 建立系统内存页区链表
page_alloc_init(); // 处理用于CPU热插拔的页 /* 打印命令行信息 */
pr_notice("Kernel command line: %s\n", boot_command_line); //
parse_early_param(); // 解释命令行中console参数
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); // 设置log使用的缓冲区
pidhash_init(); // 构建PID哈希表
vfs_caches_init_early(); // 预先初始化 vfs(虚拟文件系统)的目录项和索引节点缓存
sort_main_extable(); // 定义内核异常列表
trap_init(); // 完成对系统保留中断向量的初始化
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(); // 初始化调度器,主要初始化一些结构体
/*
* 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 初始化,IDR 是 Linux 内核的整数管理机制,也就是将一个整数 ID 与一个指针关联起来
rcu_init(); // 初始化 RCU,RCU 全称为 Read Copy Update(读-拷贝修改 /* trace_printk() and trace points may be used after this */
trace_init(); // 跟踪调试相关初始化 context_tracking_init(); //
radix_tree_init(); // 基数树相关数据结构初始化
/* init some links before init_ISA_irqs() */
early_irq_init(); // 初始中断相关初始化,主要是注册 irq_desc 结构体变量,因为 Linux 内核使用 irq_desc 来描述一个中断。
init_IRQ(); // 中断初始化
tick_init(); // tick 初始化
rcu_init_nohz(); //
init_timers(); // 初始化定时器
hrtimers_init(); // 初始化高精度定时器
softirq_init(); // 初始化软中端
timekeeping_init(); //
time_init(); // 初始化系统时间
sched_clock_postinit(); //
perf_event_init(); //
profile_init(); //
call_function_init(); //
WARN(!irqs_disabled(), "Interrupts were enabled early\n"); //
early_boot_irqs_disabled = false; //
local_irq_enable(); // 使能中断 kmem_cache_init_late(); // slab 初始化,slab 是 Linux 内存分配器 /*
* 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(); // /* 初始化控制台,之前 printk 打印的信息都存放
* 缓冲区中,并没有打印出来。只有调用此函数
* 初始化控制台以后才能在控制台上打印信息。
*/
if (panic_later)
panic("Too many boot %s vars at `%s'", panic_later,
panic_param); // lockdep_info(); // 如果定义了宏 CONFIG_LOCKDEP,那么此函数打印一些信息。 /*
* 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(); // 锁自测 #ifdef CONFIG_BLK_DEV_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;
}
#endif
page_ext_init(); //
debug_objects_mem_init(); //
kmemleak_init(); // kmemleak 初始化,kmemleak 用于检查内存泄漏
setup_per_cpu_pageset(); //
numa_policy_init(); //
if (late_time_init)
late_time_init(); //
sched_clock_init(); //
calibrate_delay(); // 测定 BogoMIPS 值,可以通过 BogoMIPS 来判断 CPU 的性能 BogoMIPS 设置越大,说明 CPU 性能越好。
pidmap_init(); // PID 位图初始化
anon_vma_init(); // 生成 anon_vma slab 缓存
acpi_early_init(); //
#ifdef CONFIG_X86
if (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();
#endif
thread_info_cache_init(); //
cred_init(); // 为对象的每个用于赋予资格(凭证)
fork_init(); // 初始化一些结构体以使用 fork 函数
proc_caches_init(); // 给各种资源管理结构分配缓存
buffer_init(); // 初始化缓冲缓存
key_init(); // 初始化秘钥
security_init(); // 安全相关初始化
dbg_late_init(); //
vfs_caches_init(totalram_pages); // 为 VFS 创建缓存
signals_init(); // 初始化信号
/* rootfs populating might need page-writeback */
page_writeback_init(); // 页回写初始化
proc_root_init(); // 注册并挂载proc文件系统
nsfs_init(); //
cpuset_init(); // 初始化 cpuset,cpuset 是将 CPU 和内存资源以逻辑性和层次性集成的一种机制,是 cgroup 使用的子系统之一
cgroup_init(); // 初始化 cgroup
taskstats_init_early(); // 进程状态初始化
delayacct_init(); // check_bugs(); // 检查写缓冲一致性 acpi_subsystem_init(); //
sfi_init_late(); // if (efi_enabled(EFI_RUNTIME_SERVICES)) {
efi_late_init(); //
efi_free_boot_services(); //
} ftrace_init(); // /* Do the rest non-__init'ed, we're now alive */
rest_init(); //
}

函数的最后调用了reset_init

2.4 reset_init函数

这个函数里面介绍了面试常问的PID 0 1 2进程(到现在还只是不太明白)

static noinline void __init_refok rest_init(void)
{
int pid; rcu_scheduler_starting(); //启动RCU锁调度器
smpboot_thread_init();
/*
* 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 进程,也就是大名鼎鼎的 init 内核进程。
// init 进程的 PID 为 1。init 进程一开始是内核进程(也就是运行在内核态),后面 init 进程会在根
// 文件系统中查找名为“init”这个程序,这个“init”程序处于用户态,通过运行这个“init”程
// 序,init 进程就会实现从内核态到用户态的转变。
kernel_thread(kernel_init, NULL, CLONE_FS);
numa_default_policy();
// 调用函数 kernel_thread 创建 kthreadd 内核进程,此内核进程的 PID 为 2。kthreadd
// 进程负责所有内核进程的调度和管理
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);
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
// 调用函数 cpu_startup_entry 来进入 idle 进程,cpu_startup_entry 会调用
// cpu_idle_loop,cpu_idle_loop 是个 while 循环,也就是 idle 进程代码。idle 进程的 PID 为 0,idle
// 进程叫做空闲进程,如果学过 FreeRTOS 或者 UCOS 的话应该听说过空闲任务。idle 空闲进程
// 就和空闲任务一样,当 CPU 没有事情做的时候就在 idle 空闲进程里面“瞎逛游”,反正就是给
// CPU 找点事做。当其他进程要工作的时候就会抢占 idle 进程,从而夺取 CPU 使用权。其实大
// 家应该可以看到 idle 进程并没有使用 kernel_thread 或者 fork 函数来创建,因为它是有主进程演
// 变而来的, idle 进程是内核进程
cpu_startup_entry(CPUHP_ONLINE);
}

2.5 init进程

static int __ref kernel_init(void *unused)
{
int ret; kernel_init_freeable(); /* init 进程的一些其他初始化工作 */
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();/* 等待所有的异步调用执行完成 */
free_initmem();/* 释放 init 段内存 */
mark_rodata_ro();
system_state = SYSTEM_RUNNING;/* 标记系统正在运行 */
numa_default_policy(); flush_delayed_fput(); // ramdisk_execute_command 是一个全局的 char 指针变量,此变量值为“/init”,
// 也就是根目录下的 init 程序。ramdisk_execute_command 也可以通过 uboot 传递,在 bootargs 中
// 使用“rdinit=xxx”即可,xxx 为具体的 init 程序名字
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.
*/
// 如果 ramdisk_execute_command 为空的话就看 execute_command 是否为空,反
// 正不管如何一定要在根文件系统中找到一个可运行的 init 程序。execute_command 的值是通过
// uboot 传递,在 bootargs 中使用“init=xxxx”就可以了,比如“init=/linuxrc”表示根文件系统中
// 的 linuxrc 就是要执行的用户空间 init 程序
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
// 如果 ramdisk_execute_command 和 execute_command 都为空,那么就依次
// 查找“/sbin/init”、“/etc/init”、“/bin/init”和“/bin/sh”,这四个相当于备用 init 程序,如果这四
// 个也不存在,那么 Linux 启动失败!
// 如果以上步骤都没有找到用户空间的 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.");
}

简单看一下 kernel_init_freeable 函数,前面说了,kernel_init 会调用此函数来做一些init 进程初始化工作。kernel_init_freeable 定义在文件 init/main.c 中,缩减后的函数内容如下:

static noinline void __init kernel_init_freeable(void)
{
/*
* Wait until kthreadd is all set-up.
*/
wait_for_completion(&kthreadd_done);/* 等待 kthreadd 进程准备就绪 */ /* Now the scheduler is fully set up and can do blocking allocations */
gfp_allowed_mask = __GFP_BITS_MASK; /*
* init can allocate pages on any node
*/
set_mems_allowed(node_states[N_MEMORY]);
/*
* init can run on any cpu.
*/
set_cpus_allowed_ptr(current, cpu_all_mask); cad_pid = task_pid(current); smp_prepare_cpus(setup_max_cpus); do_pre_smp_initcalls();
lockup_detector_init(); smp_init();/* SMP 初始化 */
sched_init_smp();/* 多核(SMP)调度初始化 */ do_basic_setup();/* 设备初始化都在此函数中完成 */
//do_basic_setup 会调用 driver_init 函数完成 Linux 下驱动模型子系统的初始化 /* Open the /dev/console on the rootfs, this should never fail */
// 打开设备“/dev/console”,在 Linux 中一切皆为文件!因此“/dev/console”也
// 是一个文件,此文件为控制台设备。每个文件都有一个文件描述符,此处打开的“/dev/console”
// 文件描述符为 0,作为标准输入(0)
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
pr_err("Warning: unable to open an initial console.\n"); // sys_dup 函数将标准输入(0)的文件描述符复制了 2 次,一个作为标准
// 输出(1),一个作为标准错误(2)。这样标准输入、输出、错误都是/dev/console 了。console 通过
// uboot 的 bootargs 环境变量设置,“console=ttymxc0,115200”表示将/dev/ttymxc0 设置为 console,
// 也就是 I.MX6U 的串口 1。当然,也可以设置其他的设备为 console,比如虚拟控制台 tty1,设
// 置 tty1 为 console 就可以在 LCD 屏幕上看到系统的提示信息
(void) sys_dup(0);
(void) sys_dup(0);
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/ if (!ramdisk_execute_command)
ramdisk_execute_command = "/init"; if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
// 调用函数 prepare_namespace 来挂载根文件系统。跟文件系统也是由命令行参
// 数指定的,也就是 uboot 的 bootargs 环境变量。比如“root=/dev/mmcblk1p2 rootwait rw”就表示
// 根文件系统在/dev/mmcblk1p2 中,也就是 EMMC 的分区 2 中
prepare_namespace();
} /*
* Ok, we have completed the initial bootup, and
* we're essentially up and running. Get rid of the
* initmem segments and start the user-mode stuff..
*
* rootfs is available now, try loading the public keys
* and default modules
*/ integrity_load_keys();
load_default_modules();
}

Linux内核启动流程(简介)的更多相关文章

  1. linux 内核启动流程

    Linux内核启动流程详细分析: http://www.linuxidc.com/Linux/2014-10/108034.htm ARM Linux内核启动过程: http://blog.csdn. ...

  2. 【内核】linux内核启动流程详细分析

    Linux内核启动流程 arch/arm/kernel/head-armv.S 该文件是内核最先执行的一个文件,包括内核入口ENTRY(stext)到start_kernel间的初始化代码, 主要作用 ...

  3. 【内核】linux内核启动流程详细分析【转】

    转自:http://www.cnblogs.com/lcw/p/3337937.html Linux内核启动流程 arch/arm/kernel/head-armv.S 该文件是内核最先执行的一个文件 ...

  4. linux 内核启动流程分析,移植

    分析 linux-2.6.22.6 内核启动流程 移植 linux-3.4.2 到 JZ2440 开发板 Linux内核源码百度云链接: https://pan.baidu.com/s/1m1ymGl ...

  5. Linux内核启动流程与模块机制

    本文旨在简单的介绍一下Linux的启动流程与模块机制: Linux启动的C入口位于/Linux.2.6.22.6/init/main.c::start_kernel() 下图简要的描述了一下内核初始化 ...

  6. Tiny4412 Linux 内核启动流程

    Linux内核的启动分为压缩内核和非压缩内核两种,这里我们以压缩内核为例.压缩内核运行时,将运行一段解压缩程序,得到真正的内核镜像,然后跳转到内核镜像运行.此时,Linux进入非压缩内核入口,在非压缩 ...

  7. Linux内核启动流程分析(一)【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-3380535.html 很久以前分析的,一直在电脑的一个角落,今天发现贴出来和大家分享下.由于是word直接 ...

  8. linux内核启动流程[转]

    启动流程一览 既然启动是很严肃的一件事,那我们就来了解一下整个启动的过程吧! 好让大家比较容易发现启动过程里面可能会发生问题的地方,以及出现问题后的解决之道! 不过,由於启动的过程中,那个启动管理程序 ...

  9. Linux内核启动流程分析(二)【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-3380544.html S3C2410 Linux 2.6.35.7启动分析(第二阶段) 接着上面的分析,第 ...

随机推荐

  1. 业务领先模型(Business Leadership Model; BLM)

    1.什么是业务领先模型 业务领先模型是指是一个完整的战略规划方法论.这套方法论是IBM在2003年的时候,和美国某商学院一起研发的.后来,这个方法论成为IBM公司全球从公司层面到各个业务部门共同使用的 ...

  2. 环境(8)Linux用户组权限

    一:Linux时间日期-时间同步策略 1.日期与时间 ①时间命令 data:查看当前系统时间 cal :查看日历     cal  2020 修改时间:   date -s  11:11:11    ...

  3. Python 爬取 房天下

    ... import requests from requests import ConnectionError from bs4 import BeautifulSoup import pymong ...

  4. linux安全 设置登录失败次数后,拒绝登录

    设置登录失败3次后锁定用户300秒可以通过配合文件/etc/pam.d/sshd配置如下 在第一行 #%PAM-1.0 的下一行添加1a auth required pam_tally2.so den ...

  5. bsp工程管理

    1. bsp工程管理的目的 模块化项目,使得项目清晰 2. 代码 拷贝原来工程 创建文件夹 bsp就是工程驱动文件 imx6ull是和芯片有关的文件 obj是生成文件的文件夹 project 工程文件 ...

  6. I.MX启动方式和头部

    1. 启动方式 2. 头部信息 编译好的bin文件烧写到SD卡中,需要加一些头部文件,才可以执行. Image vector table,简称 IVT,IVT 里面包含了一系列的地址信息,这些地址信息 ...

  7. 互联网java面试宝典

    1.为什么使用消息队列啊? 答题: 消息队列的核心功能就是:解耦合,异步,流量削峰解耦:接口调用发送,那如果E系统也要这个数据呢?那如果C系统现在不需要了呢?现在A系统又要发送第二种数据了呢?A系统负 ...

  8. [CSP-S2019] 树的重心

    也是一个很不错的题目. 考虑我们钦定重心为根. 那么就有这样一个性质: 如果我们断的边在一个点的子树里,则这个点不会成为断边后的分裂树的根. 所以我们就只要考虑断边在子树外即可. 那么我们设\(x\) ...

  9. 【基因组注释】ncRNA注释

    目录 1. ncRNA 2. 软件 tRNA注释 rRNA注释 其他ncRNA注释 3. 注释 tRNA rRNA snRNA.miRNA等 4. snRNA.miRNA等结果的统计 1. ncRNA ...

  10. Python基础笔记1

    这篇笔记来自廖雪峰的Python教程. 一.Python基础 Python使用缩进来组织代码块,务必遵守约定俗成的习惯,坚持使用4个空格的缩进. 在文本编辑器中,需要设置把Tab自动转换为4个空格,确 ...