前面我们学习一些函数调用堆栈和进程切换的一些知识,现在开始来接触linux的内核代码。
Linux内核代码很庞大,不可能完全吃下它,我们只能选取一些核心的我们关心的来解读。
现在,我们先来看看Linux内核的目录结构,选取比较新的Linux-3.18.6这个版本来看一看。
Linux内核代码目录结构
arch : 针对不同的计算机体系结构
block : 块设备驱动
crypto
documentation : 内核文档
drivers : 设备驱动
fs : 文件系统
include : 头文件
init : 初始化
ipc : 进程间通信
kernel : Linux大多数关键的核心功能都在此目录
lib : 库
mm : 内存管理
net : 网络协议
samples :
scripts: 配置内核的脚本
security :
sound : 音频设备驱动
usr :
virt :
启动内核
使用实验楼环境,内核启动完成后进入menu程序(《软件工程C编码实践篇》的课程项目),支持三个命令help、version和quit。实验截图如下
gdb跟踪调试
我们主要关心的是start_kernel的启动过程,使用gdb在关键位置设置断点观察。
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
# -S freeze CPU at startup (use ’c’ to start execution)
# -s shorthand for -gdb tcp::1234 若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项
分析代码
start_kernel的详细代码,可以看到,里面调用了很多函数,这些都是重量级函数,如
setup_arch(&command_line) ,完成内存映像的初始化;
page_alloc_init(),创建内核页表,映射所有物理内存和io空间;
trap_init(),初始化硬件中断,函数中设置了很多中断门;
sched_init(),任务调度初始化。
还有很多重要初始化工作都在这里进行,就不一一列举了。
我们来看看start_kernel里面最后一个调用的函数rest_init()。
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);
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);
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);
}
kernel_thread()创建了一个线程,其参数kernel_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;
执行/sbin/init程序。init进程是Linux系统的1号进程,由Linux内核直接启动,是其他用户进程的祖先。
然后新建kthread进程,即2号进程,是内核态进程的祖先。
总结
linux内核很复杂,这里只是分析从start_kernel到init,来理解linux的第一个用户进程的产生过程。然而这只是九牛一毛,还需要做很多很多的工作才能更深入的了解操作系统的内核。