4.1 进程概念
4.1.1进程的基本概念
操作系统在并发/分时环境下的特点是: OS会在任何时候暂停或继续一个程序的运行。
程序运行在并发环境中的问题
- 运行过程不确定
- 结果不可再现(程序运行被干扰)
- 解决方案:对运行过程施加相互制约
新的概念:进程
描述和管理程序的“运行过程”——进程。
进程的定义
- 进程是程序在某个集合上的一次运行活动
- 数据集合:软/硬件环境,多个进程共存/共享环境
进程的特征
- 动态性:进程是程序的一次执行过程,动态产生/消亡
- 并发性:进程同其他进程一起向前推进
- 异步性:进程是系统分配资源和调度CPU的单位
进程与程序的区别
动态与静态
- 进程是动态的:程序的一次执行过程
- 程序是静态的:一组指令的有效集合
暂存与长存
- 进程是暂存的:在内存驻留
- 程序是长存的:在介质上长期保存
程序和进程的对应:一个程序可能有多个进程
进程的类型
按使用资源的权限
- 系统进程:指系统内核相关的进程
- 用户进程:运行于用户态的进程
按对CPU的依赖性
- 偏CPU进程:计算型进程
- 偏I/O进程:侧重于I/O的进程
4.1.2进程状态
运行状态(Running):进程已经占有CPU,在CPU上运行
就绪状态(Ready):具备运行条件但由于无CPU,暂时不能运行
阻塞状态(Block,Wait):因为等待某项服务完成或信号不能运行的状态,如等待系统调用,I/O操作,合作进程信号...
进程的状态变迁
Linux进程的状态(示意图如下)
可运行态:就绪,运行
阻塞(等待)态:浅度阻塞(可中断),深度阻塞(不可中断)
僵死态:进程终止执行,释放大部分资源
挂起态:进程被挂起
4.1.3进程控制块
进程控制块(Process Control Block, PCB)
- 描述进程状态、资源和与之相关关系的数据结构
- PCB是进程的标志
- 创建进程时创建PCB,进程撤销后PCB同时撤销
进程=程序+PCB
PCB的基本成员
- name(ID) :进程名词(标识符)
- status:状态
- next:指向下一个PCB的指针
- start_addr:程序地址
- priority:优先级
- cpu_status:现场保留区(堆栈)
- comm_info:进程通信
- process_family:家族
- own_resource:资源
Linux的进程控制块PCB:task_struct
strcut task_struct
- 1) 进程状态
- 2)调度信息
- 3)标识符
- 4)内部进程通信信息
- 5)链接信息
- 6)时间和计时器
- 7)文件系统
- 8)虚拟内存信息
- 9)处理器信息
和进程标识相关的成员变量
LInux进程的标识:PID; PPID(父进程ID); PGID(进程组ID)
Linux进程的用户标识: UID(用户ID); GID(用户组ID)
进程的上下文
Context,进程运行环境,CPU环境
进程切换过程:
换入进程的上下文进入CPU(从栈上来)
换出进程的上下文离开CPU(到栈上去)
4.2 进程控制
4.2.1 进程控制的概念
在进程生存全期间,对其全部行为的控制。四个典型的控制行为:创建进程;阻塞进程;撤销进程;唤醒进程
创建进程的过程
- 创建一个空白PCB
- 获得并赋予进程标识符ID
- 为进程分配空间
- 初始化PCB
- 插入相应的进程队列
进程创建的伪代码
进程撤销
功能
- 撤销一个指定的进程
- 收回进程所占有的资源,撤销该进程的PCB
进程撤销的时机/事件
- 正常结束
- 异常结束
- 外接干预
参数:被撤销进程名(ID)
进程撤销的实现
- 在PCB队列中检索出该PCB
- 获取该进程的状态
- 若该进程处在运行态,立即终止该进程
- 释放进程占有的资源
- 将进程从PCB队列中移除
进程阻塞
功能:停止进程的执行,变为阻塞
阻塞的时机/事件
- 请求系统服务: 由于某种原因,OS不能立即满足进程的要求
- 启动某种操作: 进程启动某操作,阻塞等待该操作完成
- 新数据尚未到达: A进程要获得B进程的中间结果,A进程等待
- 无新工作可做: 进程完成任务后,自我阻塞,等待新任务到达)
参数
- 阻塞原因
- 不同原因构建有不同的阻塞队列
进程阻塞的实现
- 停止运行
- 将PCB "运行态"改"阻塞态"
- 插入相应原因的阻塞队列
- 转调度程序
进程唤醒
功能: 唤醒处于阻塞队列当中的某个进程
引起唤醒的时机/事件
- 系统服务由不满足到满足
- I/O完成
- 新数据到达
- 进程提出新请求(服务)
参数: 被唤醒进程的标识
进程控制原语
- 由若干指令构成的具有特定功能的函数
- 具有原子性,其操作不可分割
进程控制原语
- 创建原语
- 撤销原语
- 阻塞原语
- 唤醒原语
4.2.2 Windows进程控制
Windows通过编程启动一个exe程序的常用方法,
#方法1
system ("C:\\DrawRec.exe");
#方法2
WinExec("C:\\DrawRec.exe",SW_SHOWMAXIMIZED);
#方法3
ShellExecute(NULL, "open","C:\\DrawRec.exe",NULL,NULL,SW_SHOWNORMAL);
#方法4
CreateProcess(NULL, "open","C:\\DrawRec.exe",NULL,NULL,FALSE,NULL,NULL,NULL,&si, &pi);
创建新进程
- 创建进程内核对象,创建虚拟地址空间
- 装载EXE和 / 或 DLL的代码和数据到地址空间中
- 创建主线程和线程内核对象
- 启动主线程,进入主函数.
结束进程
- ExitProcess
- TerminateProcess
4.2.3 Linux进程控制
创建进程: fork()
pid_t fork(void);
- 新进程是当前进程的子进程
- 父进程和子进程:父进程(fork()的调用者),子进程(新建的进程)
- 子进程是父进程的复制
- 子进程和父进程并发运行
子进程从fork()后开始运行代码
fork函数在文件 /kerneL/fork.c中实现, 主要步骤:
- 为子进程分配task_strcut空间
- 初始化子进程task_struct;
- 复制父进程的file, fs, sighand, mm等信息
int do_fork(
unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs;
unsigned long stack_size
);
4.3 线程
4.3.1 线程概念
- 线程是可由CPU直接运行的实体;
- 一个进程可以创建多个线程;
- 多个线程共享CPU可以实现并发运行;
CreateThread()原型
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES Ip ThreadAttributes,
DWORD dwStackSize,
LPTHREAD_ROUTINE ThreadFunction, //线程函数
LPVOID IpParameter //线程函数的参数
如同时实现画圆和画方两个函数
void DrawCircleAndRect()
{
...
CreateThread(0,0,DrawCircle,0);
CreateThread(0,0.DrawRect,0);
...
}
单线程程序和多线程程序
单线程程序: 整个进程只有一个线程.Windows缺省只有一个线程(主线程, main线程)
多线程程序: 整个进程至少有2个线程.主线程和至少一个用户线程.
4.3.2 线程典型应用场景
程序的多个功能需要并发运行.
暴风影音播放器应该有的并发功能:视频解码,音频解码,网络接收等
提高窗口程序的交互性
解决办法:把"后台计算"函数创建为"线程".
解决办法: 把后台的"文件拷贝" 设置为独立的"线程".
需要改善程序结构的地方
多核CPU上的应用,充分发挥多核性能
关于创建线程的补充说明
Win32库创建线程: CreateThread(线程函数,参数)
MFC创建线程:AfxBeginThread(线程函数,参数)
创建远程线程: CreateRemoteThread(进程,线程函数,参数)
Linux创建线程: pthread_create(线程函数,参数)
使用线程的麻烦
- 程序难以调试
- 并发过程难以控制
- 线程安全问题
4.4 临界区和锁
4.4.1 临界资源和临界区
临界资源(Critical Resource):一次只允许一个进程独占访问(使用)资源, 如 共享变量
临界区(Critical Section): 进程中访问临界资源的程序段
临界区核临界资源的访问特点:具有排他性;并发进程不能同时进入临界区
设计临界区访问机制的四个原则
忙则等待 :当临界区忙时,其他进程必须在临界区外等待
空闲让进 :当无进程处于临界区时,任何有权进程可进入临界区
有限等待 :进程进入临界区的请求应在有限时间内得到满足(临界区设置小些好)
让权等待 :等待进程放弃CPU.(让其它进程有机会得到CPU)
4.4.2 锁机制
基本原理: 设置一个"标志" S,表明临界资源"可用",还是"不可用
上锁操作
进入临界区之前检查标志是否可用? 不可用等待,可用则访问临界资源,将标志修改为"不可用"
退出临界区时将标志修改为"可用"状态
步骤
- 第1步:检测锁S的状态(0或1?)
- 第2步: 如果S=0, 则返回第1步
- 第3步: 如果S=1, 则置其为0
//上锁原语
LOCK(S)
{
test: if(S==0)
goto test; //测试锁标志
else
S=0; //上锁
}
开锁操作: 把锁S的状态置1
UnLock(S)
{
S=1; //开锁
}
用锁机制访问临界区
步骤:
- 初始化锁的状态S=1(可用)
- 进入临界区之前执行上锁Lock(s)操作
- 离开临界区之后执行开锁unLock(s)操作
4.5 同步和P-V操作
4.5.1 同步和互斥的概念
互斥关系
- 多个进程由于共享了独占性资源,必须协调各进程对资源的存取顺序,确保没有任何两个或以上的进程同时进行存取操作.
- 互斥和资源共享相关
- 资源:临界资源
- 存取操作区域:临界区
同步关系
- 若干合作进程为了完成一个共同的任务,需要相互协调运行步伐:一个进程开始某个操作之前必须要求另一个进程已经完成了某个操作,否则前面的进程只能等待
- 合作进程中某些操作之间需要满足某种先后关系或某个操作能否进行需要满足某个前提条件,否则只能等待
- 互斥关系属于特殊的同步关系
公交车上, 司机和售票员之间的操作属于同步关系
司机起步前售票员先关门,否则等待;售票员开门前司机先停车,否则等待
4.5.2 P-V操作概念
信号灯的概念
信号灯是一种卓有成效的进程同步机制.进程在运行过程中受信号灯状态控制,并能改变信号灯状态.进程受控制:信号灯的状态可以阻塞或唤醒进程;改变信号灯:信号灯的状态可以被进程改变
信号灯数据结构
信号灯变量定义为一个二元矢量(S,q)
- S: 整数,初值非负,(S又称信号量)
- q: PCB队列,初值为空集
信号灯的操作
信号灯有两个操作,P ,V是荷兰语,Passeren通过,Vrijgeven释放
- P操作(函数或过程,P(S, q)
- V操作(函数或过程, V(S,q)
P操作的原理
- S值减1;
- 若差大于或等于零,该过程继续;
- 若差小于零,则该进程阻塞并加入到队列q中,并转调度函数
//P操作可能使进程在调用处阻塞,S的初值设置很重要
P(S,q)
{
S=S-1
if(s<0)
{
Indert(Caller,q);
Block(Caller);
转调度函数();
}
}
V操作原理
S值加1;
若和大于零,该进程继续;
若和小于或等于零,该进程继续同时从q中唤醒一个进程
V(S,q)
{
S=S+1;
if (S<0)
{
Remove(q, pid);//pid:进程ID
Wakeup(pid);
}
}
4.5.3 P-V操作解决互斥问题
实质是实现对临界区的互斥访问, 允许最多1个进程处于临界区
应用过程
- 进入临界区之前先执行P操作;(可能阻塞当前进程)
- 离开临界区之后再执行V操作;(可能唤醒某个进程)
- S初值设置要合理
同步机制的实质:运行条件不满足时,能让进程暂停
- 当运行条件不满足时,能让进程暂停
- 运行条件满足时,能让进程立即继续
P-V操作应用于进程同步的基本思路
- 暂停当前进程:再关键操作之前执行P操作
- 继续进程: 在关键操作之后执行V操作
- 定义有意义的信号量S, 并设置合适的初值
4.5.4 P-V操作经典同步问题
经典同步问题: 生产者和消费者问题
分析过程:
4.6 Windows和Linux同步机制
4.6.1 Windows进程同步机制
- 临界区(锁)
- 互斥量(锁)
- 信号量
- 时间
- 等待操作
临界区
- 在进程内使用,保证仅一个线程申请到该对象
- 临界区内时临界资源的访问
相关的API函数
- initializeCriticalSection():初始化临界区
- DeleteCriticalSection(); 删除临界区
- EnterCriticalSection():
- LeaveCriticalSection();退出临界区(开锁)
互斥量(Mutex)
- 保证只有一个线程或进程可以申请到该对象
- 可以跨进程使用
- 可以有名称
- 互斥量比临界区要耗费更多资源,速度慢
信号量(Semaphore)
- 允许指定数目的多个线程/进程访问临界区
- 一种资源计数器,用于限制并发线程的数量
- 初始值可设为N, 则标识允许N个进程/线程并发访问资源
信号量的值可以通过相应函数增或减
信号状态
- 信号量的值大于0时,有信号状态
- 信号量的值小于等于0时,为无信号状态
4.6.2 Linux父子进程同步
进程的阻塞
进程调用wait (int status) 阻塞自己
- 阻塞到有子进程结束?没有:进程一致阻塞; 有(僵尸进程) wait收集该子进程信息并彻底销毁它后返回
- status 保存进程退出时的状态: 若忽略退出信息,pid=wait(NULL);
进程的终结
- 调用void exit(int status) 终结进程
- 进程终结时要释放资源并报告父进程
进程的休眠
sleep(int nSecond)
- 进程暂停执行nSecond秒
- 系统暂停调度该进程
- 相当于windows的suspend(),挂起若干秒
4.7 进程通信
4.7.1 匿名管道通信
管道通信 pipe
定义: 管道是进程间的一种通信机制.一个进程(A)可以通过管道把数据传输给另外一个进程(B), 前者(A)向管道输入数据,后者(B)从管道读取数据.
应用管道的注意事项:双向通信必须建立2个管道
4.7.2 Linux信号通信
信号的概念
- 信号是LInux进程间一种重要的通信机制
- 信号是向进程发送的一个通知,通知某个事件已发送
- 收到信号的进程可以立即执行指定的操作
- 信号的发出可以是进程,也可以是系统(含硬件)
信号的产生
- 来源1:键盘输入特殊组合键产生信号,例: "ctrl + C"
- 来源1: 执行终端命令产生信号,例: kill 系列命令
- 来源3: 程序中调用函数产生信号,例 kill(), abort()
- 来源4: 硬件异常或内核产生相应信号, 例:内存访问错
linux定义了64种信号,信号用整数1~64表示.如下几种信号
kill发送信号的原型
void kill(
int PID; //接收信号的目标进程ID
int SigNo //待发送的信号
)
//功能: 向目标进程PID发送SigNo信号