一、Linux内核创建一个新进程的过程
1.知识准备
- 操作系统内核三大功能是进程管理,内存管理,文件系统,最核心的是进程管理。
- linux 进程的状态和操作系统原理的描述进程状态有所不同,比如就绪状态和运行状态都是TASK_RUNNING。(这个表示它是可运行的,但是实际上有没有在运行取决于它是否占有 CPU )。
- fork 被调用一次,能够返回两次。在父进程中返回新创建子进程的 pid;在子进程中返回 0。
- 调用 fork 之后,数据、堆、栈有两份,代码仍然为一份(这个代码段成为两个进程的共享代码段)。当父子进程有一个想要修改数据或者堆栈时,两个进程真正分裂。
2.内核代码分析
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
return do_fork(SIGCHLD, 0, 0, NULL, NULL);
#else
return -EINVAL;
#endif
}
SYSCALL_DEFINE0(vfork)
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
0, NULL, NULL);
}
#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);
}
#endif
通过上面的代码可以看出 fork、vfork 和 clone 三个系统调用都可以创建一个新进程,而且都是通过 do_fork 来创建进程,只不过传递的参数不同。
(1)do_fork
long do_fork(unsigned long clone_flags, unsigned long stack_start,
unsigned long stack_size, int __user *parent_tidptr,
int __user *child_tidptr)
首先了解一下 do_fork () 的参数:
- clone_flags:子进程创建相关标志,通过此标志可以对父进程的资源进行有选择的复制。
- stack_start:子进程用户态堆栈的地址。
- regs:指向 pt_regs 结构体(当系统发生系统调用时,pt_regs 结构体保存寄存器中的值并按顺序压入内核栈)的指针。
- stack_size:用户态栈的大小,通常是不必要的,总被设置为0。
- parent_tidptr 和 child_tidptr:父进程、子进程用户态下 pid 地址。
精简后的关键代码: