线程

线程是程序执行流的最小单元。

各个线程之间共享程序的内存空间和进程级资源。

组成

线程 ID,当前指令指针,寄存器集合,堆栈

拥有自己的空间

栈,线程局部储存(Thread Local Storage),寄存器

线程之间共享

全局变量,堆上的数据,函数的静态变量,代码,文件

A850F730-0105-4814-A347-63BCB13B8D18

调度中状态

运行,就绪,等待

线程优先级改变

线程安全

多线程模型

一对一,多对一,多对多

多线程的优点

  1. 某个操作可能陷入长时间等待-等待的线程会进入睡眠状态而无法继续执行
  2. 某个操作可能会消耗大量时间-程序和用户的交互会中断
  3. 程序的逻辑要求并发操作
  4. 单线程无法发挥多核计算机全部能力
  5. 资源共享效率提高

编译

编译器的过程

ABI

Application Binary Interface,符号修饰标准,变量内存布局,函数调用方式等和可执行代码二进制兼容性相关的内容,二进制层面的接口,ABI 互不兼容导致目标文件无法相互链接

API

Application Programing Interface,应用程序编程接口

目标文件

未经链接的可执行文件,部分地址未被调整,和可执行文件格式类型内容和结构类似

ELF

Executable and Linkable Format,可执行可链接格式

程序源代码被编译后主要分成两种段:程序指令、程序数据。 优点:

  1. 指令只读,数据读写,分别控制权限
  2. 数据缓存和指令缓存分离,程序局部性
  3. 指令等只读数据可共享

2CD88E87-3225-4A58-AF20-535F19AC2E0E

目标文件中有相应的符号表。符号值为变量或函数的地址 符号表里分为:全局符号,外部符号,编译器产生的段名,行号信息 函数修饰和函数签名为了防止符号冲突

静态链接

暂时搁置引用符号的目标地址(地址空间分配),根据引用的符号自动去查找模块里符号的真实地址(符号决议),并在对引用符号的地方进行重新修正为真实地址(重定位)

空间分配

相似段合并,虚拟空间分配

装载与进程

计算机按照程序只是把输入数据加工成输出数据。 每个程序被运行起来后,拥有独立的虚拟地址空间,最大理论上限由 CPU 位数决定,比如 32 位(0-2^32-1,0x00000000~0xFFFFFFFF) C语言指针所占空间和虚拟空间位数相同,32 位- 4 字节

PAE

Physical Address Extension,扩展物理地址空间,提供窗口映射方法,把额外的内存映射到进程的物理空间

动态载入

常用驻留在内存中,不常用在磁盘中 常用的有覆盖装入(Overlay)(几乎被淘汰)和页映射(Paging)

进程创建

  1. 创建独立的虚拟地址空间(建立物理内存地址和虚拟地址空间的映射函数所需要的相应的数据结构)
  2. 装载-读取可执行文件头,建立与虚拟地址空间的映射关系(页与可执行文件位置的对应关系,是一种数据结构)
  3. 将 CPU 指令寄存器设置成可执行文件的入口地址,启动(指令寄存器将控制权转交给进程)

可执行文件又叫映像文件(Image)因为装载时实际是被映射的虚拟空间。

VMA

Virtual Memory Area,虚拟内存区域,Linux 中将进程虚拟空间的一个段

页错误处理

  1. 当执行地址的指令,发现是空页面时,CPU 将控制权给操作系统
  2. 操作系统查询装载时映射关系的数据结构,找到对应虚拟内存区域,计算对应执行文件中的偏移
  3. 在物理内存中分配一个物理页面,建立和虚拟页的映射关系
  4. 控制权交还,重新开始执行

ELF文件中段的权限

目标文件链接成可执行文件时,链接器会尽量将相同权限属性的段分配在同一空间。

Section

从链接角度划分 ELF 文件(一般情况下段的含义)

Segment

从执行角度划分 ELF 文件(装载时段的含义)

进程的VMA种类

重定位

链接器将指令地址进行修正,符号目标地址确定

重定位表

用来表述如何修改相应段中的内容

符号解析

查找所有输入目标文件的符号表组成的全局符号表,找到对应符号重定位

Ar

Ar 压缩程序将目标文件库压缩成后缀为a的静态库文件

BFD库

Binary File Descriptor library,统一的接口来处理不同的目标文件格式

动态链接

将模块分割开形成独立文件,将链接过程推迟到运行时

55B18065-42D1-4180-ACD6-7B7A68677CFB

优点:

  1. 减少重复文件
  2. 升级无需重新链接
  3. 独立耦合度更小
  4. 动态插入实现拓展
  5. 提高兼容性

动态链接文件在 Linux 系统中为 DSO(Dynamic Share Objects 动态共享对象,.so 拓展名) Windows 中为动态链接库(Dynamical Linking Libarary,.dll 拓展名)

so 文件中保存了完整的符号信息,链接器在解析负号时判定为动态符号,不进行地址重定位,留到装载时。

解决绝对地址引用的方法

  1. 装载时才进行重定位(指令被多个进程重定位,无法共享代码段,运行速度较快)
  2. PIC(Position-independent Code,地址无关代码,共享对象在被装载时确定在进程虚拟地址空间中位置的方案)

地址引用方式

PIE

Position-Independent Executable,以地址无关方式编译的可执行文件

PLT

Procedure Linkage Table,延迟绑定的方法,在函数第一次使用时才进行绑定(符号查找,重定位等)

动态链接过程

  1. 启动动态链接器
  2. 装载需要的共享对象
  3. 重定位和初始化

动态链接器

内存

内存区域

保存了函数调用所需要的维护信息:堆栈帧(Stack Frame)或活动记录(Activate Record)

包括:

函数调用惯例

程序可申请一块连续内存自由使用直到主动放弃 运行库管理着堆空间的分配

  1. 设置进程数据段结束地址
  2. 申请一段之后不映射某个文件的虚拟地址空间,也称匿名空间

堆分配算法

空闲链表

堆中空闲块连接,需要时找到合适大小拆卸下来,释放时合并上去,头部 4 字节记录该分配的大小 优点:实现简单 缺点:链表和记录大小的4字节被破坏则无法工作

位图

分成大量相同大小的块,分配时第一块为头 11,其余为主体 10,未分配为空闲 00,三种状态,每两位表示一块,一个整数数组存储 优点:速度快-容易命中 cache,稳定性好-易备份,部分破坏影响小 缺点:块太大容易产生碎片浪费,块太小位图很大,失去 cache 命中率高

对象池

分成固定大小的块,每次分配一个块

运行库

程序开始运行

  1. 操作系统在创建进程后,把控制权交到了程序的入口,这个入口往往是运行库中的某个入口函数
  2. 入口函数对运行库和程序运行环境进行初始化,包括堆、I/O、线程、全局变量构造
  3. 入口函数在完成初始化之后,调用 main 函数,正式开始执行程序主体部分
  4. main 函数执行完毕以后,返回到入口函数,入口函数进行清理工作,包括全局变量析构、堆销毁、关闭 I/O 等
  5. 进行系统调用结束进程

操作系统设计一个句柄(Handle)原因在于防止用户随意读写系统内核的文件对象,内核可通过句柄计算内核文件对象地址。

new 实现:申请堆空间 -> malloc 传入 size -> 返回空间地址 delete 实现:运行析构函数 -> 释放堆空间

系统调用

操作系统特权等级

用户态和内核态

中断

用户态通过中断切换内核态 中断号和中断处理程序(ISR,Interrupt Service Routine),一一对应 内核中有中断向量表(Interrupt Vector Table),第 n 项对应 n 号的中断处理程序的指针 触发中断 -> 切换栈控件 -> 处理中断处理程序