Linux系统编程——进程(二)创建进程函数(fork) 父进程给子进程拷贝了什么内容 以及 fork为什么要创建子进程

一、fork函数

头文件

#include <unistd.h>

函数原型

pid_t fork(void);

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
    pid_t pid;
    pid = getpid();  //等于父进程pid值
    fork();   //创建进程
    printf("my pid is %d   %d\n",pid,getpid());
    return 0;
}
/*  pid_t pid;
    pid_t pid2;
    pid_t retpid;
    pid = getpid();
    printf("before fork pid : %d\n",pid); //父进程
    retpid = fork();
    pid2 = getpid();
    printf("after fork pid : %d\n",pid2); //第一遍打印父进程,第二遍打印子进程
    if(pid == pid2){
        printf("this is father pid : %d\n",retpid); //父进程
     }
     else{
        printf("this is child pid : %d\n",getpid()); //子进程
     }
*/

在这里插入图片描述
运行结果中第一排打印的是父进程的信息,在fork()函数后面创建了一个子进程,第二排打印的是子进程的信息

返回值
fork函数调用成功,返回两次
返回值为0,代表当前进程是子进程
返回值为非负数,代表当前进程是父进程
父进程返回的是新子进程的PID号
调用失败返回 -1

根据fork函数的返回值可以做一个判断

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
    pid_t pid;
    printf("father pid : %d\n",getpid());  //打印的是父进程的pid
    pid = fork();                        //创建子进程
    if(pid > 0){
        printf("this is father pid : %d\n",getpid()); //父进程pid
    }
    else if(pid == 0){
        printf("this is child pid : %d\n",getpid()); //子进程pid
    }
    return 0;
}

在这里插入图片描述
第一排打印的是父进程的pid值,根据fork函数的返回值执行if else语句,刚开始在父进程里面,所以返回值大于0,所以打印了第二句话,因为创建了子进程,所以会在执行一遍 pid = fork()函数下面的语句,又因为在子进程里面,所以打印第三句话

简单来说就是在fork()函数之前的语句只会被执行一次,fork函数后面的语句将会分别被父进程和子进程分别执行

二、 父进程给子进程拷贝了什么内容

fork函数调用成功,早期的Unix内核:父进程会把所有的内容都拷贝一分给子进程,包括正文(代码段)、初始化的数据段、未初始化的数据(BSS段)、堆、栈、命令行参数和环境变量等。
现在的Unix内核(包括Linux):现在是共享数据,而不是把父进程里面所有的东西都拷贝到子进程,只有当子进程需要更改数据时,才会将父进程中需要更改的数据拷贝给子进程,也就是写时拷贝。

在子进程里面修改的值和父进程没有关系

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
    int data = 100;
    pid_t pid;
    pid = fork();
    if(pid == 0){
        printf("child"); //子进程
        data += 100;
    }else{
        printf("father"); //父进程
    }
    printf("data = %d\n",data);
    return 0;
}

在这里插入图片描述

三、fork为什么要创建子进程

1、一个父进程希望复制自己,使父,子进程同时执行不同的代码段

这在网络服务进程中是常见的——父进程等待客户端的服务请求,当这种请求达到要求时,父进程调用fork函数,使子进程处理此请求,父进程则继续等待下一个服务请求达到。
如图
在这里插入图片描述

用代码可以模拟上面场景

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
    pid_t pid;
    int data = 0;
    while(1){
       printf("please input a data\n");
       scanf("%d",&data);
       if(data == 1){            //判断是否输入了1
           pid = fork();         //创建子进程
           if(pid > 0){          //表示在父进程,什么事都不做
            }
        	else if(pid == 0){   //进入子进程
            	while(1){        //子进程里面不断打印当前子进程的pid
            		printf("do some things, pid = %d\n",getpid());
                	sleep(3);    //延时3s打印一次
             	}
          	}
        }else{
        	printf("wait, do nothing\n");
    }
	return 0;
}

在这里插入图片描述
当输入1时每过3秒打印创建的子进程pid,并且子进程并不影响父进程的数据输入,当再输入一个1时,又创建一个子进程,同时打印两个子进程的pid,这样就模拟了服务器和客户端。
在这里插入图片描述
在系统里面也可以看见两个创建了两个pid值为18491和18493的进程。

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

四、总结:

由fork创建的新进程称为子进程(chile process),fork函数被调用一次,但返回两次,两次返回的唯一区别是子进程的返回值是0,而父进程的返回值是则是新子进程的进程PID,将子进程PID返回给父进程的理由是:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的进程PID。fork函数使子进程得到返回值0的理由是:一个进程只会有一个父进程,所有子进程总是可以调用getppid以获得父进程的进程PID,(进程PID 0总是由内核交换进程使用,所以一个子进程的进程PID 不可能为0)。
子进程和父进程继续执行fork函数调用后的指令。子进程是父进程的副本,例如,子进程获得父进程数据空间,堆和栈的副本。注意,这是子进程所拥有的副本,父,子进程并不共享这些存储空间部分,父,子进程共享正文段。

由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段,栈和堆的完全复制。作为代替,使用了写时复制(Copy-On-Writ,COW)技术。这些区域由父,子进程共享,而且内核将它们的访问权限改变为只读的,如果父,子进程中的任何一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储器系统中的一“页”。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值