①代码运行完毕,结果正确,退出码为0
②代码运行完毕,结果不正确,退出码!0,但是有多种可能
③代码异常终止
- #include
- int main()
- {
- printf("hello world\n");
- return 123;
- }

①echo $?输出最近一次进程退出时的退出码
②第一次输出的退出码123,是main函数的return值
③第二次就是正常的退出码
- #include<stdio.h>
- #include<string.h>
- int main()
- {
- for(int i = 0;i < 100;i++)
- {
- printf("%d: %s\n",i,strerror(i));
- }
- return 0;
- }

- #include<stdio.h>
- #include<string.h>
- 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<stdio.h>
- #include<string.h>
- #include<stdlib.h>
- #include<unistd.h>
- 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<stdio.h>
- #include<string.h>
- #include<stdlib.h>
- #include<unistd.h>
- 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<stdio.h>
- #include<string.h>
- #include<stdlib.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<sys/wait.h>
-
- 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<string.h>
- #include<stdlib.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<sys/wait.h>
-
- 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<string.h>
- #include<stdlib.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<sys/wait.h>
-
- 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<stdio.h>
- #include<string.h>
- #include<stdlib.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<sys/wait.h>
-
- 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<stdio.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<stdlib.h>
- #include<sys/wait.h>
- 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<stdio.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<stdlib.h>
- #include<sys/wait.h>
- 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<stdio.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<stdlib.h>
- #include<sys/wait.h>
- 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<stdio.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<stdlib.h>
- #include<sys/wait.h>
- 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<stdio.h>
- #include<string.h>
- #include<stdlib.h>
- #include<sys/types.h>
- #include<sys/wait.h>
- #include<unistd.h>
-
- #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);
- }
- }