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 }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章