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是否兼容,如果不兼容,返回一个错误码。
if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return ERR_PTR(-EINVAL);
/*
* Thread groups must share signals as well, and detached threads
* can only be started up within the thread group.
*/
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL);
/*
* Shared signal handlers imply shared VM. By way of the above,
* thread groups also imply shared VM. Blocking this case allows
* for various simplifications in other code.
*/
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
return ERR_PTR(-EINVAL);
(2) 调用security_task_create进行附加的安全检查。
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
f78815925 于 2010-08-25 20:40:28发表:
看的有点晕头{:2_102:}
kencae 于 2010-08-22 11:57:10发表:
确实看不太懂。。。初学的。
xianglin134 于 2010-08-20 16:50:27发表:
{:2_91:}
xianglin134 于 2010-08-20 16:50:20发表:
{:2_91:}
chameleon 于 2006-11-29 08:37:44发表:
学习中
nickck 于 2006-11-28 22:58:54发表:
谢谢提供
xxh2005 于 2006-11-28 21:10:48发表:
:0(1 初学不大懂