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关注公众号,第一时间技术交流。

相关推荐
uhakadotcom3 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
范文杰3 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪3 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪3 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy4 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom4 小时前
快速开始使用 n8n
后端·面试·github
uhakadotcom4 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom4 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom5 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom5 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试