Linux 进程控制

server/2024/12/23 12:20:22/

目录

一、进程终止

1、进程退出场景

2、进程常见退出方法

a. 正常终止(可以通过 echo $? 查看进程退出码):

b. 异常退出:

3、分类

a. main函数返回值(return 退出)

I. 退出码: 

II. 错误码: 

III. 异常信号:用 kill -l 查看

 b. _exit 函数&&exit 函数

二、进程等待 

1、为什么要进行进程等待?

2、进程等待的方法

a. wait && waitpid

b. 获取子进程status 

​编辑 3、阻塞等待&&非阻塞等待 

a. 阻塞等待

b. 非阻塞等待 

三、进程程序替换

1、替换原理

2、替换函数

execlp:

execv:

execvp:

execle,execve,execvpe:

总结:


一、进程终止

1、进程退出场景

1、代码运行完毕,结果正确   2、代码运行完毕,结果错误   3、代码异常终止

2、进程常见退出方法

a. 正常终止(可以通过 echo $? 查看进程退出码):

I.  从main返回   II.  调用 exit    III.  _exit

b. 异常退出:

ctrl + c,信号终止

3、分类

a. main函数返回值(return 退出)

main函数返回值,叫做进程的退出码,一般0,表示进程执行成功,非0表示失败。

I. 退出码: 

进程的退出码(exit_code),也称为退出状态或返回值,是一个整数,用于表示进程执行结束时的状态,通常由进程的主函数(C语言中是main函数)返回,或者是在进程遇到异常或者错误是由操作系统进行设置。可以使用 echo $? 命令查看最近一次进程退出的退出码信息。$? 是一个特殊的shell变量,保存了上一个命令的退出码

II. 错误码: 

进程的错误码( errno ) 用于表示进程在执行系统调用或库函数时遇到的错误情况,每个错误码都对应一个特定的错误情况,是的程序能够识别并处理这些错误。而进程的退出码是一个整数,用于表示进程执行结束时的状态。

错误码errno通常是一组预定义的数值,每个值对应一个特定的错误情况。这些定义通常包含在系统的头文件<errno.h>中。当系统调用或库函数失败时,它们会设置全局变量errno为相应的错误码。

如果errno的值为0,则表示系统调用成功,如果errno的值不为0,则表示系统调用失败,并且其值对应着特定的错误代码。常见处理errno的方法,如使用perror函数或者strerror函数,perror函数可以将errno的值映射为对应错误信息,并将其打印到标准错误流(stderr),而strerror函数则可以将errno的值转化为对应的错误字符串。

 

III. 异常信号:用 kill -l 查看

所以,任何进程最终的执行情况,我们都可以使用两个数字表明具体的执行情况

 b. _exit 函数&&exit 函数

 #include <stdio.h>#include <errno.h>#include <string.h>#include <unistd.h>#include <stdlib.h>int main(){while(1){printf("I am a process: %d\n", getpid());sleep(1);exit(3); // exit 终止进程,status:进程退出时候,退出码                                                                                                            }                                         }  

 #include <stdio.h>#include <errno.h>#include <string.h>#include <unistd.h>#include <stdlib.h> void Print(){printf("hello\n");exit(5);}int main(){while(1){printf("I am a process: %d\n", getpid());sleep(1);Print();                                                                                                                                                           //exit(3); // exit 终止进程,status:进程退出时候,退出码}}

这就告诉我们,exit就是用来终止进程的,exit(退出码)。

那么这个_exit又是什么呢?把上面的代码中的 exit 改成 _exit 验证一下,结果和exit是一样。难道说exit 和 _exit 是一样的吗?它们到底有什么区别?

所以,可以得出,exit 会支持刷新缓冲区,而_exit不支持。

二、进程等待 

1、为什么要进行进程等待?

2、进程等待的方法

a. wait && waitpid

对于waitpid中的参数:

status: 
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真(用于查看进程是否退出)。
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码(用于
查看你进程的退出码)。

options:

WNOHANG: 若pid指定的子进程还没有结束,则waitpid()函数返回0,不予等待。若正常结束,则返回该子进程的ID。

对于waitpid中的返回值:

正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中的waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。

如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。

如果不存在该子进程,则立即出错返回。

 

 #include <stdio.h>#include <errno.h>#include <string.h>#include <unistd.h>#include <stdlib.h>#include <sys/types.h>#include <sys/wait.h>  int main()  {                                          pid_t id = fork();                       if(id == 0)  {  //child  int cnt = 5;  while(cnt)  {  printf("Child is running, pid: %d, ppid: %d\n",getpid(),getppid());  sleep(1);  cnt--;  }  exit(1);  }  int status = 0;  pid_t rid = waitpid(id, &status, 0);//阻塞等待  if(rid > 0)  {  printf("wait sucess, rid: %d, status: %d\n", rid, status);                                                                                                         }                                                                                                                               return 0;                                                                                                                       }  

等待成功,但是我们发现这个status怎么是256呢?这256从哪来的? 

b. 获取子进程status 

wait 和 waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,则表示不关心子进程的退出状态信息,否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整型来看待,可以当作位图来看待。

可以用下图表示(只研究status低16比特位):

那么回到刚刚的问题,上面那个代码的256是怎么算的呢?

我们上面说过,任何进程最终执行情况,我们都可以使用两个数字表明具体执行情况。

 3、阻塞等待&&非阻塞等待 

阻塞和非阻塞指的是调用者(程序)在等待返回结果(或输入)时的状态。阻塞时,在调用返回结果前,当前线程会被挂起,并在得到结果之后返回。非阻塞时,如果不能立刻得到结果,则该调用者不会阻塞当前进程。

a. 阻塞等待

#include <stdio.h>    
#include <errno.h>    
#include <string.h>    
#include <unistd.h>    
#include <stdlib.h>    
#include <sys/types.h>    
#include <sys/wait.h>    int main()    
{    //阻塞等待    pid_t id = fork();    if(id<0) return 1;    else if(id == 0)    {    printf("I am child, pid: %d, ppid: %d\n",getpid(), getppid());    sleep(3);    exit(100);    }    else{    int status = 0;    pid_t rid = waitpid(id, &status, 0);    if(rid > 0)    {    printf("wait sucess, rid: %d, status: %d, exitnum: %d, signo1: %d, signo2:: %d, isexit: %d\n",    rid, status, status&0x7f, (status>>8)&0xff, WEXITSTATUS(status), WIFEXITED(status));    }                                                                                                                                                                      }    return 0;    
}

看这串代码中,status&0x7f,(status>>8)&0xff,这两个是什么啊?

b. 非阻塞等待 

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id= fork();if(id < 0) return 1;else if( id == 0){printf("child is running..., pid is: %d\n",getpid());sleep(5);exit(10);}else{int status = 0;pid_t rid = 0;do{rid = waitpid(-1, &status, WNOHANG);//非阻塞等待if(rid == 0){printf("child is running...\n");}sleep(1);                                                                                                                                                            }while(rid == 0);if(WIFEXITED(status) && rid == id){printf("wait child 5s success, child return code is: %d\n",WEXITSTATUS(status));}else{printf("wait child failed\n");return 1;}}

 

三、进程程序替换

1、替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另外一个程序。当进程调用一种exec函数时,该进程的用户空间和数据被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

下面我们来用一个最简单的exec的接口来表示一下替换原理:

 

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>    int main()    
{    printf("I am a process, pid: %d\n",getpid());    printf("exec begin...\n");    execl("/usr/bin/ls", "ls", "-a", "-l", NULL);//注意这里是NULL,不是"NULL"    printf("exec end ...\n");                                                                                                                                                return 0;    
}

 

这里代码最后的printf并没有被执行?为什么?

上面替换原理说了:当进程调用一种exec函数时,该进程的用户空间和数据被新程序替换,从新程序的启动例程开始执行。

2、替换函数

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。如果调用出错则返回-1。
所以exec函数只有出错的返回值,而没有成功的返回值。

那这么多我们该如何记住它们呢?---看命名

带p:PATH,你不用告诉系统,程序在哪里,只要告诉我名字是什么,系统替换的时候,会自动去PATH环境变量中查找。

execlp:
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>    int main()
{         printf("I am a process, pid: %d\n",getpid());pid_t id=fork();if(id == 0)     {          sleep(3);printf("exec begin...\n");// execl("/usr/bin/ls", "ls", "-a", "-l", NULL);//注意这里是NULL,不是"NULL"execlp("ls", "ls","-a","-l",NULL);                                        printf("exec end...\n");          exit(1);                }         pid_t rid= waitpid(id,NULL,0);if(rid>0)                     {        printf("wait sucess\n");}                         exit(1);
} 

 

 

带v:vector 数组        带l:list,列表,参数列表

execv:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>        int main()                   
{                            printf("I am a process, pid: %d\n",getpid());      pid_t id=fork();           if(id == 0)                {                          char *const argv[] = {      (char*)"ls",           (char*)"-a",           (char*)"-l",NULL            };                       sleep(3);                printf("exec begin...\n");      execv("/usr/bin/ls", argv);                                                                                                                                            printf("exec end...\n");                                                                                             exit(1);                                                                                                             } pid_t rid= waitpid(id,NULL,0);                                                                                         if(rid>0)                                                                                                              {                                                                                                                      printf("wait sucess\n");}exit(1);
}

execvp:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>        int main()                   
{                            printf("I am a process, pid: %d\n",getpid());      pid_t id=fork();           if(id == 0)                {                          char *const argv[] = {      (char*)"ls",           (char*)"-a",           (char*)"-l",NULL            };                       sleep(3);                printf("exec begin...\n");                                 execvp("ls", argv);                                                                                                                                           printf("exec end...\n");                                                                                             exit(1);                                                                                                             } pid_t rid= waitpid(id,NULL,0);                                                                                         if(rid>0)                                                                                                              {                                                                                                                      printf("wait sucess\n");}exit(1);
}

 

当然对于所有的替换函数,我们都不仅可以和上面一样执行系统的指令,也可以执行自己的程序。

//test.c#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>        int main()                   
{                            printf("I am a process, pid: %d\n",getpid());      pid_t id=fork();           if(id == 0)                {                          char *const argv[] = {      (char*)"ls",           (char*)"-a",           (char*)"-l",NULL           };                       sleep(3);                printf("exec begin...\n");                                 execl("./mytest", "mytest", "-a", "-b", NULL);//注意这里是NULL,不是"NULL"                                                                                                                                            printf("exec end...\n");exit(1);                                                                                                             } pid_t rid= waitpid(id,NULL,0);                                                                                         if(rid>0)                                                                                                              {                                                                                                                      printf("wait sucess\n");}exit(1);
}//mytest.cc#include <iotream>
using namespace std;int main()
{cout<<"hello world"<<endl;cout<<"hello world"<<endl;cout<<"hello world"<<endl;return 0;
}
execle,execve,execvpe:

带e:自己维护环境变量  execle,execve,execvpe

先观察下面代码:

//test.c
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main
{printf("I am a process, pid: %d\n", getpid());pid_t id=fork();if(id == 0){sleep(1);execl("./mytest","mytest",NULL);printf("exec end...\n");exit(1);}pid_t rid = waitpid(id, NULL, 0);if(rid > 0){printf("wait sucess\n");}exit(1);
}//mytest.cc
#include <iostream>    
#include <unistd.h>    
using namespace std;    int main()    
{    for(int i = 0; environ[i]; i++)                                                                  {    printf("env[%d]: %s\n", i, environ[i]);    }    cout<<"hello world"<<endl;    return 0;    
}

 

这里我们传递环境变量表了吗?? ---没有,子进程默认就拿到了,它是怎么做到的? 

默认可以通过地址空间继承的方式,让所有子进程拿到环境变量,进程替换不会替换环境变量数据

1、如果我们想让子进程继承全部的环境变量,直接能拿到

2、如果单纯的新增我们可以使用putenv

3、那么我想设置全新的环境变量给子进程呢?

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()    
{    char* const env[]={    (char*)"one=1111111111111",    (char*)"two=2222222222222", NULL};    printf("I am a process, pid: %d\n",getpid());    pid_t id=fork();    if(id == 0)    {    sleep(1);    execle("./mytest","mytest",NULL,env);   printf("exec end...\n");exit(1);}pid_t rid= waitpid(id,NULL,0);if(rid>0){printf("wait sucess\n");}exit(1);
}

这样我就设置了全新的环境变量给子进程! 

总结:

事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,都是系统调用 execve的封装,所以execve在man手册的第2节,其它函数在man手册的第3节。

http://www.ppmy.cn/server/152488.html

相关文章

Unity中通过代码设置材质HDR颜色的方法参考

在Unity中&#xff0c;如果一个材质的颜色是HDR的颜色&#xff0c;此时要使用代码来设置HDR颜色的效果&#xff0c;应该使用Material.SetVector&#xff0c;而不是Material.SetColor&#xff0c;因为使用Material.Color设置颜色时&#xff0c;rgb的值无法突破1&#xff0c;但是…

本地电脑生成SSH公钥私钥对,用于SSH远程连接服务器

1、使用ssh的方式去访问Linux服务器&#xff0c;首先需要在自己电脑上生成一对公钥和私钥。 生成方式&#xff1a;打开Windows的cmd&#xff0c;输入Windows命令行命令ssh-keygen&#xff0c;然后一直回车回车回车。 回车结束后可以在 C:\User\“你的用户名”\ 文件夹下看到一…

前端知识补充—CSS

CSS介绍 什么是CSS CSS(Cascading Style Sheet)&#xff0c;层叠样式表, ⽤于控制⻚⾯的样式 CSS 能够对⽹⻚中元素位置的排版进⾏像素级精确控制, 实现美化⻚⾯的效果. 能够做到⻚⾯的样式和结构分离 基本语法规范 选择器 {⼀条/N条声明} 1&#xff09;选择器决定针对谁修改…

数据结构经典算法总复习(下卷)

第五章:树和二叉树 先序遍历二叉树的非递归算法。 void PreOrderTraverse(BiTree T, void (*Visit)(TElemType)) {//表示用于查找的函数的指针Stack S; BiTree p T;InitStack(S);//S模拟工作栈while (p || !StackEmpty(S)) {//S为空且下一个结点为空&#xff0c;意味着结束遍…

mysql的事务控制和数据库的备份和恢复

事务控制语句 行锁和死锁 行锁 两个客户端同时对同一索引行进行操作 客户端1正常运行 客户端2想修改&#xff0c;被锁行 除非将事务提交才能继续运行 死锁 客户端1删除第5行 客户端2设置第1行为排他锁 客户端1删除行1被锁 客户端2更新行5被锁 如何避免死锁 mysql的备份和还…

Knowledge Graph Prompting for Multi-Document Question Answering

题目 知识图谱提示多文档问答 论文地址&#xff1a;https://arxiv.org/abs/2308.11730 项目地址&#xff1a;https://github.com/YuWVandy/KG-LLM-MDQA 摘要 大型语言模型 (LLM) 的“预训练、提示、预测”范式在开放域问答 (OD-QA) 中取得了显著成功。然而&#xff0c;很少有研…

每日计划-1221

1. 完成 SQL6 查找学校是北大的学生信息 2. 八股部分 1) 选择排序冒泡排序 包括 代码 时间复杂度 空间复杂度 稳定性 是否能对代码进行提升 选择排序代码&#xff1a; #include <iostream> #include <vector>using namespace std;// 选择排序函数 void sel…

vue入门教程:组件透传 Attributes

一、透传Attributes的基本概念 透传Attributes指的是传递给一个组件&#xff0c;但没有被该组件声明为props或emits的attribute或者v-on事件监听器。最常见的例子包括class、style和id。当一个组件以单个元素为根路径渲染时&#xff0c;透传的attribute会自动被添加到根元素上…