对于fork函数的参数和返回值可以阅读本篇博客
Linux进程编程实践1——进程的基本概念、fork创建进程
这里直接上图了解fork的头文件和参数
现实生活中,任何孩子都知道自己的父亲(排除特殊情况),而父亲却可能有多个孩子,因此就需要一个代号来标识每一个孩子,简单来说就是父亲:孩子=1:n
因此,对于操作系统来说,父进程有多个子进程,需要返回子进程的pid来调度每个子进程,而任何子进程都知道自己的父进程,因此返回0。
对于fork函数创建子进程,简单来说就是创建子进程都是以父进程为模板的,那么进程调用fork后,内核做了什么呢?
用一张图来演示
当一个进程调用fork之后,就有两个二进制代码相同的进程。而且他们都会运行到相同的地方,如上图所示,当父进程运行到fork时,会创建一个一模一样的子进程,子进程会接着父进程运行到的地方继续向下执行也就是after,而父进程也会运行after!
通常,父子进程共享代码,但是数据各自私有一份。
对于一个程序来说,由两部分构成代码+数据
程序种的代码运行中不可被修改,如果每个子进程都有一份,势必造成空间的浪费,因此所有代码都是共享的,在一定程度上节省了空间。
进程之间具有独立性,当数据过多时,并不是所有的数据都会立马使用,更不会将所有数据拷贝。当一个进程需要立马独立时,如果系统将数据全部拷贝,把本来可以在后面拷贝或者不拷贝的都拷贝了,会造成实间和空间的浪费!
解释了上面两个原因后,可以知道,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。 如下图:
在修改内容前,子进程以父进程为模板,父子进程页表种的虚拟地址对应的物理地址是一样的,且系统会将页表项置为只读状态,一旦发生内容修改,即子进程需要写入时,操作系统就会报错,以写时拷贝的方式为子进程的页表项重新开辟一块物理空间,并将内容修改,这就是写时拷贝。
优点:
节省空间和时间!
一个进程退出的三种情况可以分为以下三种:
代码运行完毕,结果正确
代码运行完毕,结果不正确
代码异常终止
正常终止(可以通过 echo $? 查看进程退出码):
#include
#include
int main()
{
printf("hello world\n");
return 0;
}
调用exit
_exit
异常退出:
#include
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值
说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。
#include
void exit(int status);
exit最后也会调用exit, 但在调用exit之前,还做了其他工作
return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做exit的参数。
Linux进程编程实践2——进程状态、模拟实现僵尸进程和孤儿进程
简单来说,因为父进程很容易对子进程进行管理(垃圾回收)。处理业务需要让父进程帮我们拿到子进程的执行结果。
#include
#include
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成NULL
#include
#include
#include
#include
#include
using namespace std;
int main()
{
pid_t id =fork();
if(id < 0)
{
cout<<"fork error"<<endl;;
return 1;
}
if(id == 0)
{
int count = 0;
while(1)
{
sleep(1);
cout<<"child.........."<<endl;
if(count >= 5)
{
break;
}
count++;
}
exit(0);
}
//父进程
if(id > 0)
{
cout<<"father...."<<endl;
wait(NULL);//父进程阻塞等待,等待子进程退出后,父进程退出
cout<<"after father...."<<endl;
}
return 0;
}
pid_t waippid(pid_t pid, int *status, int options)
返回值:
- 当正常返回的时候waitpid返回收集到的子进程的进程ID;
- 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
- 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
我们不关心status和options,将其设为NULL和0,只观察返回值
#include
#include
#include
#include
#include
using namespace std;
int main()
{
pid_t id =fork();
if(id < 0)
{
cout<<"fork error"<<endl;;
return 1;
}
if(id == 0)
{
int count = 1;
while(count < 20)
{
cout<<"child is running...."<<endl;
++count;
}
}
//父进程
if(id > 0)
{
cout<<"father...."<<endl;
int ret = waitpid(id,NULL,0);
if(ret == id)
{
cout<<"等待成功"<<endl;
}
else
{
cout<<"等待失败"<<endl;
}
cout<<"after father...."<<endl;
}
return 0;
}
首先了解参数:
pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
其次了解获取子进程status
如上图所示,如果进程处于正常状态,高8位是进程的退出状态,低8位全0表示进程正常退出
#include
#include
#include
#include
#include
using namespace std;
int main()
{
pid_t id =fork();
if(id < 0)
{
perror("fork error");
exit(1);
}
if(id == 0)
{
sleep(10);
exit(10);
}
if(id > 0)
{
int st;
int ret = waitpid(id, &st,0);
if(ret > 0 && (st & 0x7f) == 0)
{
cout<<"child exit code: "<<((st >> 8)&0xFF)<<endl;
}
else if(ret > 0)
{
cout<<"sig code: "<<(st & 0x7F)<<endl;
}
}
return 0;
}
如果进程被信号所杀,只关心第7位的core dump标志位
#include
#include
#include
#include
#include
using namespace std;
int main()
{
pid_t id =fork();
if(id < 0)
{
perror("fork error");
exit(1);
}
if(id == 0)
{
int d = 1/0;
exit(10);
}
if(id > 0)
{
int st;
int ret = waitpid(id, &st,0);
if(ret > 0 && (st & 0x7f) == 0)
{
cout<<"child exit code: "<<((st >> 8)&0xFF)<<endl;
}
else if(ret > 0)
{
cout<<"sig code: "<<(st & 0x7F)<<endl;
}
}
return 0;
}
对于进程的以上两种状态操作起来较为复杂,因此在库函数中使用宏函数封装好了这两种状态,对于进程的status(重点):
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WIFEXITED(status) == !(status & 0x7F)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
WEXITSTATUS(status) == (status >> 8) & 0xFF
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
1. 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息
2. 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
3. 如果不存在该子进程,则立即出错返回
#include
#include
#include
#include
#include
using namespace std;
int main()
{
pid_t id =fork();
if(id < 0)
{
perror("fork error");
exit(1);
}
if(id == 0)
{
cout<<"child is running..."<<endl;
sleep(10);
exit(10);
}
if(id > 0)
{
int status=0;
pid_t ret = waitpid(-1, &status,0);
if(ret == id && WIFEXITED(status))
{
cout<<"wait child 10s success, child exit code: "<<WEXITSTATUS(status)<<endl;
}
else
{
cout<<"wait child failed"<<endl;
}
}
return 0;
}
进程的非阻塞等待方式,这种方式则是让父进程不断访问子进程是否退出,直到子进程退出,父进程才执行后续代码,需要使用options选项
#include
#include
#include
#include
#include
using namespace std;
int main()
{
pid_t id =fork();
if(id < 0)
{
perror("fork error");
exit(1);
}
if(id == 0)
{
cout<<"child is running..."<<endl;
sleep(10);
exit(10);
}
if(id > 0)
{
int status=0;
pid_t ret =0;
do
{
ret = waitpid(-1, &status,WNOHANG);
if(ret ==0 )
{
cout<<"child is running"<<endl;
}
sleep(1);
}while(ret == 0);
if(WIFEXITED(status) && ret == id)
{
cout<<"wait child 10s success, child return code:"<<WEXITSTATUS(status)<<endl;
}
else
{
cout<<"wait child failed,"<<endl;
return 1;
}
}
return 0;
}
有六种以exe开头的函数,统称exec函数:
#include //头文件
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 arv[]);
int execvp(const char *file, char *const arv[]);
以上五个函数均调用的是下面的系统函数
int execve(const char* path, char *const argv[], char *const envp[]);
这些函数后面的字母分别表示以下含义:
#include
int main()
{
execl("/usr/bin/ls","ls","-a","-l",NULL);
//带p的,可以使用环境变量无需写路径
execlp("ls","ls","-a","-l",NULL);
//execv需要讲参数放入到列表当中
char *const argv[] = {"ls","-a","-l",NULL};
execv("/usr/bin/ls",argv);
//带p的无需写全路径,可以使用环境变量
execvp("ls",argv);
return 0;
}
对于环境变量我们可以自己定义,也可以调用系统变量
注意:
#include
#include
#include
int main()
{
printf("begin......\n");
char *env[] ={
"myenv=you_can_see_me",
NULL
};
execle("./mycmd", "./mycmd",NULL, env);
printf("after....\n");
return 0;
}
#include
#include
#include
int main()
{
printf("begin......\n");
char *env[] ={
"myenv=you_can_see_me",
NULL
};
execle("./mycmd", "./mycmd",NULL, env);
printf("after....\n");
return 0;
}
[leon@VM-0-2-centos Process_Control]$ cat mycmd.c
#include
#include
int main()
{
printf("myenv:%s\n", getenv("myenv"));
return 0;
}