fork函数
头文件:#include<unistd.h>
函数原型:pid_t fork(void)
功能:它是从已经存在的进程中创建一个新进程,
新进程为子进程,而原来的进程为父进程
1.fork函数的返回值情况
ps:子进程返回0,父进程返回子进程的pid。
2.父进程与子进程之间的关系
(1)子进程是以父进程为模板的,数据结构一样;
(2)代码共享一份(也就是两个进程共同使用同一份代码,但每个进程会因为fork的返回值从而进入不同的选项);
(3)父进程和子进程的内存指针相同;
(4)子进程会继承父进程的环境变量;
(5)pid不相同,父进程和子进程都有各自的pid(ps:每个进程都有自己的pid,不会跟别的进程相同,此pid是唯一的)
(6)fork之后,父进程和子进程之间的执行顺序不确定,谁先执行谁后指向是根据系统决定的。
(7)父进程的数据和子进程的数据各自有各自的虚拟地址空间。
3.fork调用失败的原因
主要原因
(1)系统中有太多的进程;
(2)实际用户的进程数超过了限制;
其他原因
(1)内存不足;
(2)系统不支持‘
4.fork常规用法
(1)一个父进程希望复制自己,使子进程同时执行不同的代码。例如:父进程等待客户端请求,生成子进程处理请求;
(2)一个进程要执行一个不同的程序。
5.例子
(1)代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid;//pit_t是无符号的整型
printf("Before :pid is %d\n",getpid());
pid = fork();//创建子进程
if(pid<0)
{
perror("fork");
exit(1);
}
printf("After:pid is %d,fork return %d\n",getpid(),pid);
return 0;
}
运行结果:
解释:此例子说明,fork之后会有两个返回值,父进程返回的是子进程的pid,而子进程返回的是0
(2)代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid;
int count = 0;
pid = fork();
if(pid<0)
{
perror("fork()");
exit(1);
}
else if(pid>0)
{
count += 2;
printf("this is the father process.\n");
printf("pid is %d,fork return %d\n",getpid(),pid);
//getpid()函数是返回当前进程的pid
printf("count = %d\n",count);
}
else
{
count++;
printf("this is the child process.\n");
printf("pid is %d,fork return %d\n",getpid(),pid);
printf("count = %d\n",count);
}
return 0;
}
运行结果:
解释:此例子说明,子进程和父进程各自有各自的数据空间,因为count有两个不同的结果,而且也说明,子进程和父进程共享一份代码,但是谁走哪个选项,就会根据pid决定
vfork函数
头文件:#include<unistd.h>
函数原型:pid_t vfork(void)
功能:Vfork和fork一样,都是在已有的进程中创建一个新的进程,
但是vfork创建的子进程和fork创建的子进程是有区别的
1.vfork返回值的情况
和fork的返回值一样,都是子进程中返回0,父进程中返回子进程的id
2.父进程与子进程之间的关系
(1)父进程和子进程共享一份地址空间;
(2)vfork保证子进程先运行,在它调用exex或exit函数之后父进程才会被调度运行
ps:因为子进程和父进程共用一份地址空间,而且子进程先执行,所以子进程会修改父进程中的变量值。
3.例子
分别使用fork函数vfork函数修改同一个全局变量
(1)代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int g_val=100;//定义一个全局变量
int main()
{
pid_t pid;
pid = fork();
if(pid<0)
{
perror("fork()");
exit(1);
}
if(pid==0)
{
printf("this is the child process.\n");
sleep(5);
g_val = 200;
printf("g_val= %d\n",g_val);
}
else
{
printf("this is the father process.\n");
printf("g_val=%d\n",g_val);
}
return 0;
}
运行结果:
(2)代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int g_val=100;//定义一个全局变量
int main()
{
pid_t pid;
pid = vfork();
if(pid<0)
{
perror("vfork()");
exit(1);
}
if(pid==0)
{
printf("this is the child process.\n");
sleep(5);
g_val = 200;
printf("g_val= %d\n",g_val);
exit(0);
}
else
{
printf("this is the father process.\n");
printf("g_val=%d\n",g_val);
}
return 0;
}
这两个例子说明,fork出来的父进程和子进程有自己的独立地址空间,而vfork出来的父进程和子进程共用一份地址空间,所以vfork出来的子进程会修改父进程的变量值,因为子进程在父进程的地址空间中运行。
其他创建进程的方式
clone
clone函数允许子进程共享一部分父进程的数据
system
是对fork的封装,system函数在等待命令终止时将忽略SIGINT和SIGQUIT信号,同时阻塞SIGCHLD信号
这会导致应用程序错过一个人终止它的信号,,故应用程序应该检查system函数的返回值,如果由于收到某个信号而终止了命令,应用程序应采取一切适当的措施
popen
也是对fork的封装,popen函数会打开一个管道用于进程间通信