编辑推荐: |
本文来自于csdn,本文主要介绍了进程、PCB、进程的创建以及同步进程和异步进程等相关知识。 |
|
一,进程概念,为什么是多进程而不是多线程
进程是由操作系统创建的工作单元。值得注意的是进程和程序未必是等同的。一个程序可能由多个任务组成,而每个任务可以和一个或多个进程相关联。程序是由程序员创建的,而进程是由操作系统创建的。一个工作单元要想被称作进程,它必须要有操作系统指派给他的地址空间,必须拥有进程ID,必须拥有状态和进程表中的表项。进程和线程之间最大的区别是进程有着自己的地址空间,而线程共享创建它们的进程的地址空间。
在将C++任务映射为操作系统能够理解的执行单元时,结果证明线程更易于编程,其主要原因是线程共享相同的地址空间,使得线程间的通信和同步都要易于进程。创建或终止线程所要做的工作都要少于创建进程的相关工作,而且速度也要快于进程。
那为什么还有使用进程呢?
首先,进程有自己的地址空间,可以有效的提供安全性和隔离性,阻止流氓进程的干扰。
其次,线程所能使用的打开的文件数目受限于一个进程能拥有的打开的文件数目。这使得使用进程时能够对更多的资源进行访问。
第三,对于多用户的程序,每个用户程序还是应该更独立一些,如果一个用户进程失败,其他用户还是可以继续工作的,但如果一个用户线程失败,则有可能影响所有用户的操作。
但使用多进程时进程间通信和启动时间是主要的代价。
二, 对进程的详细讨论
当执行进程时,操作系统将它指定到一个处理器上。进程在一个时间片(quantum)内执行他的指令。进程是可抢占的。分为操作系统进程和用户进程。
操作系统进程:是执行的系统代码,也被称作内核进程。执行的是系统管理的任务。
用户系统进程:是执行的用户代码。
进程控制块 PCB
进程拥有一些标识来描述他们运行的特性。内核维护数据,并提供可以允许用户访问的接口。这些信息被保存在进程控制块中(Process
Control Block)。
PCB中的信息:
- 进程当前的状态和优先级
- 进程标识,父进程的标识,子进程的标识
- 指向以分配资源的指针
- 指向进程内存位置的指针
- 指向父进程和子进程的指针
- 进程所使用的处理 器
- 控制和状态寄存器
- 栈指针
在进程的地址空间内分为3个逻辑段: 代码段,数据段,栈段。
进程的地址空间是虚拟的。虚拟存储使得在执行的进程中引用的地址同内存中的实际可用的地址是无关的。这使得可寻址的存储空间大小远远大于实际的内存大小。进程的虚拟地址空间的段是连续的。每个段及物理地址空间被分成页。每个页有唯一的帧号(page
frame number)。虚拟页帧号(virtual page frame number) 用作进程页表中的索引。虚拟地址空间是连续的,但可以以任何顺序映射到物理页面。虽然进程的虚拟地址空间都受到保护,但进程的代码段是可以在不同进程间共享的。
三,进程的创建,优先级调度,删除和进程的资源
3.1 进程的创建
要想运行任何一个程序,操作系统必须首先创建一个进程。当创建新的进程之后,在主进程表中会加入一个新的条目,创建并初始化一个新的PCB。PCB中会包含进程id,父进程id,进程的入口等。
创建进程有着不同的方法 fork() system() posix_spawn(),这里将主要讨论posix_spawn()方法。
一个简单的posix_spawn() 的示例:
#include <spawn.h>
#include <iostream.h>
#include <pthread.h>
int main()
{
pid_t nPid;
posix_spawnattr_t X;
posix_spawn_file_actions_t Y;
char* argv[] = {"/bin/ps", "-lf",
NULL};
char* envp[] = {"PROCESS=2"};
posix_spawnattr_init(&X);
posix_spawn_file_actions_init(&Y);
posix_spawn(&nPid, "/bin/ps", &Y,
&X, argv, envp);
cout << "spawned PID" <<
nPid << endl;
return 0;
} |
posix_spawn() 函数的说明:
int posix_spawn(pid_t
*restrict pid,
const char *restrict path,
const posix_spawn_file_actions_t
*file_actions,
const posix_spawnattr_t *restrict
attrp,
char *const argv[restrict], char
*const envp[restrict]);
int posix_spawnp(pid_t *restrict pid,
const char *restrict file,
const posix_spawn_file_actions_t
*file_actions,
const posix_spawnattr_t *restrict
attrp,
char *const argv[restrict], char
* const envp[restrict]);
|
file_atction: 包含了新进程将要对文件描述符执行的动作的信息的数据结构。
struct posix_spawn_file_actions_t
{
int __allocated;
int __used;
struct __spawn_action *actions;
int __pad[16];
}; |
attrp 参数: 包含关于新进程的调度策略,进程组,信号,标志等信息。
struct posix_spawnattr_t
{
short int __flags; //用于指示在新进程中将要修改哪个属性
pid_t __pgrp; //新进程即将加入的进程组ID
sigset_t __sd; //新进程被迫默认使用的默认信号处理的信号集合
sigset_t __ss; //新进程将要使用的信号掩码
struct sched_param __sp; //将要赋给新进程的调度参数
int __policy; //新进程将要使用的调度策略
int __pad[16];
}; |
3.2 进程的调度
进程在运行期间,会有不同的状态
运行(Running)
就绪(runnable, ready)
僵死(zombied)
等待(waiting, block)
停止(stopped)
进程状态的改变取决于操作系统所创造的环境。
当一个就绪队列包含多个进程时,调度器就会决定首先将那个进程指派给处理器。每个进程都会被赋予一个优先级,有相同优先级的进程被放在一个队列中。进程的优先级可能是静态的,也可能是动态的。调度器根据不同的调度策略来处理进程。在POSIX
API中主要包含2中调度策略,FIFO(先进先出),RR(轮询)
FIFO 先进先出:进程是根据到达队列的时间被指派给处理器的。当正在运行的进程时间片耗尽时他会被放到队列的头部,当一个休眠进程变为可运行时,它将被放在队列的尾部。
RR 轮询:所有的进程被同等对待,当进程时间片被耗尽后进程被放到队列的后端。
3.3 进程的删除
进程终止后其PCB将会被清除,而且它所使用的资源和地址空间将全部被释放。
父进程负责子进程的终止和释放,父进程应处于等待状态,直到它所有的子进程已经终止。当父进程提取子进程的退出码后,子进程将正常的退出系统。进程在父进程接受信号之前一直处于僵死状态。如果父进程一直不接受信号(父进程终止或退出),那么子进程将一直处于僵死状态。
进程终止函数 exit() abort() kill()
函数exit():
进程可以自身调用该函数来终止自己, exit()会导致调用的进程正常的终止。与进程相关的被打开的文件描述符都会被关闭。
函数abort():
会导致调用该函数的进程异常终止。
函数kill()
可导致其他的进程终止。
四,同步进程和异步进程
异步进程间是相互独立的。进程A运行到结束不需要考虑其他进程。
同步进程定义为交错进程,进程A运行一段时间后将会挂起自身,等待进程B运行结束。fork() fork-exec()
posix_spawn等函数创建的进程都是异步进程,父进程在成功创建子进程后并不挂起,两个进程将会各自独立的运行。
system() 创建的进程是同步进程,它会创建一个shell来执行命令或可执行文件。父进程将会被挂起,直到子进程结束,且system()调用返回。
wait() 函数:异步进程可以通过执行它将自身挂起,等到子进程结束。当子进程结束后,等待中的父进程收集子进程的退出状态,防止出现僵死进程。
五,谓词
谓词是一个返回bool值的函数的对象。谓词不仅仅是函数,它有着对象的语义。谓词为动作序列提供了声明式的解释。
使用谓词可以是用户脱离并行化的过程式算法。
|