原创作品转载请注明出处,《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
一、实验环境准备
1、编译源代码:
1 2 3 4 5 6 7 |
mkdir kernel cd kernel wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.6.tar.xz tar xvf linux-3.18.6.tar.xz #解压代码 cd linux-3.18.6 make i386_defconfig #采用x86架构的默认配置 make #开始编译 |
2、 制作initrd
1 2 3 4 5 6 7 8 |
cd ~/kernel mkdir rootfs git clone https://github.com/mengning/menu.git cd menu gcc -o init linktable.c menu.c test.c -m32 -static -lpthread cd ../rootfs cp ../menu/init ./ find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img |
3、尝试运行
1 2 |
cd ~/kernel qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img |
4、重新编译内核,打开调试功能
1 2 3 4 5 |
make menuconfig 选中:Kernel hacking --> Compile-time checks and compiler options --> Compile the kernel with debug info make #开始编译 |
5、附:上诉操作所依赖的组件
1 2 3 4 5 |
apt-get install libncurses5-dev #运行menuconfig的必须 apt-get install lib32readline-gplv2-dev #安装32位库等,实现64位系统编译32位程序 |
二、调试内核启动进程
1、qemu启动内核,同时打开调试端口。
1 2 3 |
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S # -S 启动时中断内核执行(按'c',意为continue,继续执行) # -s 代表参数: -gdb tcp::1234 |
2、运行gdb开始调试
1 2 3 4 5 6 7 8 9 |
gdb (gdb)file linux-3.18.6/vmlinux # 加载内核符号表 (gdb)target remote:1234 # 连接gdbserver (gdb)break start_kernel # 在start_kernel()处设置断点 (gdb)c # 输入‘c’让内核继续执行 |
如下图,内核在运行到start_kernel( )的时候终止执行
三、关键代码分析
1、start_kernel( ) 函数,进行一系列内核初始化准备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
/* file: linux-3.18.6/init/main.c */ 500 asmlinkage __visible void __init start_kernel(void) 501 { 502 char *command_line; 503 char *after_dashes; 509 lockdep_init(); /* 初始化hash表(全局锁链表)*/ 510 set_task_stack_end_magic(&init_task); /*0号进程,最终的idle进程*/ ...... ...... 521 local_irq_disable(); /*关闭CPU中断,保证初始化过程完成*/ ...... ...... 528 boot_cpu_init(); 529 page_address_init(); /*初始化内存页*/ 530 pr_notice("%s", linux_banner); /*显示linux版本信息*/ 531 setup_arch(&command_line); /*设置相应的CPU架构*/ 532 mm_init_cpumask(&init_mm); 533 setup_command_line(command_line); 534 setup_nr_cpu_ids(); 535 setup_per_cpu_areas(); ...... ...... 561 trap_init(); /*设置各种“陷阱门”,应该就是各种系统调用以及中断程序*/ 562 mm_init(); /*初始化内存管理*/ 564 /* 565 * Set up the scheduler prior starting any interrupts (such as the 566 * timer interrupt). Full topology setup happens at smp_init() 567 * time - but meanwhile we still have a functioning scheduler. 568 */ 569 sched_init(); /*进程调度程序初始化*/ 570 /* 571 * Disable preemption - early bootup scheduling is extremely 572 * fragile until we cpu_idle() for the first time. 573 */ ...... ...... 579 rcu_init(); /*初始化内核的Read-Copy Update(RCU)特性*/ 580 context_tracking_init(); 581 radix_tree_init(); 582 /* init some links before init_ISA_irqs() */ 583 early_irq_init(); 584 init_IRQ(); /*初始化进程通讯相关程序*/ 585 tick_init(); 586 rcu_init_nohz(); 587 init_timers(); ...... ...... 607 console_init(); /*初始化控制台,之后才能显示输出的信息*/ ...... ...... 652 fork_init(totalram_pages); /*根据内存大小计算可创建进程数量*/ ...... ...... 668 check_bugs(); /*检测CPU缺陷*/ ...... ...... 679 /* Do the rest non-__init'ed, we're now alive */ 680 rest_init(); /*最后创建init进程,即1号进程,第一个用户态进程*/ 681 } |
2、init进程的启动 rest_init( )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
/* file: linux-3.18.6/init/main.c */ 391 static __initdata DECLARE_COMPLETION(kthreadd_done); 392 /*定义一个complete变量,用于将kthreadd进程创建完毕的消息传递给kernel_init */ 393 static noinline void __init_refok rest_init(void) 394 { 395 int pid; 396 397 rcu_scheduler_starting(); /*启动内核的RCU锁机制*/ 398 /* 399 * We need to spawn init first so that it obtains pid 1, however 400 * the init task will end up wanting to create kthreads, which, if 401 * we schedule it before we create kthreadd, will OOPS. 402 */ 403 kernel_thread(kernel_init, NULL, CLONE_FS); /*创建kernel_init内核进程,pid=1*/ 404 numa_default_policy(); 405 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); /*创建kthreadd进程,pid=2*/ 406 rcu_read_lock(); 407 kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns); /*获取kthread的相关信息*/ 408 rcu_read_unlock(); 409 complete(&kthreadd_done); /*通知kernel_init进程kthreadd已经创建成功*/ 410 411 /* 412 * The boot idle thread must execute schedule() 413 * at least once to get things moving: 414 */ 415 init_idle_bootup_task(current); /*将当前进程设为idle进程*/ 416 schedule_preempt_disabled(); /* * 启动抢占,然后执行调度,最后再禁用抢占 * 稍后有对该段代码的分析 */ 417 /* Call into cpu_idle with preempt disabled */ 418 cpu_startup_entry(CPUHP_ONLINE); /*内核进入idle状态,循环消耗CPU周期*/ 419 } |
3、 schedule_preempt_disabled( )的细节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/* file: linux-3.18.6/kernel/sched/core.c */ ...... ...... 2898 void __sched schedule_preempt_disabled(void) 2899 { 2900 sched_preempt_enable_no_resched(); /*启用内核的抢占功能*/ 2901 schedule(); /*执行进程调度*/ 2902 preempt_disable(); /*禁用内核的抢占功能*/ 2903 } ...... ...... |
4、附kernel_init() 函数的内容,未分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
/* file: linux-3.18.6/init/main.c */ 930 static int __ref kernel_init(void *unused) 931 { 932 int ret; 933 934 kernel_init_freeable(); 935 /* need to finish all async __init code before freeing the memory */ 936 async_synchronize_full(); 937 free_initmem(); 938 mark_rodata_ro(); 939 system_state = SYSTEM_RUNNING; 940 numa_default_policy(); 941 942 flush_delayed_fput(); 943 944 if (ramdisk_execute_command) { 945 ret = run_init_process(ramdisk_execute_command); 946 if (!ret) 947 return 0; 948 pr_err("Failed to execute %s (error %d)\n", 949 ramdisk_execute_command, ret); 950 } 951 952 /* 953 * We try each of these until one succeeds. 954 * 955 * The Bourne shell can be used instead of init if we are 956 * trying to recover a really broken machine. 957 */ 958 if (execute_command) { 959 ret = run_init_process(execute_command); 960 if (!ret) 961 return 0; 962 pr_err("Failed to execute %s (error %d). Attempting defaults...\n", 963 execute_command, ret); 964 } 965 if (!try_to_run_init_process("/sbin/init") || 966 !try_to_run_init_process("/etc/init") || 967 !try_to_run_init_process("/bin/init") || 968 !try_to_run_init_process("/bin/sh")) 969 return 0; 970 971 panic("No working init found. Try passing init= option to kernel. " 972 "See Linux Documentation/init.txt for guidance."); 973 } |
四、作业小结
(对“Linux系统启动过程”的理解)
详细了解了linux内核的启动过程,心里真是舒畅啊,windows用户基本上应该没有我们的这种机会啦,哈哈。
好了,现在说正事。BIOS加载引导程序的那些我们暂且不谈,就从内核被引导开始说起吧。
初期,也就是start_kernel() 之前,最基本的准备阶段,应该用到了大量的汇编代码初始化硬件用来准备内核启动环境。然后进入start_kernel() ,在这里进行了进一步的各种初始化、环境准备,比如内核锁、调度程序、内存页等的初始化操作,是用户态进程运行的基础。
在start_kernel() 的最后,调用了函数rest_init() ,在该函数里启动了第一个用户态进程 -- init,然后CPU进入idle状态,当前进程也变成了传说中的idle进程。之后init就开始不断地fork各种进程,一个可以操作的linux系统也就成功启动了。
参考资料:
《Linux 内核启动代码简析》有1个想法