这是网易云课堂《Linux 内核分析》这门课的作业
先上实验截图
实验步骤:
1 # 下载内核源代码编译内核 2 cd ~/LinuxKernel/ 3 wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.6.tar.xz 4 xz -d linux-3.18.6.tar.xz 5 tar -xvf linux-3.18.6.tar 6 cd linux-3.18.6 7 make i386_defconfig 8 make # 一般要编译很长时间,少则20分钟多则数小时 9 10 # 制作根文件系统 11 cd ~/LinuxKernel/ 12 mkdir rootfs 13 git clone https://github.com/mengning/menu.git 14 cd menu 15 gcc -o init linktable.c menu.c test.c -m32 -static –lpthread 16 cd ../rootfs 17 cp ../menu/init ./ 18 find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img 19 20 # 启动MenuOS系统 21 cd ~/LinuxKernel/ 22 qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
先看看几个重要的Linux代码文件夹结构:
/linux-3.18.6/arch 支持不同CPU的源代码
/linux-3.18.6/arch/x86 X86体系的代码
/linux-3.18.6/drivers 驱动相关
/linux-3.18.6/firmware 硬件相关
/linux-3.18.6/init 启动相关
/linux-3.18.6/ipc 进程通信
/linux-3.18.6/kernel 内核
/linux-3.18.6/lib 库文件
/linux-3.18.6/mm 内存管理
/linux-3.18.6/net 网络
内核启动的起点在/linux-3.18.6/init/main.c 的 asmlinkage __visible void __init start_kernel(void) 函数。这个函数非常复杂了,我现在的水平只能走马观花的看看。以后继续深入研究相关模块的时候再回来精读。
asmlinkage __visible void __init start_kernel(void) { lockdep_init(); // 这个函数主要作用是初始化锁的状态跟踪模块。锁的异常状态包括:
// 1. 同一个进程递归地加锁同一把锁。
// 2. 同一把锁在两次中断里加锁。
// 3. 几把锁形成一个闭环死锁。
set_task_stack_end_magic(&init_task); // 设置一个magic number到初始task的栈底,做栈溢出保护
smp_setup_processor_id(); // 获取当前CPU, 如果是单CPU这个函数是空的
debug_objects_early_init(); // 对调试对象进行早期的初始化
cgroup_init_early(); // 对控制组进行早期的初始化。控制组是用来分配资源用的。
local_irq_disable(); // 关闭中断响应
boot_cpu_init(); // 设置CPU可用, 内核维护了两个bit map, cpu_online_map 和 cpu_possible_map 用来指示当前多少CPU可用和最多多少CPU可用
page_address_init(); // 初始化高地址内存的映射表。这个主要是为32位系统做的,因为32位系统只有4G内存空间,其中内核占用1G空间,如果要访问高于1G空 // 间的内存就需要使用映射表管理。
setup_arch(&command_line); // 这个函数主要作用是对内核架构进行初始化。分析引导程序传入的命令行参数,进行页面内存初始化,处理器初始化,中断早期 // 初始化等等。
setup_command_line(command_line); // 保存命令行
setup_nr_cpu_ids(); // 设置最多有多少个nr_cpu_ids结构
setup_per_cpu_areas(); // 设置每个CPU使用的内存空间
trap_init(); // 初始化中断向量
mm_init(); // 初始化内存
sched_init(); // 调度初始化
rest_init(); // 启动0号和1号进程
}
trap.c下的 void __init trap_init(void) 初始化了各种硬件中断和系统调用,像这样:
set_intr_gate(X86_TRAP_TS, invalid_TSS); #ifdef CONFIG_X86_32 set_system_trap_gate(SYSCALL_VECTOR, &system_call); set_bit(SYSCALL_VECTOR, used_vectors); #endif
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); // 创建1号进程 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); // 进入idle循环 }
总结:Linux启动过程会执行start_kernel函数,在此之前都是些硬件准备。在start_kernel里会初始化各种模块,比如内存管理,进程调度等等。然后会初始化和启动1号进程,1号进程就是在根目录名为init的进程,这是第一个用户态进程。随后执行idle循环,等待有新的进程开始切换过去。
刘聪 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000