Linux内核设计第三周
——构造一个简单的Linux系统
一、知识点总结
计算机三个法宝:
- 存储程序计算机
- 函数调用堆栈
- 中断
操作系统两把宝剑:
- 中断上下文的切换
- 进程上下文的切换
linux内核源代码分析
arch/目录保存支持多种CPU类型的源代码
- 其中的关键目录包括:Documentation、drivers、firewall、fs(文件系统)、include
init目录:含有main.c,内核启动相关的代码基本都在init目录下
- start_kernal()函数为启动函数,初始化内核的起点。
ipc目录:进程间的通信
kernel目录:有Linux内核的核心代码。
- pid.c、kthread.c
lib目录:公用库文件
mm目录:内存管理代码
net目录:与网络相关代码
scripts目录:与脚本相关
security目录:与安全相关
sound目录:与声音相关
tools目录:与工具相关README文件:
- 清理中间文件:
cd linux
make mrproper- 安装一些模块,最终执行make
sudo make O = /home/name/build/kernal modules
install install // O= output/dir
将所选项关闭,以提高安装速度
make allnoconfig
二、实验过程
图1 构建Menu系统的过程
cd LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
其中rootfs.img 为根文件系统,目前只支持help、version、quit功能。
图2 使用gdb进行调试
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
关于-s和-S选项的说明:
-S freeze CPU at startup (use ’c’ to start execution)
-s shorthand for -gdb tcp::1234
若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项
图3 将断点设置到start_kernal
gdb
(gdb)file linux-3.18.6/vmlinux
- 在gdb界面中targe remote之前加载符号表
(gdb)target remote:1234
- 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
(gdb)break start_kernel
- 断点的设置可以在target remote之前,也可以在之后
(gdb)c
- 开始执行
(gdb)list
- 列出断点位置的源代码
图4 将断点设置到rest_init
(gdb)break rest_init
- 断点的设置可以在target remote之前,也可以在之后
(gdb)c
- 开始执行
(gdb)list
- 列出断点位置的源代码
三、分析从start_kernel到init进程启动
在init目录下的main.c有函数
基本所有模块,都需要start_kernel来进行初始化。
asmlinkage __visible void __init start_kernel(void)
其中,有init_ task,set_ task_ stack_ end_ magic(&init_task);这个是手工创建的PCB,0号进程,即最终的idle进程。
trap_init();//中断向量初始化
mm_init();//内存管理模块初始化
sched_init();//调度模块初始化
console_init();//控制模块初始化
rest_init(); //其他模块初始化
其中rest_ init()调用了
-->kernel_ thread(kernel_ init, NULL, CLONE_ FS);
-->run_ init_ process(ramdisk_ execute_ command);
//init是第一个用户态进程,是1号进程
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
//创建了线程
在rest_init中,各部分启动完毕后,
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);
调用static void cpu_idle_loop(void)
里面有个while(1)
也就是在系统没有进程需要执行时就调度idle进程
总结下来:在start_ kernel启动后,rest_ init的中0号进程会一直存在。
分析start_kernel函数的执行过程
如图所示,第500行代码有start_kernel函数
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\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}
四、总结
阐明对“Linux系统启动过程”的理解。
①在start_ kernel启动后,rest_ init的中0号进程会一直存在。
②道生一(startkernel....cpuidle),一生二(kernel_init和kthreadd),二生三(即前面0、1和2三个进程),三生万物(1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先)
五、实验中遇到的问题
1、在gdb调试的过程中,出现了如下图所示的问题,即输入“fil e linux-3.18.6/vmlinux”后,提示“没有那个文件或目录”。