作者:于波
声明:原创作品,转载请注明出处
参考:《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
这是网易云课堂《Linux内核分析》课程第三周的作业,要求分析Linux内核从start_kernel到init进程启动的过程。
一、实验环境搭建:
首先按照课程帮助在自己的机器上建立实验环境。需要一个本地的Ubuntu,我用的版本是64位的14.04。
建立一个目录,下载老师给我们准备好的Linux源代码,解压,编译,编译的结果是一个32位的Linux内核。
cd ~/LinuxKernel/
wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.6.tar.xz
xz -d linux-3.18.6.tar.xz
tar -xvf linux-3.18.6.tar
cd linux-3.18.6
make i386_defconfig
make
然后我们要创建根文件系统,并从这个跟文件系统来启动我们的Linux内核。步骤如下:
cd ~/LinuxKernel/
mkdir rootfs
git clone https://github.com/mengning/menu.git
cd menu
gcc -o init linktable.c menu.c test.c -m32 -static –lpthread
# -m32表示在64位机器上编译32位程序,
# 如果编译时提示头文件缺失的错误,很可能是因为还没有安装32位的开发包,
# 可以尝试sudo apt-get install libc6-dev-i386 来安装
cd ../rootfs
cp ../menu/init ./
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img
根文件系统制作完成之后,就可以用虚拟机启动Linux内核了:
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
为了跟踪内核代码的执行过程,需要先在运行的内核中加入调试信息,这样我们就能用GDB在我们感兴趣的代码行上设置断点,观察程序的执行过程了。添加方法是在linux源代码目录下用make menuconfig命令选中kernel hacking项目的compile the kernel with debug info项,然后重新编译内核,也就是在linux-3.18.6目录下重新执行make, 编译时间会比较长,需要耐心等待。选项的具体位置如下图所示:
重新编译完成之后,我们的内核中就包含调试信息了,也就是每条指令都可以回溯到对应的源代码了,下面就可以用gdb进行调试了。
用GDB调试内核的方法是,首先用qemu启动系统的根文件系统,并添加-s和-S选项,-S选项的作用是让系统启动之后就挂起,等待用户指令再继续运行,而-s是一个gdb命令的缩写,相当于-gdb tcp:1234,如果想换用其他的端口号,就直接使用相应的gdb命令代替-s。
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
接下来就可以另起一个终端,启动gdb,加载系统的符号表,连接到之前启动的内核进行调试了。
二、内核启动过程分析
start_kernel函数定义在init/main.c中,我们看看他都干了些什么事情。分析见程序中的中文注释。
500 asmlinkage __visible void __init start_kernel(void)
501 {
502 char *command_line;
503 char *after_dashes;
504
505 /*
506 * Need to run as early as possible, to initialize the
507 * lockdep hash:
508 */
509 lockdep_init(); /* 初始化两个全局的哈希表结构,classhash_table和chainhash_table */
510 set_task_stack_end_magic(&init_task);
/* 在0号进程的栈底位置设置一个值为0x57AC6E9D的魔术值,用于栈溢出检测 */
511 smp_setup_processor_id(); /*单核机器上什么都不做,多核机器上设置进程的启动CPU号*/
512 debug_objects_early_init(); /**/
513
514 /*
515 * Set up the the initial canary ASAP:
516 */
517 boot_init_stack_canary(); /* 初始化用于栈保护的随机数 */
518
519 cgroup_init_early(); /* 初始化进程组 */
520
521 local_irq_disable(); /* 暂时关闭中断响应,并记录在全局标志中 */
522 early_boot_irqs_disabled = true;
523
524 /*
525 * Interrupts are still disabled. Do necessary setups, then
526 * enable them
527 */
528 boot_cpu_init(); /* 设置当前CPU的上线和激活标志 */
529 page_address_init(); /* 初始化分页地址表 */
530 pr_notice("%s", linux_banner); /* 在屏幕上输出Linux的旗标 */
531 setup_arch(&command_line); /* 根据/proc/cmdline中的信息初始化体系相关的CPU,内存机IO数据,
这个函数会根据选择的CPU类型执行不同体系结构下响应的函数 */
532 mm_init_cpumask(&init_mm); /* 初始化CPU屏蔽字为全0,也就是不屏蔽任何CPU */
533 setup_command_line(command_line); /* 将启动命令行保存起来 */
534 setup_nr_cpu_ids(); /* 设置CPU的最大ID,在单核CPU上,不做任何事情 */
535 setup_per_cpu_areas(); /* 初始化每个CPU自己的私有数据 */
536 smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
537
538 build_all_zonelists(NULL, NULL);
539 page_alloc_init();
540
541 pr_notice("Kernel command line: %s\n", boot_command_line);
542 parse_early_param();
543 after_dashes = parse_args("Booting kernel",
544 static_command_line, __start___param,
545 __stop___param - __start___param,
546 -1, -1, &unknown_bootoption);
547 if (!IS_ERR_OR_NULL(after_dashes))
548 parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
549 set_init_arg);
550
551 jump_label_init();
552
553 /*
554 * These use large bootmem allocations and must precede
555 * kmem_cache_init()
556 */
557 setup_log_buf(0);
558 pidhash_init();
559 vfs_caches_init_early();
560 sort_main_extable();
561 trap_init();
562 mm_init();
563
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 */
574 preempt_disable();
575 if (WARN(!irqs_disabled(),
576 "Interrupts were enabled *very* early, fixing it\n"))
577 local_irq_disable();
578 idr_init_cache();
579 rcu_init();
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();
588 hrtimers_init();
589 softirq_init();
590 timekeeping_init();
591 time_init();
592 sched_clock_postinit();
593 perf_event_init();
594 profile_init();
595 call_function_init();
596 WARN(!irqs_disabled(), "Interrupts were enabled early\n");
597 early_boot_irqs_disabled = false;
598 local_irq_enable();
599
600 kmem_cache_init_late();
601
602 /*
603 * HACK ALERT! This is early. We're enabling the console before
604 * we've done PCI setups etc, and console_init() must be aware of
605 * this. But we do want output early, in case something goes wrong.
606 */
607 console_init();
608 if (panic_later)
609 panic("Too many boot %s vars at `%s'", panic_later,
610 panic_param);
611
612 lockdep_info();
613
614 /*
615 * Need to run this when irqs are enabled, because it wants
616 * to self-test [hard/soft]-irqs on/off lock inversion bugs
617 * too:
618 */
619 locking_selftest();
620
621 #ifdef CONFIG_BLK_DEV_INITRD
622 if (initrd_start && !initrd_below_start_ok &&
623 page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
624 pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it. \n",
625 page_to_pfn(virt_to_page((void *)initrd_start)),
626 min_low_pfn);
627 initrd_start = 0;
628 }
629 #endif
630 page_cgroup_init();
631 debug_objects_mem_init();
632 kmemleak_init();
633 setup_per_cpu_pageset();
634 numa_policy_init();
635 if (late_time_init)
636 late_time_init();
637 sched_clock_init();
638 calibrate_delay();
639 pidmap_init();
640 anon_vma_init();
641 acpi_early_init();
642 #ifdef CONFIG_X86
643 if (efi_enabled(EFI_RUNTIME_SERVICES))
644 efi_enter_virtual_mode();
645 #endif
646 #ifdef CONFIG_X86_ESPFIX64
647 /* Should be run before the first non-init thread is created */
648 init_espfix_bsp();
649 #endif
650 thread_info_cache_init();
651 cred_init();
652 fork_init(totalram_pages);
653 proc_caches_init();
654 buffer_init();
655 key_init();
656 security_init();
657 dbg_late_init();
658 vfs_caches_init(totalram_pages);
659 signals_init();
660 /* rootfs populating might need page-writeback */
661 page_writeback_init();
662 proc_root_init();
663 cgroup_init();
664 cpuset_init();
665 taskstats_init_early();
666 delayacct_init();
667
668 check_bugs();
669
670 sfi_init_late();
671
672 if (efi_enabled(EFI_RUNTIME_SERVICES)) {
673 efi_late_init();
674 efi_free_boot_services();
675 }
676
677 ftrace_init();
678
679 /* Do the rest non-__init'ed, we're now alive */
680 rest_init();
681 }