赞
踩
返回值
失败返回-1;
fork方法被调用一次,成功就会有两次返回;
#include<unistd.h>//引用fork时的头文件
pid_t fork(void)//进程复制的系统调用
**************************************
pid_t pid = fork();
if(pid == 0)//子进程
{}
else//父进程
{}
调用fork(),当控制转移到内核中的fork代码后,内核开始做:
新进程复制出现时,是在fork的return之前
并发执行
fork之后父子进程的执行顺序是不确定的,父子进程是并发执行的,内核能够以任意的方式交替执行他们的逻辑控制流中的指令。
复制进程必须要操作系统支持
复制出来的进程会接着原来的进程执行的逻辑继续向下执行,父子进程都会执行fork方法返回之后的逻辑代码
相同但是独立的地址空间
因为父进程和子进程是独立的进程,他们都有自己私有的地址空间,当父进程或者子进程单独改变时,不会影响到彼此,类似于c++的写实拷贝
的形式自建一个副本。
下面看一个例子简单地理解一下fork函数:
经典面试题:
接下来,让我们研究一下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"); } }
结论:fork之前的数据的虚拟地址都是相等的,但是fork之后两个进程的页表不同,所以数据是存储在不同的物理空间上。
一般在fork之后,两个进程的页表几乎相同,甚至有些数据都是相同的。
在linux中,fork()产生一个父进程相似的子进程,但是一般情况下,子进程在此之后会调用exec(进程替换)函数族。在复制前,这些区域由父子进程共享,而且内核将他们的访问权限改变为只读,若父子进程中的任意一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储器系统中的一“页”如果这个时候把各个段都拷贝到子进程,就会造成很大的效率浪费。
个人理解:
不修改某些区域就共享,一旦试图修改就利用写时拷贝技术。
注:
malloc函数仅仅申请虚拟内存空间,在使用虚拟内存空间存储数据时,才会给其分配物理存储空间,然后将虚拟内存空间映射到物理空间上。
若当前物理内存空间足够使用,系统就不会选择交换分区存储存储数据,内存不够时,才会将部分数据置换到交换分区中。
物理内存如果有空闲也不会立即将交换分区的数据置换回来,只有当系统需要使用存储在交换分区的数据时,才会将其重新置换到内存中。
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的指针
今天就这么多!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。