一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数
系统中有太多的进程。 实际用户的进程数超过了限制 。
概念:main函数的返回值,又叫做进程的退出码。
代码执行成功,程序能够执行到main函数的末尾并返回,而不是说程序中的每一行都按预期执行了,因为有些错误不能被捕获或者导致程序提前退出了。
非0返回值,通常用于表示不同类型错误/异常的原因,退出码的字符串含义取决于程序的设计者。
退出码:分为 0 和 !0 0:表示程序正常运行,进程执行成功。 !0:表示程序异常退出,进程执行失败。非零又用1 2 3 4等等,数字表示不同的错误信息
bash会自动记录上一个程序的退出码 : echo $?
错误信息大概133种,自己可以去看看。
#include<stdio.h>
#include<string.h>
enum{
success=0,
malloc_err,
open_err
};
const char* errorDesc(int code)
{
switch(code)
{
case success:
return "running sucess!";
case malloc_err:
return "malloc failure!";
case open_err:
return "file open failure!";
default:
return "unkown error!";
}
}
int main()
{
int exit_code = malloc_err;
printf("%s\n", errorDesc(exit_code));
return 0;
}
普通函数一般返回值或者状态。
以fopen为例 执行结果:文件打开成功,fopen()返回指向该文件的指针;文件打开失败,fopen()返回NULL。 执行情况:返回了非空的FILE*指针,则可认为函数执行成功;返回了NULL,则可认为函数执行失败,需要进一步检查错误的原因(errno变量或调用perror()函数)。
普通函数退出,仅仅表示函数调用完毕。
函数也被称为子程序,与进程退出时返回退出码类似,函数执行完毕也会返回一个值,这个值通常用于表示函数的执行结果或状态。
调用函数,我们通常想看到两种结果:a.函数的执行结果(函数的返回值);b.函数的执行情况(函数是否成功执行了预期的任务),例如:fopen()函数的执行情况是通过其执行结果来间接表示。
为了获取普通函数的错误信息,操作系统提供了错误码这个接口
errno是错误码,它是记录系统最后一次错误代码的一个整数值,不同值表示不同含义,在#include<errno.h>中定义。
当函数运行成功时,errno值不会被修改,因此我们不能通过测试errno的值来判断是否有错误存在,而应该在被调用的函数提示有错误发生时,再检查errno的值。
注:错误码只有当库函数调用失败了才会被设置。
退出码是进程结束时给系统返回的状态码,通常简单地表示成功或失败 错误码是函数调用或操作失败时的具体错误信息,提供了更详细的错误类型
要是本身你给退出码定义了详细的分类,那么就会进而编程错误码
任何程序退出的情况我们都可以分为一下三种:用两个数字表示退出情况。
进程信号 = 0 ; 退出码 = 0 进程正常结束,也是成功执行 进程信号 = 0 ; 退出码 = !0 进程正常结束,但是进程执行结果不正确 进程信号 = !0 ; 退出码 = 0/!0 进程没有正常运行,退出码没有任何意义
就是在正常的程序代码中,如main函数走到结尾,或者是遇到return。这样的正常结束。
注:main函数返回、调用exit()、_exit()函数,都表示程序主动退出,即:正常终止;接受到信号(如:ctrl c,信号终止),表示程序被动退出,即:异常退出。
为什么语言具有可移植性和跨平台性?在库层面上,对系统强相关的接口进行了封装,从而屏蔽了底层差异。
1.需要父进程去回收子进程的资源(内存空间),如果子进程结束了,需要父进程去回收空间,否则子进程就会变成僵尸进程,造成内存泄漏。
2.一般来说,进程都会有一个目的,进程等待就可以让父进程得到子进程的运行结果。
子进程的退出信息(exit code、exit signal),需要通过内核数据结构来维护,保存在子进程的task_struct中,属于内核数据。
pid_t wait(int *status)
pid_t waitpid(pid_t pid, int* status, int options);
定义:进程在发出某个请求(如:I/O操作、等待某个条件成立等)后,如果请求不能立即得到满足(如:数据未准备好、资源被占用等),进程会被挂起,在此期间无法继续执行其他任务,直到等待条件满足或被唤醒。
a.行为 -> 进程在等待期间无法执行其他任务(干等着)。 b.触发方式 -> 等待由外部条件触发(如:数据到达、资源释放等)。 c.管理层面:由操作系统或者底层系统资源管理。 d.效率与并发性:效率低。
#include<stdio.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 is running, id:%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 success, status:%d\n", status);
else if(rid == -1) //调用失败
perror("wait error!\n");
return 0;
}
定义:进程在发出某个请求后,不会被立即挂起已等待请求的完成,即使请求不能立即得到满足,进程在等待期间可以继续执行其他任务,同时可能会以某种方式(轮询访问、回调等)定期检查请求状态或者等待结果的通知。
行为 -> 进程在等待期间可以执行其他任务; b.触发方式 -> 可能通过编程的方式实现,如:轮询、回调等。 c.管理层面:在应用层通过编程实现。 d.效率与并发性:效率高,提高并发性和响应能力。
status不能简单的当作整形来看,他是一种类似于位图的, 它有自己的格式,只研究status低16位比特位。
#include<stdio.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 is running, id:%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 success, status:%d, exit code:%d, exit sign:%d\n", status, (status>>8)&0xff, status&0x7f); //位操作获取子进程的退出码、退出信号
return 0;
}
WIFEXITED(status):检查子进程是否正常退出。
如果子进程通过调用exit函数或main函数return返回而退出,则WIFEXITED返回非0值(真) ->正常退出;(进程是正常退出的,进程信号返回的是0,当进程信号为0时,WIFEXITED的返回值是非0)
如果子进程是由于接收到信号而退出,则WIFEXITED返回0(假) -> 异常退出。(进程是异常退出的,进程信号返回的是非,当进程信号为非时,WIFEXITED的返回值是0)
WEXITSTATUS(status):只有当WIFEXITED为真时(即进程是正常退出的,进程信号为0),接着才会使用WEXITSTATUS获取子进程的退出码。
#include<stdio.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 is running, id:%d, ppid:%d\n", getpid(), getppid());
sleep(1);
cnt--;
}
exit(1); //子进程退出
}
int status = 0; //存储子进程退出状态
pid_t rid = waitpid(id, &status, 0);
if(rid > 0) //等待成功
{
if(WIFEXITED(status)) //子进程正常退出
printf("wait success, status:%d, exit code:%d\n", status, WEXITSTATUS(status)); //提取退出码 宏
else //子进程异常退出
printf("child process error!\n");
}
return 0;
}
问题1:在父进程中定义两个全局变量(exit code、exit sign),子进程修改exit code值,父进程可以获取到子进程的退出信息吗?
问题2:为什么要有wait、waitpid?
exec
函数实现的。即:用全新的程序替换原有的程序。exec
函数时,当前进程的用户空间中的代码和数据会被新程序的代码和数据完全覆盖,因此从新程序的启动例程开始执行。这里面有很多的接口,我们先来演示一个最简单的函数 execl
这里有四个细节:
补充一个点:我们这么写传参方式是标准传参,当然也可以非标准传参,就是在遇到非标准的情况也不要奇怪。
这七个接口在功能上没有不同(都是封装系统调用接口execve),只是在使用方式上有所不同,传参方式不同。
我们发现其实前面的名字都差不,后面另外添加的字母就展现出区别。
我们发现execlp,的第一个参数是file,而execl是path,说明execl第一个参数需要传递的是要替换的文件的路径,而第二个只需要传递文件名称就行了,会自动取环境变量指定的路径下去寻找文件。
有了e,就代表这在参数当中添加了 env[]数组,说明了,可以自己写环境变量,要注意这个写入是覆盖式的写入,会将当前进程从父进程那里继承来的参数覆盖掉。
有了v以后,就说明后面的参数传递,不再使用可变参数来传递了,直接传递一个存储参数的字符指针数组。
如果指定的环境变量已经存在,那么它的值会被新的字符串中的值所替换;如果指定的环境变量不存在,那么它会被添加到环境变量表中。