启动

BIOS

basic input output system 基本输入输出系统

BIOS启动流程: 加电自检, 然后读出启动盘的第一个扇区 到 0x7c00 位置 然后调整到 0x7c00位置执行代码, 这个时候把执行权限交给我们, 我们需要将代码写入到第一个扇区, 然后将操作系统加载到内存中

此时CPU处于的模式是在实模式下 , 在这个模式下我们能够直接操作内存, 想怎么操作怎么操作, 但是只有1M的寻址空间

中断

中断处理流程

  1. 中断产生和检测
    • 当外部设备或其他源产生中断信号时,外部中断控制器(如PIC或APIC)负责接收这些信号。
    • 当CPU完成当前执行的指令后,它会检查中断控制器是否标记了任何待处理的中断。
  2. 中断向量和中断服务例程(ISR)的查找
    • 如果中断被允许(即处理器的中断标志IF位为1),CPU会从中断控制器获取一个中断向量号。
    • 使用这个中断向量号,CPU会查找IDT(中断描述符表),以确定相应的中断服务例程的地址。
  3. 状态保存和中断响应
    • 在跳转到中断服务例程之前,CPU自动保存当前的代码段寄存器(CS)和指令指针(IP)到堆栈中。这确保了中断处理完成后能够返回到正确的位置继续执行。
    • CPU同时保存状态寄存器(如EFLAGS),其中包括中断标志位。
    • 接着,CPU会清除IF位以关闭进一步的中断,防止在处理当前中断时受到干扰。
  4. 执行中断服务例程
    • 执行的中断处理函数由操作系统提供。这个函数可以根据需要保存和恢复更多的执行上下文,如寄存器等。
    • 在某些情况下,中断服务程序可以根据设计选择重新开启中断(即在ISR中重新设置IF位),这允许嵌套中断或二级中断。
  5. 中断处理完成后的恢复与返回
    • 中断服务例程完成后,执行特定的返回指令(如x86的IRET),这将从堆栈中恢复之前保存的IP、CS和EFLAGS等状态寄存器。
    • 恢复这些寄存器后,CPU返回到被中断的代码继续执行。

前三步硬件自动完成

实模式

实模式下, 系统通过中断号, 在中断向量表中找到对应的中断向量, 中断向量 是由cs:ip 组成, 记录了中断处理函数的位置

保护模式

保护模式下, 系统通过中断号, 在中断描述符表中找到中断描述符, 中断描述符中记录了很多信息

1
2
3
4
5
6
7
8
9
10
11
typedef struct gate_t
{
u16 offset0; // 段内偏移 0 ~ 15 位
u16 selector; // 代码段选择子
u8 reserved; // 保留不用
u8 type : 4; // 任务门/中断门/陷阱门
u8 segment : 1; // segment = 0 表示系统段
u8 DPL : 2; // 使用 int 指令访问的最低权限
u8 present : 1; // 是否有效
u16 offset1; // 段内偏移 16 ~ 31 位
} _packed gate_t;

通过中断描述符就能找到中断处理函数了

加载中断描述符表

定义中断描述符表

1
2
#define ENTRY_SIZE 0x30   // 这里可以根据需要更改, 这里只定义一些常用的
gate_t idt[IDT_SIZE];

中断描述符指针

1
2
3
4
5
6
7
8
typedef struct pointer_t
{
u16 limit;
u32 base;
} _packed pointer_t;
pointer_t idt_ptr; // 中断描述符指针
idt_ptr.base = (u32)idt;
idt_ptr.limit = sizeof(idt)-1;

加载中断描述符表

1
asm volatile("lidt idt_ptr\n"); 

中断函数编写思路

首先定义一个做验证, 和 保存上下文 和 恢复上下文的函数 handle_entry 作为中断描述符入口地址, 里面调用 handle 函数 来处理真正的中断,

物理内存分配

0x1000 页目录

0x2000-0x3000 内核页表 (前八兆的虚拟地址就是物理地址)

0x4000-0x40DF 内核虚拟内存位图 (一位代表一页, 第一位代表0x100000开始的那一页, 值为1代表分配了, 值为0代表未分配, 总共0x700页)

0x10000-0x101000 内核物理内存分配图(一个字节代表一页)

疑问解答

一个物理地址是怎么到硬件的?

  1. 硬件映射BIOS地址
    • 系统的主板设计(尤其是芯片组的设计)固定了BIOS ROM在物理地址空间的位置。这通常是在系统的物理地址空间的高地址端。这个映射是固定的,确保在计算机启动时,CPU能够找到并执行BIOS的启动代码。CPU在复位时自动将指令指针设置到这个预定的高地址处,通常是0xFFFFFFF0(实模式下的0xFFFF0),这里的代码负责跳转到BIOS的主体部分。
  2. BIOS映射其他硬件地址(通过芯片组)
    • 在系统启动和BIOS初始化阶段,BIOS负责探测系统中的硬件(如内存、显卡、存储设备等),并进行相应的配置和初始化。在这个过程中,BIOS根据探测到的硬件配置情况来设置和调整系统资源(如IRQs, DMA, I/O端口等)的分配和物理地址空间的映射。
    • 对于系统内存,BIOS会检测安装的内存条和配置,并设置内存控制器将物理内存映射到物理地址空间的适当位置,这通常从低地址开始。

cpu如何限制用户态功能

特权级别(Rings)

在 x86 架构中,有四个特权级别,从 Ring 0 到 Ring 3:

  • Ring 0(内核态):具有最高权限的执行级别,操作系统的核心部分(内核)在这个级别运行。可以访问所有的 CPU 指令和直接管理硬件资源。
  • Ring 1 和 Ring 2:这两个级别在大多数现代操作系统中很少使用,它们为设备驱动程序和其他特定的系统代码提供了中间层的权限级别。
  • Ring 3(用户态):具有最低权限的执行级别,绝大多数应用程序在这个级别运行。用户态程序被限制只能执行非特权指令,且不能直接访问硬件资源。

在用户程序中, 段选择子中是用户级别, 可以限制很多执行无法使用

为什么中断(系统调用)可以从用户态到内核态

中断描述符

中断描述符有下面两个属性, 一个是可以执行当前中断的特权级, 一个是执行中断的时候拥有的特权级

1. DPL(Descriptor Privilege Level)

DPL是中断门描述符中的一个字段,它指定触发该中断所需的最低CPL(当前特权级)。这意味着:

  • 对于系统调用,中断门的DPL通常设置为3,这允许用户态程序(CPL为3)触发这些中断。这是因为系统调用是由用户程序意图执行的操作,需要从用户态切换到内核态以执行更高权限的操作。
  • 对于其他大多数硬件中断和异常处理,DPL通常设置为0。这意味着只有在内核态(CPL为0)代码执行时才能处理这些中断,或者由硬件自动触发,无需用户态程序直接触发。

2. 段选择子(Segment Selector)

段选择子指向全局描述符表(GDT)中的一个段,该段包含中断处理程序(ISR)的代码。这个段的特权级(即代码段的DPL)通常是0,表示ISR运行在内核态。

  • 当中断发生时,CPU会检查触发中断的CPL是否满足访问IDT条目中指定的段选择子的DPL要求。对于系统调用,由于DPL为3,用户态程序可以访问;对于其他中断,只有内核代码或由硬件触发的事件才能访问。

系统调用与常规中断

  • 系统调用(如int 0x80syscall)是用户程序请求操作系统服务的方式。操作系统提供的系统调用接口在IDT中配置,以允许用户态程序触发这些中断进而执行内核代码。系统调用中断门的DPL设置为3,使得用户态程序可以触发它们。
  • 常规中断和异常:如硬件中断(来自外设)和异常(如页错误),它们的中断门DPL通常设置为0,这意味着它们只能在内核态被处理或由硬件直接触发。