Processes进程

为什么我们需要同时运行多个程序?称为“多道程序设计” 因为它会提高 CPU 利用率
I/O 密集型程序大部分时间都在等待 I/O,因此最好让 CPU 忙于其他任务

多道程序设计:在一个物理地址空间中容纳多个进程 每个进程可以是 I/O 密集型或 CPU 密集型 混合使用 I/O 密集型和 CPU 密集型进程会很好 目标是提高 CPU 利用率 调度程序决定哪个进程执行

分时(或“多任务”):非常快速地在进程之间来回切换 - 称为“上下文切换”
目标是减少用户与计算机交互时的延迟

程序由静态代码和数据(如磁盘上的数据)组成。 进程是程序的运行实例。在任何时候,一个程序都可能有 0 个或多个运行实例,例如,一个用户可能同时运行多个 shell

从运行时上下文的角度来看,进程是一个执行流。执行流是​​执行指令的序列(即“控制线程”)。运行时上下文包含执行指令可能影响或受其影响的所有内容(例如,寄存器、地址空间、文件等持久数据)

进程可访问的一组内存部分称为进程的地址空间

  • 文本 — 程序代码(通常是只读)
  • 数据 — 全局变量和常量
  • 栈(Stack) — 每个帧包含参数、局部变量和函数的返回地址
  • 堆(Heap)— 动态分配内存,例如,通过在 C 中调用 malloc()

允许在同一物理地址空间中执行多个程序
虚拟化CPU:多个独立进程同时运行在一台物理机上 但实际上,每个CPU上任一时刻最多只能有一个进程处于活动状态。

程序是磁盘上的应用程序,由代码和数据组成;
程序在执行时就成为进程进程是程序的运行实例。进程以单个执行线程和地址空间开始。一个进程可以在同一地址空间中启动多个执行线程。每个线程都有自己的堆栈,但它们共享全局数据、代码和堆

当用户执行程序时,操作系统会创建一个进程。操作系统在多个进程之间分时共享 CPU。操作系统调度程序选择要运行的可执行进程之一。
调度程序必须保留进程列表
调度程序必须保留调度策略的元数据

策略和机制之间的区别可以实现模块化。调度策略独立于上下文切换功能。

运行(Running):此进程当前正在执行
就绪(Ready):此进程已准备好执行(并且将在策略决定时进行调度)
阻塞(Blocked):此进程已挂起(例如,等待某些操作;当该操作完成时,操作系统将取消阻止它)
New:此进程正在创建(以确保它不会被调度) Dead/termination:此进程已终止(例如,如果父进程尚未读出返回值)

如果所有进程都被阻塞,应该调度什么进程?空闲进程(idle)。现代内核使用低优先级空闲进程,如果没有其他进程准备好,该进程就会被调度并执行。空闲进程从不阻塞或执行任何 I/O。空闲进程是解决挑战性问题的简单方法。如果没有空闲进程,调度程序将必须检查是否没有进程准备好运行,并且必须保守地采取行动。空闲进程保证至少有一个进程可以运行

操作系统维护活动进程的数据结构(数组/列表)。每个进程的信息都存储在进程控制块(在 Linux 上,称为 task_struct)中,其中包含:
进程标识符 (PID)
进程状态(例如,就绪)
指向父进程的指针 (cat /proc/self/status)
CPU 上下文(如果进程未运行)
指向地址空间的指针 (cat /proc/self/maps)
指向打开文件列表的指针(文件描述符,cat /proc/self/fdinfo/*)

保存进程的所有状态允许进程暂时挂起并稍后从同一点恢复
然后可以通过恢复其保存的状态来恢复另一个进程
执行上下文切换所需的时间是我们希望最小化的开销

程序:由磁盘上的可执行文件组成。包含启动进程的所有信息
进程:程序的运行实例;具有数据部分和堆栈初始化
线程:一个进程可以在同一地址空间中拥有多个线程(计算相同的数据)

读取地址0xc0f3的两个进程可能读取到不同的值。而同一进程中的两个线程会读取相同的值

进程可以通过系统调用API(应用程序编程接口)请求服务
进程 API 使进程能够通过一组系统调用来控制自身和其他进程:
getpid() 检索进程的 ID,每个进程都有唯一的 PID
fork() 创建一个新的子进程(进程的副本)
exec () 执行一个新程序
exit() 终止当前进程
wait() 阻塞父进程,直到子进程终止

操作系统为新进程(子进程)分配数据结构。操作系统复制调用者(父级)的地址空间。子进程已准备就绪并添加到进程列表中。 fork() 为父/子返回不同的值。父级和子级继续在各自的地址空间副本中执行
exec() 替换地址空间,从磁盘加载新程序。总是执行同一个程序很无聊。程序可以传递命令行参数和环境。旧的地址空间/状态被销毁,除了保留的 STDIN、STDOUT、STDERR 之外,允许父级重定向/重新连接子级的输出!
假设用户想要启动另一个程序。为此,操作系统需要创建一个新进程并创建一个新的地址空间来加载程序。
 fork() 使用该地址空间的副本创建一个新进程 exec() 为程序创建一个新的地址空间 clone() 将一个(执行的)线程添加到该地址空间
 子进程与其父进程相关联。 exit(int retval) 接受一个返回值参数。父级可以 wait() 终止子级并读取子级的返回值
fork() 通过复制调用进程地址空间的内容来创建新进程 新进程有自己的地址空间(内容从父进程复制) 操作系统中的进程控制块

进程直接在CPU上执行指令

进程可能会做一些非法的事情(读/写不属于该进程的内存,直接访问硬件) 进程可能会永远运行(操作系统必须保持控制) 进程可能会做一些缓慢的事情,例如 I/O(操作系统可能想要切换到另一个进程)
解决方案:操作系统在硬件的帮助下维持一些控制。例如,操作系统维护定时器以定期拦截执行,并且进程可能不会执行直接访问硬件的特权指令

在大多数操作系统上,进程是: 相互隔离 与操作系统隔离 隔离是安全的核心要求: 将错误限制在进程中 启用权限隔离 启用分区(将复杂系统分解为独立的故障域)

进程的组成–PCB

进程控制块PCB,记录PID、UID
进程被创建时创建唯一PCB,进程结束时回收
image.png

进程的特征

image.png

进程的状态

image.png

进程的等待队列用指针建立表

进程控制

实现原语的原子性:

关中断指令和开中断指令。这两个指令属于特权指令,只能CPU调用
CPU执行了关中断指令后就不再检查中断信号,直到执行开中断指令。

进程控制相关的原语

创建原语:image.png
撤销原语:
image.png

阻塞原语和唤醒原语:
image.png

切换原语:
image.png

进程通信

共享存储

操作系统在内存中划分一块共享存储区
image.png
基于储存区的共享:高级通信方式,灵活性高,速度快
基于数据结构的共享:低级通信方式,速度慢、限制多

消息传递

进程间的数据交换以格式化的消息为单位,通过发送和接收消息两个原语进行数据交换。
直接通信方式
间接通信方式
管道通信


Processes进程
http://example.com/2024/11/27/Notes/课程/大三(上)/操作系统/Processes进程/
许可协议