- Linux内核源码目录结构
- 调试Linux内核启动过程
- 启动过程分析(start_kernel)
- 宏定义
- lockdep_init
- set_task_stack_end_magic
- smp_setup_processor_id
- debug_objects_early_init
- cgroup_init_early
- local_irq_disable
- early_boot_irqs_off
- boot_cpu_init
- page_address_init
- setup_arch
- mm_init_owner
- setup_command_line
- setup_nr_cpu_ids
- setup_per_cpu_areas
- smp_prepare_boot_cpu
- build_all_zonelists
- page_alloc_init
- pidhash_init
- vfs_caches_init_early
- sort_main_extable
- trap_init
- mm_init
- sched_init
- preempt_disable
- rcu_init
- radix_tree_init
- init_IRQ
- prio_tree_init
- init_timers
- hrtimers_init
- softirq_init
- timekeeping_init
- time_init
- profile_init
- early_boot_irqs_on
- local_irq_enable
- console_init
- lockdep_info
- locking_selftest
- idr_init_cache
- setup_per_cpu_pageset
- numa_policy_init
- calibrate_delay
- thread_info_cache_init
- fork_init
- proc_caches_init
- buffer_init
- key_init
- security_init
- vfs_caches_init
- signals_init
- cgroup_init
- taskstats_init_early
- delayacct_init
- 启动过程分析(rest_init)
Linux内核源码目录结构
- arch:与CPU体系结构相关的子目录列表
- block:存放Linux存储体系中关于块设备管理的代码
- crypto:存放常见的加密算法的C语言代码
- Documentation:用于存放文档
- drivers:驱动目录,里面分门别类地存放了Linux内核支持的所有硬件设备的驱动源代码
- firmware:固件目录
- fs:文件系统,列出了Linux支持的各种文件系统的实现。
- include:头文件目录,存放公共的头文件
- init:存放Linux内核启动时的初始化代码
- ipc:ipc目录里面是Linux支持的进程间通信的代码实现
- kernel:存放内核本身需要的一些核心代码文件,包括进程号pid等
- lib:公用的库文件
- mm:与内存管理相关
- net:存放网络相关的代码
调试Linux内核启动过程
使用gdb跟踪调试内核
断点调试与按步运行
启动过程分析(start_kernel)
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
lockdep_init();
set_task_stack_end_magic(&init_task);
smp_setup_processor_id();
debug_objects_early_init();
boot_init_stack_canary();
cgroup_init_early();
local_irq_disable();
early_boot_irqs_disabled = true;
boot_cpu_init();
page_address_init();
pr_notice("%s", linux_banner);
setup_arch(&command_line);
mm_init_cpumask(&init_mm);
setup_command_line(command_line);
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
build_all_zonelists(NULL, NULL);
page_alloc_init();
pr_notice("Kernel command line: %s
", boot_command_line);
parse_early_param();
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();
setup_log_buf(0);
pidhash_init();
vfs_caches_init_early();
sort_main_extable();
trap_init();
mm_init();
sched_init();
preempt_disable();
if (WARN(!irqs_disabled(),
"Interrupts were enabled *very* early, fixing it
"))
local_irq_disable();
idr_init_cache();
rcu_init();
context_tracking_init();
radix_tree_init();
early_irq_init();
init_IRQ();
tick_init();
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
");
early_boot_irqs_disabled = false;
local_irq_enable();
kmem_cache_init_late();
console_init();
if (panic_later)
panic("Too many boot %s vars at `%s'", panic_later,
panic_param);
lockdep_info();
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.
",
page_to_pfn(virt_to_page((void *)initrd_start)),
min_low_pfn);
initrd_start = 0;
}
#endif
page_cgroup_init();
debug_objects_mem_init();
kmemleak_init();
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
late_time_init();
sched_clock_init();
calibrate_delay();
pidmap_init();
anon_vma_init();
acpi_early_init();
#ifdef CONFIG_X86
if (efi_enabled(EFI_RUNTIME_SERVICES))
efi_enter_virtual_mode();
#endif
#ifdef CONFIG_X86_ESPFIX64
init_espfix_bsp();
#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();
page_writeback_init();
proc_root_init();
cgroup_init();
cpuset_init();
taskstats_init_early();
delayacct_init();
check_bugs();
sfi_init_late();
if (efi_enabled(EFI_RUNTIME_SERVICES)) {
efi_late_init();
efi_free_boot_services();
}
ftrace_init();
rest_init();
}
宏定义
asmlinkage宏定义作用主要是让传送给函数的参数全部使用栈式传送,不用寄存器来传送,由于寄存器数量有限,使用栈可以容纳更多参数。
__init用来标志这个函数编译出来的目标代码具体放在那一段里。
lockdep_init
这个函数主要作用是初始化锁的状态跟踪模块,内核中使用锁来进行多线程与多处理器的同步操作,该函数用于调试内核加锁顺序,检测死锁可能。
set_task_stack_end_magic
该函数设置整个系统的第一个进程,其中的参数init_task为系统创建的第一个进程,也就是第0号进程,是唯一没有通过fork或kernel_thread产生的进程,是进程列表的第一个。
smp_setup_processor_id
该函数目的是直接获取对应多处理器的ID,相比于smp_processor_id函数其不需要使用setup_arch函数进行初始化。
debug_objects_early_init
函数主要作用是对调试对象进行早期的初始化,大致是对HASH锁和静态对象池进行初始化。
cgroup_init_early
这个函数主要作用是控制组进行早期的初始化,而控制组就是定义一组进程具有相同资源的占有程度。
local_irq_disable
这个函数主要作用是关闭当前CPU的所有中断响应。
early_boot_irqs_off
这个函数主要作用是标记内核还在早期初始化代码阶段,并且中断在关闭状态,如果有任何中断打开或请求中断的事情出现,都是会提出警告,以便跟踪代码错误情况。等到早期代码初始化结束之后,就会调用函数early_boot_irqs_on来设置这个标志为真。
boot_cpu_init
这个函数主要作用是设置当前引导系统的CPU在物理上存在,在逻辑上可以使用,并且初始化准备好。
page_address_init
初始化高端内存的映射表,在32位机中系统仅能访问4G,内核能访问的空间就更小了,所以为了映射更多的空间在原有内存的基础上分出高端内存,以访问更多的物理内存空间。
setup_arch
对内核架构进行初始化。再次获取CPU类型和系统架构,分析引导程序传入的命令行参数,进行页面内存初始化,处理器初始化,中断早期初始化等等。
mm_init_owner
设置最开始的初始化任务属于init_mm内存,而init_mm为mm_struct内存描述符的结构体实例。
setup_command_line
保存命令行,以便后面可以使用,与前面声明的command_line指针对应,这个指针是指向命令行参数的指针,主要用来指向引导程序传送给内核的命令行参数,函数setup_arch和函数setup_command_line会对它进行处理。
setup_nr_cpu_ids
设置最多有多少个nr_cpu_ids结构,nr_cpu_ids是一个特殊的值,在单CPU情况下是1,而SMP情况下,则为一个全局变量。
setup_per_cpu_areas
设置SMP体系每个CPU使用的内存空间,同时拷贝初始化段里数据。
smp_prepare_boot_cpu
为SMP系统里引导CPU进行准备工作。
build_all_zonelists
初始化所有内存管理节点列表,以便后面进行内存管理初始化。
page_alloc_init
设置内存页分配通知器。
pidhash_init
初始化进程ID的hash表,以便通过PID进行高效访问进程结构的信息。LINUX里共有四种类型的PID,因此就有四种HASH表相对应。
vfs_caches_init_early
虚拟文件系统的缓存初始化。
sort_main_extable
对内核内部的异常表进行堆排序,以便加速访问。
trap_init
对中断向量进行初始化。
mm_init
标记那些内存可以使用,并且告诉系统还有多少内存(除内存使用的)可以使用。
sched_init
对进程调度器进行初始化,比如分配调度器占用的内存,初始化任务队列,为当前任务设置空线程。
preempt_disable
关闭优先级调度。由于每个进程任务都有优先级,目前系统还没有完全初始化,还不能打开优先级调度。
rcu_init
初始化直接读拷贝更新的锁机制。
radix_tree_init
初始化radix树,radix树是基于二进制键值的查找树。
init_IRQ
初始化中断相关的工作,主要初始化中断描述数组,然后调用每个CPU架构中断初始化。
prio_tree_init
初始化优先搜索树,主要用在内存反向搜索方面。
init_timers
初始化引导CPU的时钟相关的数据结构,注册时钟的回调函数,当时钟到达时可以回调时钟处理函数,最后初始化时钟软件中断处理。
hrtimers_init
初始化高精度的定时器。
softirq_init
初始化软件中断,软件中断是使用线程来监视中断信号,而硬件中断是使用CPU硬件来监视中断。
timekeeping_init
设置初始化系统时钟计时,初始化内核里与时钟计时相关的变量。
time_init
初始化系统时钟。
profile_init
分配内核性能统计保存的内存,以便统计的性能变量可以保存到这里。
early_boot_irqs_on
设置内核还在早期初始化阶段的标志,以便用来调试时输出信息,与上面的early_boot_irqs_off对应。
local_irq_enable
打开CPU的中断,允许本CPU处理中断事件,与上面的local_irq_disable对应。
console_init
初始化控制台,从这个函数之后就可以输出内容到控制台了,这个函数初化之前,都没有办法输出内容,就是输出,也是写到输出缓冲区里,缓存起来,等到这个函数调用之后,就立即输出内容。
lockdep_info
打印锁的依赖信息,用来调试锁,这个函数可以查看目前锁的状态,以便可以发现那些锁产生死锁,那些锁使用有问题。
locking_selftest
用来测试锁的API是否使用正常,进行自我测试。比如测试自旋锁、读写锁、一般信号量和读写信号量。
idr_init_cache
创建IDR机制的内存缓存对象,IDR就是整数标识管理机制,用于管理整数的ID与对象的指针的关系,由于这个ID可以达到32位,也就是说,如果使用线性数组来管理,那么分配的内存太大了;如果使用线性表来管理,又效率太低了,所以就引用IDR管理机制来实现这个需求。
setup_per_cpu_pageset
创建每个CPU的高速缓存集合数组。因为每个CPU都不定时需要使用一些页面内存和释放页面内存,为了提高效率,就预先创建一些内存页面作为每个CPU的页面集合。
numa_policy_init
初始化NUMA的内存访问策略。NUMA(NonUniform Memory AccessAchitecture)主要用来提高多个CPU访问内存的速度。多个CPU访问同一个节点的内存速度远远比访问多个节点的速度来得快。
calibrate_delay
主要计算CPU需要校准的时间,对应的时间是CPU执行时间。
thread_info_cache_init
初始化线程信息的缓存。
fork_init
根据当前物理内存计算出来可以创建进程(线程)的数量,并进行进程环境初始化。
proc_caches_init
进程缓存初始化。
buffer_init
初始化文件系统的缓冲区,并计算最大可以使用的文件缓存。
key_init
初始化安全键管理列表和结构。
security_init
初始化安全管理框架,以便提供访问文件/登录等权限。
vfs_caches_init
对虚拟文件系统进行缓存初始化,提高虚拟文件系统的访问速度。
signals_init
初始化信号队列缓存。
cgroup_init
初始化进程控制组,主要用来为进程和其子程提供性能控制。比如限定这组进程的CPU使用率.
taskstats_init_early
初始化任务状态相关的缓存、队列和信号量。任务状态主要向用户提供任务的状态信息。
delayacct_init
初始化每个任务延时计数。当一个任务等CPU运行,或者等IO同步时,都需要计算等待时间。
启动过程分析(rest_init)
static noinline void __init_refok rest_init(void)
{
int pid;
rcu_scheduler_starting();
kernel_thread(kernel_init, NULL, CLONE_FS);
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);
init_idle_bootup_task(current);
schedule_preempt_disabled();
cpu_startup_entry(CPUHP_ONLINE);
}
rest_init中调用kernel_thread函数启动了2个内核线程,分别是:kernel_init和kthreadd。之后调用schedule函数开启内核的调度系统,使系统开始运转。最终其结束了整个内核的启动,以一个死循环的方式保持系统运行,linux内核最终的状态是:有任务的时候去执行各个进程任务,没有任务执行就运行死循环(空闲进程)维持。
- 进程0:进程0其实就是空闲进程(死循环)以维持系统运行。
- 进程1:kernel_init函数就是进程1,这个进程被称为init进程,也是第一个用户进程。
- 进程2:kthreadd函数就是进程2,这个进程是linux内核的守护进程。它的作用是管理调度其他内核进程,保证linux内核自身能正常工作的。