一、vmlinux、zImage与uImage之间的关系
vmlinux是编译出来的最原始的内核文件,未压缩;zImage是经过gzip压缩后的文件;uImage是u-boot专用的映像文件,它是在zImage之前加上一个长度为64字节的tag。
注:影响系统启动速度的关键因素分析
看到这,我本想不压缩以提升启动速度,但是实验发现,影响系统启动速度的关键因素还是数据从Nand Flash加载到内存的时间占了绝大多数。
a. NAND read这部分读取内核到内存,花了3秒多时间。
b.以上两部分之间虽然有很多打印信息,但是差不多只花了3秒多一点。
c.以上两部分之间的时间花的最多,9秒左右!其中第一部分打印出后停留了5秒才有打印信息数来。
经过以上分析实验,系统启动所花时间绝大多数花在内核加载到内存和文件系统挂载上。
二、系统启动汇编部分
三、系统启动C语言部分
架构相关的汇编部分代码运行完之后,就是架构相关性比较小的C语言部分,位于init/main.c里的start_kernel函数。
1、start_kernel函数
start_kernel函数对于驱动工程师来说,需要重点关注的是内核启动参数的获取与处理、setup_arch(&command_line)函数、rest_init()函数。其它是内存管理的初始化和内核各个组件的数据结构的内存申请并初始化。
asmlinkage void __init start_kernel(void)
{
char * command_line;
//地址指针,指向内核启动参数在内存中的位置(虚拟地址)
extern const struct kernel_param __start___param[], __stop___param[];
smp_setup_processor_id(); //针对SMP处理器,如果不是,则是弱引用函数
/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init(); //内核调试模块,用于检查内核互斥机制潜在的死锁问题
debug_objects_early_init();
/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary(); //初始化栈canary值,canary值用于防止栈溢出攻击的堆栈的保护字
cgroup_init_early(); //一组进程的行为控制,做数据结构和其中链表的初始化
local_irq_disable(); //关闭系统总中断
early_boot_irqs_disabled = true;
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
tick_init(); //初始化内核时钟系统
boot_cpu_init(); //激活当前CPU
page_address_init(); //高端内存相关,未定义的话为空函数
printk(KERN_NOTICE "%s", linux_banner); //打印内核版本信息,内核启动的第一行信息就来自这
setup_arch(&command_line); //1 内核架构相关初始化函数
mm_init_owner(&init_mm, &init_task); //初始化init_mm结构体
mm_init_cpumask(&init_mm);
setup_command_line(command_line); //对command_line进行备份与保存
setup_nr_cpu_ids(); //一下三个函数针对SMP处理器,不是SMP处理器都为空函数
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
build_all_zonelists(NULL); //设置内存相关节点和其中的内存域数据结构
page_alloc_init();
//2 打印与解析内核启动参数
printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
parse_early_param();
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
jump_label_init();
/*
* These use large bootmem allocations and must precede
* kmem_cache_init()
*/
setup_log_buf(0); //使用bootmem分配一个启动信息的缓冲区
pidhash_init(); //使用bootmem分配并初始化PID散列表
vfs_caches_init_early(); //前期VFS缓存初始化
sort_main_extable(); //对内核异常表进行排序
trap_init(); //对内核陷阱异常经行初始化,ARM架构中位空函数
mm_init(); //初始化内核内存分配器,启动信息中的内存信息来自此函数中的mem_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 (!irqs_disabled()) {
printk(KERN_WARNING "start_kernel(): bug: interrupts were "
"enabled *very* early, fixing it\n");
local_irq_disable();
}
idr_init_cache(); //为IDR机制分配缓存
perf_event_init(); //CPU性能检测机制初始化
rcu_init(); //内核RCU机制初始化
radix_tree_init(); //内核radix树算法初始化
/* init some links before init_ISA_irqs() */
early_irq_init(); //前期外部中断描述符初始化
init_IRQ(); //架构相关中断初始化
prio_tree_init(); //基于radix树的优先级搜索树(PST)初始化
init_timers(); //以下5个函数是软中断和内核时钟机制初始化
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
profile_init(); //profile子系统初始化,内核的性能调试工具
call_function_init();
if (!irqs_disabled())
printk(KERN_CRIT "start_kernel(): bug: interrupts were "
"enabled early\n");
early_boot_irqs_disabled = false;
local_irq_enable(); //开启总中断
/* Interrupts are enabled now so all GFP allocations are safe. */
gfp_allowed_mask = __GFP_BITS_MASK;
kmem_cache_init_late(); //slab分配器后期初始化
/*
* HACK 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(panic_later, panic_param);
lockdep_info(); //打印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();
//检查initrd的位置是否符合要求
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
printk(KERN_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_cgroup_init();
enable_debug_pagealloc(); //使能页分配的调试标志
debug_objects_mem_init();
kmemleak_init(); //内存泄露检测机制的初始化
setup_per_cpu_pageset(); //设置每个CPU的页组并初始化
numa_policy_init(); //分一致性内存访问(NUMA)初始化
if (late_time_init)
late_time_init();
sched_clock_init(); //初始化调度时钟
calibrate_delay();
pidmap_init(); //PID分配映射初始化
anon_vma_init(); //匿名虚拟内存域初始化
#ifdef CONFIG_X86
if (efi_enabled)
efi_enter_virtual_mode();
#endif
thread_info_cache_init();
cred_init(); //任务信用系统初始化
fork_init(totalram_pages); //进程创建机制初始化
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(); //页回写机制初始化
#ifdef CONFIG_PROC_FS
proc_root_init(); //proc文件系统初始化
#endif
cgroup_init(); //control group正式初始化
cpuset_init(); //CPUSET初始化
taskstats_init_early(); //任务状态早期初始化函数,为任务获取高速缓存并初始化互斥机制
delayacct_init(); //任务延迟机制初始化
check_bugs();
acpi_early_init(); /* before LAPIC and SMP init */
sfi_init_late();
ftrace_init();
/* Do the rest non-__init'ed, we're now alive */
rest_init(); //3 剩余的初始化
}
2、setup_arch函数
下面分析架构相关的初始化函数setup_arch函数。
void __init setup_arch(char **cmdline_p)
{
struct machine_desc *mdesc; //设备描述结构体
setup_processor(); //再次检测处理器类型,并初始化处理器相关的底层变量,内核启动时处理器信息就是通过该函数打印
mdesc = setup_machine_fdt(__atags_pointer); //检测bootloader传递的Machine ID与机器ID是否匹配。若匹配打印Machine ID,处理tagged_list;若不匹配,给出出错信息,死循环。
if (!mdesc)
mdesc = setup_machine_tags(machine_arch_type);
machine_desc = mdesc;
machine_name = mdesc->name;
#ifdef CONFIG_ZONE_DMA
if (mdesc->dma_zone_size) {
extern unsigned long arm_dma_zone_size;
arm_dma_zone_size = mdesc->dma_zone_size;
}
#endif
if (mdesc->soft_reboot) //设置重启类型,“s”软件重启,“h”硬件重启
reboot_setup("s");
init_mm.start_code = (unsigned long) _text; //init_mm部分数据初始化
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;
/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;
parse_early_param();
sanity_check_meminfo();
arm_memblock_init(&meminfo, mdesc);
paging_init(mdesc);
request_standard_resources(mdesc);
unflatten_device_tree();
#ifdef CONFIG_SMP
if (is_smp())
smp_init_cpus();
#endif
reserve_crashkernel();
tcm_init();
#ifdef CONFIG_MULTI_IRQ_HANDLER
handle_arch_irq = mdesc->handle_irq;
#endif
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
early_trap_init();
if (mdesc->init_early)
mdesc->init_early();
}
3、rest_init函数
rest_init函数的主要功能是创建并启动内核线程init。
/*
* We need to finalize in a non-__init function or else race conditions
* between the root thread and the init thread may cause start_kernel to
* be reaped by free_initmem before the root thread has proceeded to
* cpu_idle.
*
* gcc-3.4 accidentally inlines this function, so use noinline.
*/
//定义一个complete变量告诉init线程:kthreads线程已经创建完成。
static __initdata DECLARE_COMPLETION(kthreadd_done);
static noinline void __init_refok rest_init(void)
{
int pid;
rcu_scheduler_starting(); //内核RCU锁机制调度启动
/*
* 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 | CLONE_SIGHAND); //创建kernel_init内核线程,PID=1
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); //创建kthread内核线程,PID=2
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns); //获取kthread线程信息
rcu_read_unlock();
complete(&kthreadd_done); //通过complete通知kernel_init线程kthread线程已创建成功
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current); //设置当前进程为idle进程类
preempt_enable_no_resched(); //使能抢占,但不重新调度
schedule(); //执行调度,切换进程
/* Call into cpu_idle with preempt disabled */
preempt_disable(); //进程调度完成,禁用抢占
cpu_idle(); //内核本体进入idle状态,用循环消耗空闲的CPU时间
}
4、kernel_init函数
kernel_init函数是内核init线程运行的函数:完成设备驱动程序的初始化,并调用init_post函数启动用户空间的init进程。
static int __init kernel_init(void * unused)
{
/*
* Wait until kthreadd is all set-up.
*/
wait_for_completion(&kthreadd_done);
/*
* init can allocate pages on any node
*/
set_mems_allowed(node_states[N_HIGH_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();
sched_init_smp();
do_basic_setup(); //重要函数,主要是初始化设备驱动程序
/* Open the /dev/console on the rootfs, this should never fail */
//这里要打开/dev/console节点,在此出错可能的原因是:
//1. 制作文件系统时忘记创建/dev/console节点;
//2. 文件系统挂载问题,挂载的文件系统不是什么都没有就是挂错了节点。
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
//复制两次标准输入(0),一个作为标准输出(1),一个作为标准出错(2)。
//完成后,标准输入、标准输出、标准出错都是/dev/console了
(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();
}
/*
* 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..
*/
init_post(); //启动用户空间init进程
return 0;
}
5、init_post函数
init_post真正启动用户空间init进程。
/* This is a non __init function. Force it to be noinline otherwise gcc
* makes it inline to init() and it becomes part of init.text section
*/
static noinline int init_post(void)
{
/* 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();
current->signal->flags |= SIGNAL_UNKILLABLE; //设置当前进程(init)为不可杀进程
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
/*
* 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) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
//检查完ramdisk_execute_command和execute_command为空的情况下,顺序执行四个初始化程序。
//如果都没有找到就打印出错信息,出现该错误的可能原因是:
//1. 启动参数配置有问题:指定了init进程,但是没找到,默认四个程序不在文件系统中;
//2. 文件系统挂载有问题;
//3. init程序没有执行权限。
panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
6、do_basic_setup函数
内核init线程调用了do_basic_setup函数,这个函数也做了很多内核与驱动的初始化工作
/*
* Ok, the machine is now initialized. None of the devices
* have been touched yet, but the CPU subsystem is up and
* running, and memory and process management works.
*
* Now we can finally start doing some real work..
*/
static void __init do_basic_setup(void)
{
cpuset_init_smp(); //对于非SMP处理器,该函数为空
usermodehelper_init(); //创建单线程工作队列khelper
shmem_init();
driver_init(); //初始化驱动模型中的各个子系统
init_irq_proc(); //在proc文件系统中创建irq目录
do_ctors(); //调用链接到所有的构造函数
usermodehelper_enable(); //使能单线程工作队列khelper
/*
* 1. 调用所有编译进内核的驱动的初始化函数,按照各个内核模块初始化函数所定义的启动级别(1~7), 按顺序调用初始化函数;
* 2. 对于同一级别的初始化函数,安装编译是链接的顺序调用,和内核的Makefile编写有关;
* 3. 基于某一子系统的驱动,其初始化函数的级别必须低于该子系统初始化函数的级别,如果编写的模块必须和依赖的模块在同一级,就必须Makefile的编写了。
*/
do_initcalls();
}
7、driver_init函数
driver_init函数的作用是驱动模型的子系统初始化,位于drivers/base/init.c。
/**
* driver_init - initialize driver model.
*
* Call the driver model init functions to initialize their
* subsystems. Called early from init/main.c.
*/
void __init driver_init(void)
{
/* These are the core pieces */
devtmpfs_init();
初始化devtmpfs文件系统,驱动核心设备将在这个文件系统中添加它们的设备节点。
这个文件系统可以由内核在挂载根文件系统之后自动挂载到/dev下,也可以在文件系统的启动脚本中手动 挂载。
devices_init();
初始化驱动模型中的部分子系统和kobject:
devices
dev
dev/block
dev/char
buses_init(); //初始化驱动模型中的bus子系统
classes_init(); //初始化驱动模型中的class子系统
firmware_init(); //初始化驱动模型中的firmware子系统
hypervisor_init(); //初始化驱动模型中的hypervisor子系统
/* These are also core pieces, but must come after the
* core core pieces.
*/
platform_bus_init(); //初始化驱动模型中的bus/platform子系统
system_bus_init(); //初始化驱动模型中的devices/system子系统
cpu_dev_init(); //初始化驱动模型中的devices/system/cpu子系统
memory_dev_init(); //初始化驱动模型中的devices/system/memory子系统
}