操作系统

文章目录
  1. 调用栈和段
  2. 运行时存储器映像
  3. 进程与线程

参考书 : 操作系统:精髓与设计原理(第六版)
参考书 : 深入理解计算机系统(原书第2版)

调用栈和段

要想理解数据结构的栈结构, 写个简单的递归程序, 调试的时候查看调用栈(Call Stack),调试一遍就知道了

调用栈描述了函数之间的调用关系, 由多个栈帧(Stack Frame)组成, 每个栈帧代表着一个未运行完的函数, 栈帧中保存着函数的返回地址和局部变量

  • 图示:
  1. esp是栈指针,始终指向栈顶元素 ebp是帧指针, 二者共同组成栈帧的界限

  2. 栈帧的末尾存放主调函数的返回地址

  3. 返回地址就是当被调函数结束后, 在主调函数继续执行的地方, 本质上就是在栈帧中会存放上一个栈帧的起始地址

  • 由图可知,
  1. 函数1调用函数2, 函数2调用函数3
  2. 随着函数调用层数的增加, 栈是向低地址方向增长的
  3. 函数3调用结束之后, 保存的数据会出栈, esp会向高地址移动, 当espebp重合时说明函数3执行过程完全结束, 这时根据保存的上一个栈帧的地址, 使得ebp指向函数2的栈帧地址,esp始终指向栈顶, 这时就界定出函数2的栈帧, 也就是返回到了主调函数函数2继续执行
  4. 就这样一步步结束了整个调用过程

栈溢出 : 每一次的函数调用都会向调用栈中增加一个栈帧, 当栈顶越界是时导致栈溢出(Stack Overflow)

段(Segments)是可执行文件中的概念, 指的是二进制文件内的区域, 该区域包含加载程序到存储器并运行的所有信息

  • 正文段.text用于存储指令, 是只读存储段
  • 数据段.data用于存储已经初始化的全局变量
  • .rodata :只读数据
  • BSS段.bss用于存储未赋值的全局变量所需的空间
  • 节头部表(Section Haeders)用于描述不同节的位置和大小,目标文件中每个节都有一个固定大小的表目
  • 段头部表用于描述不同段的位置和大小, 分为代码段数据段

运行时存储器映像

  • 由图可知,
  1. 用户栈和动态内存分配都是在运行时创建的
  2. 调用栈所在的段称为堆栈段(Stack Segment)

堆栈段也有自己的大小, 当越界访问时会出现段错误

进程与线程

进程是指一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程, 进程的两个基本元素是程序代码与程序代码相关的数据集

  • 在进程执行时, 描述该进程的所有信息组成进程控制块(一种数据结构), 正是因为进程控制块的存在才使得操作系统有了多进程和多处理的能力, 因为进程控制块保存了关于进程的所有信息, 使得当执行中的进程中断之后再次执行时就好像没有中断过一样

  • 在单处理器多道程序系统中, 多个进程的执行可以在同一时间交叉进行, 在多处理器系统中, 不仅多个进程可以交叉执行而且可以同步执行

在前面几个部分, 我们简单的介绍了程序执行中的内存分配等部分, 那么程序和进程之间的关系是什么呢?

  • 操作系统将处于执行状态的程序抽象为进程, 进程是一种动态的概念, 是执行中的程序(程序+执行状态)
  • 同一个程序多次执行对应着不同的进程, 在物理内存中时被加载到不同的进程空间地址中, 也就是说即使同一个程序, 执行过程中被加载到不同的进程控制块中, 就是不同的进程
  • 程序是静态的, 存储在文件系统中, 执行中的程序抽象为进程, 进程需要CPU和内存等资源

进程的特点

假设有ABCD四个程序代码加载到内存当中, 进程的切换就是指的CPU从执行A的指令变为执行B的指令

  • 并发执行 : 在CPU的物理指令指针依然每次只执行一条指令, 但是通过进程的切换我们可以理解为在程序层面上有4个指令指针在同时并发执行

  • 在时间轴上, ABCD四个程序在交替运行, 宏观上4个程序在并发执行

五状态模型

其中运行, 就绪, 等待(阻塞)是核心的三种状态, 假设计算机只有一个处理器, 这样一次最多只有一个进程处于运行状态; 创建和结束的状态只会出现一次, 属于过渡状态

  • 运行指进程正在处理器上执行
  • 就绪指除了处理器资源没有获得, 其他资源都已获得, 一旦获得处理器就进入运行状态
  • 等待指进程在等待某个事件而暂停运行, 当特定事件发生时, 就进入就绪状态

为了减少内存的耗用, 将进程的一部分或者进程的全部存储到外存上, 这样就和虚拟存储相关联起来, 这样就多了就绪挂起等待挂起, 挂起就是将进程从内存转到外存, 激活与挂起相反

  • 就绪 : 进程在内存中, 可以被执行
  • 等待(阻塞) : 进程在内存中, 等待时间的发生
  • 就绪挂起 : 进程存储在外存, 一旦进入内存即可运行
  • 等待挂起 : 进程存储在外存, 等待事件的发生

为什么引入线程? 因为我们希望在一个进程里面进一步提高并发性, 那么为什么我们不用多进程去实现相应的功能呢, 因为进程的引入是为了更好的隔离执行中的程序, 但是有些程序实现的功能需要共享程序执行所需的数据, 那么进程就不适用了, 那么我们需要将共享数据的程序集成到一个进程里并发执行, 这样就引入了线程(thread)

线程是进程的一部分,描述指令流的执行状态, 它是进程中的指令执行流的最小单元,是CPU调度的基本单位

线程 = 进程 - 共享数据

  • 优点 : 一个进程中可以存在多个线程, 各个线程之间可以并发地执行, 并且可以共享地址空间和文件等资源
  • 缺点 : 一个线程崩溃,会导致其所属进程的所有线程崩溃
  • 进程变为资源分配的单位 : 进程由一组相关资源构成,包括地址空间(代码段、数据段)、打开的文件等各种资源
  • 线程CPU调度的单位 : 每一个线程会在一个相同的进程地址空间内分配一个对应的线程控制块, 一个进程中的所有线程共享这个进程中的数据

线程和进程的关系

  • 进程是资源分配单位,线程是CPU调度单位
  • 进程拥有一个完整的资源平台,而线程只独享指令流执行的必要资源,如寄存器和栈
  • 线程具有就绪、等待和运行三种基本状态和状态间的转换关系
  • 线程能减少并发执行的时间和空间开销, 由于同一进程的各线程间共享内存和文件资源,可不通过内核进行直接通信, 所以线程的创建时间, 终止时间, 切换时间都比进程要短