03_进程
制块(PCB)5、进程的状态知识点2【进程号PID】1、获取进程号的函数2、获取父进程的ID3、获取进程组的I
D案例:获取进程号、父进程号、进程组号知识点3【创建进程fork】(重要)1、fork函数2、fork出来的子
进程和父进程之间的关系3、子进程 复制 父进程的资源(各自独立)4、父子进程同时运行5、父进程 给子进程 足够
的准备时间知识点4【特殊的进程】(了解)1、孤儿进程(无危害)2、僵尸进程(有害)3、守护进程知识点5【父进程
回收子进程的资源】1、wait函数案例:2、waitpid函数案例1:waitpid等价于wait的案例知识点
6【创建多个子进程】(重要)1、知识点引入 创建2个子进程2、防止子进程 创建孙进程多进程的重要代码知识点7【
进程的补充】(了解)1、终端2、进程组3、会话创建会话的步骤:案例1:创建一个会话4、创建守护进程知识点8【v
fork创建子进程】(了解)案例1:vfork创建子进程vfork创建的子进程 和父进程 共用一个空间。知识点
9【exec函数族】(了解)案例1:在代码中使用execl执行ls命令案例2:在代码中使用execlp执行ls
命令案例3:在代码中使用execvp执行ls命令案例4:vfork和exec配合使用知识点1【进程的概述】1、
程序和进程的区别(重要)程序 静态的 占磁盘空间进程 动态的 (调度、执行、消亡),占内存空间。(进程是程序执
行到结束间的这个过程)2、单道和多道程序设计1 单道程序设计 所有进程一个一个排队执行。若A阻塞,B只能等待,
即使CPU处于空闲状态。2 多道程序设计 在计算机内存中同时存放几道相互独立的程序,它们在管理程序控制之下,相
互穿插的运行3、并行和并发的区别(重要)并行和并发 都是值多个任务同时执行。并行(parallel):指在同一
时刻,有多条指令在多个处理器上同时执行。(多核)并发(concurrency):指在同一时刻只能有一条指令执行
,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时
间分成若干段,使多个进程快速交替的执行(单核)4、进程控制块(PCB)进程运行时,内核为每个进程分配一个PCB
(进程控制块),维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。PCB存在于进程
的内核空间里面。系统会为每一个进程分配一个进程ID,其类型为pid_t(非负整数)进程是系统分配资源的基本单位
。5、进程的状态进程的状态:就绪态、执行态、等待态就绪态:执行条件全部满足,等待CPU的执行调度执行态:正在被
CPU调度执行等待态:不具备CPU调度执行的执行条件,等待条件满足。查看进程的状态:ps -auxstat中的
参数意义如下:参数 含义D 不可中断 Uninterruptible(usually IO)R 正在运行,或在
队列中的进程S(大写) 处于休眠状态T 停止或被追踪Z 僵尸进程W 进入内存交换(从内核2。6开始无效)X 死
掉的进程< 高优先级N 低优先级s 包含子进程+ 位于前台的进程组ps命令可以查看进程信息:选项 含义-a 显
示终端上的所有进程,包括其他用户的进程-u 显示进程的详细状态-x 显示没有控制终端的进程-w 显示加宽,以便
显示更多的信息-r 只显示正在运行的进程以树状显示进程:pstree知识点2【进程号PID】每个进程都由一个进
程号来标识,其类型为 pid_t(整型),进程号的范围:0~32767。进程号总是唯一的,但进程号可以重用。当
一个进程终止后,其进程号就可以再次使用。进程号(PID): 标识进程的一个非负整型数父进程号(PPID):父进
程号进程组号(PGID): 进程组是一个或多个进程的集合。1、获取进程号的函数1 #include <sys/
types。h>2 #include <unistd。h>3 pid_t getpid(void);功能:获取
本进程号(PID)参数:无返回值:本进程号2、获取父进程的ID1 #include <sys/types。h>
2 #include <unistd。h>3 pid_t getppid(void);功能:获取调用此函数的进
程的父进程号(PPID)参数:无返回值:调用此函数的进程的父进程号(PPID)3、获取进程组的ID1 #inc
lude <sys/types。h>2 #include <unistd。h>3 pid_t getpgid(
pid_t pid);功能:获取进程组号(PGID)参数:pid:进程号返回值:参数为 0 时返回当前进程组号
,否则返回参数指定的进程的进程组号案例:获取进程号、父进程号、进程组号1 #include <stdio。h>
2 #include <sys/types。h>3 #include <unistd。h>4 int main
(int argc, char const *argv[])5 {6 printf("当前进程的ID:%d\n
", getpid());7 printf("父进程的ID:%d\n", getppid());8 print
f("当前进程所在的进程组号:%d\n", getpgid(0));9 getchar();10 return
0;11 }知识点3【创建进程fork】(重要)1、fork函数系统允许一个进程创建新进程,新进程即为子进程
,子进程还可以创建新的子进程,形成进程树结构模型。1 #include <sys/types。h>2 #inc
lude <unistd。h>3 pid_t fork(void);功能:用于从一个已存在的进程中创建一个新进
程,新进程称为子进程,原进程称为父进程。参数:无返回值:成功:子进程中返回 0,父进程中返回子进程 ID。pi
d_t,为整型。失败:返回-1。失败的两个主要原因是:1)当前的进程数已经达到了系统规定的上限,这时 errn
o 的值被设置为 EAGAIN。2)系统内存不足,这时 errno 的值被设置为 ENOMEM2、fork出来
的子进程和父进程之间的关系使用fork函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空
间。使用fork函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间。 地址空间: 包括
进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等。 子进程所独有的只有它的进程号,
计时器等。因此,使用fork函数的代价是很大的。父子进程 从fork后开始继续执行。1 #include <s
tdio。h>2 #include <unistd。h>3 int main(int argc, char c
onst *argv[])4 {5 //创建子进程6 pid_t pid = fork();7 if (pid
< 0)8 {9 perror("创建失败\n");10 return 0;11 }12 else if (
pid == 0) //子进程13 {14 printf("子进程ID:%d\n", getpid());15
}16 else if (pid > 0) //父进程17 {18 printf("父进程ID:%d\n",
getpid());19 }20 getchar();21 return 0;22 }父子进程是同时运行,空
间独立,子进程复制 父进程的所有空间,谁先运行不确定。3、子进程 复制 父进程的资源(各自独立)1 #incl
ude <stdio。h>2 #include <unistd。h>3 int main(int argc,
char const *argv[])4 {5 int num = 10;6 //创建子进程7 pid_t p
id = fork();8 if (pid < 0)9 {10 perror("创建失败\n");11 ret
urn 0;12 }13 else if (pid == 0) //子进程14 {15 //在子进程中 修改n
um的值16 num = 1000;17 printf("子进程ID:%d 中num=%d\n", getpi
d(), num);18 }19 else if (pid > 0) //父进程20 {21 printf("
父进程ID:%d 中num=%d\n", getpid(), num);22 }23 getchar();24
return 0;25 }4、父子进程同时运行1 #include <stdio。h>2 #include
<unistd。h>3 int main(int argc, char const *argv[])4 {5
int num = 10;6 //创建子进程7 pid_t pid = fork();8 if (pid <
0)9 {10 perror("创建失败\n");11 return 0;12 }13 else if (pi
d == 0) //子进程14 {15 while (1)16 {17 printf("子进程ID:%d 中n
um=%d\n", getpid(), num);18 sleep(1);19 }20 }21 else if
(pid > 0) //父进程22 {23 while (1)24 {25 printf("父进程ID:%d
中num=%d\n", getpid(), num);26 sleep(1);27 }28 }29 getc
har();30 return 0;31 }5、父进程 给子进程 足够的准备时间1 #include <std
io。h>2 #include <unistd。h>3 int main(int argc, char con
st *argv[])4 {5 int num = 10;6 //创建子进程7 pid_t pid = for
k();8 if (pid < 0)9 {10 perror("创建失败\n");11 return 0;12
}13 else if (pid == 0) //子进程14 {15 printf("子进程ID:%d 中n
um=%d\n", getpid(), num);16 }17 else if (pid > 0) //父进程
18 {19 sleep(5);20 printf("父进程ID:%d 中num=%d\n", getpid(
), num);21 }22 getchar();23 return 0;24 }知识点4【特殊的进程】(了解
)孤儿进程、僵尸进程、守护进程。1、孤儿进程(无危害)父进程先结束、子进程就是孤儿进程,会被1号进程接管(1号
进程负责给子进程回收资源)1 #include <stdio。h>2 #include <unistd。h>3
int main(int argc, char const *argv[])4 {5 //创建子进程6 pi
d_t pid = fork();7 if (pid < 0)8 {9 perror("创建失败\n");10
return 0;11 }12 else if (pid == 0) //子进程13 {14 while (
1)15 {16 printf("子进程ID:%d 父进程号=%d\n", getpid(), getppid
());17 sleep(1);18 }19 }20 else if (pid > 0) //父进程21 {2
2 printf("父进程ID:%d 3秒后结束\n", getpid());23 sleep(3);24 }
25 return 0;26 }2、僵尸进程(有害)子进程结束,父进程没有回收子进程资源(PCB),子进程就是
僵尸进程。3、守护进程守护进程 是脱离终端的 孤儿进程。在后台运行。为特殊服务存在的。(一般用于服务器)知识点
5【父进程回收子进程的资源】在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但是
仍然为其保留一定的信息,这些信息主要主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等)父进程可以
通过调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。注意:一次wait或waitpid调用
只能清理一个子进程,清理多个子进程应使用循环。wait、waitpid基本上都是在父进程调用1、wait函数1
#include <sys/types。h>2 #include <sys/wait。h>3 pid_t w
ait(int *status);功能:等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收该子进程
的资源。参数:status : 进程退出时的状态信息。返回值:成功:已经结束子进程的进程号失败: -1注意:w
ait阻塞若调用进程没有子进程,该函数立即返回子进程已经结束,该函数同样会立即返回,并且会回收那个早已结束进程
的资源状态值:WIFEXITED(status) 如果子进程是正常终止的,取出的字段值非零。WEXITSTAT
US(status) 返回子进程的退出状态,退出状态保存在status变量的8~16位案例:1 #includ
e <stdio。h>2 #include <unistd。h>3 #include <sys/wait。h>
4 int main(int argc, char const *argv[])5 {6 //创建子进程7 p
id_t pid = fork();8 if (pid < 0)9 {10 perror("创建失败\n");
11 return 0;12 }13 else if (pid == 0) //子进程14 {15 int i
= 5;16 for (i = 5; i > 0; i‐‐)17 {18 printf("子进程ID:%d
剩余生命值%ds\n", getpid(), i);19 sleep(1);20 }2122 printf("
子进程ID:%d 退出了\n", getpid());23 //显示结束24 _exit(10);25 }26
else if (pid > 0) //父进程27 {28 printf("父进程ID:%d 等待子进程结束
\n", getpid());29 int status = 0;30 pid_t pid = wait(&s
tatus);31 if (WIFEXITED(status)) //子进程正常退出32 {33 //输出状态
值34 printf("子进程退出的状态值:%d\n", WEXITSTATUS(status));35 }3
637 printf("父进程ID:%d 等到子进程%d结束\n", getpid(), pid);38 }3
9 return 0;40 }2、waitpid函数等待子进程结束1 #include <sys/types。
h>2 #include <sys/wait。h>3 pid_t waitpid(pid_t pid, int
*status, int options);功能:等待子进程终止,如果子进程终止了,此函数会回收子进程的资源
。参数:pid : 参数 pid 的值有以下几种类型:pid > 0 等待进程 ID 等于 pid 的子进程。
pid = 0 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid 不会等待它。p
id = -1 等待任一子进程,此时 waitpid 和 wait 作用一样。pid < -1 等待指定进程组
中的任何子进程,这个进程组的 ID 等于 pid 的绝对值。status : 进程退出时的状态信息。和 wai
t() 用法一样。options : options 提供了一些额外的选项来控制 waitpid()。0:同
wait(),阻塞父进程,等待子进程退出。WNOHANG:没有任何已经结束的子进程,则立即返回。WUNTRAC
ED:如果子进程暂停了则此函数马上返回,并且不予以理会子进程的结束状态。(由于涉及到一些跟踪调试方面的知识,加
之极少用到)返回值:waitpid() 的返回值比 wait() 稍微复杂一些,一共有 3 种情况:1) 当正
常返回的时候,waitpid() 返回收集到的已经回收子进程的进程号;2) 如果设置了选项 WNOHANG,而
调用中 waitpid() 还有子进程在运行,且没有子进程退出,返回0; 父进程的所有子进程都已经退出了 返回
-1; 返回>0表示等到一个子进程退出3) 如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以
指示错误所在,如:当 pid 所对应的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid()就
会出错返回,这时 errno 被设置为 ECHILD案例1:waitpid等价于wait的案例1 #inclu
de <stdio。h>2 #include <unistd。h>3 #include <sys/wait。h
>4 int main(int argc, char const *argv[])5 {6 //创建子进程7
pid_t pid = fork();8 if (pid < 0)9 {10 perror("创建失败\n")
;11 return 0;12 }13 else if (pid == 0) //子进程14 {15 int
i = 5;16 for (i = 5; i > 0; i‐‐)17 {18 printf("子进程ID:%d
剩余生命值%ds\n", getpid(), i);19 sleep(1);20 }2122 printf(
"子进程ID:%d 退出了\n", getpid());23 //显示结束24 _exit(10);25 }2
6 else if (pid > 0) //父进程27 {28 printf("父进程ID:%d 等待子进程结
束\n", getpid());29 int status = 0;30 pid_t pid = waitpi
d(‐1, &status, 0);31 if (WIFEXITED(status)) //子进程正常退出32
{33 //输出状态值34 printf("子进程退出的状态值:%d\n", WEXITSTATUS(sta
tus));35 }3637 printf("父进程ID:%d 等到子进程%d结束\n", getpid(),
pid);38 }39 return 0;40 }waitpid常用于 等待多个子进程结束。1 pid_t
pid = wait(&status);2 pid_t pid = waitpid(‐1, &status,
0);知识点6【创建多个子进程】(重要)1、知识点引入 创建2个子进程2、防止子进程 创建孙进程多进程的重要代
码1 #include <stdio。h>2 #include <unistd。h>3 #include <s
ys/wait。h>4 #define N 35 int main(int argc, char const
*argv[])6 {7 int i = 0;8 for (i = 0; i < N; i++)9 {10 p
id_t pid = fork();11 if (pid == 0) //防止子进程创建孙进程12 break
;13 }1415 //判断具体的子进程16 if (i == 0) //子进程117 {18 //完成任务A
19 int j = 5;20 for (; j > 0; j‐‐)21 {22 printf("子进程%d
剩余事件%ds\n", getpid(), j);23 sleep(1);24 }25 _exit(‐1);2
6 }27 else if (i == 1) //子进程228 {29 //完成任务B30 int j = 3
;31 for (; j > 0; j‐‐)32 {33 printf("子进程%d 剩余事件%ds\n",
getpid(), j);34 sleep(1);35 }36 _exit(‐1);37 }38 else i
f (i == 2) //子进程339 {40 //完成任务C41 int j = 8;42 for (; j
> 0; j‐‐)43 {44 printf("子进程%d 剩余事件%ds\n", getpid(), j)
;45 sleep(1);46 }47 _exit(‐1);48 }49 else if (i == N) /
/父进程50 {51 //回收所有子进程的资源52 while(1)53 {54 pid_t pid = wa
itpid(‐1,NULL, WNOHANG);//不阻塞55 if(pid > 0)//某个子进程退出了56
{57 printf("子进程%d退出了\n",pid);58 }59 else if(pid == 0)/
/还有子进程在运行60 {61 continue;62 }63 else if(pid == ‐1)//所有子
进程都退出了64 {65 break;66 }67 }68 }6970 return 0;71 }知识点7【进
程的补充】(了解)1、终端用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端(
Controlling Terminal),进程中,控制终端是保存在PCB中的信息,而fork会复制PCB中的
信息,因此由Shell进程启动的其它进程的控制终端也是这个终端1 #include <unistd。h>2 c
har *ttyname(int fd);3 功能:由文件描述符查出对应的文件名4 参数:5 fd:文件描述符
6 返回值:7 成功:终端名8 失败:NULL2、进程组多个进程的集合当父进程,创建子进程的时候,默认子进程与
父进程属于同一进程组。进程组ID为第一个进程ID(组长进程):进程ID和进程组ID相同的进程就是 组长进程。可
以使用kill -SIGKILL -进程组ID(负的)来将整个进程组内的进程全部杀死只要进程组中有一个进程存在
,进程组就存在,与组长进程是否终止无关。 进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组
)。1 #include <unistd。h>2 pid_t getpgrp(void); /* POSIX。
1 version */功能:获取当前进程的进程组ID参数:无返回值:总是返回调用者的进程组ID1 pid_t
getpgid(pid_t pid);功能:获取指定进程的进程组ID参数:pid:进程号,如果pid = 0
,那么该函数作用和getpgrp一样返回值:成功:进程组ID失败:-11 int setpgid(pid_t
pid, pid_t pgid)功能:改变进程默认所属的进程组。通常可用来加入一个现有的进程组或创建一个新进程
组。参数:将参1对应的进程,加入参2对应的进程组中返回值:成功:0失败:-13、会话会话是一个或多个进程组的集
合。 一个会话可以有一个控制终端。如果进程ID==进程组ID==会话ID 那么该进程为会话首进程。创建会话的步
骤:1) 调用进程不能是进程组组长,该进程变成新会话首进程(session header)2) 该调用进程是组
长进程,则出错返回 。3) 该进程成为一个新进程组的组长进程4) 需有root权限(ubuntu不需要)5)
新会话丢弃原有的控制终端,该会话没有控制终端6) 建立新会话时,先调用fork, 父进程终止,子进程调用set
sid1 #include <unistd。h>2 pid_t getsid(pid_t pid);功能:获取
进程所属的会话ID参数:pid:进程号,pid为0表示查看当前进程session ID返回值:成功:返回调用进
程的会话ID失败:-11 #include <unistd。h>2 pid_t setsid(void);功能
:创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID。调用了setsid函数的进程,既是新的会长
,也是新的组长。参数:无返回值:成功:返回调用进程的会话ID失败:-1案例1:创建一个会话4、创建守护进程1)
创建子进程,父进程退出(必须) 所有工作在子进程中进行形式上脱离了控制终端2) 在子进程中创建新会话(必须)
setsid()函数 使子进程完全独立出来,脱离控制3) 改变当前目录为根目录(不是必须) chdir()函
数 防止占用可卸载的文件系统 也可以换成其它路径4) 重设文件权限掩码(不是必须) umask()函数 防止继
承的文件创建屏蔽字拒绝某些权限 增加守护进程灵活性5) 关闭文件描述符(不是必须) 继承的打开文件不会用到,浪
费系统资源,无法卸载6) 开始执行守护进程核心工作(必须) 守护进程退出处理程序模型1 #include <s
tdio。h>2 #include <unistd。h>3 #include <sys/types。h>4 #
include <sys/stat。h>5 int main(int argc, char const *ar
gv[])6 {7 pid_t pid = fork();8 //父进程结束9 if (pid > 0)10
_exit(‐1);1112 //子进程设置会话13 setsid();1415 //改变工作目录(非必须)1
6 chdir("/");1718 //设置权限掩码19 umask(0002);2021 //关闭文件描述符
0 1 222 close(0);23 close(1);24 close(2);2526 //守护进程的核心
任务27 while (1)28 {29 //核心任务30 }31 return 0;32 }知识点8【vfo
rk创建子进程】(了解)vfork函数:创建一个新进程pid_t vfork(void)功能:vfork函数和
fork函数一样都是在已有的进程中创建一个新的进程,但它们创建的子进程是有区别的。返回值:创建子进程成功,则在
子进程中返回0,父进程中返回子进程ID。出错则返回-1。案例1:vfork创建子进程1 #include <s
tdio。h>2 #include <unistd。h>3 #include <sys/types。h>45
int main(int argc, char const *argv[])6 {7 //vfork创建子进程
8 pid_t pid = vfork();9 if (pid == 0) //子进程10 {11 int i
= 0;12 for (; i < 5; i++)13 {14 printf("子进程%d中的i=%d\n"
, getpid(), i);15 sleep(1);16 }17 //显示退出18 _exit(‐1);19
}20 else if (pid > 0) //父进程21 {22 int i = 0;23 for (;
i < 5; i++)24 {25 printf("父进程%d中的i=%d\n", getpid(), i);
26 sleep(1);27 }28 }29 return 0;30 }从上面运行结果得出:vfork创建的子
进程 会保证子进程先运行,只有当子进程退出(调用exec)的时候,父进程才运行。vfork创建的子进程 和父进
程 共用一个空间。知识点9【exec函数族】(了解)exec函数族:在进程中 启动另一个进程。#include
<unistd。h>extern char **environ;int execl(const char *
path, const char *arg, 。。。/* (char *) NULL */);int exec
lp(const char *file,cconst char *arg, 。。。 /* (char *) N
ULL */);int execle(const char *path, const char *arg, 。
。。/*, (char *) NULL, char * const envp[]*/);int execv(c
onst char *path, char *const argv[]);int execvp(const c
har *file, char *const argv[]);int execvpe(const char *
file, char *const argv[], char *const envp[]);int execv
e(const char *filename, char *const argv[], char *const
envp[]);函数中有l(list)表明使用列表方式传参,函数中有v(vector)表明使用指针数组传参。
函数中有p(path)表明 到系统环境中 找可执行性文件函数中有e(evn) 表明exec可以使用环境变量值案
例1:在代码中使用execl执行ls命令1 execl(可执行文件位置,可执行文件名,可执行文件的选项,以NU
LL结尾);一个进程调用exec后,除了进程ID,进程还保留了下列特征不变: 父进程号 进程组号 控制终端 根
目录 当前工作目录 进程信号屏蔽集 未处理信号 。。。案例2:在代码中使用execlp执行ls命令案例3:在代
码中使用execvp执行ls命令案例4:vfork和exec配合使用1 #include <stdio。h>2
#include <unistd。h>3 #include <sys/types。h>45 int main
(int argc, char const *argv[])6 {7 int num = 10;8 //vfo
rk创建子进程9 pid_t pid = vfork();10 if (pid == 0) //子进程11 {
12 //子进程负责启动起到程序13 sleep(3);14 execlp("ls", "ls", "‐a",
"‐l", "‐h", NULL);1516 //显示退出17 _exit(‐1);18 }19 else
if (pid > 0) //父进程20 {21 //父进程运行自己的程序22 int i = 0;23 fo
r (; i < 5; i++)24 {25 printf("父进程%d中的i=%d\n", getpid()
, i);26 sleep(1);27 }28 }29 return 0;30 }