窦猛汉 sa15226344 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
1.进程简介:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
进程控制块(PCB)是系统为了管理进程设置的一个专门的数据结构。系统用它来记录进程的外部特征,描述进程的运动变化过程。同时,系统可以利用PCB来控制和管理进程,所以说,PCB(进程控制块)是系统感知进程存在的唯一标志。
2.进程创建
我们可以通过fork,vfork,clone来创建进程,他们需要调用对应的系统调用函数。
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;
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;
}
//复制进程进程资源,返回task_struct 的指针
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);
nr = pid_vnr(pid);
if (clone_flags & CLONE_PARENT_SETTID)
put_user(nr, parent_tidptr);
//如果是vfork 則执行
if (clone_flags & CLONE_VFORK)
{
p->vfork_done = &vfork;
init_completion(&vfork);
get_task_struct(p);
}
//把进程添加到调度器队列,等待调度器调度
wake_up_new_task(p);
if (unlikely(trace))
ptrace_event_pid(trace, pid);
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;
}
我们可以进一步查看copy_process源码
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;
/*。。。。。。。。。。。。。。。。省略一些代码*/
/*分配一个新的 task_struct,复制current的task_struct*/
p = dup_task_struct(current);
/*。。。。。。。。。。。。。。。。。。。。。。。*/
/*完成对新进程调度程序数据结构的初始化,并把新进程的状态设置为TASK_RUNNING*/
retval = sched_fork(clone_flags, p);
if (retval)
goto bad_fork_cleanup_policy;
/*。。。。。。。。。。。。。。。。省略的代码是对*p的初始化*/
/* 设置子进程的内核栈*/
retval = copy_thread(clone_flags, stack_start, stack_size, p);
if (retval)
goto bad_fork_cleanup_io;
if (pid != &init_struct_pid)
{
retval = -ENOMEM;
/*为新进程分配新的 pid 号*/
pid = alloc_pid(p->nsproxy->pid_ns_for_children);
if (!pid)
goto bad_fork_cleanup_io;
}
/*。。。。。。。。。。。。。。。。继续对*p初始化*/
/*设置新进程的pid*/
p->pid = pid_nr(pid);
/*。。。。。。。。。。。。。。。。。。。。。。。。*/
if (clone_flags & (CLONE_PARENT|CLONE_THREAD))
{
p->real_parent = current->real_parent;
p->parent_exec_id = current->parent_exec_id;
}
else
{
/*如果是创建线程,当前进程是子进程的parent*/
p->real_parent = current;
p->parent_exec_id = current->self_exec_id;
}
/*。。。。。。。。。。。。。。。。。。。。*/
return ERR_PTR(retval);
}
可以看出在copy_process中 主要调用 dup_tark_struct,sched_fork,copy_thread,alloc_pid,pid_nr等
以下是copy_thread代码
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();
/*设置eax 及设置返回值*/
childregs->ax = 0;
if (sp)
childregs->sp = sp;
/*设置子进程从ret_from_fork开始执行及设置eip*/
p->thread.ip = (unsigned long) ret_from_fork;
task_user_gs(p) = get_user_gs(current_pt_regs());
p->thread.io_bitmap_ptr = NULL;
tsk = current;
err = -ENOMEM;
if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP)))
{
p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr,IO_BITMAP_BYTES, GFP_KERNEL);
if (!p->thread.io_bitmap_ptr)
{
p->thread.io_bitmap_max = 0;
return -ENOMEM;
}
set_tsk_thread_flag(p, TIF_IO_BITMAP);
}
err = 0;
if (clone_flags & CLONE_SETTLS)
err = do_set_thread_area(p, -1,(struct user_desc __user *)childregs->si, 0);
if (err && p->thread.io_bitmap_ptr)
{
kfree(p->thread.io_bitmap_ptr);
p->thread.io_bitmap_max = 0;193
}
return err;
}
3.实验图片
4.实验总结
在执行fork函数后,调用copy_process,用以创建进程描述符以及子进程所需要的数据结构,在copy_process中调用dup_task_struct,用以分配一个新的 task_struct,以及复制父进程的task_struct,在dup_task_struct,后 调用copy_thread,用于初始化子进程的内核栈,在copy_thread中会设置子进程的eip。在执行完 copy_thread后回到do_fork函数执行wake_up_new_task把进程添加到调度器队列,等待调度器调度,这样调度器在调用子进程时会自动执行ret_from_fork,真正执行子进程