Android下Linux创建进程的姿势(下)

引子

前文我们讲了fork和COW的原理,本文接着上文续写vfork,clone等方式创建进程原理。

vfork

vfork也是创建一个子进程,但是子进程共享父进程的空间。在vfork创建子进程之后,父进程阻塞,直到子进程执行了exec()或者exit()。vfork最初是因为fork没有实现COW机制,而很多情况下fork之后会紧接着exec,而exec的执行相当于之前fork复制的空间全部变成了无用功,所以设计了vfork。

vfork和fork之间的另一个区别是:vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行,当子进程调用这两个函数中的任意一个时,父进程会恢复运行。

也可以这么理解,vfork创建出来的不是真正意义上的进程,而是一个线程。

arduino 复制代码
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char*argv[]) {
    pid_t ret;
    int count = 0;
    //在父进程的空间中,定义一个count 共享变量
    printf("【parent】 shared 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 from pid=%d\n", getpid());
        count = 10;
        printf("【child】assign count=%p ,count=%d\n", & count, count);
        sleep(2);
        _exit(0);//退出子进程,必须调用,因为使用vfork()创建子进程后,父进程会被阻塞,直至子进程调用exec或者_exit函数退出,否则会报vfork: cxa_atexit.c:100: __new_exitfn: Assertion `l != ((void *)0)' failed
    } else {
        printf("【parent】continue in parent pid=%d\n", getpid());
        printf("【parent】ret=%d, &count=%p , count=%d\n", ret, & count, count);
        printf("【parent】pid=%d\n", getpid());
    }
    return 0;
}

结果:

ini 复制代码
【parent】shared count=0x7ffe774fe418 in pid=7950
【parent】fork in pid=7950【child】start from pid=7951
【child】assign count=0x7ffe774fe418,count=10 //这里会sleep(2) 然后 父进程才会继续执行
【parent】continue in parent pid=7950
【parent】ret=7951, &count=0x7ffe774fe418 , count=10
【parent】the pid=7950

我搜了下android的源码工程,发现用vfork的地方也寥寥无几,在recovery的升级模块有用到。文件路径:/bootable/recovery/updater/install.cpp。

clone

clone是Linux为创建线程设计的,不仅可以创建进程或者线程,还可以指定创建新的命名空间(namespace)、有选择的继承父进程的内存、甚至可以将创建出来的进程变成父进程的兄弟进程等等。

系统调用fork()和vfork()是无参数的,而clone()则带有参数。fork()是全部复制,vfork()是共享内存,而clone()是则可以将父进程资源有选择地复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享,具体要复制哪些资源给子进程,由参数列表中的clone_flags决决定。

clone函数功能强大,带了众多参数,它提供了一个非常灵活自由的创建进程的方法。因此由他创建的进程要比前面2种方法要复杂。clone可以让你有选择性的继承父进程的资源,你可以选择像vfork一样和父进程共享一个虚存空间,从而使创造的是线程,你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系。先有必要说下这个函数的结构:

arduino 复制代码
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

其中关键参数:

  • child_stack为给子进程分配系统堆栈的指针(在linux下系统堆栈空间是2page大小,就是8K的内存,其中在这块内存中,低地址上存放的值就是进程控制块task_struct的值);
  • flags为要复制资源的标志,描述你需要从父进程继承那些资源(是资源复制还是共
参数 含义
CLONE_PARENT 创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了"兄弟"而不是"父子"
CLONE_FS 子进程与父进程共享相同的文件系统,包括root、当前目录
CLONE_FILES 子进程与父进程共享相同的fd表
CLONE_NEWNS 在新的namespace启动子进程,namespace描述了进程的文件层级
CLONE_SIGHAND 子进程与父进程共享相同的信号处理(signal handler)表
CLONE_PTRACE 若父进程被trace,子进程也被trace
CLONE_VFORK 父进程被挂起,直至子进程释放虚拟内存资源
CLONE_VM 子进程与父进程运行于相同的内存空间
CLONE_PID 子进程在创建时PID与父进程一致
CLONE_THREAD Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群

fork不对父子进程的执行次序进行任何限制,fork返回后,子进程和父进程都从调用fork函数的下一条语句开始行,但父子进程运行顺序是不定的,它取决于内核的调度算法.而在vfork调用中,子进程先运行,父进程挂起,直到子进程调用了exec或exit之后,父子进程的执行次序才不再有限制;clone中由标志CLONE_VFORK来决定子进程在执行时父进程是阻塞还是运行,若没有设置该标志,则父子进程同时运行,设置了该标志,则父进程挂起,直到子进程结束为止。

Android下底层创建线程的方式也是用clone的方式实现的,参考如下图:

exec

exec和其他三种创建进程原理上不属于同一个层次,可以理解为是它是为了加载程序而存在,所以这里我们就浅聊下就可以了。一般我们创建子进程都是为了运行新的程序代码,因此Linux系统提供了一个exec()函数族,用于创建和修改子进程。调用exec()函数时,子进程中的代码段、数据段和堆栈段都将被替换。由于调用exec()函数并没有创建新进程,因此修改后的子进程的ID并没有改变。

arduino 复制代码
//第一组,l->list,p->path,e->envp
int execl(const char *pathname, const char *arg, ..., (char*)NULL);
int execlp(const char *file, const char *arg, ..., (char*)NULL);
int execle(const char *pathname, const char *arg, ..., (char*)NULL, char *const envp[]);
//第二组,v->vector,p->path,e->envp
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
//第三组,v->vector,e->envp
int execve(const char *pathname, char *const argv[], char *const envp[]);

总结

本系列文章因在看Android底层源码时候接触到的一个小知识点引发而来,分上下两篇文章道明了Linux下创建进程的方式并阐明了在Android场景下的使用方式,希望大家能从中解惑,也希望大家能从中学到持续学习,吾日三省吾身的精神,欢迎各位coder关注公众号,第一时间技术交流。

相关推荐
丁总学Java8 分钟前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
懒羊羊大王呀19 分钟前
CSS——属性值计算
前端·css
睡觉然后上课28 分钟前
c基础面试题
c语言·开发语言·c++·面试
无咎.lsy44 分钟前
vue之vuex的使用及举例
前端·javascript·vue.js
fishmemory7sec1 小时前
Electron 主进程与渲染进程、预加载preload.js
前端·javascript·electron
fishmemory7sec1 小时前
Electron 使⽤ electron-builder 打包应用
前端·javascript·electron
豆豆2 小时前
为什么用PageAdmin CMS建设网站?
服务器·开发语言·前端·php·软件构建
落落落sss2 小时前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
代码敲上天.3 小时前
数据库语句优化
android·数据库·adb
twins35203 小时前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js