通常我们在代码中调用fork()来创建一个进程或者调用pthread_create()来创建一个线程,创建一个进程需要为其分配内存资源,文件资源,时间片资源等,在这里来描述一下linux进程的创建过程及写时复制技术。
一写时复制
子进程和父进程通常拥有着不同的进程内存空间(线程除外),传统的unix在创建子进程后,会复制父进程的地址空间的所有内容,这就十分的低效,因为经常子进程会立即执行exec操作,创建一个崭新的内存空间,另外像进程代码段这样的内存,父子进程只是读,而没有写操作,完全可以共享,而不用去复制,这样会节省大量的时间。
写时复制机制就是在这个背景下产生的,子进程创建后,不会去复制所有的父进程的内存空间物理内存,通常只复制下页全局目录,并把所有父进程的物理页设置为写保护,这样当父子进程中有一个对物理页进行写时,就会触发写保护异常,就复制一下对应的物理页,加入到对应的页表中即可。
二clone(), fork(),vfork()
fork(),vfork()系统调用都是通过clone()函数来实现的,clone()函数介绍如下:
函数原型:
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
这里fn是函数指针,我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本", child_stack明显是为子进程分配用户态堆栈空间,flags就是标志用来描述你需要从父进程继承那些资源, arg就是传给子进程的参数)。下面是flags可以取的值:
标志 含义
CLONE_PARENT 创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
CLONE_FS 子进程与父进程共享相同的文件系统,包括root、当前目录、umask
CLONE_FILES 子进程与父进程共享相同的文件描述符(file descriptor)表
CLONE_NEWNS 在新的namespace启动子进程,namespace描述了进程的文件hierarchy
CLONE_SIGHAND 子进程与父进程共享相同的信号处理(signal handler)表
CLONE_PTRACE 若父进程被trace,子进程也被trace
CLONE_VFORK 父进程被挂起,直至子进程释放虚拟内存资源
CLONE_VM 子进程与父进程运行于相同的内存空间
CLONE_PID 子进程在创建时PID与父进程一致
CLONE_THREAD Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群
下面的例子是创建一个线程(子进程共享了父进程虚存空间,没有自己独立的虚存空间不能称其为进程)。父进程被挂起当子线程释放虚存资源后再继续执行。
实现clone()系统调用的服务例程是sys_clone(),sys_clone()例程并没有fn和arg参数,clone()函数会把fn放在子进程堆栈的某个位置,该位置就是封装函数本身返回地址的存放位置,arg指针放在fn堆栈的下面,当封装函数结束时,cpu取出fn,执行fn(arg).
与系统调用clone功能相似的系统调用有fork,但fork事实上只是clone的功能的一部分,clone与fork的主要区别在于传递了几个参数,而当中最重要的参数就是conle_flags,下表是系统定义的几个clone_flags标志,同时child_stack传递的也是父进程的用户态堆栈,由于写时复制,会在父子进程对堆栈进行操作时进行复制。
标志 Value 含义
CLONE_VM 0x00000100 置起此标志在进程间共享地址空间
CLONE_FS 0x00000200 置起此标志在进程间共享文件系统信息
CLONE_FILES 0x00000400 置起此标志在进程间共享打开的文件
CLONE_SIGHAND 0x00000800 置起此标志在进程间共享信号处理程序
三sys_clone()服务例程源码解析
fork(),vfork(),clone()三个系统调用最后都是使用sys_clone()服务例程来完成了系统调用,sys_clone()服务例程会去调用do_fork()函数,主要的处理流程就在do_fork()中。
3.1do_fork()
-
long do_fork(unsigned long clone_flags,
-
unsigned long stack_start,
-
struct pt_regs *regs,
-
unsigned long stack_size,
-
int __user *parent_tidptr,
-
int __user *child_tidptr)
-
{
-
struct task_struct *p;
-
int trace = 0;
-
-
long pid = alloc_pidmap();
-
-
if (unlikely(current->ptrace)) {
-
trace = fork_traceflag (clone_flags);
-
if (trace)
-
clone_flags |= CLONE_PTRACE;
-
}
-
-
p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);
-
if (!IS_ERR(p)) {
-
struct completion vfork;
-
-
if (clone_flags & CLONE_VFORK) {
-
p->vfork_done = &vfork;
-
init_completion(&vfork);
-
}
-
-
向该进程发送SIG_CONT信号使其恢复执行
-
if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
-
sigaddset(&p->pending.signal, SIGSTOP);
-
set_tsk_thread_flag(p, TIF_SIGPENDING);
-
}
-
-
if (!(clone_flags & CLONE_STOPPED))
-
wake_up_new_task(p, clone_flags);
-
else
-
p->state = TASK_STOPPED;
-
++total_forks;
-
-
if (unlikely (trace)) {
-
current->ptrace_message = pid;
-
ptrace_notify ((trace << 8) | SIGTRAP);
-
}
-
-
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);
-
}
-
} else {
-
free_pidmap(pid);
-
pid = PTR_ERR(p);
-
}
-
return pid;
-
}
3.2copy_process()
-
static task_t *copy_process(unsigned long clone_flags,
-
unsigned long stack_start,
-
struct pt_regs *regs,
-
unsigned long stack_size,
-
int __user *parent_tidptr,
-
int __user *child_tidptr,
-
int pid)
-
{
-
int retval;
-
struct task_struct *p = NULL;
-
-
if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
-
return ERR_PTR(-EINVAL);
-
-
-
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
-
return ERR_PTR(-EINVAL);
-
-
-
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
-
return ERR_PTR(-EINVAL);
-
-
if (retval)
-
goto fork_out;
-
retval = -ENOMEM;
-
-
p = dup_task_struct(current);
-
if (!p)
-
goto fork_out;
-
retval = -EAGAIN;
-
-
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;
-
}
-
-
atomic_inc(&p->user->__count);
-
atomic_inc(&p->user->processes);
-
-
-
if (nr_threads >= max_threads)
-
goto bad_fork_cleanup_count;
-
-
p->did_exec = 0;
-
-
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;
-
-
p->proc_dentry = NULL;
-
-
INIT_LIST_HEAD(&p->children);
-
INIT_LIST_HEAD(&p->sibling);
-
-
init_waitqueue_head(&p->wait_chldexit);
-
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->tgid = p->pid;
-
if (clone_flags & CLONE_THREAD)
-
p->tgid = current->tgid;
-
-
-
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;
-
retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
-
if (retval)
-
goto bad_fork_cleanup_namespace;
-
-
-
clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);
-
-
p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 : (clone_flags & CSIGNAL);
-
p->pdeath_signal = 0;
-
p->exit_state = 0;
-
-
-
sched_fork(p);
-
-
-
p->group_leader = p;
-
INIT_LIST_HEAD(&p->ptrace_children);
-
INIT_LIST_HEAD(&p->ptrace_list);
-
-
write_lock_irq(&tasklist_lock);
-
-
-
p->cpus_allowed = current->cpus_allowed;
-
-
set_task_cpu(p, smp_processor_id());
-
if (sigismember(¤t->pending.signal, SIGKILL)) {
-
write_unlock_irq(&tasklist_lock);
-
retval = -EINTR;
-
goto bad_fork_cleanup_namespace;
-
}
-
-
-
if (clone_flags & (CLONE_PARENT|CLONE_THREAD))
-
p->real_parent = current->real_parent;
-
else
-
p->real_parent = current;
-
p->parent = p->real_parent;
-
-
if (clone_flags & CLONE_THREAD) {
-
spin_lock(¤t->sighand->siglock);
-
-
p->group_leader = current->group_leader;
-
spin_unlock(¤t->sighand->siglock);
-
}
-
-
SET_LINKS(p);
-
-
if (unlikely(p->ptrace & PT_PTRACED))
-
__ptrace_link(p, current->parent);
-
-
attach_pid(p, PIDTYPE_PID, p->pid);
-
-
attach_pid(p, PIDTYPE_TGID, p->tgid);
-
-
if (thread_group_leader(p)) {
-
-
attach_pid(p, PIDTYPE_PGID, process_group(p));
-
-
attach_pid(p, PIDTYPE_SID, p->signal->session);
-
if (p->pid)
-
__get_cpu_var(process_counts)++;
-
}
-
-
nr_threads++;
-
write_unlock_irq(&tasklist_lock);
-
retval = 0;
-
:
-
:
-
}
3.3copy_thread
-
int copy_thread(int nr, unsigned long clone_flags, unsigned long esp,
-
unsigned long unused,
-
struct task_struct * p, struct pt_regs * regs)
-
{
-
struct pt_regs * childregs;
-
struct task_struct *tsk;
-
int err;
-
-
-
childregs = ((struct pt_regs *) (THREAD_SIZE + (unsigned long) p->thread_info)) - 1;
-
-
*childregs = *regs;
-
-
childregs->eax = 0;
-
-
childregs->esp = esp;
-
-
-
p->thread.esp = (unsigned long) childregs;
-
p->thread.esp0 = (unsigned long) (childregs+1);
-
-
p->thread.eip = (unsigned long) ret_from_fork;
-
-
savesegment(fs,p->thread.fs);
-
savesegment(gs,p->thread.gs);
-
-
tsk = current;
-
-
if (unlikely(NULL != tsk->thread.io_bitmap_ptr)) {
-
p->thread.io_bitmap_ptr = kmalloc(IO_BITMAP_BYTES, GFP_KERNEL);
-
if (!p->thread.io_bitmap_ptr) {
-
p->thread.io_bitmap_max = 0;
-
return -ENOMEM;
-
}
-
memcpy(p->thread.io_bitmap_ptr, tsk->thread.io_bitmap_ptr,
-
IO_BITMAP_BYTES);
-
}
-
:
-
:
-
return err;
-
}
四do_fork()之后发生了什么
现在我们有了完整的可以运行的进程,但还有对其进行调度,在以后的进程切换时,会对其进行完善,将子进程描述符中thread字段的值放入几个寄存器中,主要是将thread.esp放入esp寄存器中,把ret_from_fork()函数的地址放入到eip寄存器中,(参看上面的copy_thread函数)然后进程切换后会去执行ret_from_fork()函数,ret_form_fork()回去调用schedule_tail()函数,用存放在内核栈中的值装载所有的寄存器,并强迫cpu返回用户态。系统调用的返回值存放在了eax中,返回给子进程的是0,父进程的是子进程的id号。
阅读(1877) | 评论(0) | 转发(0) |