进程
进程的概念
进程与程序
- 一个操作系统可以运行许多个程序,一个运行中的程序被称为进程(Process)
- 进程与程序的关系:
- 程序是被动和静态的,进程是主动和动态的
- 一个程序对应的可能有多个进程
- 程序可以通过GUI或命令行启动
进程的组成
- 程序代码
- 内容:可执行的程序指令
- 特点:只读,多个相同进程可以共享同一份代码
- 运行时CPU状态
- 程序技术去(PC): 指向下一条要执行的指令地址
- 寄存器组:
- 通用寄存器:存储计算数据
- 状态寄存器:保存处理器状态标志
- 专用寄存器:如栈指针、基址寄存器等
- 内存区域
- 栈(Stack)
- 数据段(Data Section)
- 堆(Heap)
进程的状态(State)
- 一个进程包含以下状态
- new
- running
- wating/blocking
- ready
- terminated
- 状态转换示意图
进程控制块(Process Control Block, PCB)
基本概念
- PCB是操作系统管理进程的核心数据结构,每个进程都有唯一的PCB
PCB的四大类信息、
- 进程标识信息
- PID:进程唯一标识符
- PPID:父进程ID
- UID/GID:用户和组标识符
- 处理机状态信息
- 程序计数器(PC):下一条指令地址
- 寄存器组:CPU寄存器的值
- 栈指针:当前栈位置
- 进程调度信息:
- 优先级:调度优先级
- 进程状态:运行/就绪/阻塞等
- CPU时间:已使用和分配的时间
- 进程控制信息
- 内存管理:页表、内存映射
- 文件管理:打开的文件列表
- 信号处理:信号处理机制
PCB的关键作用
- 上下文切换
- 保存当前进程状态到PCB,然后从PCB恢复目标进程状态
- 进程管理
- 创建:分配新PCB
- 调度:基于PCB信息选择进程
- 终止:释放PCB和相关资源
- 资源跟踪
- 内存分配情况
- 打开的文件
- 拥有的设备
PCB在linux中的实现: task_struct
- Linux使用task_struct结构体实现PCB,包含:
- 进程状态和标识
- 内存管理信息(mm_struct)
- 文件系统信息(files_struct)
- 父子进程关系
- PCB在系统中的组织
- 进程链表:所有进程形成链表
- 哈希表:通过PID快速查找
- 运行队列:就绪进程的调度队列
线程(Thread)
进程调度
调度
- CPU调度器会选择接下来要运行的进程并分配内存。这个操作一般是非常快的
调度队列
定义
- 操作系统内核用来组织和管理不同状态进程的数据结构,是实现搞笑进程调度的基础。
三种主要调度队列
- 作业队列(Job Queue)
- 范围:系统中的所有进程
- 用途:全局管理和统计
- 对应命令:ps aux
- 就绪队列(Ready Queue)
- 范围:准备执行的进程
- 特点:按优先级组织,支持快速选择
- 实现:多级队列 + 位图索引
- 设备队列(Device Queue)
- 范围:等待I/O的进程
- 分类:磁盘、网络、键盘等不同设备
- 状态:TASK_INTERRUPTIBLE/UNINTERRUPTIBLE
上下文切换(Context Switch)
定义
- 内核切换到另一个进程去执行,保存就进程的状态并加载新进程的已保存状态
开销
- 上下文切换是开销,CPU在切换时不做任何有用的工作。操作系统和PCB越复杂,上下文切换时间越长,时间取决于硬件支持。某些硬件为每个CPU提供多组寄存器,可以同时加载多个上下文
进程创建
概念
- 父进程可以创建子进程,子进程可以进一步创建子进程,形成进程树。进程通过进程标识符(PID)来识别和管理
Design choices
- 三种可能的资源共享级别:全部、子集、无
- 父进程和子进程的地址空间管理
- 子进程复制父进程地址空间(Linux)
- 子进程加载新程序(Windows)
- 父进程和子进程的执行
- 父进程和子进程并发执行
- 父进程等待子进程终止
用于进程创建的系统调用
- fork: 创建一个新的进程副本,结束时会返回
- exec: 使用一个新的进程的地址覆盖当前进程地址,加载了新程序,不会返回原程序
- wait: 阻塞直到子进程结束
进程终止
工作流程
- 正常终止:进程执行最后一条语句并请求内核删除它(exit)
- 操作系统将子进程的返回值传递给父进程(wait)
- 进程的资源被操作系统释放
- 异常终止:父进程可能终止子进程的执行(abort)
- 子进程超出了分配到的资源
- 分配给子进程的任务不在被需要
- 如果父进程退出,一些操作系统不允许子进程继续
- 所有子进程(整个子树)将被终止-这被称为级联终止(cascading termination)
注:exit与_exit
- exit为标准库函数,执行终止进程和清理
- _exit为系统调用,直接请求内核终止进程,不做清理
可能存在的错误————僵尸进程与孤儿进程
僵尸进程
- 僵尸进程时已经执行完毕但父进程还没有回收其退出状态的子进程
- 特征
- 进程已死亡:不再执行任何代码
- PCB仍存在:内核保留进程控制块
- 保存退出状态:等待父进程读取
- 不占用内存:代码段、数据段、栈都释放
- 占用PID槽位:PID不能被其他进程使用
- ps显示为<defunct>:状态标记为Z
孤儿进程
- 孤儿进程是父进程已经退出,但子进程仍然在运行的进程
- 特征
- 仍在运行:进程仍然正常执行
- 父进程变更:PPID变为1(init进程)
- 正常运行:功能不受影响
- 自动回收:退出时由init进程回收
- 通常无害:不会造成资源泄漏
关键区别
- 僵尸进程是管理问题,有害,大量积累会耗尽系统资源,需要程序员解决
- 孤儿进程是自然现象,无害,系统自动解决
Android进程
Android进程重要性层次结构
- 移动操作系统经常需要终止进程来回收系统资源(如内存)。按重要性从高到低排列
- 前台进程:在屏幕上可见
- 用户正在使用微信
- 可见进程:不直接可见,但执行前台进程正在引用的活动
- 视频应用播放时弹出权限对话框
- 服务进程:如流媒体音乐
- 音乐应用后台播放
- 后台进程:执行活动,但用户不明显感知
- 用户切换应用后原应用进入后台
- 空进程:不包含任何活动
- 前台进程:在屏幕上可见
- Android将开始终止最不重要的进程
浏览器的多进程架构
- 在过去许多网页浏览器作为单一进程运行(有些仍然如此)。这会导致如果一个网站出现问题,整个浏览器都可能挂起或崩溃
- Google Chrome浏览器采用多进程架构,包含3中不同类型的进程:
- 浏览器进程:管理用户界面、磁盘和网络I/O
- 渲染进程:渲染网页,处理HTML、Javascript。为每个打开的网页创建新的渲染进程
- 运行在沙箱中,限制磁盘和网络I/O,最小化安全漏洞的影响
- 插件进程:为每种类型的插件创建进程
进程间通信
概念
- 系统中的进程可能是独立的或协作的
- 独立进程:无法影响或被其他进程的执行所影响的进程
- 协作进程:可以影响或被其他进程影响的进程,包括共享数据
- 协作进程的原因:信息共享、计算加速、模块化、便利性、安全性
- 写作进程需要进程间通信(IPC)
IPC模型
- 存在两种IPC模型:
- 共享内存
- 消息传递
- 示意图:
生产者-消费者问题
- 协作进程的范例,生产者进程产生信息,被消费者进程消费
- 无界缓冲区:对缓冲区大小没有实际限制
- 有界缓冲区:假设有固定的缓冲区大小
消息传递
- 进程通过交换消息互相通信
- 无需依赖共享变量
- 消息传递提供两个操作
- send:发送消息
- receive:接受消息
- 如果P和Q希望通信,它们需要
- 在它们之间建立通信链路
- 例如:邮箱(间接)或基于pid(直接)
- 通过send/receive交换消息
- 直接与间接通信
- 直接通信
- 对称寻址: send(P, Message), receive(Q, Message)
- 非对称寻址: send(P, message), receive(id, Message)
- 间接通信
- send(A, Message), receive(A, Message) -邮箱A
- 邮箱可以由进程和操作系统实现
- 邮箱所有者:谁可以接收消息
- 直接通信
- 同步机制
- 消息传递可以是阻塞的或非阻塞的
- 阻塞被认为是同步的
- 阻塞发送:发送者阻塞直到消息被接收
- 阻塞接收:接收者阻塞直到有消息可用
- 非阻塞被认为是异步的
- 非阻塞发送:发送者发送消息后继续执行
- 非阻塞接收:接收者接收有效消息或返回空值
- 缓冲机制
- 附加到链路的消息队列
- 零容量:0条消息
- 发送者必须等待接收者
- 有些容量:有线长度的n条消息
- 如果链路满,发送者必须等待
- 误解容量:无限长度
- 发送者永不等待
POSIX共享内存
概念
- 进程首先创建共享内存段
- 也用于打开现有的内存段
- 设置对象的大小
- 使用mmap()将文件指针内存映射到共享内存对象
- 对共享内存的督学通过mmap()返回的指针完成
管道
- 管道作为一个通道,允许两个本地进程通信
- 关键问题
- 通信是单向的还是双向的?
- 在双向通信的情况下,是半双工还是全双工?
- 进程之间是否必须存在关系(即父子关系)?
- 管道是否可以在网络上使用?
- 通常只用于本地进程
- 普通管道
- 普通管道允许生产者-消费者风格的通信
- 生产者写入一端
- 消费者从另一端读取
- 因此普通管道是单向的
- 如果需要双向通信,需要两个管道
- 要求通信进程之间有父子关系
- 命名管道
- 命名管道比普通管道更强大
- 通信是双向的
- 进程之间不需要父子关系
- 多个进程可以使用命名管道进行通信
- 命名管道在UNIX和Windows系统上都有提供
- 在Linux上,它被称为FIFO
客户端-用户交互
套接字(Socket)
- 套接字被定义为通信的端点
- IP地址和端口的连接
- 套接字 161.25.19.8:1625 指的是主机 161.25.19.8 上的端口 1625
- 通信在一对套接字之间进行