AI智能
改变未来

关于linux的一点好奇心(一):linux启动过程

  一直很好奇,操作系统是如何工作的?我们知道平时编程,是如何让代码跑起来的,但那些都是比较高层次的东西。越往后,你会越觉得,像是空中楼阁,或者说只是有人帮你铺平了许多道理,而你却对此一无所知。

1. 操作系统的困惑

  当然了,也不是真的一无所知。因为有很多的操作系统方面的书籍,教你了解操作系统是如何如何工作的,它的各种原理。但总有一种任督二脉不通的感觉。好像说的都知道一点,但好像知道这是什么,在哪里用,和为什么了。

  我曾经看过一系列关于一个如何自制操作系统的文章,非常棒。https://wiki.0xffffff.org/ 里面完全展示了一个求知者的过程,硬件加载,软件接管,操作系统,内存,中断,驱动,线程等等方面的知道。可以说,是一个用于解惑,不可多得的文章了。应该说,很多关于操作系统的困惑,在这里找到答案,当然你得自己总结下才行。

  但是,我还是会有那么一种感觉,原理看得再多,还是很空虚的。上面那个demo虽然把所有的东西都讲了一遍,好像已经把所有问题都讲了,但还毕竟只是demo。也许实际情况并非如此呢?至少不会那么简单。这着实困扰着自己跳动的心。

  再后来,遇到了一篇讲关于epoll的文章: https://bbs.gameres.com/thread_842984_1_1.html 。 经过这篇文章的讲解,可以说把整个io的原理讲得非常之透彻了。而我本人的确也从这里出发,给团队内部做了一次分享。不知道他们感觉怎么样,反正我是感觉挺通透的。这关于io东西,可以说是操作系统中的一个小点。但我个人觉得,框架也许只需要你了解一次就好,但小点却是需要反复琢磨的。我们需要带着问题去找答案,这个问题往往是关于小点的多。

2. 敢不敢啃一啃操作系统的硬骨头?

  说实话,我是不敢的。原因是,它太复杂,太宏大,这是比较大方向的困难。其次是,我单就语言这一关,可能就难以过去,因为你至少汇编、C之类的语言要足够好才行,而自己却只算是皮毛。正所谓一生清贫怎敢入繁华,两袖清风怎敢误佳人。

  难道就这样得过且过么?但心里总是有一些疑问,不知道怎么去解决。一是问不了许多人,二是自己也不知道咋问,三是即使别人告诉了你你就能懂吗?(就像教书一样)

  所以,还是自己找答案吧。其实网上有太多零零散散的答案,好像都能看懂,但又好像都不是很明白。

  最好的文档,都在官方资料里。最好的答案,都在代码里。所以,去看看又何妨。

3. linux内核源码地址

  也许大家一般都是在github上去看这些源码。但在国内,github的速度实在是不敢恭维。

  gitee地址: https://gitee.com/mirrors/linux

  github地址: https://github.com/torvalds/linux

  至于阅读工具嘛,纯粹打酱油的,使用 sublime56c之类的就可以了,如果想更好一点,就eclipse也行,当然可能还要设置其他好些环境问题。

4. linux框架结构

  关于阅读技巧,可参考文章:https://www.geek-share.com/detail/2574746000.html

  整体目录结构如下:

  细节简略描述如下:

    arch——与体系结构相关的代码。 对应于每个支持的体系结构,有一个相应的目录如x86、 arm、alpha等。每个体系结构子目录下包含几个主要的子目录: kernel、mm、lib。
    Documentation——内核方面的相关文档。
    drivers——设备驱动代码。每类设备有相应的子目录,如char、 block、net等 fs 文件系统代码。每个支持文件系统有相应的子目录, 如ext2、proc等。
    fs——文件系统实现。如fat, ext4…
    include——内核头文件。 对每种支持的体系结构有相应的子目录,如asm-x86、 asm-arm、asm-alpha等。
    init——内核初始化代码。提供main.c,包含start_kernel函数。     ipc——进程间通讯代码。
    kernel——内核管理代码。
    lib——与体系结构无关的内核库代码,特定体系结构的库代码保存在arch/*/lib目录下。
    mm——内存管理代码。
    net——内核的网络代码。
    samples——一些使用功能接口的样例,有点类似于单元测试。
    scripts——此目录包含了内核设置时用到的脚本。
    security——安全相关的实现。
    tools——一些附带工具类实现。

  其中,Documentation目录可能是源码不相关的目录,但对我们理解系统却是非常重要的地方。(因为我们多半只能看得懂文字的表面意思)

5. linux-86启动过程

  以x86的实现为例,其启动过程大致如下:以 header.S 开始,以main.c结束(我们自认为看得懂的地方)。

/arch/x86/boot/header.S-> calll main    ->    /arch/x86/boot/main.c-> go_to_protected_mode()    ->    /arch/x86/boot/pmjump.S-> jmpl    *%eax    ->    /arch/x86/kernel/head_32.S-> .long i386_start_kernel    ->    /arch/x86/kernel/head32.c-> start_kernel()    ->    /init/main.c    (C语言入口)

  细节代码样例如下:

// /arch/x86/boot/header.S#include <asm/segment.h>#include <asm/boot.h>#include <asm/page_types.h>#include <asm/setup.h>#include <asm/bootparam.h>#include \"boot.h\"#include \"voffset.h\"#include \"zoffset.h\"...6:# Check signature at end of setupcmpl    $0x5a5aaa55, setup_sigjne    setup_bad# Zero the bssmovw    $__bss_start, %dimovw    $_end+3, %cxxorl    %eax, %eaxsubw    %di, %cxshrw    $2, %cxrep; stosl# Jump to C code (should not return)calll    main// /arch/x86/boot/main.cvoid main(void){/* First, copy the boot header into the \"zeropage\" */copy_boot_params();/* Initialize the early-boot console */console_init();if (cmdline_find_option_bool(\"debug\"))puts(\"early console in setup code\\n\");/* End of heap check */init_heap();/* Make sure we have all the proper CPU support */if (validate_cpu()) {puts(\"Unable to boot - please use a kernel appropriate \"\"for your CPU.\\n\");die();}/* Tell the BIOS what CPU mode we intend to run in. */set_bios_mode();/* Detect memory layout */detect_memory();/* Set keyboard repeat rate (why?) and query the lock flags */keyboard_init();/* Query Intel SpeedStep (IST) information */query_ist();/* Query APM information */#if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)query_apm_bios();#endif/* Query EDD information */#if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)query_edd();#endif/* Set the video mode */set_video();/* Do the last things and invoke protected mode */go_to_protected_mode();}// /arch/x86/boot/pmjump.S/** The actual transition into protected mode*/#include <asm/boot.h>#include <asm/processor-flags.h>#include <asm/segment.h>#include <linux/linkage.h>.text.code16...2:    .long    in_pm32            # offset.word    __BOOT_CS        # segmentENDPROC(protected_mode_jump).code32.section \".text32\",\"ax\"GLOBAL(in_pm32)# Set up data segments for flat 32-bit modemovl    %ecx, %dsmovl    %ecx, %esmovl    %ecx, %fsmovl    %ecx, %gsmovl    %ecx, %ss# The 32-bit code sets up its own stack, but this way we do have# a valid stack if some debugging hack wants to use it.addl    %ebx, %esp# Set up TR to make Intel VT happyltr    %di# Clear registers to allow for future extensions to the# 32-bit boot protocolxorl    %ecx, %ecxxorl    %edx, %edxxorl    %ebx, %ebxxorl    %ebp, %ebpxorl    %edi, %edi# Set up LDTR to make Intel VT happylldt    %cxjmpl    *%eax            # Jump to the 32-bit entrypointENDPROC(in_pm32)// /arch/x86/kernel/head_32.S.text#include <linux/threads.h>#include <linux/init.h>#include <linux/linkage.h>#include <asm/segment.h>#include <asm/page_types.h>#include <asm/pgtable_types.h>#include <asm/cache.h>#include <asm/thread_info.h>#include <asm/asm-offsets.h>#include <asm/setup.h>#include <asm/processor-flags.h>#include <asm/msr-index.h>#include <asm/cpufeatures.h>#include <asm/percpu.h>#include <asm/nops.h>#include <asm/bootparam.h>#include <asm/export.h>#include <asm/pgtable_32.h>.../** 32-bit kernel entrypoint; only used by the boot CPU.  On entry,* %esi points to the real-mode code as a 32-bit pointer.* CS and DS must be 4 GB flat segments, but we don\'t depend on* any particular GDT layout, because we load our own as soon as we* can.*/__HEADENTRY(startup_32)...hlt_loop:hltjmp hlt_loopENDPROC(early_ignore_irq)ad8__INITDATA.align 4GLOBAL(early_recursion_flag).long 0__REFDATA.align 4ENTRY(initial_code).long i386_start_kernelENTRY(setup_once_ref).long setup_once// /arch/x86/kernel/head32.c#include <linux/init.h>#include <linux/start_kernel.h>#include <linux/mm.h>#include <linux/memblock.h>#include <asm/desc.h>#include <asm/setup.h>#include <asm/sections.h>#include <asm/e820/api.h>#include <asm/page.h>#include <asm/apic.h>#include <asm/io_apic.h>#include <asm/bios_ebda.h>#include <asm/tlbflush.h>#include <asm/bootparam_utils.h>...asmlinkage __visible void __init i386_start_kernel(void){/* Make sure IDT is set up before any exception happens */idt_setup_early_handler();cr4_init_shadow();sanitize_boot_params(&boot_params);x86_early_init_platform_quirks();/* Call the subarch specific early setup function */switch (boot_params.hdr.hardware_subarch) {case X86_SUBARCH_INTEL_MID:x86_intel_mid_early_setup();break;case X86_SUBARCH_CE4100:x86_ce4100_early_setup();break;default:i386_default_early_setup();break;}start_kernel();}// /init/main.c#define DEBUG        /* Enable initcall_debug */#include <linux/types.h>#include <linux/extable.h>#include <linux/module.h>#include <linux/proc_fs.h>#include <linux/binfmts.h>#include <linux/kernel.h>#include <linux/syscalls.h>#include <linux/stackprotector.h>#include <linux/string.h>#include <linux/ctype.h>#include <linux/delay.h>#include <linux/ioport.h>#include <linux/init.h>#include <linux/initrd.h>#include <linux/bootmem.h>#include <linux/acpi.h>#include <linux/console.h>#include <linux/nmi.h>#include <linux/percpu.h>#include <linux/kmod.h>#include <linux/vmalloc.h>#include <linux/kernel_stat.h>#include <linux/start_kernel.h>#include <linux/security.h>#include <linux/smp.h>#include <linux/profile.h>#include <linux/rcupdate.h>#include <linux/moduleparam.h>#include <linux/kallsyms.h>#include <linux/writeback.h>#include <linux/cpu.h>#include <linux/cpuset.h>#include <linux/cgroup.h>#include <linux/efi.h>#include <linux/tick.h>#include <linux/sched/isolation.h>#include <linux/interrupt.h>#include <linux/taskstats_kern.h>#include <linux/delayacct.h>#include <linux/unistd.h>#include <linux/utsname.h>#include <linux/rmap.h>#include <linux/mempolicy.h>#include <linux/key.h>#include <linux/buffer_head.h>#include <linux/page_ext.h>#include <linux/debug_locks.h>#include <linux/debugobjects.h>#include <linux/lockdep.h>#include <linux/kmemleak.h>#include <linux/pid_namespace.h>#include <linux/device.h>#include <linux/kthread.h>#include <linux/sched.h>#include <linux/sched/init.h>#include <linux/signal.h>#include <linux/idr.h>#include <linux/kgdb.h>#include <linux/ftrace.h>#include <linux/async.h>#include <linux/sfi.h>#include <linux/shmem_fs.h>#include <linux/slab.h>#include <linux/perf_event.h>#include <linux/ptrace.h>#include <linux/pti.h>#include <linux/blkdev.h>#include <linux/elevator.h>#include <linux/sched_clock.h>#include <linux/sched/task.h>#include <linux/sched/task_stack.h>#include <linux/context_tracking.h>#include <linux/random.h>#include <linux/list.h>#include <linux/integrity.h>#include <linux/proc_ns.h>#include <linux/io.h>#include <linux/cache.h>#include <linux/rodata_test.h>#include <linux/jump_label.h>#include <linux/mem_encrypt.h>#include <asm/io.h>#include <asm/bugs.h>#include <asm/setup.h>#include <asm/sections.h>#include <asm/cacheflush.h>// 平台无关启动代码入口asmlinkage __visible void __init start_kernel(void){char *command_line;char *after_dashes;set_task_stack_end_magic(&init_task);smp_setup_processor_id();debug_objects_early_init();cgroup_init_early();local_irq_disable();early_boot_irqs_disabled = true;/** Interrupts are still disabled. Do necessary setups, then* enable them.*/boot_cpu_init();page_address_init();pr_notice(\"%s\", linux_banner);setup_arch(&command_line);/** Set up the the initial canary and entropy after arch* and after adding latent and command line entropy.*/add_latent_entropy();add_device_randomness(command_line, strlen(command_line));boot_init_stack_canary();mm_init_cpumask(&init_mm);setup_command_line(command_line);setup_nr_cpu_ids();setup_per_cpu_areas();smp_prepare_boot_cpu();    /* arch-specific boot-cpu hooks */boot_cpu_hotplug_init();build_all_zonelists(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, NULL, &unknown_bootoption);if (!IS_ERR_OR_NULL(after_dashes))parse_args(\"Setting init args\", after_dashes, NULL, 0, -1, -1,NULL, set_init_arg);jump_label_init();/** These use large bootmem allocations and must precede* kmem_cache_init()*/setup_log_buf(0);vfs_caches_init_early();sort_main_extable();trap_init();mm_init();ftrace_init();/* trace_printk can be enabled here */early_trace_init();/** Set up the scheduler prior starting any interrupts (such as the* timer interrupt). Full topology setup happens at smp_init()* time - but meanwhile we still have a functioning scheduler.*/sched_init();/** Disable preemption - early bootup scheduling is extremely* fragile until we cpu_idle() for the first time.*/preempt_disable();if (WARN(!irqs_disabled(),\"Interrupts were enabled *very* early, fixing it\\n\"))local_irq_disable();radix_tree_init();/** Set up housekeeping before setting up workqueues to allow the unbound* workqueue to take non-housekeeping into account.*/housekeeping_init();/** Allow workqueue creation and work item queueing/cancelling* early.  Work item execution depends on kthreads and starts after* workqueue_init().*/workqueue_init_early();rcu_init();/* Trace events are available after this */trace_init();if (initcall_debug)initcall_debug_enable();context_tracking_init();/* init some links before init_ISA_irqs() */early_irq_init();init_IRQ();tick_init();rcu_init_nohad8z();init_timers();hrtimers_init();softirq_init();timekeeping_init();time_init();sched_clock_postinit();printk_safe_init();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();/** HACK ALERT! This is early. We\'re enabling the console before* we\'ve done PCI setups etc, and console_init() must be aware of* this. But we do want output early, in case something goes wrong.*/console_init();if (panic_later)panic(\"Too many boot %s vars at `%s\'\", panic_later,panic_param);lockdep_info();/** Need to run this when irqs are enabled, because it wants* to self-test [hard/soft]-irqs on/off lock inversion bugs* too:*/locking_selftest();/** This needs to be called before any devices perform DMA* operations that might use the SWIOTLB bounce buffers. It will* mark the bounce buffers as decrypted so that their usage will* not cause \"plain-text\" data to be decrypted when accessed.*/mem_encrypt_init();#ifdef CONFIG_BLK_DEV_INITRDif (initrd_start && !initrd_below_start_ok &&page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {pr_crit(270a\"initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\\n\",page_to_pfn(virt_to_page((void *)initrd_start)),min_low_pfn);initrd_start = 0;}#endifpage_ext_init();kmemleak_init();debug_objects_mem_init();setup_per_cpu_pageset();numa_policy_init();acpi_early_init();if (late_time_init)late_time_init();calibrate_delay();pid_idr_init();anon_vma_init();#ifdef CONFIG_X86if (efi_enabled(EFI_RUNTIME_SERVICES))efi_enter_virtual_mode();#endifthread_stack_cache_init();cred_init();fork_init();proc_caches_init();uts_ns_init();buffer_init();key_init();security_init();dbg_late_init();vfs_caches_init();pagecache_init();signals_init();seq_file_init();proc_root_init();nsfs_init();cpuset_init();cgroup_init();taskstats_init_early();delayacct_init();check_bugs();acpi_subsystem_init();arch_post_acpi_subsys_init();sfi_init_late();if (efi_enabled(EFI_RUNTIME_SERVICES)) {efi_free_boot_services();}/* Do the rest non-__init\'ed, we\'re now alive */rest_init();}

  本篇不做深入探讨,仅为梳理来龙去脉。其中深意还需各自领悟了。反正大致就是,上电启动后,进入BIOS,交权限转交特殊地址,然后转到系统启动处,加载对应平台汇编指令,做各种硬件设置,最后转到我们熟悉一点的C代码入口的过程。这个过程中,更多的是内存地址,寄存器之类的操作,可以说涉及到的东西相当广泛,所以我们不能要求太多可能也没有必要要求太多。

  老铁,linux之旅愉快啊!

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » 关于linux的一点好奇心(一):linux启动过程