一、fork 入门知识
一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
fork出错可能有两种原因:
1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
2)系统内存不足,这时errno的值被设置为ENOMEM。
每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。
另外,还需提及一点:fork与execve的区别,fork是创建两个一模一样的进程,但是具有父子关系;而execve是将自己的进程替换成别人的进程,execve之后只有一个进程。
通俗的讲:fork是分身,而execve是变身。
僵死进程和孤儿进程含义
僵死进程
fork之后,父进程没有调用wait或者waitpid函数,子进程就退出了,这个时候子进程就成了僵死进程。子进程在退出前操作系统希望父进程得到子进程的退出码,所以父进程一定要wait子进程,这个可以避免僵死进程。
僵死进程的危害:系统内核在子进程退出是就释放其内存,但是系统并没有回收其pid,而系统的pid是有有限的,当系统内的僵死进程超过限制,系统将会崩溃。
孤儿进程
父进程在调用wait或waitpid之前就已经退出了,也就是父进程早于子进程退出,此时init进程就成为了子进程的父进程,init进程为子进程的父进程收集退出状态,从而避免僵死进程。孤儿进程并没有危害,常运行于后台。
通俗的解释僵死进程就是:儿子死了,老子不知道;孤儿进程:老子死了,儿子还在。
下面以两道例题来巩固fork的使用
1、编写一个孤儿进程,这个孤儿进程可以同时创建100个僵死进程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int main(void)
{
pid_t pid=fork();
if(pid<0)
{
printf("%s\n",strerror(errno));
return -1;
}
if(pid>0)
{
printf("parent pid = %d and my child pid = %d\n",getppid(),pid);
exit(0);//父进程退出后,子进程变为孤儿进程
}else
{
int i=0;
for(;i<100;i++)//不断循环100次
{
pid_t pid_tmp=fork();
if(pid_tmp==0)//返回pid等于0说明是子进程
{
exit(0);//子进程退出,由于父进程没有wait,这时子进程就变成了僵死进程
}
}
pause();
}
return 0;
}
运行结果
使用命令ps -u vicent(用户名)可以查看linux下的所有进程
结果:
2、编写两个不同的可执行程序,名称分别为a和b,b为a的子进程。在a程序中调用open函数打开a.txt文件。在b程序不可以调用open或者fopen,只允许调用read函数来实现读取a.txt文件。(a程序中可以使用 fork与execve函数创建子进程)。
a.txt文件内容如下:
hello world
a.c文件
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int arg,char*args[])
{
printf("a begin\n");
int fd = open("a.txt", O_RDONLY);//以只读的方式打开a.txt文件
if(fd==-1)
{
printf("err id %s\n",strerror(errno));
return -1;
}
pid_t pid=fork();
if(pid<0)
{
printf("fork failed %s\n",strerror(errno));
return -1;
}
if(pid>0)//父进程
{
close(fd);
}
if(pid==0)//子进程
{
char s[124];
memset(s,0,sizeof(s));
sprintf(s,"%d",fd);//将打开a.txt文件后的文件描述符fd格式化为字符串
char *args[]={"b",s,NULL};
if(execve("b",args,NULL)==-1)//将文件描述符fd做为启动参数传递给b程序,如果成功此时的a进程就变成了b进程。如果等于-1说明execve失败。
{
printf("exevce failed %s\n",strerror(errno));
}
}
printf("a end\n");
return 0;
}
b.c文件
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int main(int arg, char*args[])
{
printf("b begin\n");
if(args[1]==NULL)//b启动的时候,如果没有参数,返回错误
{
return -1;
}
int fd=atoi(args[1]);//将第二个参数char转换为int,也就是文件描述符fd
char buf[1024];
memset(buf,0,sizeof(buf));
read(fd,buf,sizeof(buf)-1);
printf("%s\n",buf);
close(fd);
printf("b end\n");
return 0;
}
结果: