Linux fork和vfork
fork 系统调用
可以通过fork系统调用创建新的进程。调用进程称为父进程,被创建的进程称为子进程。
fork函数复制当前进程,会在内核进程表中创建一个新的进程表项。新的进程表项有很多属性和原进程相同,如堆指针、栈指针、标志寄存器的值。也有许多属性被赋予新的值,如子进程PPID为原来进程PID。
原型
#include <unistd.h>
pid_t fork(void);
fork的特点是:
1)调用一次,返回2次。
2次返回区别:子进程返回的是0,父进程返回的是子进程的pid(进程id)。
原因:一个进程可以有多个子进程,但一个进程却只有一个父进程,并且没有一个函数使得进程可以获得其所有子进程的pid。
2)子进程和父进程继续执行fork调用之后的指令。
3)子进程是父进程的副本,子进程获得父进程的数据空间、堆、栈 副本。但子进程和父进程不共享这些存储空间。
典型的进程存储空间安排如下:
也就是说,调用fork之后,子进程完全是父进程的副本,进程的存储空间完全一样。不过,由于fork之后经常跟着调用exec,所以很多实现并不执行一个父进程数据段、栈完全一样的副本。
fork,exec与信号处理函数:fork创建子进程后,子进程具有父进程一样存储空间,信号捕捉函数位于text代码段,在子进程中也是有意义的,子进程会继承父进程的信号处理方式。不过,调用exec后,运行新的程序会覆盖从父进程继承而来的text段,那么信号捕捉函数在新程序中无意义,所以exec会将原先已经设置过的信号捕捉函数重新设置为默认值。
fork示例程序:
子进程和父进程同时对变量a自增,然后打印其值
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int a = 1;
pid_t pid;
if ((pid = fork()) == -1) { /* error */
perror("fork error");
exit(1);
}
else if (pid == 0) { /* child */
++a;
printf("child process a = %d\n", a);
}
else {
sleep(1);
++a;
printf("parent process a = %d\n", a);
wait(pid);
}
return 0;
}
运行结果:
可以看到父子进程变量a是相互独立的
child process a = 2
parent process a = 2
vfork 系统调用
vfork也用来创建新进程。
原型
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
跟fork区别:
1.调用序列同fork,但语义不同:vfork用于创建一个新进程,但并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit)。子进程调用exec或exit之前,在父进程的进程空间中运行,也就是说,vfork子进程和父进程共享进程空间。
如果没有调用exec或exit,直接return返回,可能会带来未知的结果。
2.vfork保证子进程先运行,调用exec或exit之后,父进程才可能被调度运行。期间,父进程阻塞。fork不能保证父、子进程谁先执行,fork返回后,父子进程都开始并发执行,不存在由于fork导致的阻塞。
PS:vfork子进程不能return,只能通过exec,再return,或者直接exit。
vfork示例程序:
子进程和父进程同时对变量a自增,然后打印其值
int main()
{
int a = 1;
pid_t pid;
if ((pid = vfork()) == -1) { /* error */
perror("fork error");
exit(1);
}
else if (pid == 0) { /* child */
++a;
printf("child process a = %d\n", a);
exit(1);
}
else {
//sleep(1);
++a;
printf("parent process a = %d\n", a);
wait(pid);
}
return 0;
}
运行结果:
可以看到父子进程共享了变量a的值
child process a = 2
parent process a = 3