how to create a process
在操作系统教科书中进程是一个非常重要的概念,书中定义为“系统进行资源分配和调度的基本单位”,初步接触 Linux kernel 准备进程概念开始。
0x00 process descriptor
在 Linux 中表示 PCB(进程控制块)的结构体叫 task_struct. task_strcut 相关的信息放在 include/linux/sched.h 中,而单独看 task_struct 意义不是很大,很难把握到 Linux 的进程工作原理,所以才有了本文来梳理 Linux 进程管理的信息。
0x01 how to crate a process ?
可以通过 fork 系统调用创建一个进程
1 |
|
Linux 下创建进程会完全复制它的父进程,所以 fork 调用成功返回两次,在父进程中范围子进程 pid,子进程中返回 0。
0x02 what happened in kernel ?
这里需要引入系统调用
的概念,简而言之系统调用是用户通过 API 和系统沟通的方式。上述源代码会通过系统调用在 Linux 中创建进程。
观察系统调用有个好工具strace
, -f
是继续跟踪子进程。
1 | strace -f ./a.out |
可以看到在 x86_64 下 fork 函数创建一个进程是通过系统调用 clone (在调用 clone 之前的东西可以在 glibc) 来做的,通过一些看上起奇怪的 flags 的组合来达到创建一个进程的目的。系统调用层面之下就是 Linux kernel 了。
下面通过 kernel 4.12-rc2 源代码跟踪一下 clone 系统调用的过程(大体流程依然和 ^ulk 说的类似但是,细节有点不同,参考1d4b4b2994b5fc208963c0b795291f8c1f18becf
),clone 在系统调用表中的 stub_clone 实现,stub_clone 由 sys_clone 定义,而 sys_clone 在 64 位下# define __ARCH_WANT_SYS_CLONE
,后在 fork.c 里面进行条件编译:
1 | #ifdef __ARCH_WANT_SYS_CLONE |
看到 sys_clone 实现是配置相关的,参数不同,最后调用 _do_fork (不同于之前调用 do_fork) 进入下一个流程:
1 | /* |
_do_fork
函数不是很长,主要做了几件事情 (因为没有 vfork 所以不关注它的处理路径):
- copy_process 函数准备进进程的地址空间。
- 如果 p 有效,通过 get_task_pid 分配 pid,通过 wake_up_new_task 将新 p 加入调度器。
这里需要引入systemtap
来观察 kernel,工作原理简而言之就是在通过调试信息在内核函数调用之前或之后插入一些预定义的代码。
1 | probe kernel.function("_do_fork").return { |
可以看_do_fork 代码看到返回值是新进程的 pid,我们可以在它的换回点验证一下和用户态对比。
systemtap 输出:
1 | stap -v fork.stp |
strace 输出:
1 | strace ./a.out |
可以看到clone
系统调用的返回值和_do_fork
返回值一致,只是中间多生成了其他进程。