2、系统结构

计算机体系结构基础

1、总线

2 ^ 16 byte = 64 KB,2 ^ 20 byte = 1 MB,2 ^ 32 = 4 GB

  • 地址总线(AB)的宽度决定了 CPU 的寻址能力
  • 数据总线(DB)的宽度决定了 CPU 与其他器件进行数据传送时的一次数据传送量
  • 控制总线(CB)的宽度决定了 CPU 对系统中其他器件的控制能力

8086:16 位寄存器(64 KB)+ 20 位地址总线(1 MB),CS 左移 4 位 + 偏移地址 IP = 20 位内存地址
80386(i386):32 位地址总线(4 GB),8086 的模式也叫实模式,i386 会从实模式进入保护模式

2、CPU

2.1、普通寄存器

8 个通用寄存器:EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP
6 个段寄存器:CS、DS、SS、ES、FS、GS
EFLAGS、EIP
单位:b 字节 8、w 字 16、l 双字 32、q 四字 64
一般搭配:CS:EIP、SS:EBP + SS:ESP、DS:SI + ES:DI + ECX + rep + movw、默认段寄存器为 DS

image

以下内容来自 Intel 开发手册 Chapter 3 - 3.4

  • General-purpose registers. These eight registers are available for storing operands and pointers.
  • Segment registers. These registers hold up to six segment selectors.
  • EFLAGS (program status and control) register.
    The EFLAGS register report on the status of the program being executed and allows limited (application-program level) control of the processor.
  • EIP (instruction pointer) register. The EIP register contains a 32-bit pointer to the next instruction to be executed.

image

8 个通用寄存器:EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP

  • EAX — Accumulator for operands and results data
  • EBX — Pointer to data in the DS segment
  • ECX — Counter for string and loop operations
  • EDX — I/O pointer
  • ESI — Pointer to data in the segment pointed to by the DS register; source pointer for string operations
  • EDI — Pointer to data (or destination) in the segment pointed to by the ES register; destination pointer for string operations
  • ESP — Stack pointer (in the SS segment)
  • EBP — Pointer to data on the stack (in the SS segment)

image
image

2.2、系统寄存器

Linux 内核完全注释 V5.0

  • ZF:1 非零,0 为零
  • DF:cld 0 递增,std 1 递减
  • IF:sti 1 允许,cli 拒绝 0

image
image

CR2 和 CR3 用于分页机制,CR3 含有存放页目录表页面的物理地址,因此 CR3 也被称为 PDBR
因为页目录表页面是页对齐的,所以该寄存器只有高 20 位是有效的,而低 12 位保留供更高级处理器使用,因此在往 CR3 中加载一个新值时低 12 位必须设置为 0

使用 MOV 指令加载 CR3 时具有让页高速缓冲无效的副作用
为了减少地址转换所要求的总线周期数量,最近访问的页目录和页表会被存放在处理器的页高速缓冲器件中
该缓冲器件被称为转换查找缓冲区 TLB(Translation Lookaside Buffer),只有当 TLB 中不包含要求的页表项时才会使用额外的总线周期从内 存中读取页表项

即使 CR0 中的 PG 位处于复位状态(PG = 0),我们也能先加载 CR3,以允许对分页机制进行初始化
当切换任务时,CR3 的内容也会随之改变,但是如果新任务的 CR3 值与原任务的一样,处理器就无需刷新页高速缓冲,这样共享页表的任务可以执行得更快

CR2 用于出现页异常时报告出错信息
在报告页异常时,处理器会把引起异常的线性地址存放在 CR2 中,因此操作系统中的页异常处理程序可以通过检查 CR2 的内容来确定线性地址空间中哪一个页面引发了异常
image

系统指令用于处理系统级功能,例如加载系统寄存器、管理中断等
大多数系统指令只能由处于特权级 0 的操作系统软件执行,其余一些指令可以在任何特权级上执行,因此应用程序也能使用

指令 指令全名 受保护 说明
LGDT Load GDT Register 加载全局描述符表寄存器 GDTR
SGDT Store GDT Register 保存全局描述符表寄存器 GDTR
LLDT Load LDT Register 加载局部描述符表寄存器 LDTR
SLDT Store LDT Regiter 保存局部描述符表寄存器 LDTR
LTR Load Task Register 加载任务寄存器 TR:把 TSS 段描述符加载到任务寄存器中
STR Store Task Register 保存任务寄存器 TR:把 TR 中 TSS 当前任务段描述符保存到内存或通用寄存器中
LIDT Load IDT Register 加载中断描述符表寄存器 IDTR
SIDT Store IDT Register 保存中断描述符表寄存器 IDTR
MOV CRn Move Control Registers 加载和保存控制寄存器 CR0、CR1、CR2、CR3
LMSW Load Machine State Word 加载机器状态字(对应 CR0 寄存器位 15 - 0)
SMSW Store Machine State Word 保存机器状态字
CLTS Clear TS flag 清除 CR0 中的任务已切换标志 TS,用于处理设备(协处理器)不存在异常
LSL Load Segment Limit 加载段限长
HLT Halt Processor 停止处理器执行

2.3、内存管理

image
image

2.4、段寄存器

image
image

2.5、段描述符

image
image
image

32 位段基址,最大 4 G
20 位段界限,最大 1 MB

G 位是粒度(Granularity)位
1、0 表示段界限以字节为单位
   此时,段的扩展范围是从 1 字节到 1 兆字节(1Byte~1MB),因为描述符中的界限值是 20 位的
2、1 表示段界限是以 4KB 为单位的
   这样,段的扩展范围是从 4KB 到 4GB(实际使用的段界限 = 描述符中的段界限 × 0x1000 + 0xFFF)

D / B 位是 "默认的操作数大小"(Default Operation Size)
      或者 "默认的栈指针大小"(Default Stack Pointer Size)
      或者 "上部边界"(Upper Bound)标志
1、对于代码段, 此位称做 "D" 位,用于指示指令中 "默认的偏移地址和操作数尺寸"
   0 代表 16 位:IP
   1 代表 32 位:EIP
2、对于栈段来说, 该位被叫做 "B" 位, 用于在进行隐式的栈操作时, 是使用 SP 寄存器还是 ESP 寄存器
   0 代表 16 位:SP,栈段的上部边界(也就是 SP 寄存器的最大值)为 0xFFFF
   1 代表 32 位:ESP,栈段的上部边界(也就是 ESP 寄存器的最大值)为 0xFFFFFFFF

L 位是 64 位代码段标志(64-bit Code Segment),保留此位给 64 位处理器使用
AVL 是软件可以使用的位(Available),通常由操作系统来用,处理器并不使用它

P 是段存在位(Segment Present),用于指示描述符所对应的段是否存在:0 表示存在,1 表示不存在
DPL 表示描述符的特权级(Descriptor Privilege Level, DPL),这两位用于指定段的特权级
    共有 4 种处理器支持的特权级别,分别是:00、01、10、11
S 位用于指定描述符的类型(Descriptor Type)
1、0 表示是一个系统段
2、1 表示是一个代码段或者数据段(栈段也是特殊的数据段)

TYPE:0010 数据(读 + 写),1010 代码(执行 + 写)

image
image
image

2.6、分页机制

image
image

P 存在位:0 不存在,1 存在
R / W 位:0 只读只执行,1 读写执行
U / S 位:0 特权级 012 可以访问,1 都可以访问
A 位:已访问标志,OS 定期地复位来统计页面的使用情况
D 位:脏位,写操作后页面置脏
AVL 位:供程序员使用的位

image
image

2.7、中断

CPU 在每一个指令周期的最后,都会留一个 CPU 周期去查看是否有中断,如果有,就把中断号取出,去中断向量表中寻找中断处理程序,然后跳过去执行

image
image
image

当处理器执行异常或中断处理过程调用时会进行以下操作

  • 如果处理过程将在高特权级(例如 0 级)上执行时就会发生堆栈切换操作,堆栈切换过程如下
    • 处理器从当前执行任务的 TSS 段中,得到中断或异常处理过程使用的堆栈的段选择符和栈指针(例如 tss.ss0、tss.sp0)
      然后处理器会把被中断程序(或任务)的栈选择符和栈指针压入新栈中
    • 接着处理器会把 EFLAGS、CS 和 EIP 寄存器的当前值也压入新栈中
    • 如果异常会产生一个错误号,那么该错误号也会被最后压入新栈中
  • 如果处理过程将在被中断任务同一个特权级上运行,那么
    • 处理器把 EFLAGS、CS 和 EIP寄存器的当前值保存在当前堆栈上
    • 如果异常会产生一个错误号,那么该错误号也会被最后压入新栈中

为了从中断处理过程中返回,处理过程必须使用 IRET 指令,IRET 指令与 RET 指令类似,但 IRET 还会把保存的寄存器内容恢复到 EFLAGS 中
不过只有当 CPL 是 0 时才会恢复 EFLAGS 中的 IOPL 字段,并且只有当 CPL <= IOPL 时,IF 标志才会被改变
如果当调用中断处理过程时发生了堆栈切换,那么在返回时 IRET 指令会切换回到原来的堆栈

image
image

P:0 页面不存在(缺页异常),1 违反页级保护权限
W / R:W 写,R 读
U / S:发生异常时 CPU 执行的代码级别,0 内核态,1 用户态

处理器还会把引起页面故障异常所访问用的线性地址存放在 CR2 中,页出错异常处理程序可以使用这个地址来定位相关的页目录和页表项
错误不会被 IRET 指令自动地弹出堆栈,因此中断处理程序在返回之前必须清除堆栈上的错误码
处理产生的某些异常会产生错误码并会自动地保存到处理过程的堆栈中,但是外部硬件中断或者程序执行 INT n 指令产生的异常并不会把错误码压入堆栈中

2.8、任务

image
image
image
image

2.9、特权级

处于用户态的程序,通过触发中断,可以进入内核态,之后再通过中断返回,又可以恢复为用户态

  • 代码:同级跳转
  • 数据:同级 + 高访问低

image
image

3、中断

认认真真聊聊中断

认认真真聊聊软中断

3.1、中断分类

The processor provides two mechanisms for interrupting program execution, interrupts and exceptions

  • An interrupt is an asynchronous event that is typically triggered by an I/O device.
  • An exception is a synchronous event that is generated when the processor detects one or more predefined conditions while executing an instruction.
    The IA-32 architecture specifies three classes of exceptions: faults, traps, and aborts.

CPU 提供了两种中断程序执行的机制,中断和异常

  • 中断是一个异步事件:通常由 IO 设备触发
    通过中断控制器给 CPU 的 INTR 引脚发送信号,并且允许 CPU 从中断控制器的一个端口上读取中断号,比如按下键盘的一个按键,最终会给到 CPU 一个 0x21 中断号
  • 异常是一个同步事件:是 CPU 在执行指令时检测到的反常条件,比如除法异常、错误指令异常,缺页异常等
    CPU 执行某条指令发现了异常,会自己触发并给自己一个中断号,比如执行到了无效指令,CPU 会给自己一个 0x06 的中断号
  • 软件中断:执行 INT n 指令,会直接给 CPU 一个中断号 n,比如触发了 Linux 的系统调用,实际上就是执行了 INT 0x80 指令,那么 CPU 收到的就是一个 0x80 中断号

image
image

3.2、中断处理程序

中断描述符分类

  • 任务门:Task Gate,Linux 中几乎没有用到
  • 中断门:Interrupt Gate,不允许中断嵌套
  • 陷阱门:Trap Gate,允许中断嵌套

CPU 拿到中断号后,就一视同仁:IDTR -> IDT -> Interrupt Descriptors -> GDTR-> GDT -> 段基址 + 偏移地址

  • 如果发生了特权级转移,压入之前的堆栈段寄存器 SS 及栈顶指针 ESP 保存到栈中,并将堆栈切换为 TSS 中的堆栈
  • 压入标志寄存器 EFLAGS
  • 压入之前的代码段寄存器 CS 和指令寄存器 EIP,相当于压入返回地址
  • 如果此中断有错误码的,压入错误码 ERROR_CODE
  • 结束(之后就跳转到中断程序了)
  • 通过配合 IRET 或 IRETD 指令返回

image
image

3.3、软中断

有一个单独的守护进程,不断轮询一组标志位,如果哪个标志位有值了,那去这个标志位对应的软中断向量表数组的相应位置,找到软中断处理函数,然后跳过去执行
Linux-2.6.0 主方法 start_kernel() -> rest_init() -> spawn_ksoftirqd() 开启内核软中断守护进程 -> do_softirq()
不断遍历 pending 这个软中断标志位的每一位,如果是 0 就忽略
如果是 1,那从上面的 h 软中断向量表中找到对应的元素,然后执行 action 方法,action 就对应着不同的软中断处理函数

// 这就是软中断处理函数表(软中断向量表)
// 和硬中断的中断向量表一样
static struct softirq_action softirq_vec[32];

asmlinkage void do_softirq(void) {
    h = softirq_vec;                   // h = 软中断向量表起始地址指针
    pending = local_softirq_pending(); // 这个是软中断标志位,一次性拿到所有的软中断标志位
    do {
        // 此时的软中断标志位有值(说明有软中断)
        if (pending & 1) {
            h->action(h);  // 去对应的软中断向量表执行对应的处理函数
        }
        h++;               // 软中断向量表指针向后移动
        pending >>= 1;     // 同时软中断处理标志位也向后移动
    } while (pending);
}

image

3.4、Linux 中断

硬中断资源又非常宝贵,如果占着硬中断函数不返回,会影响到其他硬中断的相应速度,比如点击鼠标、按下键盘等
所以 Linux 会把中断分成上下两半部分执行,上半部分由硬中断处理最简单的逻辑,下半部分直接丢给一个软中断异步处理

4、系统调用

系统调用:int 0x80(陷阱门 Trap Gate,允许中断嵌套,DPL = 3 允许用户进程使用该门描述符),eax 存放子功能号和返回值

为了从中断处理过程中返回,处理过程必须使用 IRET 指令,IRET 指令与 RET 指令类似,但 IRET 还会把保存的寄存器内容恢复到 EFLAGS 中
image

一次系统调用的过程,其实是发生了两次 CPU 上下文切换(用户态 -> 内核态 -> 用户态)

image
image

5、ELF

image
image
image
image

6、用户线程和内存态线程

  • 在 CPU 层面:进程、线程、协程,都是一段代码
  • 在操作系统层面:线程、协程,都是进程
  • 在 R3 用户态层面:进程、线程、协程

本质:让 CPU 最大限度执行用户的代码,减少 CPU 干别的事情

  • 线程引入的原因:进程切换,导致 CPU 刷新 TLB(页表缓存)、加载 CR3、切换上下文,发现 CPU 没有执行用户的代码
  • 协程引入的原因:让我们写代码更方便,让 CPU 进一步释放出来,比如 epoll,丢过去,OS 自己去轮询处理,CPU 继续执行用户的代码

用户态线程工作在用户空间,内核态线程工作在内核空间

  • 用户态线程调度完全由进程负责,通常就是由进程的主线程负责,相当于进程主线程的延展,使用的是操作系统分配给进程主线程的时间片段
    用户态线程无法跨核心,一个进程的多个用户态线程不能并发,阻塞一个用户态线程会导致进程的主线程阻塞,直接交出执行权限,这些都是用户态线程的劣势
  • 内核线程由内核维护,由操作系统调度
    内核线程可以独立执行,操作系统会分配时间片段,因此内核态线程更完整,也称作轻量级进程,内核态线程创建成本高,切换成本高,创建太多还会给调度算法增加压力,因此不会太多

7、cmpxchg

Compares the value in the AL, AX, EAX, or RAX register with the first operand (destination operand).
If the two values are equal, the second operand (source operand) is loaded into the destination operand.
Otherwise, the destination operand is loaded into the AL, AX, EAX or RAX register. RAX register is available only in 64-bit mode.

CAS:lock cmpxchg xxx, xxx;

cmpxchg 内存中的值,修改后的新值(AL、AX、EAX、RAX 中为期望值)
1、若内存中的值 == 期望值:内存中的值 = 修改后的新值,ZF = 1
2、若内存中的值 != 期望值:期望值 = 内存中的值,ZF = 0

保证线程安全的两种方式:1、上锁	2、无锁(单线程处理某个事情)
cmpxchg cx, dx

期望:ax = 2300H
内存:cx = 2300H
新值:dx = 2400H

cx == ax
cx = dx = 2400H, ZF = 1
cmpxchg cx, dx

期望:ax = 2500H
内存:cx = 2300H
新值:dx = 2400H

cx != ax
ax = 2300H, ZF = 0

8、Lock

Causes the processor’s LOCK# signal to be asserted during execution of the accompanying instruction (turns the instruction into an atomic instruction).
In a multiprocessor environment, the LOCK# signal ensures that the processor has exclusive use of any shared memory while the signal is asserted.
The LOCK prefix is typically used with the BTS instruction to perform a read-modify-write operation on a memory location in shared memory environment.

Beginning with the P6 family processors, when the LOCK prefix is prefixed to an instruction and the memory area being accessed is cached internally in the processor, the LOCK# signal is generally not asserted. Instead, only the processor’s cache is locked. Here, the processor’s cache coherency mechanism ensures that the operation is carried out atomically with regards to memory. See "Effects of a Locked Operation on Internal Processor Caches" in Chapter 8 of Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A, the for more informa-tion on locking of caches.

使处理器的 "LOCK#信号" 在 "执行伴随的指令期间" 被断言(将指令转换为原子指令)
在多处理器环境中,"LOCK#信号" 确保处理器在断言该信号时独占使用任何共享内存
LOCK 前缀通常与 BTS 指令一起使用,以对 "共享存储器环境" 中的存储器位置执行 "读取 - 修改 - 写入" 操作

从 P6 系列处理器开始,当 LOCK 前缀以指令为前缀,并且正在访问的内存区域在处理器内部缓存时,"LOCK#信号" 通常不会被断言,相反,只有处理器的缓存被锁定
在这里,处理器的缓存一致性机制确保了操作是 "相对于内存" AND "原子地执行" 的
如需有关缓存锁定的详细信息,请参阅 "英特尔® 64 与 IA-32 体系结构软件开发人员手册" 第 3A 卷第 8 章中的 "锁定操作对内部处理器缓存的影响"

Intel 64 and IA-32 processors provide a LOCK# signal that is asserted automatically during certain critical memory operations to lock the system bus or equivalent link. While this output signal is asserted, requests from other processors or bus agents for control of the bus are blocked. Software can specify other occasions when the LOCK semantics are to be followed by prepending the LOCK prefix to an instruction.

For the P6 and more recent processor families, if the area of memory being locked during a LOCK operation is cached in the processor that is performing the LOCK operation as write-back memory and is completely contained in a cache line, the processor may not assert the LOCK# signal on the bus. Instead, it will modify the memory loca-tion internally and allow it’s cache coherency mechanism to ensure that the operation is carried out atomically. This operation is called “cache locking.” The cache coherency mechanism automatically prevents two or more proces-sors that have cached the same area of memory from simultaneously modifying data in that area.

英特尔 64 和 IA-32 处理器提供 "LOCK#信号",该信号在某些关键内存操作期间自动断言,以锁定系统总线或等效链路
当该输出信号被断言时,来自其他处理器或总线代理的用于控制总线的请求被阻止,软件可以通过在指令前面加上 LOCK 前缀来指定 LOCK 语义的其他情况

对于 P6 和更新的处理器系列,如果在 LOCK 操作期间,被锁定的存储器区域被高速缓存在作为回写存储器执行 LOCK 操作的处理器中,并且完全包含在高速缓存行中
则处理器可能不会在总线上断言 "LOCK#信号",相反,它将在内部修改内存位置,并允许其缓存一致性机制来确保操作以原子方式执行
此操作称为 "缓存锁定",缓存一致性机制会自动阻止 "缓存同一内存区域" 的 "两个或多个进程" 同时 "修改" 该区域中的数据

posted @ 2023-08-07 15:03  lidongdongdong~  阅读(40)  评论(0编辑  收藏  举报