进程控制

进程生命周期存在多个状态,多个状态间可以相互转换。在unix系统中提供了一系列与进程状态相关的系统调用,比如fork用于创建新进程,exit用于退出进程,wait用于父进程等待子进程结束。

进程切换

进程切换的概念

  • 暂停当前运行进程 从运行状态变成其他状态
  • 调度另一个进程从就绪状态变成运行状态

进程切换的要求

  • 切换前 保存进程上下文
  • 切换后 恢复进程上下文
  • 切换快速,因为在程序执行过程中进程切换的频率相当高

进程切换图示

进程控制块

进程控制块记录内核的进程状态,内核为每个进程维护对应的进程控制块,将相同状态的进程的PCB放在同一队列。

进程创建

不同操作系统均提供了进程创建的系统调用接口。

  • Windows进程创建API CreateProcess()
  • Unix进程创建系统调用 fork/exec
    • fork() 把一个进程复制成两个进程 父子进程的PID不同
    • exec() 用新程序来重写当前进程 PID 不变

fork示例

1
2
3
4
5
int pid = fork();		// 创建子进程,复制两个进程
if(pid == 0) { // 子进程在这里继续
// Do anything (unmap memory, close net connections…)
exec(“program”, argc, argv0, argv1, …);//加载程序
}
  • fork() 创建一个继承的子进程
    • 复制父进程的所有变量和内存
    • 复制父进程的所有 CPU 寄存器(一个寄存器例外,用于区别父进程和子进程)
  • fork() 的返回值
    • 子进程的 fork() 返回值 为 0
    • 父进程的 fork() 返回值为 子进程标识符
    • 子进程可使用 getpid() 获取 PID
  • 系统调用exec()加载新程序取代当前运行进程,除了pid相同,其余均替换。

fork地址空间复制

fork使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main() {
pid_t pid;
int i;
for (i=0; i<LOOP; i++) {
pid = fork();
if (pid < 0) {
fprintf(stderr, “Fork Failed”);
exit(-1);
}
else if (pid == 0) {
fprintf(stdout, “i=%d, pid=%d, parent pid=%d\n”,I,
getpid() ,getppid());
}
}
wait(NULL);
exit(0);
}

执行后生成进程如下所示

进程个数:1->2->4->8,从上我们可以看出,进程pid号并不是严格按照创建进程的顺序执行,根据调度算法,就绪队列执行顺序不同。

fork 的开销

  • 对子进程分配内存
  • 复制父进程的内存和CPU寄存器到子进程

在大多数情况下,调用 fork() 以后会调用 exec() 将 fork() 复制出来的子进程的内存给覆盖掉 fork()。因此在fork()操作中内存操作是没有任何作用的,子进程可能关闭打开文件和连接。

vfork()

  • 创建进程时,不再创建一个同样的内存映像。
  • 子进程应该立即调用 exec()
  • 现在使用 Copy on Write (COW)即写时复制技术

(北京工业大学)子进程可以继承它的父进程所拥有的所有资源()

子进程继承了父进程的代码段和数据段资源,堆栈段则是自己的

程序加载与执行

exec()加载新程序并覆盖原来的内存地址空间取代当前运行程序,代码段、堆栈、和堆完全重写,pid号仍和之前相同。

系统调用exec( )允许进程“加载”一个完全不同的程序,并从main开始执行。

进程等待与退出

进程等待

wait() 系统调用用于父进程等待子进程的结束

  • 子进程结束时通过exit() 向父进程返回一个值
  • 父进程通过wait() 接受并处理返回值

父进程wait()先于子进程exit(),

  • 父进程进入等待状态,等待子进程的返回结果
  • 当某子进程调用 exit() 时 唤醒父进程 将 exit() 返回值作为 父进程 wait() 的返回值

父进程wait()后于子进程exit(),有僵尸子进程等待,wait()立即返回其中一个值。

当无子进程存活时,wait()立即返回。

进程退出

进程结束执行时,调用 exit() 完成进程资源回收。

exit() 系统调用的功能

  • 将调用参数作为进程的 结果(返回值)
  • 关闭所有打开的文件等占用资源
  • 释放内存
  • 释放大部分进程相关的内核数据结构
  • 检查父进程是否还存活
    • 存活 保留结果的值 直到父进程需要它 进入 僵尸(zombie/defunct)状态
    • 非存活 释放所有的数据结构和结果
  • 清理所有等待的僵尸进程

什么是僵尸进程和孤儿进程

孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。

进程控制与进程状态关系

其他进程控制系统调用

  • 优先级控制
    • nice() 指定进程的初始优先级
    • Unix系统中 进程优先级会随着执行时间而衰减
  • 进程调试支持
    • ptrace() 允许一个进程控制另一个进程的执行
    • 设置断点和查看寄存器等
  • 定时
    • sleep() 可以让进程在定时器的等待队列中等待指定的时间