目录
一、进程相关概念
进程和程序
程序:死的。只占用磁盘空间。(类似生活中的剧本)
进程;活的。运行起来的程序。占用内存、cpu等系统资源。(类似生活中的戏)
并发和并行:并行是宏观上并发,微观上串行
虚拟内存和物理内存映射关系
进程控制块 PCB
/usr/src/linux-headers-5.4.0-150-generic/include/linux/sched.h文件中可以查看 struct task_struct
结构体定义。其内部成员有很多,我们重点掌握以下部分即可:
1、进程id
2、文件描述符表
3、进程状态: 初始态、就绪态、运行态、挂起态、终止态。
4、进程工作目录位置
5、信号相关信息资源。
6、用户id和组id
7、描述虚拟地址空间的信息
环境变量
环境变量, 是指在操作系统中用来指定操作系统运行环境的一些参数。 通常具备以下特
征
常见环境变量
echo $PATH 查看环境变量
path环境变量里记录了一系列的值,当运行一个可执行文件时,系统会去环境变量记录的位置里查找这个文件并执行。
echo $TERM 查看终端
echo $LANG 查看语言
env 查看所有环境变量
获取环境变量值
char *getenv(const char *name);
成功:返回环境变量的值;失败: NULL (name 不存在)
设置环境变量的值
int setenv(const char *name, const char *value, int overwrite); 成功: 0;失败:-1
参数 overwrite 取值: 1:覆盖原环境变量
0:不覆盖。 (该参数常用于设置新环境变量,如: ABC =haha-day-night)
删除环境变量 name 的定义
int unsetenv(const char *name); 成功: 0;失败: -1
注意事项: name 不存在仍返回 0(成功),当 name 命名为"ABC="时则会出错。
二、进程控制
fork函数
创建一个子进程。
pid_t fork(void);
失败返回-1;成功返回:父进程返回子进程的ID(非负) 子进程返回 0
pid_t 类型表示进程 ID,但为了表示-1,它是有符号整型。 (0 不是有效进程 ID,
init 最小,为 1)
注意返回值,不是 fork 函数能返回两个值,而是 fork 后, fork 函数变为两个,父
子需【各自】返回一个。
小注释:PS ajx ——>可查看 pid ppid gid sid
其他相关函数
获取当前进程 ID
pid_t getpid(void);
获取当前进程的父进程 ID
pid_t getppid(void);
获取当前进程实际用户 ID
uid_t getuid(void);
获取当前进程有效用户 ID
uid_t geteuid(void);
获取当前进程使用用户组 ID
gid_t getgid(void);
获取当前进程有效用户组 ID
gid_t getegid(void);
循环创建n个子进程
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char*arv[]){
int i = 0;
for(i = 0;i < 5;i++){
if(fork() == 0)
break;
}
if(5 == i){
printf("I'm parent ID")
}else{
sleep(i);
printf("I'm %dth children ID", i);
}
return 0;
}
若不枝剪,则会造成如下情况:
从上图我们可以很清晰的看到, 当 n 为 3 时候,循环创建了(2^n)-1 个子进程,而不是 N
的子进程。需要在循环的过程,保证子进程不再执行 fork ,因此当(fork() == 0)时, 子进程
应该立即 break;才正确。
进程共享
父子进程之间在刚fork后。
父子相同处: 全局变量、.data、.bbs、.text、栈、堆、环境变量、用户ID、宿主目录(进程用户家目录)、进程工作目录、信号处理方式等等,即0~3G的用户空间是完全一样的。
父子不同处: 1.进程ID 2.fork返回值 3.父进程ID 4.进程运行时间 5.闹钟(定时器) 6.未决信号集
父子进程间遵循读时共享写时复制的原则。
注意:只有进程空间的各段的内容要发生变化时(子进程或父进程进行写操作时,都会引起复制),才会将父进程的内容复制一份给子进程。在fork之后两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。即父子进程在逻辑上仍然是严格相互独立的两个进程,各自维护各自的参数,只是在物理上实现了读时共享,写时复制。
父子进程共享: 1. 文件描述符(打开文件的结构体) 2. mmap 建立的映射区(进程间通信详解)
父子进程gdb调试
gdb调试:
设置父进程调试路径:set follow-fork-mode parent (默认)
设置子进程调试路径:set follow-fork-mode child
注意,一定要在fork函数调用之前设置才有效。
exec函数族
fork 创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种 exec 函数以执行另一个程序。当进程调用一种 exec 函数时,该进程的用户空间代码和数据(.text、.data)完全被新程序替换,从新程序的启动例程开始执行。调用 exec 并不创建新进程,所以调用 exec 前后该进程的 id 并未改变。
#include <unistd.h>
加载一个进程, 通过 路径+程序名 来加载
int execl(const char *path, const char *arg, ...,NULL);
参1: 程序名
参2: argv0
参3: argv1
...: argvN
哨兵:NULL
加载一个进程,借助 PATH 环境变量
int execlp(const char *file, const char *arg, ...,NULL);
例:execlp("ls", "ls", "-l", "-F", NULL); 使用程序名在 PATH 中搜索。
execl("/bin/ls", "ls", "-l", "-F", NULL); 使用参数 1 给出的绝对路径搜索。
int execle(const char *path, const char *arg, ...
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char* argv[]){
int fd;
fd = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0664);
dup2(fd, STDOUT_FILENO);
execlp("ps","ps","ax",NULL);
//close(fd);
return 0;
}
exec函数族特性
exec 函数一旦调用成功即执行新的程序,不返回。 只有失败才返回,错误值-1。所以通常我们直接在 exec 函数调用后直接调用 perror()和 exit(),无需 if 判断。
l(list) | 命令行参数列表 |
p(path) | 搜索file时使用path变量 |
v(vector) | 使用命令行参数数组 |
e(environment) | 使用环境变量数组,不适用进程原有的环境变量, 设置新加载程序运行的环境变量 |
事实上,只有 execve 是真正的系统调用,其它五个函数最终都调用 execve,所以 execve在 man 手册第 2 节,其它函数在 man 手册第 3 节。
孤儿进程和僵尸进程!!!
孤儿进程:
父进程先于子进终止,子进程沦为“孤儿进程”,会被 init 进程领养。
僵尸进程:
子进程终止,父进程尚未对子进程进行回收,在此期间,子进程为“僵尸进程”。 kill 对其无效。这里要注意,每个进程结束后都必然会经历僵尸态,时间长短的差别而已。
子进程终止时,子进程残留资源PCB存放于内核中,PCB记录了进程结束原因,进程回收就是回收PCB。回收僵尸进程,得kill它的父进程,让孤儿院去回收它。
wait回收子进程!!!
回收子进程退出资源, 阻塞回收任意一个。
#include <sys/types.h>
#include <sys/wait.h>
回收子进程退出资源, 阻塞回收任意一个子进程。
pid_t wait(int *wstatus);
参数:(传出) 回收进程的状态。
返回值:成功: 回收进程的pid
失败: -1, errno
父进程调用 wait 函数可以回收子进程终止信息。 该函数有三个功能:
① 阻塞等待子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)。
获取子进程退出值和异常终止信号
<1> 一个进程终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。
<2> 这个进程的父进程可以调用wait或者waitpid获取这些信息,然后彻底清除掉这个进程。
<3> 一个进程的退出状态可以在shell中用特殊变量$?查看,因为shell是它的父进程,当它终止时,shell调用wait或者waitpid得到它的退出状态,同时彻底清除掉这个进程。
获取子进程正常终止值:
WIFEXITED(status) --> 为真 -->调用 WEXITSTATUS(status) --> 得到 子进程 退出值。
获取导致子进程异常终止信号:
WIFSIGNALED(status) --> 为真 -->调用 WTERMSIG(status) --> 得到 导致子进程异常终止的信号编号。
捕获程序异常终止的信号并打印
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc, char*argv[]){
pid_t pid,wpid;
int status;
pid = fork();
if(pid > 0){
wpid = wait(&status);
if(wpid == -1){
perror("wait error");
exit(1);
}
if(WIFEXITED(status)){
printf("child exit with %d\n", WEXITSTATUS(status));
}
if(WIFSIGNALED(status)){
printf("child kill with signal %d\n", WTERMSIG(status));
}
}else if(pid == 0){
printf("---child, my id = %d",getpid());
sleep(10);
printf("---------------die");
return 73;
}else if(pid ==-1){
perror("fork error");
exit(1);
}
return 0;
}
waitpid回收子进程
指定某一个进程进行回收。可以设置非阻塞
pid_t waitpid(pid_t pid, int *status, int options);
参数:
pid:指定回收某一个子进程pid
> 0: 待回收的子进程pid
-1:任意子进程
0:同组的子进程。
status:(传出) 回收进程的状态。
options:WNOHANG 指定回收方式为,非阻塞。
返回值:
> 0 : 表成功回收的子进程 pid
0 : 函数调用时, 参3 指定了WNOHANG, 并且,没有子进程结束。
-1: 失败。errno
< -1 回收指定进程组内的任意子进程
回收指定子进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
int main(int argc,char*argv[]){
int i;
pid_t pid,wpid,tpid;
for(i = 0;i < 5;i++){
pid = fork();
if(pid == 0){
break;
}
if(2 == i){
tpid = pid;
printf("-----pid = %d\n",tpid);
}
}
if(5==i){
sleep(5);
wpid = waitpid(tpid,NULL,WNOHANG);
if(wpid==-1){
perror("wpid error");
exit(1);
}
printf("I'm parent ,wait a child finish:%d\n",wpid);
}else{
sleep(i);
printf("I'm %dth child pid:%d\n",i+1,getpid());
}
return 0;
}
waitpid回收多个子进程
一次wait/waitpid函数调用,只能回收一个子进程。
while ((wpid = waitpid(-1, NULL, WNOHANG)) != -1) { //使用非阻塞方式,回收子进程.
if (wpid > 0) {
printf("wait child %d \n", wpid);
} else if (wpid == 0) {
sleep(1);
continue;
}
}
while ((wpid = waitpid(-1, NULL, 0)) != -1) { //使用阻塞方式,回收子进程.
printf("wait child %d \n", wpid);
}
Demo
父进程 fork 3 个子进程,三个子进程一个调用 ps 命令, 一个调用自定义程序1(正常),一个调用自定义程序 2。父进程使用 waitpid 对其子进程进行回收。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
int main(int argc,char*argv[]){
int i;
int status;
pid_t wpid;
for(i = 0;i<3;i++){
if(fork() == 0){
break;
}
}
if(i == 0){
execlp("ps","ps","ax",NULL);
}else if(i == 1){
execl("./test","./test",NULL);
}else if(i == 2){
int a =1,b=2;
printf("%d\n",a+b);
}else if(i == 3){
printf("I'm parent :%d\n",getpid());
while((wpid = waitpid(-1,&status,WNOHANG))!=-1){
if(wpid == 0){
sleep(1);
continue;
}else if(wpid > 0){
printf("finish a child:%d\n",wpid);
}
}
}
return 0;
}