日期:2014-05-16  浏览次数:20758 次

linux进程解析--进程的创建

     通常我们在代码中调用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;
    //查看pidmap_array查找新进程的pid
	long pid = alloc_pidmap();
    //查看当前进程是否被trace,设置新进程的trace状态,只要进程不是内核线程就应该被trace
	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;
        //如果该进程是vfork出来的,需要等待子进程结束或退出后再执行父进程
		if (clone_flags & CLONE_VFORK) {
			p->vfork_done = &vfork;
			init_completion(&vfork);	
		}
	    //如果该进程被追踪,或者设置了clone_stopped标记,给该进程发送STOP信号,	设置了CLONE_STOPPED标记的话,进程不能够立即执行,需要先stop下来,后面通过 
    向该进程发送SIG_CONT信号使其恢复执行
		if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
			sigaddset(&p->pending.signal, SIGSTOP);
			set_tsk_thread_flag(p, TIF_SIGPENDING);
		}
        //未设置clone_stopped标记,唤醒新进程,此后新进程可以参与调度
		if (!(clone_flags & CLONE_STOPPED))
			wake_up_new_task(p, clone_flags);
		else
			p->state = TASK_STOPPED;
		++total_f