1.程序和进程
程序:指编译好的二进制文件,在磁盘上,不占用系统资源(CPU、内存、打开文件、设备、锁······)
进程:进程是活跃的程序,占用系统资源,是一个抽象的概念,与操作系统原理联系紧密。在内存中执。
程序是静态的概念,进程是动态的概念。程序运行起来,产生一个进程。
2.并发和并行
并发:同一时刻,只能有一条指令执行,多个进程指令被快速的轮换执行。
宏观:有多个进程同时运行的效果。
微观:只是把时间分成若干段,使多个进程快速交替的执行。
并行:同一时刻,有多条指令在多个处理器上同时执行。
异同点
相同点:并发和并行的目标都是最大化CPU的使用率,将CPU的性能充分压榨出来。
不同点:(1) 并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在
(2)并行要求程序能同时执行多个操作,而并发只是要求程序“看着像是”同时执行多个操作,其本质是交替执行。
3.进程控制块PCB
每个进程在内核中都有一个进程控制块(PCB)来维护进程的相关信息,Linux内核的进程控制块是task_struct结构体。
/user/src/linux-headers-3.16.0-30/include/linux /sched.h文件中可以查看struct task_struct结构体定义。重点掌握以下:
* 进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
* 进程的状态,初始态、就绪态、运行态、挂起态、终止态。
* 进程切换时需要保存和恢复的一些CPU寄存器。
* 描述虚拟地址空间的信息。
* 描述控制终端的信息。
* 当前工作目录(Current Working Directory)。
* umask掩码。
* 文件描述符表,包含很多指向file结构体的指针。
* 和信号相关的信息。
* 用户id和组id。
* 会话(Session)和进程组。
* 进程可以使用的资源上限(Resource Limit)。
4.进程的状态
进程的基本状态有5种:
创建状态:进程在创建时需要申请一个空白的进程控制块(PCB),向其中填写控制和管理进程的信息,完成资源分配。如果创建失败,比如资源无法满足,就无法被调度运行,此时的状态成为创建状态。
就绪状态:进程已经准备好,已分配到所需资源,只要分配到CPU就能立即运行。
执行状态:进程处于就绪状态被调度后,进程进入执行状态。
阻塞状态:正在执行的进程由于某些事件(I/O请求,申请缓存区失败)而暂时无法运行,进程受到阻塞。在满足请求时进入就绪状态等待系统调用。
终止状态:进程结束或出现错误,或被系统终止,进入终止状态,无法再执行。

进程的五种状态及转换
5.环境变量
环境变量:指在操作系统中用来指定操作系统运行环境的一些参数。常具备以下特征:
字符串(本质)
有统一的格式: 名= 值 [:值]
值用来描述进程环境信息。
6.常用进程控制函数
fork函数
创建一个子进程
pid_t fork(void) 失败返回-1,成功返回非负整数。
父进程返回子进程的id,子进程返回0
pid_t 类型表示进程ID,是int类型。
getpid函数
获取当前进程id。
getppid函数
获取当前进程的父进程id。
7.进程共享
使用fork后,父子进程的异同点
相同点:全局变量、.data、.text 、堆 、栈 、环境变量......
不同点:进程id 、 fork返回值 、 进程运行时间......
8. exec族函数
fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核不换壳。
其实有六种以exec开头的函数,统称exec函数:
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
execlp函数
加载一个进程,借助PATH环境变量
int execlp(const char *file, const char *arg, ...); 成功:无返回;失败:-1
参数1:要加载的程序的名字。该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数1则出错返回。
该函数通常用来调用系统程序。如:ls、date、cp、cat等命令。
execl函数
加载一个进程,通过路径+程序名 来加载。
int execl(const char *path, const char *arg, ...); 成功:无返回;失败:-1
对比execlp,如加载"ls"命令带有-l,-F参数
execlp("ls", "ls", "-l", "-F", NULL); 使用程序名在PATH中搜索。
execl("/bin/ls", "ls", "-l", "-F", NULL); 使用参数1给出的绝对路径搜索。
execvp函数
加载一个进程,使用自定义环境变量env
int execvp(const char *file, const char *argv[]);
变参形式:①... ② argv[] (main函数也是变参函数,形式上等同于 int main(int argc, char *argv0, ...))
变参终止条件:① NULL结尾 ② 固参指定
execvp与execlp参数形式不同,原理一致。
练习:将当前系统中的进程信息,打印到文件中。 【exec_ps.c】
exec函数族一般规律
exec函数一旦调用成功即执行新的程序,不返回。只有失败才返回,错误值-1。所以通常我们直接在exec函数调用后直接调用perror()和exit(),无需if判断。
l (list) 命令行参数列表
p (path) 搜素file时使用path变量
v (vector) 使用命令行参数数组
e (environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量
事实上,只有execve是真正的系统调用,其它五个函数最终都调用execve,所以execve在man手册第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。

exec函数族
9.回收子进程
孤儿进程
孤儿进程:父进程先于子进程结束,则子进程为孤儿进程,子进程别init进程领养。
僵尸进程
僵尸进程:进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程。
僵尸进程不能用kill命令清除掉,因为kill命令只能用来终止进程,而僵尸进程已经终止。
wait函数
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。
父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)。
pid_t wait(int *status); 成功:清理掉的子进程ID;失败:-1 (没有子进程)
当进程终止时,操作系统的隐式回收机制会:1.关闭所有文件描述符 2. 释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)
可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:
1. WIFEXITED(status) 为非0 → 进程正常结束
WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
2. WIFSIGNALED(status) 为非0 → 进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
*3. WIFSTOPPED(status) 为非0 → 进程处于暂停状态
WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行
waitpid函数
作用同wait,但可指定pid进程清理,可以不阻塞。
pid_t waitpid(pid_t pid, int *status, in options); 成功:返回清理掉的子进程ID;失败:-1(无子进程)
特殊参数和返回情况:
参数pid:
> 0 回收指定ID的子进程
-1 回收任意子进程(相当于wait)
0 回收和当前调用waitpid一个组的所有子进程
< -1 回收指定进程组内的任意子进程
返回0:参3为WNOHANG,且子进程正在运行。
注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。