Linux内核分析(三)Linux内核启动过程分析

作者:于波

声明:原创作品,转载请注明出处

参考:《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 }

猜你喜欢

转载自blog.csdn.net/yubo112002/article/details/82527014