当前位置:   article > 正文

Linux——fork()函数_linux fork()函数

linux fork()函数

fork()函数

  1. 一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
  2. 在linux中fork()函数是非常重要的函数,它的作用是从已经存在的进程中创建一个子进程,而原进程称为父进程。

返回值
失败返回-1;
fork方法被调用一次,成功就会有两次返回;

  1. 在父进程中返回一次,返回的是子进程的pid(非0)
  2. 在子进程中返回一次,返回值为0
#include<unistd.h>//引用fork时的头文件
pid_t fork(void)//进程复制的系统调用

**************************************

pid_t pid = fork();
if(pid == 0)//子进程
{}
else//父进程
{}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

调用fork(),当控制转移到内核中的fork代码后,内核开始做:

  1. 分配新的内存块和内核数据结构给子进程。
  2. 将父进程部分数据结构内容拷贝至子进程。
  3. 将子进程添加到系统进程列表。
  4. fork返回开始调度器,调度。

在这里插入图片描述
新进程复制出现时,是在fork的return之前
在这里插入图片描述
并发执行
fork之后父子进程的执行顺序是不确定的,父子进程是并发执行的,内核能够以任意的方式交替执行他们的逻辑控制流中的指令。
复制进程必须要操作系统支持
复制出来的进程会接着原来的进程执行的逻辑继续向下执行,父子进程都会执行fork方法返回之后的逻辑代码
相同但是独立的地址空间
因为父进程和子进程是独立的进程,他们都有自己私有的地址空间,当父进程或者子进程单独改变时,不会影响到彼此,类似于c++的写实拷贝的形式自建一个副本。

下面看一个例子简单地理解一下fork函数:
在这里插入图片描述
经典面试题:
在这里插入图片描述

系统调用:pid_t getpid()//返回调用此方法的进程pid

接下来,让我们研究一下fork之后,父子进程对于数据的共享问题:
先回顾一下有关进程的4G虚拟内存空间中数据存储的一些区域的主要作用:

栈(stack) 包括以下内容和用途:
1 函数的返回值和参数。
2 临时变量,包括非静态局部变量,以及编译器自动生成的临时变量。
3 保存上下文:包括函数调用前后需保持不变的寄存器。
堆(heap)
堆用于存放进程运行时动态分配的内存段,可动态扩张或缩减。堆中内容是匿名的,不能按名字直接访问,只能通过指针间接访问。当进程调用malloc©/new(C++)等函数分配内存时,新分配的内存动态添加到堆上(扩张);当调用free©/delete(C++)等函数释放内存时,被释放的内存从堆中剔除(缩减) 。
BSS段 .BSS(Block Started by Symbol)段中通常存放程序中以下符号:
1 未初始化的全局变量和静态局部变量 2
初始值为0的全局变量和静态局部变量(依赖于编译器实现)
3 未定义且初值不为0的符号(该初值即common block的大小)

数据段(.Data) 数据段通常用于存放程序中已初始化且初值不为0的全局变量和静态局部变量。数据段属于静态内存分配(静态存储区),可读可写。

每个进程都会有以下类型的数据:
全局数据(.data .bss)
栈区数据(.stack)
堆区数据(.heap)
因此我们需要做以下的测试:
在fork之前定义全局数据,栈区数据,堆取数据,调用fork,然后在父进程或子进程中修改三个位置的数据,在对应的另一个进程中,其数据是否被修改?
测试代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<malloc.h>
#include<assert.h>

int gdata = 10;

int main()
{
	int ldata = 10;
	int *ptr = (int *)malloc(4);
	assert(ptr != NULL);
	*ptr = 10;
	
	pid_t pid = fork();
	assert(pid != -1);
	if(pid ==0)
	{
		printf("child::gdata = %d,ldata = %d\n",gdata,ldata);
		printf("child::*ptr = %d\n",*ptr);
		
		sleep(2);//等待父进程修改完成
		
		printf("------修改之后的数据-------")
		printf("child::gdata = %d,ldata = %d\n",gdata,ldata);
		printf("child::*ptr = %d\n",*ptr);
		printf("child::0x%x,0x%x,0x%X\n",&gdata,&ldata,ptr);
	}
	else
	{
		sleep(1);//让子进程sleep前的语句打印完成
		gdata = 20;
		ldata = 20;
		*ptr  = 20;
		printf("father::0x%x,0x%x,0x%X\n",&gdata,&ldata,ptr);
		printf("father over\n");
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

在这里插入图片描述
结论:fork之前的数据的虚拟地址都是相等的,但是fork之后两个进程的页表不同,所以数据是存储在不同的物理空间上。

写时拷贝技术

一般在fork之后,两个进程的页表几乎相同,甚至有些数据都是相同的。
在linux中,fork()产生一个父进程相似的子进程,但是一般情况下,子进程在此之后会调用exec(进程替换)函数族。在复制前,这些区域由父子进程共享,而且内核将他们的访问权限改变为只读,若父子进程中的任意一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储器系统中的一“页”如果这个时候把各个段都拷贝到子进程,就会造成很大的效率浪费

个人理解:
不修改某些区域就共享,一旦试图修改就利用写时拷贝技术。
注:
malloc函数仅仅申请虚拟内存空间,在使用虚拟内存空间存储数据时,才会给其分配物理存储空间,然后将虚拟内存空间映射到物理空间上。
若当前物理内存空间足够使用,系统就不会选择交换分区存储存储数据,内存不够时,才会将部分数据置换到交换分区中。
物理内存如果有空闲也不会立即将交换分区的数据置换回来,只有当系统需要使用存储在交换分区的数据时,才会将其重新置换到内存中。

fork后,子进程是否可以共享父进程打开的文件?

fork之后,父子进程会共享文件的描述符,主要是对文件读写偏移量的共享。
进程打开文件的记录方式:
当一个文件资源打开时,会在PCB中记录下来,PCB中有一个进程文件表数组(filp),每一个元素分别为一个结构体指针(struct file *),指向一个struct file的结构体变量,在该结构中记录:f_mode,f_flag(打开标记),f_count(记录有几个指针指向struct file结构)(fork后变成2), *f_indoe, f_pos(偏移量)等信息,其中 * f_inode指向struct m_inode(实现操作文件)

在这里插入图片描述
程序中操作的文件描述符fd就是PCB中的文件表数组的下标
接下来深究一下为什么父子进程会共享文件偏移量?
current:当前进程PCB的指针
P:子进程PCB的指针
在这里插入图片描述

今天就这么多!
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/210149
推荐阅读
相关标签
  

闽ICP备14008679号