一、实验楼实验
使用实验楼的虚拟机打开shell
1 cd LinuxKernel/
2 qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
内核启动完成后进入menu程序,支持三个命令help、version和quit
使用gdb跟踪调试内核
1 qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S # 关于-s和-S选项的说明:
2 # -S freeze CPU at startup (use ’c’ to start execution)
3 # -s shorthand for -gdb tcp::1234 若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项
- 另开一个shell窗口
1 gdb
2 (gdb)file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
3 (gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
4 (gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后
按c后系统开始运行,启动到start_cernel的位置
输入list之后看到执行的位置
再设置一个断点,系统执行到rest_init的位置
二、简单分析一下start_kernel
1、在init目录下main.c里找到start_kernel函数
500asmlinkage __visible void __init start_kernel(void)
501{
502char *command_line;
503char *after_dashes;
504
505/*
506 * Need to run as early as possible, to initialize the
507 * lockdep hash:
508 */
509lockdep_init();
510set_task_stack_end_magic(&init_task); //全局变量init_task,即手工创建的PCB,0号进程即最终的idle进程
511smp_setup_processor_id();
512debug_objects_early_init();
513
514/*
515 * Set up the the initial canary ASAP:
516 */
517boot_init_stack_canary();
518
519cgroup_init_early();
520
521local_irq_disable();
522early_boot_irqs_disabled = true;
2、初始化一些中断向量 trap_init()
- 中断向量表的初始化函数,设置了很多中断门(Interrupt Gate)
set_intr_gate
:设置中断门
3、mm_init() 内存管理模块
void __init trap_init(void)
793{
794int i;
795
796#ifdef CONFIG_EISA
797void __iomem *p = early_ioremap(0x0FFFD9, 4);
798
799if (readl(p) == 'E' + ('I'<<8) + ('S'<<16) + ('A'<<24))
800EISA_bus = 1;
801early_iounmap(p, 4);
802#endif
803
804set_intr_gate(X86_TRAP_DE, divide_error);
805set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK);
806/* int4 can be called from all */
807set_system_intr_gate(X86_TRAP_OF, &overflow);
808set_intr_gate(X86_TRAP_BR, bounds);
809set_intr_gate(X86_TRAP_UD, invalid_op);
810set_intr_gate(X86_TRAP_NM, device_not_available);
811#ifdef CONFIG_X86_32
812set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS);
813#else
814set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK);
815#endif
816set_intr_gate(X86_TRAP_OLD_MF, coprocessor_segment_overrun);
817set_intr_gate(X86_TRAP_TS, invalid_TSS);
818set_intr_gate(X86_TRAP_NP, segment_not_present);
819set_intr_gate(X86_TRAP_SS, stack_segment);
820set_intr_gate(X86_TRAP_GP, general_protection);
821set_intr_gate(X86_TRAP_SPURIOUS, spurious_interrupt_bug);
822set_intr_gate(X86_TRAP_MF, coprocessor_error);
823set_intr_gate(X86_TRAP_AC, alignment_check);
824#ifdef CONFIG_X86_MCE
825set_intr_gate_ist(X86_TRAP_MC, &machine_check, MCE_STACK);
826#endif
827set_intr_gate(X86_TRAP_XF, simd_coprocessor_error);
828
829/* Reserve all the builtin and the syscall vector: */
830for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)
831set_bit(i, used_vectors);
832
833#ifdef CONFIG_IA32_EMULATION
834set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);
835set_bit(IA32_SYSCALL_VECTOR, used_vectors);
836#endif
837
838#ifdef CONFIG_X86_32
839set_system_trap_gate(SYSCALL_VECTOR, &system_call); //系统陷阱门,也是个系统调用
840set_bit(SYSCALL_VECTOR, used_vectors);
841#endif
4、sched_init() 调度模块
5、
其它模块初始化 rest_init()
- init_process是一号进程。
393static noinline void __init_refok rest_init(void)
394{
395int pid;
396
397rcu_scheduler_starting();
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 */
403kernel_thread(kernel_init, NULL, CLONE_FS); //创建第一个用户态进程
404numa_default_policy();
405pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); //内核进程来管理资源
406rcu_read_lock();
407kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
408rcu_read_unlock();
409complete(&kthreadd_done);
三、总结
总的来说在rest_init函数中,内核产生第一个真正的进程:
sched_init()初始化函数内对0号进程,即idle进程进行初始化rest_init()->cpu_startup_entry()->cpu_idle_loop()就是一直循环,当系统没有进程需要执行的时候就调度idle进程,从内核启动开始一直存在,0号进程。0号进程创建了1号进程,并且还创建了内核中其他的一些服务线程。
(pid=1):init_idle(current, smp_processor_id())函数的调用把init_task初始化成idle task
init_idle函数的第一个参数current就是&init_task,在init_idle中将会把init_task加入到cpu的运行队列中,这样当运行队列中没有别的就绪进程时,init_task(也就是idle task)将会被调用。
init目录下main.c中的start_kernel是启动内核的起点。