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

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

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

4. linux框架结构

  关于阅读技巧,可参考文章:https://www.cnblogs.com/fanzhidongyzby/archive/2013/03/20/2970624.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 setup
cmpl $0x5a5aaa55, setup_sig
jne setup_bad # Zero the bss
movw $__bss_start, %di
movw $_end+3, %cx
xorl %eax, %eax
subw %di, %cx
shrw $2, %cx
rep; stosl # Jump to C code (should not return)
calll main // /arch/x86/boot/main.c
void 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 # segment
ENDPROC(protected_mode_jump) .code32
.section ".text32","ax"
GLOBAL(in_pm32)
# Set up data segments for flat 32-bit mode
movl %ecx, %ds
movl %ecx, %es
movl %ecx, %fs
movl %ecx, %gs
movl %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 happy
ltr %di # Clear registers to allow for future extensions to the
# 32-bit boot protocol
xorl %ecx, %ecx
xorl %edx, %edx
xorl %ebx, %ebx
xorl %ebp, %ebp
xorl %edi, %edi # Set up LDTR to make Intel VT happy
lldt %cx jmpl *%eax # Jump to the 32-bit entrypoint
ENDPROC(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.
*/
__HEAD
ENTRY(startup_32)
...
hlt_loop:
hlt
jmp hlt_loop
ENDPROC(early_ignore_irq) __INITDATA
.align 4
GLOBAL(early_recursion_flag)
.long 0 __REFDATA
.align 4
ENTRY(initial_code)
.long i386_start_kernel
ENTRY(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_nohz();
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_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_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_X86
if (efi_enabled(EFI_RUNTIME_SERVICES))
efi_enter_virtual_mode();
#endif
thread_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之旅愉快啊!

关于linux的一点好奇心(一):linux启动过程的更多相关文章

  1. 关于linux的一点好奇心(五):进程线程的创建

    一直以来,进程和线程的区别,这种问题一般会被面试官拿来考考面试者,可见这事就不太简单.简单说一点差异是,进程拥有独立的内存资源信息,而线程则共享父进程的资源信息.也就是说线程不拥有内存资源,所以对系统 ...

  2. 关于linux的一点好奇心(四):tail -f文件跟踪实现

    关于文件跟踪,我们有很多的实际场景,比如查看某个系统日志的输出,当有变化时立即体现,以便进行问题排查:比如查看文件结尾的内容是啥,总之是刚需了. 1. 自己实现的文件跟踪 我们平时做功能开发时,也会遇 ...

  3. 末学者笔记--Linux中RAID磁盘阵列及centos7启动过程

    <一>RAID概念 磁盘阵列(Redundant Arrays of Independent Disks,RAID),有“独立磁盘构成的具有冗余能力的阵列”之意. 磁盘阵列是由很多价格较便 ...

  4. Android 面试必备 - 系统、App、Activity 启动过程“一锅端”

    Android 系统启动过程 从系统层看: linux 系统层 Android系统服务层 Zygote 从开机启动到Home Launcher: 启动bootloader (小程序:初始化硬件) 加载 ...

  5. Linux X Window System运行原理和启动过程

    本文主要说明X Window System的基本运行原理,其启动过程,及常见的跨网络运行X Window System. 一) 基本运行原理 X Window System采用C/S结构,但和我们常见 ...

  6. 通过从代码层面分析Linux内核启动来探知操作系统的启动过程

    通过从代码层面分析Linux内核启动来探知操作系统的启动过程 前言说明 本篇为网易云课堂Linux内核分析课程的第三周作业,我将围绕Linux 3.18的内核中的start_kernel到init进程 ...

  7. Linux内核源码分析--内核启动之(1)zImage自解压过程(Linux-3.0 ARMv7) 【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-4938388.html 研究内核源码和内核运行原理的时候,很总要的一点是要了解内核的初始情况,也就是要了解内 ...

  8. Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7)【转】

    原文地址:Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.c ...

  9. linux系统配置之开机启动过程(centos)

    1.开机流程如下: 2.BIOS BIOS是英文"Basic Input Output System"的缩略词,直译过来后中文名称就是"基本输入输出系统".其实 ...

随机推荐

  1. Java实现 HTTP/HTTPS请求绕过证书检测

    java实现 HTTP/HTTPS请求绕过证书检测 一.Java实现免证书访问Https请求 创建证书管理器类 import java.security.cert.CertificateExcepti ...

  2. git 日志技术

    1.git log, 在一个分支下, 以时间的倒序方式显示你制造的所有commit列表,包含创建人,时间,提交了什么等信息: 2. git reflog, 获取您在本地repo上还原commit所做工 ...

  3. my40_MySQL锁概述之意向锁

    本文在锁概述的基础上,通常实验举例,详细地介绍了意向锁的原理. 锁范围  全局锁(global lock)表锁(table lock)行锁 (row lock) ROW LOCK的粒度LOCK_REC ...

  4. Oracle删除重复数据记录

    删除重复记录,利用ROWID 和MIN(或MAX)函数, ROWID在整个数据库中是唯一的,由Oracle自己产生和维护,并唯一标识一行(无论该表中是否有主键和唯一性约束),ROWID确定了每条记录在 ...

  5. Mockito 入门详解

    一个测试方法主要包括三部分: setup 执行操作 验证结果 public class CalculatorTest { Calculator mCalculator; @Before // setu ...

  6. spring的核心容器ApplicationContext

    //bean.xml配置文件 <?xml version="1.0" encoding="UTF-8"?><beans xmlns=" ...

  7. 详解 java 异常

    Throwable 可以用来表示任何可以作为异常抛出的类(注意,是类不是接口),分为两种: Error(注意!error也是可以throw的,但是不建议) 和 Exception. 其中 Error ...

  8. vs2019+windows服务+nancy+打包

    一.创建windows服务  二.nuget包添加nancy 1.nancy 2.0.0和Nancy.Hosting.Self 2.0.0插件 2.项目添加文件夹Modules,在Modules文件夹 ...

  9. Nginx区分搜索引擎

    目录 一.简介 二.配置 一.简介 场景: 当从百度点进来显示中文页面,而谷歌显示英文界面. 原理: 根据referer头来判断 二.配置 这样配置以后,凡是从百度或者google点过来的请求都会跳转 ...

  10. Nginx结构原理全解析

    目录 一.Nginx简单介绍 二.Nginx优势 IO多路复用epoll模型 轻量级 Nginx的基本功能 .Nginx应用场景 Nginx代理 三.Nginx的结构解析 进程操作 事件模型 事件处理 ...