• 《Linux内核分析》第三周学习笔记


    《Linux内核分析》第三周学习笔记 构造一个简单的Linux系统MenuOS

    郭垚 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

    一、Linux内核源代码简介

    1.1 Linux内核源代码

    • arch:支持不同的CPU的源代码,其中的关键目录包括:Documentation、drivers、firewall、fs、include等
    • documentation:文档目录
    • fs:文件系统
    • init:内核启动相关的代码main.c、Makefile等基本都在该目录中。(main.c中的start_ kernel函数是Linux内核启动的起点,即初始化内核的起点)
    • kernel:Linux内核核心代码在kernel目录中。
    • lib:公用的库文件
    • mm:内存管理的代码
    • scripts:与脚本相关的代码
    • security:与安全相关的代码
    • sound目录:与声音相关的代码
    • tools目录:与工具相关的代码
    • net:与网络相关的代码
    • readme:介绍了什么是Linux,Linux能够在哪些硬件上运行,如何安装内核源代码等
    • ……

    二、构造一个简单的Linux系统

    2.1 构造一个简单的Linux系统MenuOS

    cd LinuxKernel/ 
    qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
    
    • qemu命令是模拟内核启动虚拟机,启动Linux内核需要三个参数(kernel、initrd、root所在的分区和目录),执行的第一个文件是init。
    • -kernel指明内核文件名
    • -initrd指明根文件系统,启动其中的init文件。(menuOS源代码编译->init->rootfs.img)其中rootfs.img 为根文件系统,目前只支持help、version、quit功能。
    • 启动过程为:启动内核->启动init->启动进程

    三、跟踪调试Linux内核的启动过程

    3.1 使用gdb跟踪调试Linux内核的方法

    • 使用带参数命令启动MenuOS,使得系统在刚启动时,暂停等待调试器跟踪执行。

      qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
      # -S 在CPU初始化之前冻结CPU
      # -s 1234端口上创建一个tcp接口。若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项  
      
    • 另开一个shell窗口,启动gdb。

      (gdb)file linux-3.18.6/vmlinux
      # 在gdb界面中targe remote之前加载符号表
      (gdb)target remote:1234 
      # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
      (gdb)break start_kernel 
      # 在start_kernel函数入口处设置断点
      (gdb)c 
      # 使得系统运行到start_kernel处停住
      (gdb)list
      # 显示当前行所在位置上下的代码
      

    注:编译的目的就是生成自带内核中没有的符号表。符号是全局变量、函数名,符号表即名字与地址一一对应。

        使用gdb进行调试:

        在start_kernal设置断点

    3.2 简单分析start_kernel

    在init目录下的main.c包含start_ kernel函数。基本上所有模块启动时都需要调用init中的start_kernel函数来进行初始化。

    • 全局变量init_ task:手工创建的PCB,0号进程
    • start_ kernel函数分析

      500asmlinkage __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();
      510 set_task_stack_end_magic(&init_task);
      511 smp_setup_processor_id();
      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();
      529 page_address_init();
      530 pr_notice("%s", linux_banner);
      531 setup_arch(&command_line);
      532 mm_init_cpumask(&init_mm);
      533 setup_command_line(command_line);
      534 setup_nr_cpu_ids();
      535 setup_per_cpu_areas();
      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
      ", 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
      "))
      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
      ");
      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.
      ",
      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}
      

    注:rest_ init()函数

    • rest_ init()调用了kernel_ thread(kernel_ init, NULL, CLONE_ FS); 然后又继续调用run_ init_ process(ramdisk_ execute_ command); run_ init_ process是1号进程,即第一个用户态进程。
    • 创建进程:pid = kernel_ thread(kthreadd, NULL, CLONE_ FS | CLONE_FILES);其中kthreadd是被创建的内核线程,用来管理资源。

    • rest_ init的各部分启动完毕后,调用static void cpu_ idle_loop(void);该函数一直在循环,是0号进程。当系统没有进程需要执行时就调度idle进程。

    四、总结

    rest_ init实际上是start_ kernel在内核启动时就一直存在的,即0号进程。然后0号进程创建1号进程init,还有其他服务的内核线程,这样内核就能启动起来。

    再附同学的精辟总结:道生一(start_ kernel....cpu_idle),一生二(kernel _ init和kthreadd),二生三(即前面0、1和2三个进程),三生万物(1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先)

  • 相关阅读:
    actionscript3.0 图片裁剪及保存jpg详解
    AS3 JPEG Encoder应用:从Flash中保存图片
    Kata 架构
    docker 学习笔记
    processon
    学习容器技术的思考
    cp -f 还是提示是否覆盖
    CentOS下安装桌面环境
    [PYTHON 实作] 算100
    <转>CentOS 7 安装配置 NFS
  • 原文地址:https://www.cnblogs.com/20135228guoyao/p/5270286.html
Copyright © 2020-2023  润新知