Linux创建进程
时间:2006-03-06 来源:ChinaE_OS
1:轻量级进程:
两个轻量级进程可以共享一些资源,如:地址空间,打开文件等。只要一个进程修改共享资源,其他进程就可以立刻获知,所以两个进程应该同步访问共享资源。
在传统的Unix操作系统中,当创建一个进程时,该进程将复制父进程的资源,这样创建进程时非常慢,而且效率也很低。
现代Unix已经采用下面三种方法改进了创建进程的方法:
(1) Copy On Write技术允许父进程和子进程同时读同一个物理页面;当其中的一个写物理页面时,内核复制页面上的内容到一个新的分配写进程物理页面上。
(2) 轻量级进程允许父子进程共享每个处理器的内核数据结构,如:页表,打开文件表等。
(3) Vfork系统调用创建的进程可以共享父进程的内存空间,为了阻止父进程修改子进程需要的数据,父进程可以堵塞,直到子进程执行结束。
2:clone, fork, vfork系统调用
(1) clone:使用clone创建轻量级进程。在C库中,clone只是简单分装了sys_clone系统调用,sys_clone实现了没有fn和arg参数的系统调用。
asmlinkage int sys_clone(struct pt_regs regs)
{
unsigned long clone_flags;
unsigned long newsp;
int __user *parent_tidptr, *child_tidptr;
clone_flags = regs.ebx;
newsp = regs.ecx;
parent_tidptr = (int __user *)regs.edx;
child_tidptr = (int __user *)regs.edi;
if (!newsp)
newsp = regs.esp;
return do_fork(clone_flags, newsp, ®s, 0, parent_tidptr, child_tidptr);
}
(2) fork:当clone系统调用的flag标志指定SIGCHLD信号和所有的clone标志清楚,以及child_stack是当前父进程的栈指针时,fork系统调用被调用。所以父子进程暂时共享用户态栈,但是考虑到Copy On Write机制,当他们修改栈时,他们就获取独立的用户态栈。
asmlinkage int sys_vfork(struct pt_regs regs)
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, ®s, 0, NULL, NULL);
}
(3) vfork:当clone系统调用指定flags为SIGCHLD,CLONE_VM,CLONE_VFORK以及child_stack为父进程的栈时,vfork被调用。
asmlinkage int sys_fork(struct pt_regs regs)
{
return do_fork(SIGCHLD, regs.esp, ®s, 0, NULL, NULL);
}
3:do_fork
do_fork处理clone, fork, vfork系统调用。do_fork使用辅助函数copy_process建立进程描述符以及内核数据结构。
(1) 通过查找pidmap_array给子进程分配一个PID.
long pid = alloc_pidmap();
(2) 检查父进程的ptrace,如果不为0,说明父进程被请他进程跟踪,do_fork需要检查debugger是否跟踪子进程。
if (unlikely(current->ptrace)) {
trace = fork_traceflag (clone_flags);
if (trace)
clone_flags |= CLONE_PTRACE;
}
(3) 调用copy_process,复制进程描述符。如果有可用的资源,该函数返回新建进程的task_struct。
p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);
(4) 如果设置CLONE_STOPPED或者子进程必须跟踪,即:设置p->ptrace。子进程的状态一直为TASK_STOPPED,直到起他进程将该状态变为TASK_RUNNING.
if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
/*
* We'll start up with an immediate SIGSTOP.
*/
sigaddset(&p->pending.signal, SIGSTOP);
set_tsk_thread_flag(p, TIF_SIGPENDING);
}
(5) 如果没有设置CLONE_STOPPED,调用wake_up_new_task, 否则设置子进程为TASK_STOPPED.
if (!(clone_flags & CLONE_STOPPED))
wake_up_new_task(p, clone_flags);
else
p->state = TASK_STOPPED;
(6) 如果父进程是被跟踪的,在当前进程的ptrace_message域中保存子进程的PID,并且调用ptrace_notify,停止当前进程,并且发送SIGCHD到父进程。子进程的祖父进程是被调试进程的debugger, SIGCHD告诉debugger当前进程创建了一个子进程,子进程的PID可以从当前进程的ptrace_message中找到。
if (unlikely (trace)) {
current->ptrace_message = pid;
ptrace_notify ((trace << 8) | SIGTRAP);
}
(7) 如果设置CLONE_VFORK,父进程被插入到等待队列,并且挂起它。直到子进程释放它的内存空间。
if (clone_flags & CLONE_VFORK) {
wait_for_completion(&vfork);
if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE))
ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);
}
(8) 返回子进程的PID
二 copy_process |
|
3: copy_process (1) 检查clone_flags是否兼容,如果不兼容,返回一个错误码。
retval = security_task_create(clone_flags); if (retval) goto fork_out; (3) 调用dup_task_struct获取子进程的进程描述符。 p = dup_task_struct(current); if (!p) goto fork_out; 该函数代码如下: static struct task_struct *dup_task_struct(struct task_struct *orig) { struct task_struct *tsk; struct thread_info *ti;
prepare_to_copy(orig);
tsk = alloc_task_struct(); if (!tsk) return NULL;
ti = alloc_thread_info(tsk); if (!ti) { free_task_struct(tsk); return NULL; }
*ti = *orig->thread_info; *tsk = *orig; tsk->thread_info = ti; ti->task = tsk;
/* One for us, one for whoever does the "release_task()" (usually parent) */ atomic_set(&tsk->usage,2); return tsk; }
(4) 检查存储在p->signal->rlim[RLIMIT_NPROC].rlim_cur的值是否小于等于用户拥有的进程数,如果是,并且用户没有root权限,返回错误码。该函数从每个用户的数据结构user_struct中获取当前用户拥有的进程数。user_struct可以通过进程描述符得user中的指针获取到。 if (atomic_read(&p->user->processes) >= p->signal->rlim[RLIMIT_NPROC].rlim_cur) { if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) && p->user != &root_user) goto bad_fork_free; } (5) 增加user_struct的用户数和当前用户的进程数。 atomic_inc(&p->user->__count); atomic_inc(&p->user->processes); (6) 检查系统的进程数是否大于系统允许的最大进程数。最大进程数和系统的RAM有关,一般规则是:thread_info和内核栈的大小不能超过物理空间的1/8。 (7) 如果新进程中实现执行域和可执行格式的内核函数在内核模块中,模块的使用数加1。 if (!try_module_get(p->thread_info->exec_domain->module)) goto bad_fork_cleanup_count;
if (p->binfmt && !try_module_get(p->binfmt->module)) goto bad_fork_cleanup_put_domain; (8) 保存新进程的pid, 如果设置CLONE_PARENT_SETTID,复制孩子的pid到parent_tidptr参数寻址的用户态变量中。 copy_flags(clone_flags, p); p->pid = pid; retval = -EFAULT; if (clone_flags & CLONE_PARENT_SETTID) if (put_user(p->pid, parent_tidptr)) goto bad_fork_cleanup; (9) 初始化子进程描述符中的list_head和自选锁,以及建立一些与挂起信号,时钟和时间统计的域。 INIT_LIST_HEAD(&p->children); INIT_LIST_HEAD(&p->sibling); p->vfork_done = NULL; spin_lock_init(&p->alloc_lock); spin_lock_init(&p->proc_lock);
clear_tsk_thread_flag(p, TIF_SIGPENDING); init_sigpending(&p->pending);
p->utime = cputime_zero; p->stime = cputime_zero; p->sched_time = 0; p->rchar = 0; /* I/O counter: bytes read */ p->wchar = 0; /* I/O counter: bytes written */ p->syscr = 0; /* I/O counter: read syscalls */ p->syscw = 0; /* I/O counter: write syscalls */ acct_clear_integrals(p);
p->it_virt_expires = cputime_zero; p->it_prof_expires = cputime_zero; p->it_sched_expires = 0; INIT_LIST_HEAD(&p->cpu_timers[0]); INIT_LIST_HEAD(&p->cpu_timers[1]); INIT_LIST_HEAD(&p->cpu_timers[2]); (10) 调用copy_semundo( ), copy_files( ), copy_fs( ), copy_sighand( ), copy_signal( ), copy_mm( )和copy_namespace( )创建新的数据结构,并且复制父进程中相应的内容到这些结构中。 if ((retval = security_task_alloc(p))) goto bad_fork_cleanup_policy; if ((retval = audit_alloc(p))) goto bad_fork_cleanup_security; /* copy all the process information */ if ((retval = copy_semundo(clone_flags, p))) goto bad_fork_cleanup_audit; if ((retval = copy_files(clone_flags, p))) goto bad_fork_cleanup_semundo; if ((retval = copy_fs(clone_flags, p))) goto bad_fork_cleanup_files; if ((retval = copy_sighand(clone_flags, p))) goto bad_fork_cleanup_fs; if ((retval = copy_signal(clone_flags, p))) goto bad_fork_cleanup_sighand; if ((retval = copy_mm(clone_flags, p))) goto bad_fork_cleanup_signal; if ((retval = copy_keys(clone_flags, p))) goto bad_fork_cleanup_mm; if ((retval = copy_namespace(clone_flags, p))) goto bad_fork_cleanup_keys; (11) 调用copy_thread用CPU寄存器的值初始化子进程的内核态栈。将0强制保存在eax寄存器相应的域(这是clone, fork系统调用生成的孩子进程的返回值)。用孩子进程内核栈的基地址初始化子进程描述符的thread.esp域。汇编语言写的函数(ret_from_fork)的地址保存在thread.eip.如果父进程使用I/O permission Bitmap, 子进程复制父进程的;最后,如果设置CLONE_SETTLS,子进程获取用户态数据结构设置的TSL段。 retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs); if (retval) goto bad_fork_cleanup_namespace (12) 如果设置CLONE_CHILD_SETTID,CLONE_CHILD_CLEARTID,复制child_tidptr的值到新进程的set_child_tid或者set_child_tid域。
(13) 为了防止ret_from_fork通知调试进程系统调用结束,关闭thread_info中的TIF_SYSCALL_TRACE。
|