①代码运行完毕,结果正确,退出码为0
②代码运行完毕,结果不正确,退出码!0,但是有多种可能
③代码异常终止
#include
int main()
{
printf("hello world\n");
return 123;
}
①echo $?输出最近一次进程退出时的退出码
②第一次输出的退出码123,是main函数的return值
③第二次就是正常的退出码
#include
#include
int main()
{
for(int i = 0;i < 100;i++)
{
printf("%d: %s\n",i,strerror(i));
}
return 0;
}
#include
#include
int main()
{
int a = 10;
a /= 0;
for(int i = 0;i < 100;i++)
{
printf("%d: %s\n",i,strerror(i));
}
return 0;
}
此时,相当于vs中的程序奔溃,退出码也就变得没有意义了
①main函数return,代表进程退出
②exit在任意地方调用,都代表终止进程,参数是退出码
#include
int fun()
{
exit(123);
printf("hello world\n");
return 1;
}
int main()
{
fun();
for(int i = 0;i < 100;i++)
{
printf("%d: %s\n",i,strerror(i));
}
return 0;
}
#include
#include
#include
#include
int main()
{
printf("hello world");//数据时被暂时保存在输出缓冲区中
sleep(4);
exit(EXIT_SUCCESS); //exit 或者main return 本身就会要求系统进行缓冲区刷新
//return 0;
}
//运行代码
[wh@bogon lesson11]$ ./myproc
hello world[wh@bogon lesson11]$
③_exit终止进程,强制终止进程,不会进行进程的后续收尾工作,比如刷新缓冲区(用户级缓冲区)
#include
#include
#include
#include
int main()
{
printf("hello world");
sleep(4);
_exit(12);
// exit(EXIT_SUCCESS);
//return 0;
}
④_exit()与exit的区别
系统层面,少了一个进程,释放了 PCB,mm_struct,页表和各种映射关系,代码,数据和申请的空间
fork()之后,子进程是为了帮助父进程完成某种任务,而父进程要知道子进程做的怎么样,所以让父进程fork()之后,需要通过wait/waitpid等待子进程退出信息
①通过获取子进程退出信息,能够得知子进程的执行结果
②可以保证时序的问题,子进程先退出,父进程后退出
③进程退出的时候会先进入僵尸状态,会造成内存泄漏的问题,需要通过父进程wait,释放该子进程占用的资源
方法一 wait方法:
#include
#include
pid_t wait(int*status);返回值: 成功返回被等待进程pid,失败返回-1。
参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
#include
#include
#include
#include
#include
#include
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt)
{
printf("child[%d] is running: cnt is : %d\n",getpid() ,cnt);
cnt--;
sleep(1);
}
exit(0);
}
sleep(10);
pid_t ret = wait(NULL);
if(ret > 0)
{
printf("parent wait: %d,success\n",ret);
}
else
{
printf("parent wait failed\n");
}
sleep(10);
}
子进程先执行5s,然后退出,父进程也同时执行5s,此时因为子进程已经退出,所以是僵尸进程,再5s之后,父进程等待成功,释放子进程的资源,然后又休眠10s,父进程进入睡眠状态,之后父进程结束
方法二 waitpid方法:
pid_t ret = waitpid(id,NULL,0); //等待执行一个进程
pid_t ret = waitpid(-1,NULL,0); //等待任意一个子进程,等价于wait
pid_t waitpid(pid_t pid, int *status, int options);
返回值:
pid > 0: 返回子进程的pid
pid < 0: 等待失败
参数1:
pid:
pid = -1,表示等待任意一个子进程,等价于wait
pid > 0,等待指定的子进程
参数2:
status:
①是一个输出型参数,有操作系统填充
②如果传递NULL.表示不关心子进程的退出状态信息
③否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程
④父进程拿到的status结果,一定和子进程如何退出强相关
⑤子进程的退出结果,共有三种,代码异常终止则会收到某种信号,如果没有收到信号说明没有问题,之后就会返回退出码给父进程,最终会让父进程通过status得到子进程执行的结果
①status表示的共32bit,但是高16bit没有用到,只使用低16个bit
②低8bit:表示某种信号,如果收到这种信号,则表示异常终止,未收到则表示正常运行,一般情况下值为0,但是这8bit也只用到了前7bit
③高8bit:表示子进程返回的退出状态
查看status所收到的信息:
#include
#include
#include
#include
#include
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt)
{
printf("child[%d] is running: cnt is : %d\n",getpid() ,cnt);
cnt--;
sleep(1);
}
exit(12);
}
int status = 0;
pid_t ret = waitpid(id,&status,0);
if(ret > 0)
{
printf("parent wait: %d,success,status exit code: %d,status exit signal: %d\n",ret,(status>>8)&0xFF,status&0x7F);
//status exit code:因为是高8位,所以先右移8位,然后与0xFF按位与
//status exit signal:因为是低8位,但是只用到了前7位,所以与0x7F按位与
}
else
{
printf("parent wait failed\n");
} }
status收到的退出码为12,终止信号为0,则表示代码跑完,但结果不正确
当然还有俩种情况分别是:
①代码运行完毕,结果正确,比如status收到的退出码为0,并且正常运行
②代码异常终止:比如代码没有跑完,但是子进程被干掉,此时会收到终止信号,并且退出码已经不重要了
当然想要获取退出码还有一种方式:
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
#include
#include
#include
#include
#include
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt)
{
printf("child[%d] is running: cnt is : %d\n",getpid() ,cnt);
cnt--;
sleep(1);
}
exit(12);
}
int status = 0;
pid_t ret = waitpid(id,&status,0);
if(ret > 0)
{
if(WIFEXITED(status))//没有收到任何终止信号
{
printf("exit code: %d\n",WEXITSTATUS(status));//正常结束,获取对应的退出码
}
}
}
参数3:
options:
0:默认行为,表示阻塞等待,子进程没返回,父进程什么也没做,一直等子进程 WNOHANG: 设置等待方式为非阻塞等待
阻塞等待: 张三找李四跑步,李四在学习,让张三等30分钟,张三此时让李四电话不要挂,通过电话知道李四的状态,等学习完在电话中说一声再挂电话,李四不学完,张三不走,主打一个死等,即子进程不退出,父进程不返回
非阻塞等待: 张三找李四跑步,李四在学习,让张三等30分钟,张三2分钟一个电话,检测李四学没学完,不会因为李四这次没学完而把自己卡住,一次等待可能需要多次检测,是基于非阻塞等待的轮询方案
非阻塞等待:
①如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
②如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。 如果不存在该子进程,则立即出错返回。
阻塞等待了是不是意味着父进程不被调度了?
阻塞本质:进程的PCB被放入了等待队列,并将进程的状态改为S状态
返回的本质:进程的PCB从等待队列拿到运行队列,从而被CPU调度
基于非阻塞的轮询方案:
#include
#include
#include
#include
#include
#include
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 10;
while(cnt)
{
printf("child[%d] is running: cnt is : %d\n",getpid() ,cnt);
cnt--;
sleep(1);
}
exit(12);
}
int status = 0;
while(true)
{
pid_t ret = waitpid(id,&status,WNOHANG);
if(ret == 0)
{
//子进程没有退出,但waitpid等待成功,需要父进程重复进行等待
printf("Do parent things\n");
}
else if(ret > 0)
{
//子进程退出,waitpid也成功等待,获取到对应的结果
printf("parent wait: %d,success,status exit code: %d,status exit signal: %d\n",ret,(status>>8)&0xFF,status&0x7F);
break;
}
else{
//ret < 0
//等待结果
perror("waitpid");
break;
}
sleep(1);
}
}
父进程没有等到子进程的退出信息,所以一直在做自己的事情,等子进程退出时,父进程成功等待,获得了子进程的退出信息
进程不变,仅仅替换当前进程的代码和数据的技术
#include
#include
#include
#include
#include
int main()
{
printf("I am a process,pid: %d\n",getpid());
execl("/usr/bin/ls","ls","-a","-l",NULL);
printf("hello world\n");
}
此时就会有一个问题,为什么"hello world"没有被执行呢? 而且第一句printf却被执行了呢?
因为"hello world"被execl进程了程序替换,而第一句printf是先执行,之后才进行的程序替换
程序替换的本质就是把程序的进程代码+数据,加载进特定进程的上下文中
通俗的讲,比如:C/C++程序要运行,必须先加载到内存中,那么如何加载呢?是通过加载器,也就可以理解为通过exec系列的程序替换函数将程序从磁盘加载到内存
之前我们创建子进程的目的即是让子进程执行父进程代码的一部分,但是如果我们想让子进程执行一个全新的程序呢?
此时用到的即是程序替换
#include
#include
#include
#include
#include
#include
int main()
{
pid_t id = fork();
if(id == 0)
{
printf("I am a process,pid: %d\n",getpid());
execl("/usr/bin/ls","ls","-a","-l",NULL);
printf("hello world\n");
exit(0);
}
while(1)
{
printf("I am a parent\n");
sleep(1);
}
//parent
waitpid(id,NULL,0);
printf("success\n");
}
首先执行父进程,之后子进程,子进程进行了程序替换,然后一直执行着父进程 此时我们会有一个疑问:不是说父子代码是共享的吗?为什么他们执行出的结果不一样? 因为程序替换会更改代码区的代码,此时就会发生写实拷贝,进程具有独立性,俩个进程之间互不干扰
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[]);//系统调用
理解:
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
只要进程的程序替换成功,就不会执行后续代码,意味着exec系列函数,成功的时候,不需要返回值检测 当然只要execl系列返回了,就一定是因为调用失败了
#include
#include
#include
#include
#include
#include
int main()
{
printf("I am a process,pid: %d\n",getpid());
execl("/usr/bin/lsss","ls","-a","-l",NULL);
printf("hello world\n");
}
#include
#include
#include
#include
#include
int main()
{
if(fork() == 0)
{
printf("hello world\n");
execl("/usr/bin/ls","ls","-a","-l",NULL);
printf("end\n");
exit(1);
}
pid_t ret = waitpid(-1,NULL,0);
if(ret > 0)
{
printf("wait child success\n");
}
}
#include
#include
#include
#include
#include
int main()
{
if(fork() == 0)
{
printf("hello world\n");
char* argv[] = { "ls","-a","-l",NULL };
execv("/usr/bin/ls",argv);
printf("end\n");
exit(1);
}
pid_t ret = waitpid(-1,NULL,0);
if(ret > 0)
{
printf("wait child success\n");
}
}
只是将execl的可变式列表变成了指针数组
#include
#include
#include
#include
#include
int main()
{
if(fork() == 0)
{
printf("hello world\n");
execlp("ls","ls","-a","-l","-d",NULL);
printf("end\n");
exit(1);
}
pid_t ret = waitpid(-1,NULL,0);
if(ret > 0)
{
printf("wait child success\n");
}
}
l: 表示采用列表的形式
p:自动搜索环境变量PATH,不用再写明路径
#include
#include
#include
#include
#include
int main()
{
if(fork() == 0)
{
printf("hello world\n");
char* argv[] = { "ls","-a","-l","-d",NULL };
execvp("ls",argv);
printf("end\n");
exit(1);
}
pid_t ret = waitpid(-1,NULL,0);
if(ret > 0)
{
printf("wait child success\n");
}
}
v:指针数组的形式
p:不用指明详细路径
//myproc.c,用这个程序去调用myexe
#include
#include
#include
#include
#include
int main()
{
if(fork() == 0)
{
printf("hello world\n");
execl("./myexe","myexe",NULL);
printf("end\n");
exit(1);
}
pid_t ret = waitpid(-1,NULL,0);
if(ret > 0)
{
printf("wait child success\n");
}
}
//myexe.c
#include
int main()
{
printf("hello test\n");
return 0;
}
一次形成俩个可执行程序:
因为伪目标所以总是被执行,但是没有依赖方法,所以并不会形成all,但是有依赖方法,Makefile在执行的时候一定是想先形成的是all,先形成all,那么就要先形成myexe 和myproc,但是又没有依赖方法,并不会再形成all
//Makefile
.PHONY:all
all:myproc myexe
myproc:myproc.c
gcc -o $@ $^
myexe:myexe.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f myproc myexe
#include
#include
#include
#include
#include
int main()
{
if(fork() == 0)
{
printf("hello world\n");
char* env[] = {"MYENV = hello wrold","MYENV1 = HELLO WORLD",NULL};
execle("./myexe","myexe",NULL,env);
printf("end\n");
exit(1);
}
pid_t ret = waitpid(-1,NULL,0);
if(ret > 0)
{
printf("wait child success\n");
}
}
①当只执行./myexe的时候,输出的是系统的环境变量
②当通过myproc来调用myexe的时候,使用的就是我们导入的环境变量(比如上面的例子)
#include
#include
#include
#include
#include
int main()
{
if(fork() == 0)
{
printf("hello world\n");
char* env[] = {"MYENV = hello wrold","MYENV1 = HELLO WORLD",NULL};
char* argv[] = {"myexe",NULL};
execve("./myexe",argv,env);
printf("end\n");
exit(1);
}
pid_t ret = waitpid(-1,NULL,0);
if(ret > 0)
{
printf("wait child success\n");
}
}
e:就是自己添加环境变量
v:数组
#include
#include
#define NUM 128
int main()
{
char command[NUM];
for(;;)
{
command[0] = '\0';
printf("[yh@Linux yh_shell]$");
fflush(stdout);
fgets(command,NUM,stdin);//获取命令字符串
command[strlen(command)-1] = '\0';
printf("echo: %s\n",command);
}
}
const char* sep = " ";
argv[0] = strtok(command,sep);
int i = 1;
while(argv[i] = strtok(NULL,sep))
{
i++;
}
for(i = 0;argv[i];i++)
{
printf("argv[%d],%s\n",i,argv[i]);
}
if(fork() == 0)
{
execvp(argv[0],argv);
exit(1);
}
waitpid(-1,NULL,0);
父进程路径:
①因为执行cd ..,是子进程在执行,执行回退的并非是shell,而我们是想让父进程bash去执行回退
②fork()要执行的命令是第三方命令,而我们想以内建的方式运行,即不创建子进程,而是让父进程shell自己执行,相当于调用了自己的一个函数
chdir 是C语言中的一个系统调用函数(同cd),用于改变当前工作目录,其参数为Path 目标目录,可以是绝对目录或相对目录。(参考自百度)
//检测命令是否需要shell本身执行的,内建命令
if(strcmp(argv[0],"cd") == 0)
{
if(argv[1] != NULL)
{
chdir(argv[1]);
continue;
}
}
#include
#include
#include
#include
#include
#include
#define NUM 128
#define CMD_NUM 64
int main()
{
char command[NUM];
for(;;)
{
command[0] = '\0';
char* argv[CMD_NUM] = {NULL};
printf("[yh@Linux yh_shell]$");
fflush(stdout);
fgets(command,NUM,stdin);
command[strlen(command)-1] = '\0';
// printf("echo: %s\n",command);
const char* sep = " ";
argv[0] = strtok(command,sep);
int i = 1;
while(argv[i] = strtok(NULL,sep))
{
i++;
}
// for(i = 0;argv[i];i++)
// {
// printf("argv[%d],%s\n",i,argv[i]);
// }
if(strcmp(argv[0],"cd") == 0)
{
if(argv[1] != NULL)
{
chdir(argv[1]);
continue;
}
}
if(fork() == 0)
{
execvp(argv[0],argv);
exit(1);
}
waitpid(-1,NULL,0);
}
}