You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This discussion was converted from issue #6 on June 09, 2023 03:12.
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
基本概念
CPU(Central Processing Unit)
运算单元 负责执行加法、减法、位移等操作。
数据单元 包括缓存和寄存器组,用于暂存数据和运算结果。根据数据地址,从数据段里读到数据寄存器,参与运算。
控制单元 指导运算单元执行指令操作。其中包含指令指针寄存器,存放下一条指令在内存中的地址。控制单元不断将代码段的指令取出,放入指令寄存器。而指令包括执行的操作(交由运算单元处理)、待操作的数据(交由数据单元处理)。
总线(Bus)
总线组成了 CPU 和其他设备的高速通道。包括 **地址总线(Address Bus)**和 数据总线(Data Bus)。
地址总线(Address Bus)
地址总线的位数决定可访问地址范围,比如 2 位总线可访问的地址为
2^2 == 4
个。数据总线(Data Bus)
数据总线的位数决定每次可传输的数据量,体现为访问速度。比如要在 2 位总线取 8 位数据,则需要取
8 / 2 == 4
次。内存(Memory)
...
I/O 设备
...
x86 架构
8086 处理器
源于 Intel 8086 CPU,其实现了 开放、统一、兼容 的平台。
8086 CPU 架构如图:
其中 AX、BX、CX、DX 可分成两个 8 位(AH、AL、BH、BL、CH、CL、DH、DL)。
CPU 根据它将指令从内存的代码段中加载到指令队列中,再交给运算单元去执行。
数据段寄存器 DS
代码段的偏移量在 IP 寄存器中,数据段的偏移量放在通用寄存器中。
寻址:起始地址 * 16 + 偏移量(即 16 位左移 4 位,再加上偏移量)。
最大寻址空间
2^20 == 1M
。32 位处理器
从 8086 升级到 32 位处理器要保持兼容:
将 8 个 16 位通用寄存器扩展到 8 个 32 位,保留 16 位和 8 位的使用方式。
指令指针寄存器 IP 扩展到 32 位。
段寄存器保留为 16 位,段的起始地址改为存放在内存中的 段描述符(Segment Descriptor) 表中,段寄存器存放 选择子(Selector),寻址方式改为从段寄存器找到选择子,再从段描述符表中拿到段起始地址。
为实现快速访问,段寄存器将段起始地址从内存中拿到 CPU 的描述符高速缓存器中。
实模式与保护模式
在 32 位系统架构下,系统启动时处于 实模式(Real Pattern),进行一系列操作后切换为 保护模式(Protected Pattern)。
实模式的寻址方式:从段寄存器中取段起始地址,从指令指针寄存器中取偏移量。
保护模式的寻址方式:从段寄存器取出选择子,根据选择子从内存中的段描述符表中取起始地址(存放在高速缓存中),再从指令指针寄存器中取偏移量。
BIOS
计算机主板上有 ROM(Read Only Memory,只读存储器)固定存放 BIOS(Basic Input and Output System,基本输入输出系统)程序。
在计算机启动时只有 1M 内存空间,其中 0xF0000 - 0xFFFFF 共 64K 的地址映射给 ROM。在实模式下执行以下过程:
将代码段寄存器 CS 设置为 0xFFFF,将指令指针寄存器 IP 设置为 0x0000,因此第一条指令寻址
0xFFFF * 16 + 0x0000 == 0xFFFF0
。该地址为 JMP 指令,跳到 ROM 中开始执行 BIOS 初始化。
BIOS 确认硬件正常后,建立 中断向量表 和 中断服务程序。
在 BIOS 完成后,进入 Bootloader 阶段。
Bootloader
此前 Linus 的 grub2(Grand Unified Bootloader Version 2)工具将 boot.img 安装到启动盘的 MBR 扇区(Master Boot Record,主引导记录,是启动盘的第一个扇区,共 512bytes,以 0xAA55 结束)。
在 BIOS 完成后,将 boot.img 从硬盘加载到内存 0x7c00 运行,即加载 grub2 的 core.img 镜像的各部分。
diskboot.img
负责加载 core.img 镜像的其它部分。
lzma_decompress.img
并调用
real_to_prot
,从 实模式 切换到 保护模式:启用分段:在内存建立段描述符表,将段寄存器变成段选择子、指向某个段描述符,以支持进程却换。
启动分页:将内存分成相等大小的块,使能够管理更大的内存。
打开地址线:即 Gate A20,第 21 根地址线的控制线),以支持访问大于 1M 的地址空间。
切换到保护模式后将有足够的地址空间,此时开始
startup_raw.S
解压 kernel.img。kernel.img
kernel.img 使 grub 内核,对应代码
startup.S
以及其它 c 文件:调用 grub kernel 的主函数
grub_main()
调用
grub_load_config()
执行解析 grub.conf。调用
grub_command_execute("normal", 0, 0)
调用
grub_normal_execute()
调用
grub_show_menu()
,在列表中选择操作系统。调用
grub_menu_execute_entry()
,装载指定的内核文件并传递内核启动参数。调用
grub_cmd_linux()
,读取 Linux 内核镜像头部,放到内存中的数据结构以进行检查。检查通过则会读取 Linux 内核镜像到内存。调用
grub_command_execute("boot", 0, 0)
,开始启动操作系统内核。其中 grub.conf 文件内容如下:
内核初始化
Linux 内核启动的入口函数是 init/main.c 的
start_kernel()
,主要调用了以下函数:INIT_TASK(init_task)
:初始化进程列表,创建 0 号进程。set_task_stack_end_magic(&init_task)
的参数init_task
(其定义是struct task_struct init_task = INIT_TASK(init_task)
)即 0 号进程,其不由fork
或者kernel_thread
产生,是进程列表(Process List)的第一个进程。trap_init()
:初始化中断门。set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32)
设置系统调用的中断门。mm_init()
:初始化内存管理。sched_init()
:初始化调度器。vfs_caches_init()
:初始化 rootfs(基于内存的文件系统)。调用
mnt_init()->init_rootfs()
,在 VFS 虚拟文件系统里面注册了一种类型,定义为struct file_system_type rootfs_fs_type
。为了兼容各种文件系统,VFS(Virtual File System)作为抽象层,向上提供统一的接口。
rest_init
:其他方面的初始化(1 号进程和 2 号进程)。分层权限管理
x86 提供了分层的权限机制,把区域分成了四个 Ring,越往里权限越高,越往外权限越低。
基于此权限管理的代码执行的逻辑:
用户态代码不允许执行更高权限的指令,如需要访问核心资源,则要通过系统调用。
发起系统调用,将停止当前运行(状态保存在寄存器),交由内核态代码执行。
内核态代码执行结束,从寄存器恢复状态,将结果返回给用户态,用户态代码恢复执行。
rest_init 启动 1 号进程
rest_init
调用kernel_thread(kernel_init, NULL, CLONE_FS)
创建 1 号进程(第二个进程),用于管理 Ring3 的用户态(User Mode)。执行
kernel_thread
时还处于内核态,要使得能在用户态运行程序,还需要执行以下操作。从内核态到用户态
kernel_thread
会调用kernel_init
函数,其执行文件:do_execve
是一个系统调用, 尝试运行 ramdisk 的/init
或普通文件系统上的/sbin/init
、/etc/init
、/bin/init
、/bin/sh
之其一。以下可以理解为:内核态逻辑 -> 恢复寄存器 -> 系统调用返回 -> 用户态逻辑(“用户态发起系统调用”过程的后半部分)。调用链:
do_execve
->do_execveat_common
->exec_binprm
->search_binary_handler
。其中
search_binary_handler
会加载一个 ELF(Executable and Linkable Format,可执行与可链接格式)的二进制文件,在代码中定义为:其中
load_elf_binary
,最后调用start_thread
:以上过程是直接从内核态跳转到用户态,之前没有用户态保存寄存器的操作,因此需要初始化用户态寄存器。
最后的
force_iret
用于从系统调用中返回。此时恢复寄存器,由于上面的函数已补上了寄存器,CS 和 IP 恢复,指向用户态下一个要执行的语句。DS 和 SP 也被恢复,指向用户态函数栈的栈顶,因此下一条指令就从用户态开始运行。ramdisk
ramdisk 是基于内存的文件系统,区别于其它运行在存储设备上的文件系统,它不需要驱动就能运行。
因此执行 ramdisk 的
/init
,/init
在用户态根据存储系统的类型加载驱动,再启动真正根文件系统上的/init
。随后执行系统的初始化,启动系统服务、控制台等,此时所有用户态进程的祖先
init
创建完成。rest_init 启用 2 号进程
调用
kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES
:2 号进程(第三个进程),用于管理 Ring0 的内核态(Kernel Mode)。对于内核态而言进程和线程没有区别,统称为任务(Task),存放在相同的数据结构中。
其中
kthreadd
负责所有内核态的进程的调度和管理,即内核态所有进程的祖先。Beta Was this translation helpful? Give feedback.
All reactions