1. 程式人生 > >2018-2019-1 20189215 《Linux內核原理與分析》第四周作業

2018-2019-1 20189215 《Linux內核原理與分析》第四周作業

知識 led 感覺 ini processor 所有 空行 policy 加密算法

《庖丁解牛》第三章書本知識總結

  1. 計算機的三大法寶

    存儲程序計算機
    函數調用堆棧
    中斷

  2. 操作系統的兩把寶劍

    中斷上下文的切換——保存現場和恢復現場
    進程上下文的切換

  3. Linux內核源碼的目錄結構如下所示。

    技術分享圖片

  4. 關鍵的目錄

    arch:arch目錄在Linux內核目錄中占比相當龐大,主要原因是arch目錄中的代碼可以使Linux內核支持不同的CPU和體系結構。
    block:存放Linux存儲體系中關於塊設備管理的代碼。
    crypto:存放常見的加密算法的C語言代碼,譬如crc32、md5、sha1等。
    drives:驅動目錄。
    fs:文件系統(File System)。
    init:init是初始化的意思,存放Linux內核啟動時的初始化代碼。
    ipc:Linux系統支持的IPC(進程間通信)的代碼實現。
    kernel:存放內核本身需要的一些核心代碼文件,其中有很多關鍵代碼,包括pid——進程號等。
    lib:公用的庫文件,裏面是一些公用的庫函數。需要註意的 是在內核編程中不能用C函數標準庫函數


    mm: 內存管理,存放Linux的內存管理代碼。
    net:存放網絡相關的代碼,譬如TCP/IP的協議棧等。

  5. init目錄下有main.c源文件,它是整個Linux內核啟動的起點,但它的起點不是main函數,而是start_kernel函數,start_kernel函數是初始化Linux內核啟動的起點,start_kerne前的代碼使用匯編語言來進行硬件初始化。
  6. qemu仿真kernel。
  7. bzImagevmlinux經過壓縮後的文件,是壓縮的內核映像;vmlinux是編譯出來的最原始的內核ELF文件。
  8. qemu命令中的-s-S參數,-s表示用1234端口上的gdb-server連接,可以用-gdb tcp:xxxx

    來代替;-S表示在CPU初始化之前凍結,使用c繼續執行。

實驗:跟蹤分析Linux內核的啟動過程

  1. 運行內核
    技術分享圖片
  2. 重新運行,加上參數,內核被凍結
    技術分享圖片
  3. 調試內核
    技術分享圖片
  4. 設置斷點到start_kernel
    技術分享圖片
  5. 設置斷點到rest_init
    技術分享圖片

    代碼分析

  • start_kernel()(為了減少點空間,我把所有註釋和空行刪掉了)
500 asmlinkage __visible void __init start_kernel(void)
{
    char *command_line;
    char *after_dashes;

    lockdep_init();
510 set_task_stack_end_magic(&init_task);
    smp_setup_processor_id();
    debug_objects_early_init();
    boot_init_stack_canary();
    cgroup_init_early();
    local_irq_disable();
    early_boot_irqs_disabled = true;
    boot_cpu_init();
    page_address_init();
    pr_notice("%s", linux_banner);
    setup_arch(&command_line);
    mm_init_cpumask(&init_mm);
    setup_command_line(command_line);
    setup_nr_cpu_ids();
    setup_per_cpu_areas();
    smp_prepare_boot_cpu();
    build_all_zonelists(NULL, NULL);
    page_alloc_init();
    pr_notice("Kernel command line: %s\n", boot_command_line);
    parse_early_param();
    after_dashes = parse_args("Booting kernel",
                  static_command_line, __start___param,
                  __stop___param - __start___param,
                  -1, -1, &unknown_bootoption);
    if (!IS_ERR_OR_NULL(after_dashes))
        parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
               set_init_arg);
    jump_label_init();
    setup_log_buf(0);
    pidhash_init();
    vfs_caches_init_early();
    sort_main_extable();
    trap_init();
    mm_init();
    sched_init();
    preempt_disable();
    if (WARN(!irqs_disabled(),
         "Interrupts were enabled *very* early, fixing it\n"))
        local_irq_disable();
    idr_init_cache();
    rcu_init();
    context_tracking_init();
    radix_tree_init();
    early_irq_init();
    init_IRQ();
    tick_init();
    rcu_init_nohz();
    init_timers();
    hrtimers_init();
    softirq_init();
    timekeeping_init();
    time_init();
    sched_clock_postinit();
    perf_event_init();
    profile_init();
    call_function_init();
    WARN(!irqs_disabled(), "Interrupts were enabled early\n");
    early_boot_irqs_disabled = false;
    local_irq_enable();
    kmem_cache_init_late();
    console_init();
    if (panic_later)
        panic("Too many boot %s vars at `%s‘", panic_later,
              panic_param);
    lockdep_info();
    locking_selftest();
#ifdef CONFIG_BLK_DEV_INITRD
    if (initrd_start && !initrd_below_start_ok &&
        page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
        pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
            page_to_pfn(virt_to_page((void *)initrd_start)),
            min_low_pfn);
        initrd_start = 0;
    }
#endif
    page_cgroup_init();
    debug_objects_mem_init();
    kmemleak_init();
    setup_per_cpu_pageset();
    numa_policy_init();
    if (late_time_init)
        late_time_init();
    sched_clock_init();
    calibrate_delay();
    pidmap_init();
    anon_vma_init();
    acpi_early_init();
#ifdef CONFIG_X86
    if (efi_enabled(EFI_RUNTIME_SERVICES))
        efi_enter_virtual_mode();
#endif
#ifdef CONFIG_X86_ESPFIX64
    init_espfix_bsp();
#endif
    thread_info_cache_init();
    cred_init();
    fork_init(totalram_pages);
    proc_caches_init();
    buffer_init();
    key_init();
    security_init();
    dbg_late_init();
    vfs_caches_init(totalram_pages);
    signals_init();
    page_writeback_init();
    proc_root_init();
    cgroup_init();
    cpuset_init();
    taskstats_init_early();
    delayacct_init();
    check_bugs();
    sfi_init_late();
    if (efi_enabled(EFI_RUNTIME_SERVICES)) {
        efi_late_init();
        efi_free_boot_services();
    }
    ftrace_init();
    rest_init();
}

main.c中沒有main函數,start_kernel是一切的起點,可以看到本函數中全是初始化的調用。
不論分析內核的哪一部分,都會涉及到start_kernel,因為基本上所有模塊的初始化都在main.cstart_kernel來調用。

  • 標註510行的代碼set_task_stack_end_magic(&init_task);
    init_task即手工創建的PCB,是0號進程,也是最終的idle進程,init_task是唯一的沒有通過fork方式產生的進程。
  • rest_init()
static noinline void __init_refok rest_init(void)
{
    int pid;

    rcu_scheduler_starting();
    /*
     * We need to spawn init first so that it obtains pid 1, however
     * the init task will end up wanting to create kthreads, which, if
     * we schedule it before we create kthreadd, will OOPS.
     */
    kernel_thread(kernel_init, NULL, CLONE_FS);
    numa_default_policy();
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    rcu_read_lock();
    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    rcu_read_unlock();
    complete(&kthreadd_done);

    /*
     * The boot idle thread must execute schedule()
     * at least once to get things moving:
     */
    init_idle_bootup_task(current);
    schedule_preempt_disabled();
    /* 當系統沒有進程需要執行時,就調度到idle進程 */
    cpu_startup_entry(CPUHP_ONLINE);
}

通過rest_init()新建kernel_initkernel_thread()內核線程。調用kernel_thread()創建1號內核線程。

  • kthreadd() 位於linux-3.18.6/kernel/kthread.c #483
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);這行代碼調用kernel_thread執行kthreadd,創建PID為2的內核線程。
  • trap_init()涉及到一些中斷的初始化

一些補充

Linux下有3個特殊的進程,idle進程(PID = 0), init進程(PID = 1)和kthreadd(PID = 2)

  • idle進程由系統自動創建, 運行在內核態
    idle進程其pid=0,其前身是系統創建的第一個進程,也是唯一一個沒有通過fork或者kernel_thread產生的進程。完成加載系統後,演變為進程調度、交換。
  • init進程由idle通過kernel_thread創建,在內核空間完成初始化後, 加載init程序, 並最終到用戶空間
    由0進程創建,完成系統的初始化. 是系統中所有其它用戶進程的祖先進程。
    Linux中的所有進程都是有init進程創建並運行的。首先Linux內核啟動,然後在用戶空間中啟動init進程,再啟動其他系統進程。在系統啟動完成完成後,init將變為守護進程監視系統其他進程。
  • kthreadd進程由idle通過kernel_thread創建,並始終運行在內核空間, 負責所有內核線程的調度和管理。
    它的任務就是管理和調度其他內核線程kernel_thread, 會循環執行一個kthread的函數,該函數的作用就是運行kthread_create_list全局鏈表中維護的kthread, 當我們調用kernel_thread創建的內核線程會被加入> 到此鏈表中,因此所有的內核線程都是直接或者間接的以kthreadd為父進程。

其他想法

本次實驗和書本學習,我受益匪淺,真正接觸到了Linux內核的源碼,感覺需要更多接觸,之後在自己的機器上面構建一個MenuOS,構建成功後把截圖發在下面。

參考資料

《庖丁解牛Linux》
Linux下0號進程的前世(init_task進程)今生(idle進程)

2018-2019-1 20189215 《Linux內核原理與分析》第四周作業