linux进程之fork函数

fork函数:
fork函数的作用是从调用进程中创建一个新的进程,新的进程相当于是调用进程的副本,称为子进程,而调用进程称为父进程。
本节主要讲解父子进程之间的联系和区别。
函数原型:

#include 
pid_t fork(void);

返回值:

  • 在父进程中,fork返回新创建子进程的进程ID。
  • 在子进程中,fork返回0。
  • 如果出现错误,fork返回一个负值。

fork调用为什么会返回两次呢?
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,这也是fork难以理解的点。
由于子进程复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。因此fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。

fork函数的作用:
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程。子进程是父进程的副本,子进程获得父进程数据空间、栈和堆的副本,这是子进程所拥有的副本,但是父子进程并不共享这些存储空间部分。

接下来看看示例例子,看看实验结果是不是如上所说的。
示例程序:

/* fork.c*/

#include   
#include   
#include

int main ()   
{   
    pid_t pid;
    int count=0;  
    if ((pid = fork()) < 0)
    {
        perror("fork");
        exit(-1);
    }
    else if (pid == 0)
    {
        printf("child\n");
        count++;
    }
    else
    {
        printf("parent\n");
        sleep(1);
    }
    printf("pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count); 
    return 0;  
}

以上程序fork之后,父进程相当于执行如下代码:

#include   
#include   
#include
int main ()   
{   
    pid_t pid; 
    int count=0;  
    printf("parent\n");
    sleep(1);
    printf("pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count); 
    return 0;  
}

子进程相当于执行如下代码:

#include   
#include   
#include
int main ()   
{   
    pid_t pid; //pid表示fork函数返回的值  
    int count=0;  
    printf("child\n");
    count++;
    printf("pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count); 
    return 0;  
}

实验结果:

ubuntu:~/test/process_test$ gcc fork.c -o fork  
ubuntu:~/test/process_test$ ./fork
parent
child
pid = 88270,ppid = 88269, count = 1
//隔一秒才打印下面父进程的log
pid = 88269,ppid = 25872, count = 0

以上例子可以看出,子进程和父进程的count变量是不共享的,是各自存储空间的,从打印出来的count变量的值不相同可以证明这一点。
下面再来看一个例子,可以初步证明子进程是父进程的副本。

/* fork2.c*/

#include   
#include   
 #include

int main ()   
{   
    pid_t pid; //pid表示fork函数返回的值  
    int count=0;  
    if ((pid = fork()) < 0)
    {
        perror("fork");
        exit(-1);
    }
    else if (pid == 0)
    {
        printf("child\n");
        count++;
        printf("child: pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count); 
    }
    else
    {
        printf("parent\n");
        sleep(1);
        printf("parent:pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);  
    }
    return 0;  
}

实验结果:

ubuntu:~/test/process_test$ gcc fork2.c -o fork2
ubuntu:~/test/process_test$ ./fork2
parent
child
child: pid = 88754,ppid = 88753, count = 1
//隔一秒才打印下面父进程的log
parent:pid = 88753,ppid = 25872, count = 0

从实验结果可以看出,fork2.c和fork.c效果是一致的,证明了fork.c中父进程printf代码会被子进程拷贝一个副本。一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的,这取决于内核所使用的调度算法。所以,这里我们让父进程sleep再打印log,是为了让子进程先执行打印log,方便我们对结果的分析。
还有一个重要的点是,子进程和父进程共享同一个文件偏移量。
下面用实例程序来测试文件偏移量是否共享。

/* open_fork.c*/

#include   
#include   
#include
#include 
#include 
#include 

#define FILENAME "file_fork"
/*
*测试文件偏移量是否共享
*/

int main ()   
{   
    pid_t pid; //pid表示fork函数返回的值  
    int count=0;  
    char write_child_buf[] = "11111";
    char write_parent_buf[] = "22222";
    int fd = open(FILENAME,O_RDWR | O_CREAT,0777);
    if(fd < 0)
    {
        perror("open");
    }

    if ((pid = fork()) < 0)
    {
        perror("fork");
        exit(-1);
    }
    else if (pid == 0)
    {
        printf("child\n");
        if (write(fd,write_child_buf,sizeof(write_child_buf)))
        {
            perror("write");
        }
        count++;
        printf("child: pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count); 
    }
    else
    {
        printf("parent\n");
        sleep(2);
        if (write(fd,write_parent_buf,sizeof(write_parent_buf)))
        {
            perror("write");
        }
        printf("parent:pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);  
    }
    return 0;  
}

实验结果:

ubuntu:~/test/process_test$ ./open_fork
parent
child
write: Success
child: pid = 6737,ppid = 6736, count = 1
write: Success
parent:pid = 6736,ppid = 25872, count = 0
ubuntu:~/test/process_test$ cat file_fork 
1111122222chenting

从上面的结果中,我们看到,子进程向文件file_fork写入字符串“11111”,然后父进程向file_fork向文件写入字符串“22222”,可以看到字符串“22222”是追加到字符串“11111”后面的,说明父子进程文件描述符、文件偏移量是有被共享的。
(以下内容参考《unix环境高级编程》)
这种共享文件的方式使父子进程对同一文件使用了一个文件偏移量,如果父子进程都向标准输出进行写操作,如果父进程的标准输出已经重定向,那么子进程写到标准输出时,它将更新与父进程共享的该文件的偏移量。比如当父进程等待子进程时,子进程写到标准输出,而在子进程终止后,父进程也写到标准输出,并且其输出会添加在子进程所写数据之后,如果父子进程不共享同一文件偏移量,这种形式的交互很难实现。除了打开文件之外,父进程的很多其他属性也由子进程继承,包括:

  • 实际用户ID,实际组ID,有效用户ID,有效组ID。
  • 附加组ID。
  • 进程组ID。
  • session ID。
  • 控制终端。
  • 设置用户ID标志和设置组ID标志。
  • 当前工作目录。
  • 根目录。
  • 文件模式创建屏蔽字。
  • 信号屏蔽和安排。
  • 针对任意开打文件描述符的在执行时关闭(close-on-exec)标志。
    环境。
  • 连接的共享存储段。
  • 资源映射。
  • 资源限制。

父子进程的区别是:

  • fork返回值。
  • 进程ID不同。
  • 两个进程具有不同的父进程ID。
  • 子进程的tms_utime,tms_stime,tms_cutime以及tms_ustime均设置为0.
  • 父进程设置的文件锁不会被子进程继承。
  • 子进程的未处理alarm被清除。
  • 子进程的未处理信号设置为空集。

你可能感兴趣的:(linux系统编程,fork,linux,父进程,子进程)