http://blog./uid-26772321-id-4811008.html2015 引言
fork函数是用于在linux系统中创建进程所使用,而最近看了看一个fork()调用是怎么从应用到glibc,最后到内核中实现的,这片文章就聊聊最近对这方面研究的收获吧。我们主要聊聊从glibc库进入内核,再从内核出来的情景,而从应用到glibc这部分本片文章就不详细说明了。为了方便期间,我们的硬件平台为arm,linux内核为3.18.3,glibc库版本为2.20,可从 http://ftp./gnu/glibc/下载源码。
Glibc到kernel
我们设定硬件平台为arm,glibc库版本为2.20,因为不同的CPU体系结构中,glibc库通过系统调用进入kernel库的方法是不一样的。当glibc准备进入kernel时,流程如下
-
/* glibc最后会调用到一个INLINE_SYSCALL宏,参数如下 */
-
INLINE_SYSCALL (clone, 5, CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, NULL, NULL, NULL, &THREAD_SELF->tid);
-
-
/* INLINE_SYSCALL的宏定义如下,可以看出在INLINE_SYSCALL宏中又使用到了INTERNAL_SYSCALL宏,而INTERNAL_SYSCALL宏最终会调用INTERNAL_SYSCALL_RAW */
-
#define INLINE_SYSCALL(name, nr, args...) \
-
({ unsigned int _sys_result = INTERNAL_SYSCALL (name, , nr, args); \
-
if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (_sys_result, ), 0)) \
-
{ \
-
__set_errno (INTERNAL_SYSCALL_ERRNO (_sys_result, )); \
-
_sys_result = (unsigned int) -1; \
-
} \
-
(int) _sys_result; })
-
-
/* 为了方便大家理解,将此宏写为伪代码形式 */
-
int INLINE_SYSCALL (name, nr, args...)
-
{
-
unsigned int _sys_result = INTERNAL_SYSCALL (name, , nr, args);
-
-
if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (_sys_result, ), 0)) {
-
__set_error (INTERNAL_SYSCALL_ERRNO (_sys_result, ));
-
_sys_result = (unsigned int) -1;
-
}
-
return (int)_sys_result;
-
}
-
-
/* 这里我们不需要看INTERNAL_SYSCALL宏,只需要看其最终调用的INTERNAL_SYSCALL_RAW宏,需要注意的是,INTERNAL_SYSCALL调用INTERNAL_SYSCALL_RAW时,通过SYS_ify(name)宏将name转为了系统调用号
-
* name: 120(通过SYS_ify(name)宏已经将clone转为了系统调用号120)
-
* err: NULL
-
* nr: 5
-
* args[0]: CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD
-
* args[1]: NULL
-
* args[2]: NULL
-
* args[3]: NULL
-
* args[4]: &THREAD_SELF->tid
-
*/
-
# define INTERNAL_SYSCALL_RAW(name, err, nr, args...) \
-
({ \
-
register int _a1 asm ("r0"), _nr asm ("r7"); \
-
LOAD_ARGS_##nr (args) \
-
_nr = name; \
-
asm volatile ("swi 0x0 @ syscall " #name \
-
: "=r" (_a1) \
-
: "r" (_nr) ASM_ARGS_##nr \
-
: "memory"); \
-
_a1; })
-
#endif
INTERNAL_SYSCALL_RAW实现的结果就是将args[0]存到了r0...args[4]存到了r4中,并将name(120)绑定到r7寄存器。然后通过swi 0x0指令进行了软中断。0x0是一个24位的立即数,用于软中断执行程序判断执行什么操作。当执行这条指令时,CPU会跳转至中断向量表的软中断指令处,执行该处保存的调用函数,而在函数中会根据swi后面的24位立即数(在我们的例子中是0x0)执行不同操作。在这时候CPU已经处于保护模式,陷入内核中。现在进入到linux内核中后,具体看此时内核是怎么操作的吧。
-
/* 源文件地址: 内核目录/arch/arm/kernel/entry-common.S */
-
-
ENTRY(vector_swi)
-
/*
-
* 保存现场
-
*/
-
#ifdef CONFIG_CPU_V7M
-
v7m_exception_entry
-
#else
-
sub sp, sp, #S_FRAME_SIZE
-
stmia sp, {r0 - r12} @ Calling r0 - r12
-
ARM( add r8, sp, #S_PC )
-
ARM( stmdb r8, {sp, lr}^ ) @ Calling sp, lr
-
THUMB( mov r8, sp )
-
THUMB( store_user_sp_lr r8, r10, S_SP ) @ calling sp, lr
-
mrs r8, spsr @ called from non-FIQ mode, so ok.
-
str lr, [sp, #S_PC] @ Save calling PC
-
str r8, [sp, #S_PSR] @ Save CPSR
-
str r0, [sp, #S_OLD_R0] @ Save OLD_R0
-
#endif
-
zero_fp
-
alignment_trap r10, ip, __cr_alignment
-
enable_irq
-
ct_user_exit
-
get_thread_info tsk
-
-
/*
-
* 以下代码根据不同arm体系结构获取系统调用号
-
*/
-
-
#if defined(CONFIG_OABI_COMPAT)
-
-
/*
-
* 如果内核配置了OABI兼容选项,会先判断是否为THUMB,以下出来THUMB情况
-
*/
-
#ifdef CONFIG_ARM_THUMB
-
tst r8, #PSR_T_BIT
-
movne r10, #0 @ no thumb OABI emulation
-
USER( ldreq r10, [lr, #-4] ) @ get SWI instruction
-
#else
-
USER( ldr r10, [lr, #-4] ) @ get SWI instruction
-
#endif
-
ARM_BE8(rev r10, r10) @ little endian instruction
-
-
#elif defined(CONFIG_AEABI)
-
-
/*
-
* 我们主要看这里,EABI将系统调用号保存在r7中
-
*/
-
#elif defined(CONFIG_ARM_THUMB)
-
/* 先判断是否为THUMB模式 */
-
tst r8, #PSR_T_BIT @ this is SPSR from save_user_regs
-
addne scno, r7, #__NR_SYSCALL_BASE @ put OS number in
-
USER( ldreq scno, [lr, #-4] )
-
-
#else
-
/* EABI模式 */
-
USER( ldr scno, [lr, #-4] ) @ get SWI instruction
-
#endif
-
-
adr tbl, sys_call_table @ tbl为r8,这里是将sys_call_table的地址(相对于此指令的偏移量)存入r8
-
-
#if defined(CONFIG_OABI_COMPAT)
-
/*
-
* 在EABI体系中,如果swi跟着的立即数为0,这段代码不做处理,而如果是old abi体系,则根据系统调用号调用old abi体系的系统调用表(sys_oabi_call_table)
-
* 其实说白了,在EABI体系中,系统调用时使用swi 0x0进行软中断,r7寄存器保存系统调用号
-
* 而old abi体系中,是通过swi (系统调用号|magic)进行调用的
-
*/
-
bics r10, r10, #0xff000000
-
eorne scno, r10, #__NR_OABI_SYSCALL_BASE
-
ldrne tbl, =sys_oabi_call_table
-
#elif !defined(CONFIG_AEABI)
-
bic scno, scno, #0xff000000 @ mask off SWI op-code
-
eor scno, scno, #__NR_SYSCALL_BASE @ check OS number
-
#endif
-
-
local_restart:
-
ldr r10, [tsk, #TI_FLAGS] @ 检查系统调用跟踪
-
stmdb {r4, r5} @ 将第5和第6个参数压入栈
-
-
tst r10, #_TIF_SYSCALL_WORK @ 判断是否在跟踪系统调用
-
bne __sys_trace
-
-
cmp scno, #NR_syscalls @ 检测系统调用号是否在范围内,NR_syscalls保存系统调用总数
-
adr lr, BSYM(ret_fast_syscall) @ 将返回地址保存到lr寄存器中,lr寄存器是用于函数返回的。
-
ldrcc pc, [tbl, scno, lsl #2] @ 调用相应系统调用例程,tbl(r8)保存着系统调用表地址,scno(r7)保存着系统调用号,这里就转到相应的处理例程上了。
-
-
add r1, sp, #S_OFF
-
2: cmp scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)
-
eor r0, scno, #__NR_SYSCALL_BASE @ put OS number back
-
bcs arm_syscall
-
mov why, #0 @ no longer a real syscall
-
b sys_ni_syscall @ not private func
-
-
#if defined(CONFIG_OABI_COMPAT) || !defined(CONFIG_AEABI)
-
/*
-
* We failed to handle a fault trying to access the page
-
* containing the swi instruction, but we're not really in a
-
* position to return -EFAULT. Instead, return back to the
-
* instruction and re-enter the user fault handling path trying
-
* to page it in. This will likely result in sending SEGV to the
-
* current task.
-
*/
-
9001:
-
sub lr, lr, #4
-
str lr, [sp, #S_PC]
-
b ret_fast_syscall @ 返回
-
#endif
-
ENDPROC(vector_swi)
好的,终于跳转到了系统调用表,现在我们看看系统调用表是怎么样的一个形式
-
/* 文件地址: linux内核目录/arch/arm/kernel/calls.S */
-
-
/* 0 */ CALL(sys_restart_syscall)
-
CALL(sys_exit)
-
CALL(sys_fork)
-
CALL(sys_read)
-
CALL(sys_write)
-
/* 5 */ CALL(sys_open)
-
CALL(sys_close)
-
CALL(sys_ni_syscall) /* was sys_waitpid */
-
CALL(sys_creat)
-
CALL(sys_link)
-
/* 10 */ CALL(sys_unlink)
-
CALL(sys_execve)
-
CALL(sys_chdir)
-
CALL(OBSOLETE(sys_time)) /* used by libc4 */
-
CALL(sys_mknod)
-
/* 15 */ CALL(sys_chmod)
-
CALL(sys_lchown16)
-
CALL(sys_ni_syscall) /* was sys_break */
-
CALL(sys_ni_syscall) /* was sys_stat */
-
CALL(sys_lseek)
-
/* 20 */ CALL(sys_getpid)
-
CALL(sys_mount)
-
CALL(OBSOLETE(sys_oldumount)) /* used by libc4 */
-
CALL(sys_setuid16)
-
CALL(sys_getuid16)
-
/* 25 */ CALL(OBSOLETE(sys_stime))
-
CALL(sys_ptrace)
-
CALL(OBSOLETE(sys_alarm)) /* used by libc4 */
-
CALL(sys_ni_syscall) /* was sys_fstat */
-
CALL(sys_pause)
-
-
......................
-
......................
-
-
/* 120 */ CALL(sys_clone) /* 120在此,之前传进来的系统调用号120进入内核后会到这 */
-
CALL(sys_setdomainname)
-
CALL(sys_newuname)
-
CALL(sys_ni_syscall) /* modify_ldt */
-
CALL(sys_adjtimex)
-
/* 125 */ CALL(sys_mprotect)
-
CALL(sys_sigprocmask)
-
CALL(sys_ni_syscall) /* was sys_create_module */
-
CALL(sys_init_module)
-
CALL(sys_delete_module)
-
-
......................
-
......................
-
-
/* 375 */ CALL(sys_setns)
-
CALL(sys_process_vm_readv)
-
CALL(sys_process_vm_writev)
-
CALL(sys_kcmp)
-
CALL(sys_finit_module)
-
/* 380 */ CALL(sys_sched_setattr)
-
CALL(sys_sched_getattr)
-
CALL(sys_renameat2)
-
CALL(sys_seccomp)
-
CALL(sys_getrandom)
-
/* 385 */ CALL(sys_memfd_create)
-
CALL(sys_bpf)
-
#ifndef syscalls_counted
-
.equ syscalls_padding, ((NR_syscalls + 3) & ~3) - NR_syscalls
-
#define syscalls_counted
-
#endif
-
.rept syscalls_padding
-
CALL(sys_ni_syscall)
-
.endr
CALL为一个宏,而我们使用的那一行CALL(sys_clone)配合ldrcc pc,[tbl,scno,lsl #2]使用的结果就是把sys_clone的地址放入pc寄存器。具体我们仔细分析一下,首先先看看CALL宏展开,然后把CALL代入ldrcc,结果就很清晰了
-
/* CALL(x)宏展开 */
-
#define CALL(x) .equ NR_syscalls,NR_syscalls+1
-
#include "calls.S"
-
-
.ifne NR_syscalls - __NR_syscalls
-
.error "__NR_syscalls is not equal to the size of the syscall table"
-
.endif
-
-
/* 主要是后面这一段,
-
* 上面一段主要用于统计系统调用数量,并将数量保存到NR_syscalls中,具体实现说明可以参考http://www.tuicool.com/articles/QFj6zq
-
*/
-
-
#undef CALL
-
/* 其实就是生成一个数为x,相当于.long sys_clone,因为sys_clone是函数名,所以.long生成的是sys_clone函数名对应的地址 */
-
#define CALL(x) .long x
-
-
#ifdef CONFIG_FUNCTION_TRACER
-
-
-
/* 配合ldrcc一起看,原来ldrcc是这样 */
-
ldrcc pc, [tbl, scno, lsl #2]
-
-
/* 把CALL(x)代入ldrcc,最后是这样 */
-
ldrcc pc, sys_clone(函数地址)
清楚的看出来,ldrcc最后是将sys_clone的函数地址存入了pc寄存器,而sys_clone函数内核是怎么定义的呢,如下
-
/* 文件地址: linux内核目录/kernel/Fork.c */
-
-
/* 以下代码根据不同的内核配置定义了不同的clone函数
-
* 其最终都调用的do_fork函数,我们先看看SYSCALL_DEFINE是怎么实现的吧,实现在此代码片段后面
-
*/
-
#ifdef __ARCH_WANT_SYS_CLONE
-
#ifdef CONFIG_CLONE_BACKWARDS
-
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
-
int __user *, parent_tidptr,
-
int, tls_val,
-
int __user *, child_tidptr)
-
#elif defined(CONFIG_CLONE_BACKWARDS2)
-
SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,
-
int __user *, parent_tidptr,
-
int __user *, child_tidptr,
-
int, tls_val)
-
#elif defined(CONFIG_CLONE_BACKWARDS3)
-
SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,
-
int, stack_size,
-
int __user *, parent_tidptr,
-
int __user *, child_tidptr,
-
int, tls_val)
-
#else
-
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
-
int __user *, parent_tidptr,
-
int __user *, child_tidptr,
-
int, tls_val)
-
#endif
-
{
-
return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
-
}
-
-
-
/************************************************
-
* 我是代码分界线
-
************************************************/
-
-
/* 文件地址: linux内核目录/include/linux.h */
-
-
#define SYSCALL_DEFINE0(sname) \
-
SYSCALL_METADATA(_##sname, 0); \
-
asmlinkage long sys_##sname(void)
-
-
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
-
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
-
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
-
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
-
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
-
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
-
-
#define SYSCALL_DEFINEx(x, sname, ...) \
-
SYSCALL_METADATA(sname, x, __VA_ARGS__) \
-
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
-
-
#define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
-
#define __SYSCALL_DEFINEx
可以看出系统调用是使用SYSCALL_DEFINEx进行定义的,以我们的例子,实际上最后clone函数被定义为
-
/* 展开前 */
-
-
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
-
int __user *, parent_tidptr,
-
int __user *, child_tidptr,
-
int, tls_val)
-
#endif
-
{
-
/* 应用层默认fork参数(CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, NULL, NULL, NULL, &THREAD_SELF->tid) */
-
return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
-
}
-
-
/* 展开后 */
-
-
asmlinkage long sys_clone (unsigned long clone_flags, unsigned long newsp, int __user * parent_tidptr, int __user * child_tidptr, int tls_val)
-
{
-
return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
-
}
终于看到最后系统会调用do_fork函数进行操作,接下来我们看看do_fork函数
-
/* 应用层的fork最后会通过sys_clone系统调用调用到此函数 */
-
/* 应用层默认fork参数(CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, NULL, NULL, NULL, &THREAD_SELF->tid)
-
* clone_flags: CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD
-
* stack_start: NULL
-
* stack_size: NULL
-
* parent_tidptr: NULL
-
* child_tidptr: &THREAD_SELF->tid
-
* pid: NULL
-
*/
-
long do_fork(unsigned long clone_flags,
-
unsigned long stack_start,
-
unsigned long stack_size,
-
int __user *parent_tidptr,
-
int __user *child_tidptr)
-
{
-
struct task_struct *p;
-
int trace = 0;
-
long nr;
-
-
/* 判断是否进行跟踪 */
-
if (!(clone_flags & CLONE_UNTRACED)) {
-
if (clone_flags & CLONE_VFORK)
-
trace = PTRACE_EVENT_VFORK;
-
else if ((clone_flags & CSIGNAL) != SIGCHLD)
-
trace = PTRACE_EVENT_CLONE;
-
else
-
trace = PTRACE_EVENT_FORK;
-
-
if (likely(!ptrace_event_enabled(current, trace)))
-
trace = 0;
-
}
-
-
/* 调用copy_process进行初始化,返回初始化好的struct task_struct结构体,当我们调用fork时返回两次的原因也是在这个函数当中,下回分析 */
-
p = copy_process(clone_flags, stack_start, stack_size,
-
child_tidptr, NULL, trace);
-
-
-
if (!IS_ERR(p)) {
-
/* 创建成功 */
-
struct completion vfork;
-
struct pid *pid;
-
-
trace_sched_process_fork(current, p);
-
-
/* 获取子进程PID */
-
pid = get_task_pid(p, PIDTYPE_PID);
-
/* 返回子进程pid所属的命名空间所看到的局部PID */
-
nr = pid_vnr(pid);
-
-
if (clone_flags & CLONE_PARENT_SETTID)
-
put_user(nr, parent_tidptr);
-
-
if (clone_flags & CLONE_VFORK) {
-
p->vfork_done = &vfork;
-
init_completion(&vfork);
-
get_task_struct(p);
-
}
-
-
/* 将新进程(线程)加入的CPU的运行队列中 */
-
wake_up_new_task(p);
-
-
/* 跟踪才会用到 */
-
if (unlikely(trace))
-
ptrace_event_pid(trace, pid);
-
-
/* 如果是vfork调用,则在此等待vfork的进程结束 */
-
if (clone_flags & CLONE_VFORK) {
-
if (!wait_for_vfork_done(p, &vfork))
-
ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
-
}
-
-
put_pid(pid);
-
} else {
-
/* 创建失败 */
-
nr = PTR_ERR(p);
-
}
-
/* 返回新进程PID(新进程在这会返回0) */
-
return nr;
-
}
在do_fork函数中,首先会根据clone_flags判断是否对父进程进行了跟踪(调试使用),如果进行了函数跟踪(还需要判断是否对子进程进行跟踪),之后调用 copy_process(do_fork的核心函数,之后的文章会对它进行分析),在copy_process中会对子进程的许多结构体和参数进行初始化(同时在fork正常情况中为什么会返回两次也是在此函数中实现的),do_fork最后就判断是否是通过vfork创建,如果是vfork创建,则会使父进程阻塞直到子进程结束释放所占内存空间后才继续执行,最后do_fork子进程pid。
小结
到这里,整个系统调用的入口就分析完了,其实整个流程也不算很复杂:应用层通过swi软中断进入内核---->通过系统调用表选定目标系统调用--->执行系统调用--->返回。之后的文章我会详细分析copy_process函数,此函数中涉及相当多的知识,作为学习linux内核的入口也是比较合适的。
引言 前一篇关于linux系统如何实现fork的研究(一)通过代码已经说明了从用户态怎么通过软中断实现调用系统调用clone函数,而clone函数的精华copy_process函数就在此篇文章中进行分析。我们知道,在linux系统中,应用层可以创建子进程和子线程(轻量级进程)两种程序分支结构。而对于linux内核而且,并不详细区分子进程和子线程(轻量级进程)的区别,他们都使用的是task_struct结构(此结构极其复杂,包含非常多的数据结构),而不同的是子进程和子线程的task_struct初始化结果不同。task_struct结构是一个进程或线程的标识和存在的凭证,调度程序就是通过task_struct结构来区分不同的进程(线程)。里面包含了进程(线程)所有需要用到的结构(内存描述符,文件描述符,信号描述符,信号处理函数,调度优先级等)。而我们知道,一个进程(线程)不止有自己的task_struck结构,还必须有一个自己的内核栈,当执行进程切换时,部分进程上下文会保存于其进程的内核栈中,而中断发生时的中断上下文也会保存于正在持续的进程内核栈中。在copy_process函数中内核栈的初始化导致了fork()的两次返回值不同(之后会说明)。当然,copy_process还涉及到许多操作,比如新进程(线程)的安全检测,pid的分配,关系调整(父子进程、进程组关系,命名空间关系等),内存结构的初始化等等,这些我们在之后的代码中慢慢道来。
copy_process- /* 代码目录:linux源码/kernel/Fork.c */
- static struct task_struct *copy_process(unsigned long clone_flags,
- unsigned long stack_start,
- unsigned long stack_size,
- int __user *child_tidptr,
- struct pid *pid,
- int trace)
- {
- int retval;
- struct task_struct *p;
- /* CLONE_FS 不能与 CLONE_NEWNS 或 CLONE_NEWUSER 同时设置 */
- if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
- return ERR_PTR(-EINVAL);
- if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
- return ERR_PTR(-EINVAL);
- /* 创建线程时线程之间要共享信号处理函数 */
- if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
- return ERR_PTR(-EINVAL);
- /*
- * 父子进程共享信号处理函数时必须共享内存地址空间
- * 这就是为什么书上写的fork出来的父子进程有其独立的信号处理函数,因为他们的内存地址空间不同
- */
- if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
- return ERR_PTR(-EINVAL);
- /*
- * 防止参数init进程的兄弟进程
- * 只有init进程的 signal->flags & SIGNAL_UNKILLABLE 为真
- * 因为当进程退出时实际上是成为了僵尸进程(zombie),而要通过init进程将它回收,而如果此进程为init的兄弟进程,则没办法将其回收
- */
- if ((clone_flags & CLONE_PARENT) &&
- current->signal->flags & SIGNAL_UNKILLABLE)
- return ERR_PTR(-EINVAL);
- /* 如果新的进程将会有新的用户空间或者pid,则不能让它共享父进程的线程组或者信号处理或者父进程 */
- if (clone_flags & CLONE_SIGHAND) {
- if ((clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) ||
- (task_active_pid_ns(current) !=
- current->nsproxy->pid_ns_for_children))
- return ERR_PTR(-EINVAL);
- }
- /* 附加安全检查 */
- retval = security_task_create(clone_flags);
- if (retval)
- goto fork_out;
- retval = -ENOMEM;
- /* 为新进程分配struct task_struct内存和内核栈内存 */
- p = dup_task_struct(current);
- if (!p)
- goto fork_out;
- /* ftrace是用于内核性能分析和跟踪的 */
- ftrace_graph_init_task(p);
- /* futex初始化,其用于SYSTEM V IPC,具体可见 http://blog.chinaunix.net/uid-7295895-id-3011238.html */
- rt_mutex_init_task(p);
- #ifdef CONFIG_PROVE_LOCKING
- DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled);
- DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
- #endif
- retval = -EAGAIN;
- /* 检查 tsk->signal->rlim[RLIMIT_NPROC].rlim_cur是否小于等于用户所拥有的进程数,rlim结构体表示相关资源的最大值 */
- if (atomic_read(&p->real_cred->user->processes) >= task_rlimit(p, RLIMIT_NPROC)) {
- /* INIT_USER是root权限。检查父进程是否有root权限 */
- if (p->real_cred->user != INIT_USER && !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
- goto bad_fork_free;
- }
- current->flags &= ~PF_NPROC_EXCEEDED;
- /* 将父进程的cred复制到子进程的real_cred和cred。struct cred用于安全操作的结构 */
- retval = copy_creds(p, clone_flags);
- if (retval < 0)
- goto bad_fork_free;
- retval = -EAGAIN;
- /* 进程数量是否超出系统允许最大进程数量,最大进程数量跟内存有关,一般原则是所有的进程内核栈(默认8K)加起来不超过总内存的1/8,可通过/proc/sys/kernel/threads-max改写此值 */
- if (nr_threads >= max_threads)
- goto bad_fork_cleanup_count;
- /* 如果实现新进程的执行域和可执行格式的内核函数都包含在内核模块中,则递增其使用计数 */
- if (!try_module_get(task_thread_info(p)->exec_domain->module))
- goto bad_fork_cleanup_count;
- delayacct_tsk_init(p); /* Must remain after dup_task_struct() */
- /* 清除 PF_SUPERPRIV(表示进程使用了超级用户权限) 和 PF_WQ_WORKER(使用了工作队列) */
- p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER);
- /* 设置 PF_FORKNOEXEC 表明此子进程还没有进行 execve() 系统调用 */
- p->flags |= PF_FORKNOEXEC;
- /* 初始化子进程的子进程链表和兄弟进程链表为空 */
- INIT_LIST_HEAD(&p->children);
- INIT_LIST_HEAD(&p->sibling);
- /* 见 http://www.ibm.com/developerworks/cn/linux/l-rcu/ */
- rcu_copy_process(p);
- p->vfork_done = NULL;
- /* 初始化分配锁,此锁用于保护分配内存,文件,文件系统等操作 */
- spin_lock_init(&p->alloc_lock);
- /* 信号列表初始化,此列表保存被挂起的信号 */
- init_sigpending(&p->pending);
- /* 代码执行时间变量都置为0 */
- p->utime = p->stime = p->gtime = 0;
- p->utimescaled = p->stimescaled = 0;
- #ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
- p->prev_cputime.utime = p->prev_cputime.stime = 0;
- #endif
- #ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
- seqlock_init(&p->vtime_seqlock);
- p->vtime_snap = 0;
- p->vtime_snap_whence = VTIME_SLEEPING;
- #endif
- #if defined(SPLIT_RSS_COUNTING)
- memset(&p->rss_stat, 0, sizeof(p->rss_stat));
- #endif
- /* 此变量一般用于epoll和select,从父进程复制过来 */
- p->default_timer_slack_ns = current->timer_slack_ns;
- /* 初始化进程IO计数结构 */
- task_io_accounting_init(&p->ioac);
- acct_clear_integrals(p);
- /* 初始化cputime_expires结构 */
- posix_cpu_timers_init(p);
- /* 设置进程创建时间 */
- p->start_time = ktime_get_ns();
- p->real_start_time = ktime_get_boot_ns();
- /* io_context 和 audit_context 置空 */
- p->io_context = NULL;
- p->audit_context = NULL;
- /* 如果创建的是线程,因为需要修改到当前进程的描述符,会先上锁 */
- if (clone_flags & CLONE_THREAD)
- threadgroup_change_begin(current);
- cgroup_fork(p);
- #ifdef CONFIG_NUMA
- p->mempolicy = mpol_dup(p->mempolicy);
- if (IS_ERR(p->mempolicy)) {
- retval = PTR_ERR(p->mempolicy);
- p->mempolicy = NULL;
- goto bad_fork_cleanup_threadgroup_lock;
- }
- #endif
- #ifdef CONFIG_CPUSETS
- p->cpuset_mem_spread_rotor = NUMA_NO_NODE;
- p->cpuset_slab_spread_rotor = NUMA_NO_NODE;
- seqcount_init(&p->mems_allowed_seq);
- #endif
- #ifdef CONFIG_TRACE_IRQFLAGS
- p->irq_events = 0;
- p->hardirqs_enabled = 0;
- p->hardirq_enable_ip = 0;
- p->hardirq_enable_event = 0;
- p->hardirq_disable_ip = _THIS_IP_;
- p->hardirq_disable_event = 0;
- p->softirqs_enabled = 1;
- p->softirq_enable_ip = _THIS_IP_;
- p->softirq_enable_event = 0;
- p->softirq_disable_ip = 0;
- p->softirq_disable_event = 0;
- p->hardirq_context = 0;
- p->softirq_context = 0;
- #endif
- #ifdef CONFIG_LOCKDEP
- p->lockdep_depth = 0; /* no locks held yet */
- p->curr_chain_key = 0;
- p->lockdep_recursion = 0;
- #endif
- #ifdef CONFIG_DEBUG_MUTEXES
- p->blocked_on = NULL; /* not blocked yet */
- #endif
- #ifdef CONFIG_BCACHE
- p->sequential_io = 0;
- p->sequential_io_avg = 0;
- #endif
- /* 初始化子进程的调度优先级和策略,在此并没有将此进程加入到运行队列,在copy_process返回之后加入 */
- retval = sched_fork(clone_flags, p);
- if (retval)
- goto bad_fork_cleanup_policy;
- /* perf event是一个性能调优工具,具体见 http://blog.sina.com.cn/s/blog_98822316010122ex.html */
- retval = perf_event_init_task(p);
- if (retval)
- goto bad_fork_cleanup_policy;
- retval = audit_alloc(p);
- if (retval)
- goto bad_fork_cleanup_perf;
- /* 初始化 p->sysvshm.shm_clist 链表头 */
- shm_init_task(p);
- /* copy_semundo, copy_files, copy_fs, copy_sighand, copy_signal, copy_mm, copy_namespaces, copy_io都是根据clone_flags从父进程做相应的复制 */
- retval = copy_semundo(clone_flags, p);
- if (retval)
- goto bad_fork_cleanup_audit;
- retval = copy_files(clone_flags, p);
- if (retval)
- goto bad_fork_cleanup_semundo;
- retval = copy_fs(clone_flags, p);
- if (retval)
- goto bad_fork_cleanup_files;
- /* 判断是否设置 CLONE_SIGHAND ,如果是(线程必须为是),增加父进行的sighand引用计数,如果否(创建的必定是子进程),将父线程的sighand_struct复制到子进程中 */
- retval = copy_sighand(clone_flags, p);
- if (retval)
- goto bad_fork_cleanup_fs;
- /* 如果创建的是线程,直接返回0,如果创建的是进程,则会将父进程的信号屏蔽和安排复制到子进程中 */
- retval = copy_signal(clone_flags, p);
- if (retval)
- goto bad_fork_cleanup_sighand;
- /*
- * 如果是进程,则将父进程的mm_struct结构复制到子进程中,然后修改当中属于子进程有别于父进程的信息(如页目录)
- * 如果是线程,则将子线程的mm指针和active_mm指针都指向父进程的mm指针所指结构。
- */
- retval = copy_mm(clone_flags, p);
- if (retval)
- goto bad_fork_cleanup_signal;
- retval = copy_namespaces(clone_flags, p);
- if (retval)
- goto bad_fork_cleanup_mm;
- retval = copy_io(clone_flags, p);
- if (retval)
- goto bad_fork_cleanup_namespaces;
-
- /*
- * 初始化子进程内核栈和thread_struct结构体
- * 当进程切换时,进程的硬件上下文一般保存于三个地方: tss_struct(保存进程内核栈地址,I/O许可权限位),thread_struct(大部分非通用寄存器),进程内核栈(通用寄存器)
- * copy_thread函数会将父进程的thread_struct和内核栈数据复制到子进程中,并将子进程的返回值置为0(x86返回值保存在eax中,arm保存在r0中,即把eax或者r0所在的内核栈数据置为0)
- * copy_thread函数还会将子进程的eip寄存器值设置为ret_from_fork()的地址,即当子进程首次被调用就立即执行系统调用clone返回。
- * 所以应用层调用fork()函数后,子进程返回0,父进程返回子进程ID(返回子进程ID在之后代码中会实现)
- */
- retval = copy_thread(clone_flags, stack_start, stack_size, p);
- if (retval)
- goto bad_fork_cleanup_io;
- /* 判断是不是init进程 */
- if (pid != &init_struct_pid) {
- retval = -ENOMEM;
- /* 分配pid */
- pid = alloc_pid(p->nsproxy->pid_ns_for_children);
- if (!pid)
- goto bad_fork_cleanup_io;
- }
- /* 如果设置了CLONE_CHILD_SETTID则将task_struct中的set_child_tid指向用户空间的child_tidptr,否则置空 */
- p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
- /* 如果设置了CLONE_CHILD_CLEARTID则将task_struct中的clear_child_tid指向用户空间的child_tidptr,否则置空 */
- p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr : NULL;
-
- #ifdef CONFIG_BLOCK
- p->plug = NULL;
- #endif
- #ifdef CONFIG_FUTEX
- p->robust_list = NULL;
- #ifdef CONFIG_COMPAT
- p->compat_robust_list = NULL;
- #endif
- INIT_LIST_HEAD(&p->pi_state_list);
- p->pi_state_cache = NULL;
- #endif
- /*
- * 如果共享VM或者vfork创建,信号栈清空
- */
- if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM)
- p->sas_ss_sp = p->sas_ss_size = 0;
- /*
- * 系统调用跟踪时应该禁止单步执行
- */
- user_disable_single_step(p);
- clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);
- #ifdef TIF_SYSCALL_EMU
- clear_tsk_thread_flag(p, TIF_SYSCALL_EMU);
- #endif
- clear_all_latency_tracing(p);
- /* 将子进程的PID设置为分配的PID在全局namespace中分配的值,在不同namespace中进程的PID不同,而p->pid保存的是全局的namespace中所分配的PID */
- p->pid = pid_nr(pid);
- if (clone_flags & CLONE_THREAD) {
- /* 创建的是线程 */
- p->exit_signal = -1;
- /* 线程组的所有线程的group_leader都一致 */
- p->group_leader = current->group_leader;
- /* 线程组的所有线程的tgid都一致,使用getpid返回的就是tgid */
- p->tgid = current->tgid;
- } else {
- /* 创建的是子进程 */
- if (clone_flags & CLONE_PARENT)
- p->exit_signal = current->group_leader->exit_signal;
- else
- p->exit_signal = (clone_flags & CSIGNAL);
- p->group_leader = p;
- /* tgid与pid一致,所以当创建子线程时,tgid与主线程的一致 */
- p->tgid = p->pid;
- }
- /* 初始化页框中脏页数量为0 */
- p->nr_dirtied = 0;
- /* 初始化脏页数量临界值,当脏页数量到达临界值时,会调用balance_dirty_pages()将脏页写入磁盘 */
- p->nr_dirtied_pause = 128 >> (PAGE_SHIFT - 10);
- /* 将脏页写入磁盘的开始时间 */
- p->dirty_paused_when = 0;
- p->pdeath_signal = 0;
- /* 初始化线程组链表为空 */
- INIT_LIST_HEAD(&p->thread_group);
- p->task_works = NULL;
- /* 到此系统中已经存在此进程(线程),但是它还不能够执行,需要等待父进程对其处理,这里会上锁 */
- write_lock_irq(&tasklist_lock);
- if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {
- /* 创建的是兄弟进程或者相同线程组线程 */
- /* 其父进程为父进程的父进程 */
- p->real_parent = current->real_parent;
- /* 其父进程执行域为父进程的父进程执行域 */
- p->parent_exec_id = current->parent_exec_id;
- } else {
- /* 创建的是子进程 */
- /* 父进程为父进程 */
- p->real_parent = current;
- /* 父进程的执行域为父进程的执行域 */
- p->parent_exec_id = current->self_exec_id;
- }
- /* 当前进程信号处理上锁,这里应该是禁止了信号处理 */
- spin_lock(¤t->sighand->siglock);
- /*
- * seccomp与系统安全有关,具体见 http://note.sdo.com/u/634687868481358385/NoteContent/M5cEN~kkf9BFnM4og00239
- */
- copy_seccomp(p);
- /*
- * 在fork之前,进程组和会话信号都需要送到父亲结点,而在fork之后,这些信号需要送到父亲和孩子结点。
- * 如果我们在将新进程添加到进程组的过程中出现一个信号,而这个挂起信号会导致当前进程退出(current),我们的子进程就不能够被kill或者退出了
- * 所以这里要检测父进程有没有信号被挂起。
- */
- recalc_sigpending();
- if (signal_pending(current)) {
- /* 包含有挂起进程,错误 */
- spin_unlock(¤t->sighand->siglock);
- write_unlock_irq(&tasklist_lock);
- retval = -ERESTARTNOINTR;
- goto bad_fork_free_pid;
- }
- if (likely(p->pid)) {
- /* 如果子进程需要跟踪,就将 current->parent 赋值给 tsk->parent ,并将子进程插入调试程序的跟踪链表中 */
- ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace);
- /* p->pids[PIDTYPE_PID].pid = pid; */
- init_task_pid(p, PIDTYPE_PID, pid);
- /* 如果是子进程(其实就是判断 p->exit_signal 是否大于等于0,创建的是线程的话,exit_signal的值为-1) */
- if (thread_group_leader(p)) {
- /* p->pids[PIDTYPE_PGID].pid = current->group_leader->pids[PIDTYPE_PGID].pid; PGID为进程组ID,所以直接复制父进程的pgid */
- init_task_pid(p, PIDTYPE_PGID, task_pgrp(current));
- /* p->pids[PIDTYPE_SID].pid = current->group_leader->pids[PIDTYPE_SID].pid; SID为会话组ID,当没有使用setsid()时,子进程的sid与父进程一致 */
- init_task_pid(p, PIDTYPE_SID, task_session(current));
- /* return pid->numbers[pid->level].nr == 1; 判断新进程是否处于一个新创建的namespace中(新进程所在的新namespace中的pid会为1,以此判断) */
- if (is_child_reaper(pid)) {
- /* 将当前namespace的init进程设置为此新进程 */
- ns_of_pid(pid)->child_reaper = p;
- p->signal->flags |= SIGNAL_UNKILLABLE;
- }
- p->signal->leader_pid = pid;
- p->signal->tty = tty_kref_get(current->signal->tty);
- /* 将此进程添加到父进程的子进程链表 */
- list_add_tail(&p->sibling, &p->real_parent->children);
- /* 将此进程task_struct加入到task链表中 */
- list_add_tail_rcu(&p->tasks, &init_task.tasks);
- /* 将新进程描述符的pgid结构插入pgid_hash */
- attach_pid(p, PIDTYPE_PGID);
- /* 将新进程描述符的sid结构插入sid_hash */
- attach_pid(p, PIDTYPE_SID);
- /* 当前cpu进程数量加1 */
- __this_cpu_inc(process_counts);
- } else {
- /* 创建的是线程,这里的处理导致了线程会共享信号 */
- current->signal->nr_threads++;
- atomic_inc(¤t->signal->live);
- atomic_inc(¤t->signal->sigcnt);
- /* 将新线程的thread_group结点加入到线程组的领头线程的thread_group链表中 */
- list_add_tail_rcu(&p->thread_group,
- &p->group_leader->thread_group);
- /* 将新线程的thread_node结点加入的新线程的signal->thread_head中 */
- list_add_tail_rcu(&p->thread_node,
- &p->signal->thread_head);
- }
- /* 将新进程描述符的pid结构插入pid_hash */
- attach_pid(p, PIDTYPE_PID);
- /* 当前系统进程数加1 */
- nr_threads++;
- }
- /* 已创建的进程数量加1 */
- total_forks++;
- /* 释放当前进程信号处理锁 */
- spin_unlock(¤t->sighand->siglock);
- syscall_tracepoint_update(p);
- /* 释放tasklist_lock锁 */
- write_unlock_irq(&tasklist_lock);
- /* 将新进程与proc文件系统进行关联 */
- proc_fork_connector(p);
- cgroup_post_fork(p);
- /* 如果创建的是线程,释放此锁 */
- if (clone_flags & CLONE_THREAD)
- threadgroup_change_end(current);
- perf_event_fork(p);
- trace_task_newtask(p, clone_flags);
- uprobe_copy_process(p, clone_flags);
- /* 返回新进程的task_struct结构 */
- return p;
- /* 以下为执行期间的错误处理 */
- bad_fork_free_pid:
- if (pid != &init_struct_pid)
- free_pid(pid);
- bad_fork_cleanup_io:
- if (p->io_context)
- exit_io_context(p);
- bad_fork_cleanup_namespaces:
- exit_task_namespaces(p);
- bad_fork_cleanup_mm:
- if (p->mm)
- mmput(p->mm);
- bad_fork_cleanup_signal:
- if (!(clone_flags & CLONE_THREAD))
- free_signal_struct(p->signal);
- bad_fork_cleanup_sighand:
- __cleanup_sighand(p->sighand);
- bad_fork_cleanup_fs:
- exit_fs(p); /* blocking */
- bad_fork_cleanup_files:
- exit_files(p); /* blocking */
- bad_fork_cleanup_semundo:
- exit_sem(p);
- bad_fork_cleanup_audit:
- audit_free(p);
- bad_fork_cleanup_perf:
- perf_event_free_task(p);
- bad_fork_cleanup_policy:
- #ifdef CONFIG_NUMA
- mpol_put(p->mempolicy);
- bad_fork_cleanup_threadgroup_lock:
- #endif
- if (clone_flags & CLONE_THREAD)
- threadgroup_change_end(current);
- delayacct_tsk_free(p);
- module_put(task_thread_info(p)->exec_domain->module);
- bad_fork_cleanup_count:
- atomic_dec(&p->cred->user->processes);
- exit_creds(p);
- bad_fork_free:
- free_task(p);
- fork_out:
- return ERR_PTR(retval);
- }
copy_process流程图小结 copy_process作为do_fork的主心骨,其流程并不复杂,只是每一步调用的初始化函数都非常精妙,涉及到大量的内知识和代码,这里为了篇幅着想就不继续往细节分析了,会在之后的文章中慢慢补全其中的知识和自己的理解。整篇文章读下来,其实copy_process的核心就是初始化task_struct结构体供新进程(线程)使用,并为其分配独有的pid,最后将其加入到运行队列中。而至于为什么应用层调用fork()会进行两次返回,原理就是在内核栈中,在copy_thread函数中父进程将其内核栈复制到子进程中,把子进程被调度后执行的第一条语句设置为do_fork()返回,并把保存返回值的寄存器值(一般返回值保存在eax(ARM是r0),而这些通用寄存器值保存在内核栈中,当调用后会进行进程切换,会把这些保存于内核栈的寄存器值还原到寄存器中)置为0,所以子进程的返回值为0,而父进程会继续执行copy_thread函数之后的初始化,最后返回子进程的pid(实际上是tgid)。
|