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 创建出来的不是真正意义上的进程,而是一个线程。
结果:
我搜了下 android 的源码工程,发现用 vfork 的地方也寥寥无几,在 recovery 的升级模块有用到。文件路径:/bootable/recovery/updater/install.cpp。
clone
clone 是 Linux 为创建线程设计的,不仅可以创建进程或者线程,还可以指定创建新的命名空间(namespace)、有选择的继承父进程的内存、甚至可以将创建出来的进程变成父进程的兄弟进程等等。
系统调用 fork()和 vfork()是无参数的,而 clone()则带有参数。fork()是全部复制,vfork()是共享内存,而 clone()是则可以将父进程资源有选择地复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享,具体要复制哪些资源给子进程,由参数列表中的 clone_flags 决决定。
clone 函数功能强大,带了众多参数,它提供了一个非常灵活自由的创建进程的方法。因此由他创建的进程要比前面 2 种方法要复杂。clone 可以让你有选择性的继承父进程的资源,你可以选择像 vfork 一样和父进程共享一个虚存空间,从而使创造的是线程,你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系。先有必要说下这个函数的结构:
其中关键参数:
child_stack 为给子进程分配系统堆栈的指针(在 linux 下系统堆栈空间是 2page 大小,就是 8K 的内存,其中在这块内存中,低地址上存放的值就是进程控制块 task_struct 的值);
flags 为要复制资源的标志,描述你需要从父进程继承那些资源(是资源复制还是共
fork 不对父子进程的执行次序进行任何限制,fork 返回后,子进程和父进程都从调用 fork 函数的下一条语句开始行,但父子进程运行顺序是不定的,它取决于内核的调度算法.而在 vfork 调用中,子进程先运行,父进程挂起,直到子进程调用了 exec 或 exit 之后,父子进程的执行次序才不再有限制;clone 中由标志 CLONE_VFORK 来决定子进程在执行时父进程是阻塞还是运行,若没有设置该标志,则父子进程同时运行,设置了该标志,则父进程挂起,直到子进程结束为止。
Android 下底层创建线程的方式也是用 clone 的方式实现的,参考如下图:
exec
exec 和其他三种创建进程原理上不属于同一个层次,可以理解为是它是为了加载程序而存在,所以这里我们就浅聊下就可以了。一般我们创建子进程都是为了运行新的程序代码,因此 Linux 系统提供了一个 exec()函数族,用于创建和修改子进程。调用 exec()函数时,子进程中的代码段、数据段和堆栈段都将被替换。由于调用 exec()函数并没有创建新进程,因此修改后的子进程的 ID 并没有改变。
总结
本系列文章因在看 Android 底层源码时候接触到的一个小知识点引发而来,分上下两篇文章道明了 Linux 下创建进程的方式并阐明了在 Android 场景下的使用方式,希望大家能从中解惑,也希望大家能从中学到持续学习,吾日三省吾身的精神,欢迎各位coder关注公众号,第一时间技术交流。
版权声明: 本文为 InfoQ 作者【江湖修行】的原创文章。
原文链接:【http://xie.infoq.cn/article/7b6d0f8756a06f9fa0a9b104e】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论