天天看点

fork函数与vfork函数fork函数介绍vfork函数

fork函数介绍

一个现有的进程可以调用fork函数创建一个新进程。

UNIX或类UNIX中的分叉函数,fork函数将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两个进程中的线程继续执行,就像是两个用户同时启动了该应用程序的两个副本。

#inlcude <unistd.h>
pid_t fork(void);	//返回值:子进程返回0,父进程返回子进程ID,出错返回-1
           

一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。

fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值,而父进程中返回子进程ID。

fork函数的特点概括起来就是“调用一次,返回两次”,在父进程中调用一次,在父进程和子进程中各返回一次。

父子进程关系

子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。

父子进程共享正文段。

注意:子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。

传统的fork()系统调用直接把所有的资源复制给新创建的进程。这种实现过于简单并且效率低下。在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

父子进程运行顺序

UNIX将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。在不同的UNIX (Like)系统下,我们无法确定fork之后是子进程先运行还是父进程先运行,这依赖于系统的实现。

实例

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
 
int main(int argc,char *argv[]){
    pid_t pid=fork();
    if ( pid < 0 ) {
        fprintf(stderr,"错误!");
    } else if( pid == 0 ) {
        printf("子进程空间");
        exit(0);
    } else {
        printf("父进程空间,子进程pid为%d",pid);
    }
    // 可以使用wait或waitpid函数等待子进程的结束并获取结束状态
    exit(0);//正常运行程序并退出程序
}
           

父子进程文件共享

需要注意的一点是:在重定向父进程的标准输出时,子进程的标准输出也会被重定向。实际上,fork的一个特性是父进程所有打开的文件描述符都被复制到子进程中。我们说“复制”是因为对每个文件描述符来说,就好像执行了dup函数。父进程和子进程每个相同的打开描述符共享一个文件表项。

一个进程具有三个不同的打开文件:标准输入、标准输出和标准错误。

重要的一点是, 父进程和子进程共享同一个文件偏移量。考虑下述情况: 一个进程fork了一个子进程, 然后等待子进程终止. 假定,作为普通处理的一部分, 父进程和子进程都向标准输出进行写操作。如果父进程的标准输出己重定向(很可能是由shell实现的), 那么子进程写到该标准输出时, 它将更新与父进程共亭的该文件的偏移量. 在这个例子中, 当父进程等待子进程时, 子进程写到标准输出:而在子进程终止后, 父进程也写到标准输出上, 并且知道其输出会追加到子进程所写数据之后。如果父进程和子进程不共享同一文件偏移量,要实现这种形式的交互就要困难得多。

例:

#include<unistd.h>
#include<stdio.h>
#include<sys/wait.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>

#define MAXLEN 2048

int main()
{
    pid_t pid;
    char buf[MAXLEN];
    int file_fd;
    file_fd=open("test.txt",O_RDWR|O_CREAT|O_TRUNC);   //打开一个文件

    if((pid=fork())<0)
    {
        fprintf(stderr,"fork error:[%s]\n",strerror(errno));
        exit(1);
    }
    else if(pid>0)
    {
        sleep(1);
        write(file_fd,"I am parent\n",19);   //父进程写入
    }
    else
    {
        write(file_fd,"I am child\n",18);    //子进程写入
    }
    exit(0);
}

           

test.txt文件结果为:

I am parent
I am child
           

使用方法

fork有以下两种用法:

(1)一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段。这在网络服务进程中是常见的:父进程等待客户端的服务请求,当这种请求达到时,父进程fork,使子进程处理此请求,父进程则继续等待下一服务请求。

(2)一个进程要执行不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec。

注:当进程调用exec函数时,该进程执行的程序完全替换为新程序,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段以及堆栈。

vfork函数

vfork函数的调用序列和返回值fork相同,但两者的语义不同。

vfork函数用于创建一个新进程,而该新进程的目的是exec一个新程序(如fork的第二种用法)。

fork()用法与fork()相似.但是也有区别,具体区别归结为以下3点:

1、fork():子进程拷贝父进程的数据段,代码段. vfork():子进程与父进程共享数据段.

2、fork():父子进程的执行次序不确定.vfork():保证子进程先运行,在调用exec或_exit之前与父进程数据是共享的,在它调用exec或_exit之后父进程才可能被调度运行。

3、 vfork()保证子进程先运行,在它调用exec或_exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。

4、当需要改变共享数据段中变量的值,则拷贝父进程。

继续阅读