一、概念:异常是允许操作系统内核提供进程概念的基本构造块,异步异常被称为中断。进程的一个经典的概念就是一个执行中程序的实例,系统中的每个程序都运行在某个进程的上下文中。其中上下文是指程序正确运行所需要的状态(程序存放在内存中的数据、代码、栈、存放在通用目的寄存器中的内容、程序计数器、环境变量以及打开文件描述符的集合)。例如在Linux终端运行一个可执行文件,此时shell就会创建一个进程,在进程的上下文中运行该可执行程序。每个进程都为程序提供私有地址空间,与这个地址空间相关联的内存字节不会被其他进程所访问,进程的地址空间如下图所示:
二、逻辑控制流:如果用调试器单步执行程序,我们会看到一系列的程序计数器PC的值,这些值唯一地对应包含在程序的可执行目标文件中的指令或是在运行时动态地链接到程序的共享对象的指令(动态链接共享库)。这些PC值的序列叫做逻辑控制流,简称逻辑流,如果一个系统运行着三个进程,则处理器的物理控制流被分成了三个逻辑流,每个进程一个逻辑流,每个逻辑流的执行是交错的,每个进程轮流使用处理器,每个进程执行它的流的一部分然后被抢占挂起。多个流并发地执行被成为并发,一个逻辑流在执行时间上与另一个重叠叫做并发流,如下图A和B、A和C并发。
多个进程轮流的执行被称为多任务,操作系统内核使用上下文切换来实现多任务。内核为每一个进程维护一个上下文,一个进程执行它控制流的一部分的每一段时间叫做时间片,多任务也叫做时间分片。对于一个运行在进程中上下文的程序,它看起来就像是在独占地使用处理器。如果精确地测量每条指令的使用时间,会发现在程序中一些指令的执行之间,处理器会周期性地停顿,之后在执行程序。并发流思想与处理器核数和计算机数无关,只要一个逻辑流在执行时间上与另一个重叠就叫做并发,即使它们运行在同一个处理器上。如果两个流并发地运行在不同的处理器上,称其为并行流。它们并行地运行。在进程执行的某些时刻,进程可以决定抢占当前进程,开始一个先前被抢占的进程,这种决策就叫做调度,当内核选择一个进程运行时,称内核调度了该进程。
三、进程控制:下面介绍一些Unix提供的C程序中操作进程的系统调用函数
1、获取进程ID
#include<sys/types.h>
#include<unistd.h>
pid_t getpid(void);
pid_t getppid(void);
每个进程都有一个整数ID,第一个函数返回调用进程的ID,而第二个函数返回其父进程(创建调用进程的进程)的ID。两个函数返回一个pid_t的值,在Linux系统上它们被定义为int。
2、创建和终止进程:进程总是处于以下三种状态:
●运行:进程要么在CPU上执行,要么在等待被执行且最终会被内核调度。
●停止:进程的执行被挂起,且不会被调度。当收到SIGSTOP、SIGTSTP、SIGTTIN 或者SIGTTOU信号时,进程就停止,并且保持停止直到它收到一个SIGCONT信号,在这个时刻,进程再次开始运行。
●终止:进程永远地停止了。进程会因为三种原因终止: 1)收到一个信号,该信号的默认行为是终止进程,2)从主程序返回,3)调用exit函数。
#include <stdlib.h>
void exit(int status);
exit函数以status退出状态来终止进程,该函数不返回。
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
父进程通过调用fork函数来创建一个新的子进程,新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。父进程和新创建的子进程之间最大的区别在于它们有不同的PID。fork函数只被调用一次,却会返回两次:一次是在调用进程(父进程)中,一次是在新创建的子进程中。在父进程中,fork 返回子进程的PID。在子进程中,fork返回0。因为子进程的PID总是为非零,返回值就提供一个明确的方法来分辨程序是在父进程还是在子进程中执行。
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
using namespace std;
int main(){
pid_t pid;
int x=1;
pid=fork();
if(pid==0){ // child
cout<<" in child,x="<<++x<<endl;
exit(0);
}
cout<<" in parent,x="<<--x<<endl;//parent
}
输出结果如下:
in parent ,x=0
in child ,x=2
fork函数被调用一次,返回两次:一次是在调用进程(父进程)中,一次是在新创建的子进程中。在父进程中,fork 返回子进程的PID。在子进程中,fork返回0。可以画一下进程图来理解: