进程篇——进程创建-fork及vfork

扩展内容:

我们都知道程序在系统中是以进程为单位运行的,进程是可执行程序运行的单元。进程执行过程中就需要OS为其分配可供执行的资源。其中最难理解的就是 内存资源的分配,OS到底是怎么为新进程合理的分配资源呢,这就产生了虚拟地址空间的概念。

1.虚拟地址空间和物理内存

虚拟内存存在的意义:

计算机内存资源是相对匮乏的资源,每个可执行程序都需要哦OS为其分配一定的内存资源,现代计算机已经实现了多并发、多处理的处理要求,一台计算机每时每刻都有很多的程序在运行,而每个程序又会有很多的进程在执行,每一个进程又需要独立的堆栈为其服务,这些都需要内部才能作为载体才能正确完整的执行。问题是,计算机内存容量是有限的 ,不可能为可执行程序都合理的分配内存资源供其使用,于是就提出了虚拟内存的概念。

虚拟内存是逻辑上的内存空间(逻辑空间),是在物理磁盘上划分出来的。比如OS会为每一个正在运行的程序分配大概4G的虚拟地址空间(每一个正在执行的程序都会有4G),这4G虚拟地址空间和物理内存空间之间存在某种映射关系的。

一般虚拟地址4G的空间对应的物理内存应该也是4G的,但是,虚拟地址空间是在磁盘上的,当这段代码需要执行的时候(就必须加载到内存),OS 就需要把需要执行的一部分利用某种算法加载到内存(只加载一部分,不可能把该程序虚拟地址空间的所有内容都加载到内存),然后运行。

 

2.进程的执行过程

虚拟地址空间分为两部分:用户地址空间和内核地址空间,i386系统体系结构的计算机的虚拟地址空间一般是4G , 0~3G的空间是用户空地址空间3G到4G的地址空间为内核地址空间。相应地,程序的运行状态分为 用户态内核态,程序在用户态只能占用用户地址空间,程序在内核态只能占用内核地址空间,显而易见的,内核态执行级别是高于用户态的执行级别。

每一个进程的地址空间都分为这两种地址空间,对于内核进程,由于始终运行在内核态,所以内核进程的tast_sruct结构体的mm域也就被赋值为NULL。内核地址空间中,不存在堆数据结构所以堆的概念仅仅是用户地址空间中的数据结构,所以对于内核而言,也不会需要堆这种数据结构来存储变量或代码。Kmalloc或vmalloc 用户内核进程在运行时申请内存。申请到的虚拟内存在整个内存中都可以被其他程序使用。举个例子来说,假如内核线程1 申请到了一块内存A,只要把该内存的首地址传给另一个内存线程2,那么在线程2中就可以使用这个内存A。

对于用户进程,其既有用户地址空间中的栈,也有它自己的内核栈; 对于内核进程,就只有自己的内核栈。所有的进程都有一个内核栈,在x86的32机器上内核栈大小可以为4KB 或者 8KB,这个可以在编译内核的时候配置。

创建内核堆栈的情况为:

  1. 当进程进入内核态,系统调用的参数就放在内核栈上,内核栈记录着进程在内存上的调用链。

2.在内核栈被配置为8KB大小的情况下,当中断服务程序中断当前进程时,它将使用当前被中断进程的内核栈。

 

3. fork创建进程过程

Fork()是内核程序创建进程的一种方式(其他还有vfork()和clone()方法)

Fork函数原型: pid_t  fork(void);

函数返回类型 pid_t 实质上是 int类型

fork函数每次回新生成一个进程,调用fork函数的进程是父进程,fork函数生成的函数是子进程。Fork函数调用一次,返回两次。两次分别返回的是父进程和子进程。

有三种不同的返回值:

  1. 在父进程中返回子进程的pid;
  2. 在子进程中返回0;
  3. 如果返回失败,则返回-1.

举例说明:

            

将子进程ID返回给父进程的理由:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的进程ID。

对子进程来说,之所以fork返回0给它:是因为它随时可以调用 getpid()函数来获取它自己的PID;也可以调用 getpid()来获取父进程的PID(进程ID 0总是由内核交换进程使用,所以一歌子进程的进程ID不可能为0)。

子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程的数据空间、堆栈的副本。父子进程并不共享这些存储空间部分。什么意思呢,就是父进程和子进程共享代码段,但是分别拥有自己的数据段和堆栈段(子进程复制父进程的数据段,BSS段,代码段,堆空间,栈空间,文件描述符,但是对于文件描述符关联的内核文件表项(即struct file结构体)则是采用共享的方式

Fork函数在生成子进程的时候,用到了写时拷贝技术(原因:在fork之后经跟随着exec,所以很多的实现并不执行一个父进程数据段、栈、堆的完全复制。): 不执行一个父进程数据段、栈和堆的完全复制,这些区域由父、子进程共享,而内核将他们的访问权限改为只读的。如果父、子进程任何一个试图修改这些区域,则内核只为修改区域的那块内存做一个副本,并且是以虚拟存储器系统中的“一页”为单位复制。

(父子进程共享页帧而不是赋值页帧,然而,只要页帧被共享,他们就不能被修改,即页帧保护。无论父进程还是子进程何时视图写一个共享的页帧,都会产生一个异常,这时内核就会把这个页复制到一个新的页帧中并标记为可写。原来的页帧仍然是写保护的:当其他进程视图写入时,内核检查写进程是否是这个页帧的唯一属主,如果是,就把这个页帧标记为对这个进程是可写的。)

过程:

(1)分配PID;

(2)分配进程描述符也就是PCB,,同时分配好内核堆栈;

(3)复制进程实体,即打开文件、工作目录、信号信息、进程地址空间等等。

(4)用父进程内核栈上存放的现场信息、初始化子进程的现场信息,并将eax置为0;

(5)将父进程时间片 分 子进程一半,设置状态为就绪。

 

 

4.fork函数底层实现原理:

Fork()系统调用通过复制一个现有进程来创建一个新的进程。进程被存放在一个叫做 任务队列的双向循环链表当中,链表当中的每一项都是 类型为 task_struct 称为进程描述符发结构,也就是我们所说的 进程PCB。

Tips:  内核通过一个位置的进程标识值或者PID 来标识一个进程。 //最大值默认为 32768,short int 短整型的最大值,他就是系统中允许同时存在的进程最大的数目。

可以到目录  /proc/sys/kernel 中查看pid_max :

 

当进程调用fork之后,当控制转移到 内核中的fork代码后,内核会做4件事情:

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

Fork函数在底层到底做了什么呢?

Linux平台通过 clone()函数调用 fork(),  vfork()和clone()库函数都根据各自需要的参数标志去调用clone(),然后由clone()去调用 do_fork(),再然后do_fork完成了创建中的大部分工作,该函数调用 copy_process() 做最后的那部分工作。

(图片转自博客https://blog.csdn.net/Dawn_sf/article/details/78709839)

如图所示:

                  

 

 

  1. vfork函数

Vfork 函数是另一个创建线程的函数vfork;

Vfork 最初是因为fork没有实现 cow机制,而很多情况系fork之后会跟exec,而exec的执行相当于之前fork复制的空间全部变成了无用功,所以设计了 vfork。

#include<sys/types.h>

#include<unistd.h>

Pid_t vfork(void)

函数解释:

  1. Vfork 函数用于创建一个子进程,而子进程和父进程共享地址空间,fork的子进程具有独立的地址空间。
  2. Vfork 保证子进程先运行,在它调用exec(或者exit)之后,父进程才可能被调度运行

注意:fvfork创建的子进程在退出的时候需要调用exit()函数退出,如果没有调用函数是会出错误的;因为在函数栈上,子进程运行结束了,main的函数栈被子进程释放了,然后父进程在使用的时候,就访问不到了,所以一旦vfork出子进程,退出的时候需要使用exit来退出。

 

5.fork和vfork的区别

1.fork: 子进程拷贝父进程的代码段和数据段

  Vfork: 子进程和父进程共享代码段和数据段

2.fork:父子进程的先后运行次序不定

  Vfork:保证子进程先运行,子进程exit之后,父进程才开始被调度运行。

3.vfork()保证子进程先运行,在它调用exec或者而成exit之后,父进程才开始调度运行,如果在调用这俩个函数之前,子进程依赖父进程的进一步动作,则会导致死锁。

  4.就算fork实现了写时拷贝,效率也没有 vfork高,但是vfork一般平台会出现问题,所以很少用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值