【进程管理】进程(线程)创建

本节主要研究进程(线程)创建的过程,下文将不区分进程和线程;

基本知识

在linux系统中,第一个进程是系统固有的,是由内核的设计者安排好的;一个新的进程一定要由一个已存在的进程复制出来,而不是创造出来的,其实linux系统并不提供直接创建进

程的方法;创建了子进程以后,父进程可以继续走自己的路,与子进程分道扬镳,但是如果子进程先行exit(),,那么将要向父进程发一个信号;父进程也可以选择睡眠,等子进程

exit()以后再去世,然后父进程再继续执行,可使用wait3()某个特定的子进程,wait4()所有子进程;第三,自己exit()(是每一个可执行程序映像所必有的,因此在子进程中执行完

后,不会返回);linux将进程的创建与目标程序的执行分成两步;

(1)从一个已存在的父进程像细胞分裂一样地复制出一个子进程;实际复制出来的子进程有自己的task_struct和系统空间堆栈,但是与父进程共享其他资源;例如,要是父进程打开了5个文件,那么子进程也打开了这5个文件,而且这些文件的读写位置处于相同的位置;fork()是全部复制,父进程的所有资源全部通过数据结构复制给子进程,但进程号不一样;clone()则带有参数的选择性的复制,可复制出一个线程,其他资源通过指针与父亲来共享;vfork()是除了task_struct和系统空间堆栈外的资源通过指针全部复制,因此复制出来的是个线程,效率很高;

(2)目标程序的执行,创建一个进程是为有不同的目标程序要让新的程序去执行,但复制完以后,子进程就要与父进程分道扬镳了,用execve()执行以文件形式存在的可执行程序映像;

在(1)中,复制时只复制进程基本资源,如task_struct,系统空间堆栈,页面表等,不包括父进程的代码和全局变量,这些通过只读方式的共享,在需要写的时候,通过copy_on_write()为所涉及的页面建立一个新的副本;

fork,vfork,clone

(1)clone()主要是用来创建一个线程,包括用户线程和内核线程;创建用户线程时,可以给定子线程用户空间堆栈位置,它也可以用来创建进程,有选择性的复制父进程的资源;fork()则是全面的复制;vfork()是为了提高创建时的效率,减少系统开销;

(2)Linux内核中确实有一个创建内核线程的函数,kernel_thread(),供内核线程调用,它是对clone()的包装,并不执行execve(),而是执行内核中某一个函数,会返回因此要执行一个exit()系统调用;

(3)fork,vfork,clone这三个系统调用都调用do_fork(),只不过调用的参数不一样,下面主要来讲解do_fork();

int sys_fork(struct pt_regs *regs){//clone_flags中的SIGCHLDreturn do_fork(SIGCHLD, regs->sp, regs, 0, NULL, NULL);}int sys_vfork(struct pt_regs *regs){//共享CLONE_VFORK和VMreturn do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->sp, regs, 0,NULL, NULL);}//clone负责建立起轻量级进程(可以与其他进程共享地址空间,或打开文件等),newsp是指用户堆栈指针,parent_tid表示父进程的//的用户变量地址,child_tid表示新的轻量级进程的用户变量地址:longsys_clone(unsigned long clone_flags, unsigned long newsp, void __user *parent_tid, void __user *child_tid, struct pt_regs *regs){if (!newsp)newsp = regs->sp; //有新的用户栈地址//其中clone_flags一般有参数SIGCHLD,占用一个字节,剩余的3个字节可制定,如共享内存描述符,页表,文件目录,信号处理标,跟踪等return do_fork(clone_flags, newsp, regs, 0, parent_tid, child_tid);}

说明几点

(1)newsp为子进程新的栈,该栈可能在另一个地址空间;

/* * Create a kernel thread */int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags){struct pt_regs regs;memset(&regs, 0, sizeof(regs));regs.si = (unsigned long) fn;regs.di = (unsigned long) arg;#ifdef CONFIG_X86_32regs.ds = __USER_DS;regs.es = __USER_DS;regs.fs = __KERNEL_PERCPU;:regs.gs = __KERNEL_STACK_CANARY;#elseregs.ss = __KERNEL_DS;#endifregs.orig_ax = -1;regs.ip = (unsigned long) kernel_thread_helper;regs.cs = __KERNEL_CS | get_kernel_rpl();regs.flags = X86_EFLAGS_IF | 0x2;/* Ok, create the new process.. *///其中CLONE_VM避免调用进程的页表,内核线程是不用访问用户态的地址空间;不会被跟踪的return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);}

do_fork

/* * Ok, this is the main fork-routine. * * It copies the process, and if successful kick-starts * it and waits for it to finish using the VM if required. *///sys_clone//regs是指通用寄存器指针,它是一个轻量级进程在用户态切换到内核态,保存到内核堆栈中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 nr;/* * Do some preliminary argument and permissions checking before we * actually start allocating stuff */if (clone_flags & CLONE_NEWUSER) {//创建新的用户if (clone_flags & CLONE_THREAD) //但是没有创建新的线程return -EINVAL;/* hopefully this check will go away when userns support is * complete */if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SETUID) ||!capable(CAP_SETGID))return -EPERM;}/* * When called from kernel_thread, don't do user tracing stuff. */if (likely(user_mode(regs)))trace = tracehook_prepare_clone(clone_flags);//执行生成新进程的实际工作p = copy_process(clone_flags, stack_start, regs, stack_size,child_tidptr, NULL, trace);/* * Do this prior waking up the new thread – the thread pointer * might get invalid after that point, if the thread exits quickly. */if (!IS_ERR(p)) {struct completion vfork;trace_sched_process_fork(current, p);nr = task_pid_vnr(p);//获得当前的局部nrif (clone_flags & CLONE_PARENT_SETTID)put_user(nr, parent_tidptr);//将nr复制到对应的用户空间指向的地址if (clone_flags & CLONE_VFORK) { //如果是执行vfork这个函数,父进程会睡眠下去p->vfork_done = &vfork;init_completion(&vfork); //睡眠,此时父进程等子进程}//schedule_tailaudit_finish_fork(p);tracehook_report_clone(regs, clone_flags, nr, p);/* * We set PF_STARTING at creation in case tracing wants to * use this to distinguish a fully live task from one that * hasn't gotten to tracehook_report_clone() yet. Now we * clear it and set the child going. */p->flags &= ~PF_STARTING;wake_up_new_task(p, clone_flags);//将子进程的task_struct放入到新调度器队列中tracehook_report_clone_complete(trace, regs,clone_flags, nr, p);//如果设置了CLONE_VFORK,//就把父进程插入到等待队列中,直到子进程释放了自己的内存地址空间(也就是子进程结束或执行新的程序)if (clone_flags & CLONE_VFORK) {freezer_do_not_count();wait_for_completion(&vfork);//父进程将在改变量上进入睡眠状态freezer_count();tracehook_report_vfork_done(p, nr);}} else {nr = PTR_ERR(p);}return nr;}说明几点

(1)p = copy_process(clone_flags, stack_start, regs, stack_size,child_tidptr, NULL, trace);执行实际的进程复制工作;

(2)if (clone_flags & CLONE_VFORK) 表示如果是执行vfork这个函数,父进程会睡眠下去;

copy_process中关键代码1

设置task_struct和系统堆栈

坚硬的城市里没有柔软的爱情,生活不是林黛玉,

【进程管理】进程(线程)创建

相关文章:

你感兴趣的文章:

标签云: