2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > Android NDK ——Linux 创建应用进程之 fork vs vfork 小结

Android NDK ——Linux 创建应用进程之 fork vs vfork 小结

时间:2022-02-22 02:50:09

相关推荐

Android NDK ——Linux 创建应用进程之 fork vs vfork 小结

文章大纲

引言一、Unix 进程概述二、fork、vfork、clone三、vfork 简单测试四、fork 简单测试

引言

Unix 系列的进程都是通过复制init进程或内核进程而得到的子进程,不同的实现具体细节有所不同其中Linux提供三种fork、vfork、clone系统调用。

一、Unix 进程概述

在Unix中CPU是以进程为分配单元进行资源分配和调度的,每一个进程都有一个非负整形表示的进程标识符,进程ID总是唯一的,但进程ID是可以重用的,当一个进程终止后其进程ID就可以被其他进程再次使用了,一个普通进程有且只有一个父进程,系统中有一些专用的进程。

0号进程(进程ID为0)是调度进程,又被称为交换进程(swapper),隶属内核的一部分,并不执行任何磁盘上的程序,统一称之为系统进程1 号进程(进程ID为1)又称为init进程,在系统启动时由内核通过相关的初始化脚本(*.rc 或 init.d等文件)创建并启动,init进程最终会成为所有孤儿进程的父进程。2号进程(进程ID为2)是页守护进程,负责支持虚拟存储系统的分页操作。

进程除了进程ID还有一些其他的标识,如下表所示(包含不限于)用户进程控制相关的:

通常父进程的很多属性会被子进程锁继承包括(不限于):

实际用户ID、实际组ID、有效用户ID、有效组ID、附加组ID进程组ID、会话ID、控制终端设置用户ID标志和设置组ID标志当前工作目录、根目录、文件模式创建屏蔽字针对任一打开文件描述符的在执行时关闭标志(close-on-exec)环境上下文和连接的共享存储段存储映射

它们之间主要的区别有:

fork 调用后的返回值进程ID 和进程的PPID不同子进程的tms_utime、tms_stime、tms_cutime、tms_ustime皆设置为0。父进程设置的文件锁不会被子进程继承

SIGCHLD——在一个进程终止或者停止时,将SIGCHLD 信号发送给其父进程,系统默认忽略此信号不进行处理,但如果父进程希望被告知子进程的终止或者停止状态,父进程可以监听捕获改信号。

二、fork、vfork、clone

fork的主要应用有:

一个父进程期望通过拷贝自己,使得父、子进程能同时执行不同的代码段,比如网络通信中,父进程等待客户端的请求,当接到请求时,执行fork 使得子进程去处理这个请求,而父进程则继续等待下一个请求。一个进程要执行另一个不同的程序,比如shell 命令,子进程从fork 返回后立即调用exec 。

#include <uistd.h>pid_t fork(void)

由fork 创建的新进程称为子进程,fork函数虽然只会执行一次,但是返回两次:

子进程的返回值是0,即可以通过返回值去判断执行的是子进程还是父进程,一个进程有且只有一个父进程(内核交换进程ID始终为0)父进程的返回值是子进程的pid,因为一个进程的子进程可能有很多个,如果没有告诉给父进程,父进程就无法得知自己子进程的pid到底是多少。

返回之后,父、子进程继续执行fork 调用后的指令,因为fork后子进程获取到的是父进程的数据空间、堆和栈的副本,并不是直接共享这些空间,而是仅仅共享正文段(segment),在Linux下我们可以调用以下三个系统调用来创建子进程。

注意:fork之后父进程和子进程的执行顺序是不确定的,这取决于内核的调度算法。

不同的线程库fork的实现略有不同,其他的vfork、clone功能可以看成是fork的扩展版,vfork 和fork 的系统调用差异仅在于clone_flags不一致。

传统的复制肯定会消耗大量的资源,因此Linux 设计了写时复制(Copy-on-write)的策略,其核心思想是父进程和子进程共享页帧而不是复制页帧。因为只要页帧被共享,它们就不能被修改,即页帧被保护。因此无论父进程还是子进程何时试图写一个共享的页帧,就产生一个异常,这时内核就把这个页复制到一个新的页帧中并标记为可写,这样原来的页帧仍然是写保护的,即当其他进程试图写入时,内核检查写进程是否是这个页帧的唯一属主,如果是,就把这个页帧标记为对这个进程是可写的。当进程A使用系统调用fork创建一个子进程B时,由于子进程B实际上是父进程A的一个拷贝,因此会拥有与父进程相同的物理页面。为了节约内存和加快创建速度的目标,fork()函数会让子进程B以只读方式共享父进程A的物理页面。同时将父进程A对这些物理页面的访问权限也设成只读。这样,当父进程A或子进程B任何一方对这些已共享的物理页面执行写操作时,都会产生页面出错异常(page_fault int14)中断,此时CPU会执行系统提供的异常处理函数do_wp_page()来解决这个异常。do_wp_page()会对这块导致写入异常中断的物理页面进行取消共享操作,为写进程复制一新的物理页面,使父进程A和子进程B各自拥有一块内容相同的物理页面.最后,从异常处理函数中返回时,CPU就会重新执行刚才导致异常的写入操作指令,使进程继续执行下去。

三、vfork 简单测试

vfork创造出来的是轻量级进程,也叫线程,是共享资源的进程

vfork 被调用之后,父进程将会挂起直到子进程结束(exit)和execve(2),在此之前父、子进程共享内存页。

#include<unistd.h>#include<stdio.h>#include<stdlib.h>/*** forkdemo.c* n success ,the PID of the child process is returned int the parent,and 0 is returned* in the child .* onFailure,-1 is returned in the parent,no child process is created.and errno is set appropriately.**/int main(int argc, char* argv[]){pid_t ret;int count =0;//在父进程的空间中,定义一个count 共享变量printf("【parent】assign shared var on &count=%p in pid=%d\n",&count,getpid());printf("【parent】fork in pid=%d\n",getpid());ret=vfork(); //vfork 父子进程共享对象count,父、子进程共享的count 变量其虚拟内存地址一致,在调用vfork之后父进程会挂起,子进程对count 修改会体现在父进程中if(ret==0){printf("【child】start in pid=%d\n",getpid());count=100;printf("【child】assign on &count=%p with count=%d\n",&count,count);sleep(2);_exit(0);//退出子进程,必须调用,因为使用vfork()创建子进程后,父进程会被阻塞,直至子进程调用exec或者_exit函数退出,否则会报vfork: cxa_atexit.c:100: __new_exitfn: Assertion `l != ((void *)0)' failed//execl("./vfork2",0);}else{printf("【parent】continue in parent pid=%d\n",getpid());printf("【parent】ret=%d, &count=%p , count=%d\n",ret,&count,count);printf("【parent】the pid=%d\n",getpid());}return 0;}

运行结果

unbuntu14:~/crazymo$ gcc forkdemo.c -o vforkunbuntu14:~/crazymo$ ./vfork【parent】assign shared var on &count=0x7ffe774fe418 in pid=7957【parent】fork in pid=7957【child】start in pid=7958【child】assign on &count=0x7ffe774fe418 with count=100//这里会sleep(2) 然后 父进程才会继续执行【parent】continue in parent pid=7957【parent】ret=7958, &count=0x7ffe774fe418 , count=100【parent】the pid=7957

从以上运行结果中我们可以得到简单的结论:父、子进程共享的count 变量其虚拟内存地址一致,在调用vfork之后父进程会挂起,子进程对count 修改会体现在父进程中

四、fork 简单测试

#include<unistd.h>#include<stdio.h>#include<stdlib.h>/*** forkdemo.c* n success ,the PID of the child process is returned int the parent,and 0 is returned* in the child .* onFailure,-1 is returned in the parent,no child process is created.and errno is set appropriately.**/int main(int argc, char* argv[]){pid_t ret;int count =0;//在父进程的空间中,定义一个count 共享变量printf("【parent】assign shared var on &count=%p in pid=%d\n",&count,getpid());printf("【parent】fork in pid=%d\n",getpid());//ret=vfork(); //vfork 父子进程共享对象count,父、子进程共享的count 变量其虚拟内存地址一致,在调用vfork之后父进程会挂起,子进程对count 修改会体现在父进程中ret=fork();//fork 父、子进程共享变量的地址,父进程不共享变量的值,父、子进程中的count 变量的地址一样,但是对应的值不一样,在父进程中count值为0,在子进程中count值为100,**父、子进程共享的count 变量其虚拟内存地址一致,但调用fork之后父进程不会挂起,子进程对count 修改不一定会体现在父进程中。**if(ret==0){printf("【child】start in pid=%d\n",getpid());count=100;printf("【child】assign on &count=%p with count=%d\n",&count,count);sleep(2);_exit(0);//退出子进程,必须调用,因为使用vfork()创建子进程后,父进程会被阻塞,直至子进程调用exec或者_exit函数退出,否则会报vfork: cxa_atexit.c:100: __new_exitfn: Assertion `l != ((void *)0)' failed//execl("./vfork2",0);}else{printf("【parent】continue in parent pid=%d\n",getpid());printf("【parent】ret=%d, &count=%p , count=%d\n",ret,&count,count);printf("【parent】the pid=%d\n",getpid());}}

运行结果

unbuntu14:~/crazymo$ gcc forkdemo.c -o forkunbuntu14:~/crazymo$ ./fork【parent】assign shared var on &count=0x7fffc709ab18 in pid=7950【parent】fork in pid=7950【parent】continue in parent pid=7950【parent】ret=7951, &count=0x7fffc709ab18 , count=0【parent】the pid=7950【child】start in pid=7951【child】assign on &count=0x7fffc709ab18 with count=100

从以上运行结果中我们可以得到简单的结论:父、子进程共享的count 变量其虚拟内存地址一致,但调用fork之后父进程不会挂起,因此子进程对count 修改不一定会体现在父进程中。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。