《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号进程是所有内核线程的祖先)