一.进程概览
首先,对于一个进程我们必须有对应描述其特征的结构体,在Linux中进程用一个task_struct结构体来解释。task_struct的相关描述在
task_struct结构体剖析中可以得到详细解释。该结构体就是操作系统中所提到的PCB,PCB是进程存在的唯一标识。
二.进程的创建
在linux中,进程的创建可以通过三种形式,fork()、vfork()、clone(),他们都可以由一个进程作为父进程最终调用do_fork()方法完成子进程的创建。
SYSCALL_DEFINE0(fork)
{
return do_fork(SIGCHLD, 0, 0, NULL, NULL);
}
SYSCALL_DEFINE0(vfork)
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0, 0, NULL, NULL);
}
SYSCALL_DEFINE5(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()函数进行分析:
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;
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 = get_task_pid(p, PIDTYPE_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);
}
wake_up_new_task(p);
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);
}
return nr;
}
do_fork()函数首先创建一个task_struct结构体,然后调用copy_process函数来复制一份父进程一样的结构,做个简单的总结,copy_process 函数为了创建子进程,主要做了这么几件事:
调用 dup_task_struct 复制当前的 task_struct;
调用 sched_fork 初始化进程数据结构,并把进程状态设置为 TASK_RUNNING;
复制父进程的所有信息;
调用 copy_thread 初始化子进程内核栈;
为新进程分配并设置新的 pid;
那么子进程的起点在哪里呢?。在copy_thread中,该函数为子进程创建好了内核堆栈,初始化了进程执行的上下文,ret_from_fork是子进程执行的起点。
int copy_thread(unsigned long clone_flags, unsigned long sp,
unsigned long arg, struct task_struct *p)
{
struct pt_regs *childregs = task_pt_regs(p);
struct task_struct *tsk;
int err;
p->thread.sp = (unsigned long) childregs;
p->thread.sp0 = (unsigned long) (childregs+1);
memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));
if (unlikely(p->flags & PF_KTHREAD)) {
memset(childregs, 0, sizeof(struct pt_regs));
p->thread.ip = (unsigned long) ret_from_kernel_thread;
task_user_gs(p) = __KERNEL_STACK_CANARY;
childregs->ds = __USER_DS;
childregs->es = __USER_DS;
childregs->fs = __KERNEL_PERCPU;
childregs->bx = sp;
childregs->bp = arg;
childregs->orig_ax = -1;
childregs->cs = __KERNEL_CS | get_kernel_rpl();
childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;
p->thread.io_bitmap_ptr = NULL;
return 0;
}
*childregs = *current_pt_regs();
childregs->ax = 0;
if (sp)
childregs->sp = sp;
p->thread.ip = (unsigned long) ret_from_fork;
task_user_gs(p) = get_user_gs(current_pt_regs());
return err;
}
总结:
在Linux中创建一个新进程的方法是使用fork函数,fork()执行一次但有两个返回值,一次返回在父进程中,一次返回在子进程中。
在父进程中,返回值是子进程的进程号;在子进程中,返回值为0。因此可通过返回值来判断当前进程是父进程还是子进程。
使用fork函数得到的子进程是父进程的一个复制品,它从父进程处复制了整个进程的地址空间,包括进程上下文,进程堆栈,内存信息,打开的文件描述符,信 号控制设定,进程优先级,进程组号,当前工作目录,根目录,资源限制,控制终端等。而子进程所独有的只是它的进程号,资源使用和计时器等。创建一个进程,至少涉及的函数:sys_clone, do_fork, dup_task_struct, copy_process, copy_thread, ret_from_fork
实验截图:
、
陈思宇《Linux内核分析》MOOC课程
http://mooc.study.163.com/course/USTC-1000029000