linux进程管理(2):进程创建的copy-凯发app官方网站

凯发app官方网站-凯发k8官网下载客户端中心 | | 凯发app官方网站-凯发k8官网下载客户端中心
  • 博客访问: 1125343
  • 博文数量: 146
  • 博客积分: 190
  • 博客等级: 入伍新兵
  • 技术积分: 5225
  • 用 户 组: 普通用户
  • 注册时间: 2012-06-06 08:24
个人简介

慢行者

文章分类

全部博文(146)

文章存档

2013年(145)

2012年(1)

相关博文
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·

分类: linux

2013-09-16 09:57:02

 4. copy_process: 进程描述符的处理
    copy_process函数也在./linux/kernel/fork.c中。它会用当前进程的一个副本来创建新进程并分配pid,但不会实际启动这个新进程。它会复制寄存器中的值、所有与进程环境相关的部分,每个clone标志。新进程的实际启动由调用者来完成。

    对于每一个进程而言,内核为其单独分配了一个内存区域,这个区域存储的是内核栈和该进程所对应的一个小型进程描述符thread_info结构。在./linux/arch/x86/include/asm/thread_info.h中,如下:


[cpp] 
  1. struct thread_info {  
  2.     struct task_struct  *task;      /* 主进程描述符 */  
  3.     struct exec_domain  *exec_domain;   /* 执行域 */  
  4.     __u32           flags;      /* 低级别标志 */  
  5.     __u32           status;     /* 线程同步标志 */  
  6.     __u32           cpu;        /* 当前cpu */  
  7.     int         preempt_count;  /* 0 => 可抢占, <0 => bug */  
  8.     mm_segment_t        addr_limit;  
  9.     struct restart_block    restart_block;  
  10.     void __user     *sysenter_return;  
  11. #ifdef config_x86_32  
  12.     unsigned long           previous_esp;   /* 先前栈的esp,以防嵌入的(irq)栈 */  
  13.     __u8            supervisor_stack[0];  
  14. #endif  
  15.     int         uaccess_err;  
  16. };  
  17.   
  18. /* ... */  
  19.   
  20. /* 怎样从c获取当前栈指针 */  
  21. register unsigned long current_stack_pointer asm("esp") __used;  
  22.   
  23. /* 怎样从c获取当前线程信息结构 */  
  24. static inline struct thread_info *current_thread_info(void)  
  25. {  
  26.     return (struct thread_info *)  
  27.         (current_stack_pointer & ~(thread_size - 1));  
  28. }  
    之所以将线程信息结构称之为小型的进程描述符,是因为在这个结构中并没有直接包含与进程相关的字段,而是通过task字段指向具体某个进程描述符。通常这块内存区域的大小是8kb,也就是两个页的大小(有时候也使用一个页来存储,即4kb)。一个进程的内核栈和thread_info结构之间的逻辑关系如下图所示:


图1 进程内核栈和thread_info结构的存储

    从上图可知,内核栈是从该内存区域的顶层向下(从高地址到低地址)增长的,而thread_info结构则是从该区域的开始处向上(从低地址到高地址)增长。内核栈的栈顶地址存储在esp寄存器中。所以,当进程从用户态切换到内核态后,esp寄存器指向这个区域的末端。从代码的角度来看,内核栈和thread_info结构是被定义在./linux/include/linux/sched.h中的一个联合体当中的:


[cpp] 
  1. union thread_union {  
  2.     struct thread_info thread_info;  
  3.     unsigned long stack[thread_size/sizeof(long)];  
  4. };  
    其中,thread_size的值取8192时,stack数组的大小为2048;thread_size的值取4096时,stack数组的大小为 1024。现在我们应该思考,为何要将内核栈和thread_info(其实也就相当于task_struct,只不过使用thread_info结构更节省空间)紧密的放在一起?最主要的原因就是内核可以很容易的通过esp寄存器的值获得当前正在运行进程的thread_info结构的地址,进而获得当前进程描述符的地址。在上面的current_thread_info函数中,定义current_stack_pointer的这条内联汇编语句会从esp寄存器中获取内核栈顶地址,和~(thread_size - 1)做与操作将屏蔽掉低13位(或12位,当thread_size为4096时),此时所指的地址就是这片内存区域的起始地址,也就刚好是thread_info结构的地址。但是,thread_info结构的地址并不会对我们直接有用。我们通常可以轻松的通过 current宏获得当前进程的task_struct结构,前面已经列出过get_current()函数的代码。current宏返回的是thread_info结构task字段,而task正好指向与thread_info结构关联的那个进程描述符。得到 current后,我们就可以获得当前正在运行进程的描述符中任何一个字段了,比如我们通常所做的current->pid。
    下面看copy_process的实现。



[cpp] 
  1. static struct task_struct *copy_process(unsigned long clone_flags,  
  2.                     unsigned long stack_start,  
  3.                     struct pt_regs *regs,  
  4.                     unsigned long stack_size,  
  5.                     int __user *child_tidptr,  
  6.                     struct pid *pid,  
  7.                     int trace)  
  8. {  
  9.     int retval;  
  10.     struct task_struct *p;  
  11.     int cgroup_callbacks_done = 0;  
  12.   
  13.     if ((clone_flags & (clone_newns|clone_fs)) == (clone_newns|clone_fs))  
  14.         return err_ptr(-einval);  
  15.   
  16.     /* 
  17.      * thread groups must share signals as well, and detached threads 
  18.      * can only be started up within the thread group. 
  19.      */  
  20.     if ((clone_flags & clone_thread) && !(clone_flags & clone_sighand))  
  21.         return err_ptr(-einval);  
  22.   
  23.     /* 
  24.      * shared signal handlers imply shared vm. by way of the above, 
  25.      * thread groups also imply shared vm. blocking this case allows 
  26.      * for various simplifications in other code. 
  27.      */  
  28.     if ((clone_flags & clone_sighand) && !(clone_flags & clone_vm))  
  29.         return err_ptr(-einval);  
  30.   
  31.     /* 
  32.      * siblings of global init remain as zombies on exit since they are 
  33.      * not reaped by their parent (swapper). to solve this and to avoid 
  34.      * multi-rooted process trees, prevent global and container-inits 
  35.      * from creating siblings. 
  36.      */  
  37.     if ((clone_flags & clone_parent) &&  
  38.                 current->signal->flags & signal_unkillable)  
  39.         return err_ptr(-einval);  
  40.   
  41.     retval = security_task_create(clone_flags);  
  42.     if (retval)  
  43.         goto fork_out;  
  44.   
  45.     retval = -enomem;  
  46.     p = dup_task_struct(current);  
  47.     if (!p)  
  48.         goto fork_out;  
  49.   
  50.     ftrace_graph_init_task(p);  
  51.   
  52.     rt_mutex_init_task(p);  
  53.   
  54. #ifdef config_prove_locking  
  55.     debug_locks_warn_on(!p->hardirqs_enabled);  
  56.     debug_locks_warn_on(!p->softirqs_enabled);  
  57. #endif  
  58.     retval = -eagain;  
  59.     if (atomic_read(&p->real_cred->user->processes) >=  
  60.             p->signal->rlim[rlimit_nproc].rlim_cur) {  
  61.         if (!capable(cap_sys_admin) && !capable(cap_sys_resource) &&  
  62.             p->real_cred->user != init_user)  
  63.             goto bad_fork_free;  
  64.     }  
  65.   
  66.     retval = copy_creds(p, clone_flags);  
  67.     if (retval < 0)  
  68.         goto bad_fork_free;  
  69.   
  70.     /* 
  71.      * if multiple threads are within copy_process(), then this check 
  72.      * triggers too late. this doesn't hurt, the check is only there 
  73.      * to stop root fork bombs. 
  74.      */  
  75.     retval = -eagain;  
  76.     if (nr_threads >= max_threads)  
  77.         goto bad_fork_cleanup_count;  
  78.   
  79.     if (!try_module_get(task_thread_info(p)->exec_domain->module))  
  80.         goto bad_fork_cleanup_count;  
  81.   
  82.     p->did_exec = 0;  
  83.     delayacct_tsk_init(p);  /* must remain after dup_task_struct() */  
  84.     copy_flags(clone_flags, p);  
  85.     init_list_head(&p->children);  
  86.     init_list_head(&p->sibling);  
  87.     rcu_copy_process(p);  
  88.     p->vfork_done = null;  
  89.     spin_lock_init(&p->alloc_lock);  
  90.   
  91.     init_sigpending(&p->pending);  
  92.   
  93.     p->utime = cputime_zero;  
  94.     p->stime = cputime_zero;  
  95.     p->gtime = cputime_zero;  
  96.     p->utimescaled = cputime_zero;  
  97.     p->stimescaled = cputime_zero;  
  98.     p->prev_utime = cputime_zero;  
  99.     p->prev_stime = cputime_zero;  
  100.   
  101.     p->default_timer_slack_ns = current->timer_slack_ns;  
  102.   
  103.     task_io_accounting_init(&p->ioac);  
  104.     acct_clear_integrals(p);  
  105.   
  106.     posix_cpu_timers_init(p);  
  107.   
  108.     p->lock_depth = -1;      /* -1 = no lock */  
  109.     do_posix_clock_monotonic_gettime(&p->start_time);  
  110.     p->real_start_time = p->start_time;  
  111.     monotonic_to_bootbased(&p->real_start_time);  
  112.     p->io_context = null;  
  113.     p->audit_context = null;  
  114.     cgroup_fork(p);  
  115. #ifdef config_numa  
  116.     p->mempolicy = mpol_dup(p->mempolicy);  
  117.     if (is_err(p->mempolicy)) {  
  118.         retval = ptr_err(p->mempolicy);  
  119.         p->mempolicy = null;  
  120.         goto bad_fork_cleanup_cgroup;  
  121.     }  
  122.     mpol_fix_fork_child_flag(p);  
  123. #endif  
  124. #ifdef config_trace_irqflags  
  125.     p->irq_events = 0;  
  126. #ifdef __arch_want_interrupts_on_ctxsw  
  127.     p->hardirqs_enabled = 1;  
  128. #else  
  129.     p->hardirqs_enabled = 0;  
  130. #endif  
  131.     p->hardirq_enable_ip = 0;  
  132.     p->hardirq_enable_event = 0;  
  133.     p->hardirq_disable_ip = _this_ip_;  
  134.     p->hardirq_disable_event = 0;  
  135.     p->softirqs_enabled = 1;  
  136.     p->softirq_enable_ip = _this_ip_;  
  137.     p->softirq_enable_event = 0;  
  138.     p->softirq_disable_ip = 0;  
  139.     p->softirq_disable_event = 0;  
  140.     p->hardirq_context = 0;  
  141.     p->softirq_context = 0;  
  142. #endif  
  143. #ifdef config_lockdep  
  144.     p->lockdep_depth = 0; /* no locks held yet */  
  145.     p->curr_chain_key = 0;  
  146.     p->lockdep_recursion = 0;  
  147. #endif  
  148.   
  149. #ifdef config_debug_mutexes  
  150.     p->blocked_on = null; /* not blocked yet */  
  151. #endif  
  152.   
  153.     p->bts = null;  
  154.   
  155.     /* perform scheduler related setup. assign this task to a cpu. */  
  156.     sched_fork(p, clone_flags);  
  157.   
  158.     retval = perf_event_init_task(p);  
  159.     if (retval)  
  160.         goto bad_fork_cleanup_policy;  
  161.   
  162.     if ((retval = audit_alloc(p)))  
  163.         goto bad_fork_cleanup_policy;  
  164.     /* copy all the process information */  
  165.     if ((retval = copy_semundo(clone_flags, p)))  
  166.         goto bad_fork_cleanup_audit;  
  167.     if ((retval = copy_files(clone_flags, p)))  
  168.         goto bad_fork_cleanup_semundo;  
  169.     if ((retval = copy_fs(clone_flags, p)))  
  170.         goto bad_fork_cleanup_files;  
  171.     if ((retval = copy_sighand(clone_flags, p)))  
  172.         goto bad_fork_cleanup_fs;  
  173.     if ((retval = copy_signal(clone_flags, p)))  
  174.         goto bad_fork_cleanup_sighand;  
  175.     if ((retval = copy_mm(clone_flags, p)))  
  176.         goto bad_fork_cleanup_signal;  
  177.     if ((retval = copy_namespaces(clone_flags, p)))  
  178.         goto bad_fork_cleanup_mm;  
  179.     if ((retval = copy_io(clone_flags, p)))  
  180.         goto bad_fork_cleanup_namespaces;  
  181.     retval = copy_thread(clone_flags, stack_start, stack_size, p, regs);  
  182.     if (retval)  
  183.         goto bad_fork_cleanup_io;  
  184.   
  185.     if (pid != &init_struct_pid) {  
  186.         retval = -enomem;  
  187.         pid = alloc_pid(p->nsproxy->pid_ns);  
  188.         if (!pid)  
  189.             goto bad_fork_cleanup_io;  
  190.   
  191.         if (clone_flags & clone_newpid) {  
  192.             retval = pid_ns_prepare_proc(p->nsproxy->pid_ns);  
  193.             if (retval < 0)  
  194.                 goto bad_fork_free_pid;  
  195.         }  
  196.     }  
  197.   
  198.     p->pid = pid_nr(pid);  
  199.     p->tgid = p->pid;  
  200.     if (clone_flags & clone_thread)  
  201.         p->tgid = current->tgid;  
  202.   
  203.     if (current->nsproxy != p->nsproxy) {  
  204.         retval = ns_cgroup_clone(p, pid);  
  205.         if (retval)  
  206.             goto bad_fork_free_pid;  
  207.     }  
  208.   
  209.     p->set_child_tid = (clone_flags & clone_child_settid) ? child_tidptr : null;  
  210.     /* 
  211.      * clear tid on mm_release()? 
  212.      */  
  213.     p->clear_child_tid = (clone_flags & clone_child_cleartid) ? child_tidptr: null;  
  214. #ifdef config_futex  
  215.     p->robust_list = null;  
  216. #ifdef config_compat  
  217.     p->compat_robust_list = null;  
  218. #endif  
  219.     init_list_head(&p->pi_state_list);  
  220.     p->pi_state_cache = null;  
  221. #endif  
  222.     /* 
  223.      * sigaltstack should be cleared when sharing the same vm 
  224.      */  
  225.     if ((clone_flags & (clone_vm|clone_vfork)) == clone_vm)  
  226.         p->sas_ss_sp = p->sas_ss_size = 0;  
  227.   
  228.     /* 
  229.      * syscall tracing should be turned off in the child regardless 
  230.      * of clone_ptrace. 
  231.      */  
  232.     clear_tsk_thread_flag(p, tif_syscall_trace);  
  233. #ifdef tif_syscall_emu  
  234.     clear_tsk_thread_flag(p, tif_syscall_emu);  
  235. #endif  
  236.     clear_all_latency_tracing(p);  
  237.   
  238.     /* ok, now we should be set up.. */  
  239.     p->exit_signal = (clone_flags & clone_thread) ? -1 : (clone_flags & csignal);  
  240.     p->pdeath_signal = 0;  
  241.     p->exit_state = 0;  
  242.   
  243.     /* 
  244.      * ok, make it visible to the rest of the system. 
  245.      * we dont wake it up yet. 
  246.      */  
  247.     p->group_leader = p;  
  248.     init_list_head(&p->thread_group);  
  249.   
  250.     /* now that the task is set up, run cgroup callbacks if 
  251.      * necessary. we need to run them before the task is visible 
  252.      * on the tasklist. */  
  253.     cgroup_fork_callbacks(p);  
  254.     cgroup_callbacks_done = 1;  
  255.   
  256.     /* need tasklist lock for parent etc handling! */  
  257.     write_lock_irq(&tasklist_lock);  
  258.   
  259.     /* clone_parent re-uses the old parent */  
  260.     if (clone_flags & (clone_parent|clone_thread)) {  
  261.         p->real_parent = current->real_parent;  
  262.         p->parent_exec_id = current->parent_exec_id;  
  263.     } else {  
  264.         p->real_parent = current;  
  265.         p->parent_exec_id = current->self_exec_id;  
  266.     }  
  267.   
  268.     spin_lock(¤t->sighand->siglock);  
  269.   
  270.     /* 
  271.      * process group and session signals need to be delivered to just the 
  272.      * parent before the fork or both the parent and the child after the 
  273.      * fork. restart if a signal comes in before we add the new process to 
  274.      * it's process group. 
  275.      * a fatal signal pending means that current will exit, so the new 
  276.      * thread can't slip out of an oom kill (or normal sigkill). 
  277.      */  
  278.     recalc_sigpending();  
  279.     if (signal_pending(current)) {  
  280.         spin_unlock(¤t->sighand->siglock);  
  281.         write_unlock_irq(&tasklist_lock);  
  282.         retval = -erestartnointr;  
  283.         goto bad_fork_free_pid;  
  284.     }  
  285.   
  286.     if (clone_flags & clone_thread) {  
  287.         atomic_inc(¤t->signal->count);  
  288.         atomic_inc(¤t->signal->live);  
  289.         p->group_leader = current->group_leader;  
  290.         list_add_tail_rcu(&p->thread_group, &p->group_leader->thread_group);  
  291.     }  
  292.   
  293.     if (likely(p->pid)) {  
  294.         list_add_tail(&p->sibling, &p->real_parent->children);  
  295.         tracehook_finish_clone(p, clone_flags, trace);  
  296.   
  297.         if (thread_group_leader(p)) {  
  298.             if (clone_flags & clone_newpid)  
  299.                 p->nsproxy->pid_ns->child_reaper = p;  
  300.   
  301.             p->signal->leader_pid = pid;  
  302.             tty_kref_put(p->signal->tty);  
  303.             p->signal->tty = tty_kref_get(current->signal->tty);  
  304.             attach_pid(p, pidtype_pgid, task_pgrp(current));  
  305.             attach_pid(p, pidtype_sid, task_session(current));  
  306.             list_add_tail_rcu(&p->tasks, &init_task.tasks);  
  307.             __get_cpu_var(process_counts) ;  
  308.         }  
  309.         attach_pid(p, pidtype_pid, pid);  
  310.         nr_threads ;  
  311.     }  
  312.   
  313.     total_forks ;  
  314.     spin_unlock(¤t->sighand->siglock);  
  315.     write_unlock_irq(&tasklist_lock);  
  316.     proc_fork_connector(p);  
  317.     cgroup_post_fork(p);  
  318.     perf_event_fork(p);  
  319.     return p;  
  320.   
  321. bad_fork_free_pid:  
  322.     if (pid != &init_struct_pid)  
  323.         free_pid(pid);  
  324. bad_fork_cleanup_io:  
  325.     put_io_context(p->io_context);  
  326. bad_fork_cleanup_namespaces:  
  327.     exit_task_namespaces(p);  
  328. bad_fork_cleanup_mm:  
  329.     if (p->mm)  
  330.         mmput(p->mm);  
  331. bad_fork_cleanup_signal:  
  332.     if (!(clone_flags & clone_thread))  
  333.         __cleanup_signal(p->signal);  
  334. bad_fork_cleanup_sighand:  
  335.     __cleanup_sighand(p->sighand);  
  336. bad_fork_cleanup_fs:  
  337.     exit_fs(p); /* blocking */  
  338. bad_fork_cleanup_files:  
  339.     exit_files(p); /* blocking */  
  340. bad_fork_cleanup_semundo:  
  341.     exit_sem(p);  
  342. bad_fork_cleanup_audit:  
  343.     audit_free(p);  
  344. bad_fork_cleanup_policy:  
  345.     perf_event_free_task(p);  
  346. #ifdef config_numa  
  347.     mpol_put(p->mempolicy);  
  348. bad_fork_cleanup_cgroup:  
  349. #endif  
  350.     cgroup_exit(p, cgroup_callbacks_done);  
  351.     delayacct_tsk_free(p);  
  352.     module_put(task_thread_info(p)->exec_domain->module);  
  353. bad_fork_cleanup_count:  
  354.     atomic_dec(&p->cred->user->processes);  
  355.     exit_creds(p);  
  356. bad_fork_free:  
  357.     free_task(p);  
  358. fork_out:  
  359.     return err_ptr(retval);  
  360. }  
    (1)定义返回值亦是retval和新的进程描述符task_struct结构p。
    (2)标志合法性检查。对clone_flags所传递的标志组合进行合法性检查。当出现以下三种情况时,返回出错代号:
    1)clone_newns和clone_fs同时被设置。前者标志表示子进程需要自己的命名空间,而后者标志则代表子进程共享父进程的根目录和当前工作目录,两者不可兼容。在传统的unix系统中,整个系统只有一个已经安装的文件系统树。每个进程从系统的根文件系统开始,通过合法的路径可以访问任何文件。在2.6版本中的内核中,每个进程都可以拥有属于自己的已安装文件系统树,也被称为命名空间。通常大多数进程都共享init进程所使用的已安装文件系统树,只有在clone_flags中设置了clone_newns标志时,才会为此新进程开辟一个新的命名空间。
    2)clone_thread被设置,但clone_sighand未被设置。如果子进程和父进程属于同一个线程组(clone_thread被设置),那么子进程必须共享父进程的信号(clone_sighand被设置)。
    3)clone_sighand被设置,但clone_vm未被设置。如果子进程共享父进程的信号,那么必须同时共享父进程的内存描述符和所有的页表(clone_vm被设置)。
    (3)安全性检查。通过调用security_task_create()和后面的security_task_alloc()执行所有附加的安全性检查。询问 linux security module (lsm) 看当前任务是否可以创建一个新任务。lsm是selinux的核心。
    (4)复制进程描述符。通过dup_task_struct()为子进程分配一个内核栈、thread_info结构和task_struct结构。注意,这里将当前进程描述符指针作为参数传递到此函数中。函数代码如下:
[cpp] 
  1. int __attribute__((weak)) arch_dup_task_struct(struct task_struct *dst,  
  2.                            struct task_struct *src)  
  3. {  
  4.     *dst = *src;  
  5.     return 0;  
  6. }  
  7.   
  8. static struct task_struct *dup_task_struct(struct task_struct *orig)  
  9. {  
  10.     struct task_struct *tsk;  
  11.     struct thread_info *ti;  
  12.     unsigned long *stackend;  
  13.   
  14.     int err;  
  15.   
  16.     prepare_to_copy(orig);  
  17.   
  18.     tsk = alloc_task_struct();  
  19.     if (!tsk)  
  20.         return null;  
  21.   
  22.     ti = alloc_thread_info(tsk);  
  23.     if (!ti) {  
  24.         free_task_struct(tsk);  
  25.         return null;  
  26.     }  
  27.   
  28.     err = arch_dup_task_struct(tsk, orig);  
  29.     if (err)  
  30.         goto out;  
  31.   
  32.     tsk->stack = ti;  
  33.   
  34.     err = prop_local_init_single(&tsk->dirties);  
  35.     if (err)  
  36.         goto out;  
  37.   
  38.     setup_thread_stack(tsk, orig);  
  39.     stackend = end_of_stack(tsk);  
  40.     *stackend = stack_end_magic;    /* 用于溢出检测 */  
  41.   
  42. #ifdef config_cc_stackprotector  
  43.     tsk->stack_canary = get_random_int();  
  44. #endif  
  45.   
  46.     /* one for us, one for whoever does the "release_task()" (usually parent) */  
  47.     atomic_set(&tsk->usage,2);  
  48.     atomic_set(&tsk->fs_excl, 0);  
  49. #ifdef config_blk_dev_io_trace  
  50.     tsk->btrace_seq = 0;  
  51. #endif  
  52.     tsk->splice_pipe = null;  
  53.   
  54.     account_kernel_stack(ti, 1);  
  55.   
  56.     return tsk;  
  57.   
  58. out:  
  59.     free_thread_info(ti);  
  60.     free_task_struct(tsk);  
  61.     return null;  
  62. }  
    首先,该函数分别定义了指向task_struct和thread_info结构体的指针。接着,prepare_to_copy为正式的分配进程描述符做一些准备工作。主要是将一些必要的寄存器的值保存到父进程的thread_info结构中。这些值会在稍后被复制到子进程的thread_info结构中。执行alloc_task_struct宏,该宏负责为子进程的进程描述符分配空间,将该片内存的首地址赋值给tsk,随后检查这片内存是否分配正确。执行alloc_thread_info宏,为子进程获取一块空闲的内存区,用来存放子进程的内核栈和thread_info结构,并将此会内存区的首地址赋值给ti变量,随后检查是否分配正确。
    上面已经说明过orig是传进来的current宏,指向当前进程描述符的指针。arch_dup_task_struct直接将orig指向的当前进程描述符内容复制到当前里程描述符tsk。接着,用atomic_set将子进程描述符的使用计数器设置为2,表示该进程描述符正在被使用并且处于活动状态。最后返回指向刚刚创建的子进程描述符内存区的指针。
    通过dup_task_struct可以看到,当这个函数成功操作之后,子进程和父进程的描述符中的内容是完全相同的。在稍后的copy_process代码中,我们将会看到子进程逐步与父进程区分开来。
    (5)一些初始化。通过诸如ftrace_graph_init_task,rt_mutex_init_task完成某些数据结构的初始化。调用copy_creds()复制证书(应该是复制权限及身份信息)。
    (6)检测系统中进程的总数量是否超过了max_threads所规定的进程最大数。
    (7)复制标志。通过copy_flags,将从do_fork()传递来的的clone_flags和pid分别赋值给子进程描述符中的对应字段。
    (8)初始化子进程描述符。初始化其中的各个字段,使得子进程和父进程逐渐区别出来。这部分工作包含初始化子进程中的children和sibling等队列头、初始化自旋锁和信号处理、初始化进程统计信息、初始化posix时钟、初始化调度相关的统计信息、初始化审计信息。它在copy_process函数中占据了相当长的一段的代码,不过考虑到task_struct结构本身的复杂性,也就不足为奇了。
    (9)调度器设置。调用sched_fork函数执行调度器相关的设置,为这个新进程分配cpu,使得子进程的进程状态为task_running。并禁止内核抢占。并且,为了不对其他进程的调度产生影响,此时子进程共享父进程的时间片。
    (10)复制进程的所有信息。根据clone_flags的具体取值来为子进程拷贝或共享父进程的某些数据结构。比如copy_semundo()、复制开放文件描述符(copy_files)、复制符号信息(copy_sighand 和 copy_signal)、复制进程内存(copy_mm)以及最终复制线程(copy_thread)。
    (11)复制线程。通过copy_threads()函数更新子进程的内核栈和寄存器中的值。在之前的dup_task_struct()中只是为子进程创建一个内核栈,至此才是真正的赋予它有意义的值。
    当父进程发出clone系统调用时,内核会将那个时候cpu中寄存器的值保存在父进程的内核栈中。这里就是使用父进程内核栈中的值来更新子进程寄存器中的值。特别的,内核将子进程eax寄存器中的值强制赋值为0,这也就是为什么使用fork()时子进程返回值是0。而在do_fork函数中则返回的是子进程的pid,这一点在上述内容中我们已经有所分析。另外,子进程的对应的thread_info结构中的esp字段会被初始化为子进程内核栈的基址。
    (12)分配pid。用alloc_pid函数为这个新进程分配一个pid,linux系统内的pid是循环使用的,采用位图方式来管理。简单的说,就是用每一位(bit)来标示该位所对应的pid是否被使用。分配完毕后,判断pid是否分配成功。成功则赋给p->pid。
    (13)更新属性和进程数量。根据clone_flags的值继续更新子进程的某些属性。将 nr_threads加一,表明新进程已经被加入到进程集合中。将total_forks加一,以记录被创建进程数量。
    (14)如果上述过程中某一步出现了错误,则通过goto语句跳到相应的错误代码处;如果成功执行完毕,则返回子进程的描述符p。
    至此,copy_process()的大致执行过程分析完毕。
    copy_process()执行完后返回do_fork(),do_fork()执行完毕后,虽然子进程处于可运行状态,但是它并没有立刻运行。至于子进程何时执行这完全取决于调度程序,也就是schedule()的事了。
    5. 进程调度
    创建好的进程最后被插入到运行队列中,它会通过 linux 调度程序来调度。linux调度程序维护了针对每个优先级别的一组列表,其中保存了 task_struct 引用。当正在运行的进程时间片用完时,时钟tick产生中断,调用kernel/sched.c:scheduler_tick()进程调度器的中断处理,中断返回后就会调用schedule()。运行队列中的任务通过 schedule 函数(在 ./linux/kernel/sched.c 内)来调用,它根据加载及进程执行历史决定最佳进程。这里并不涉及此函数的分析。
    6. 进程销毁
    进程销毁可以通过几个事件驱动 、通过正常的进程结束(当一个c程序从main函数返回时startup routine调用exit)、通过信号或是通过显式地对 exit 函数的调用。不管进程如何退出,进程的结束都要借助对内核函数 do_exit(在 ./linux/kernel/exit.c 内)的调用。函数的层次结构如下图:



图2 进程销毁的函数层次结构
    exit()调用通过0x80中断跳到sys_exit内核例程处,这个例程名称可以在./linux/include/linux/syscalls.h中找到(syscalls.h中导出所有平台无关的系统调用名称),定义为asmlinkage long sys_exit(int error_code); 它会直接调用do_exit。代码如下:


[cpp] 
  1. noret_type void do_exit(long code)  
  2. {  
  3.     struct task_struct *tsk = current;  
  4.     int group_dead;  
  5.   
  6.     profile_task_exit(tsk);  
  7.   
  8.     warn_on(atomic_read(&tsk->fs_excl));  
  9.   
  10.     if (unlikely(in_interrupt()))  
  11.         panic("aiee, killing interrupt handler!");  
  12.     if (unlikely(!tsk->pid))  
  13.         panic("attempted to kill the idle task!");  
  14.   
  15.     /* 
  16.      * if do_exit is called because this processes oopsed, it's possible 
  17.      * that get_fs() was left as kernel_ds, so reset it to user_ds before 
  18.      * continuing. amongst other possible reasons, this is to prevent 
  19.      * mm_release()->clear_child_tid() from writing to a user-controlled 
  20.      * kernel address. 
  21.      */  
  22.     set_fs(user_ds);  
  23.   
  24.     tracehook_report_exit(&code);  
  25.   
  26.     validate_creds_for_do_exit(tsk);  
  27.   
  28.     /* 
  29.      * we're taking recursive faults here in do_exit. safest is to just 
  30.      * leave this task alone and wait for reboot. 
  31.      */  
  32.     if (unlikely(tsk->flags & pf_exiting)) {  
  33.         printk(kern_alert  
  34.             "fixing recursive fault but reboot is needed!\n");  
  35.         /* 
  36.          * we can do this unlocked here. the futex code uses 
  37.          * this flag just to verify whether the pi state 
  38.          * cleanup has been done or not. in the worst case it 
  39.          * loops once more. we pretend that the cleanup was 
  40.          * done as there is no way to return. either the 
  41.          * owner_died bit is set by now or we push the blocked 
  42.          * task into the wait for ever nirwana as well. 
  43.          */  
  44.         tsk->flags |= pf_exitpidone;  
  45.         set_current_state(task_uninterruptible);  
  46.         schedule();  
  47.     }  
  48.   
  49.     exit_irq_thread();  
  50.   
  51.     exit_signals(tsk);  /* sets pf_exiting */  
  52.     /* 
  53.      * tsk->flags are checked in the futex code to protect against 
  54.      * an exiting task cleaning up the robust pi futexes. 
  55.      */  
  56.     smp_mb();  
  57.     spin_unlock_wait(&tsk->pi_lock);  
  58.   
  59.     if (unlikely(in_atomic()))  
  60.         printk(kern_info "note: %s[%d] exited with preempt_count %d\n",  
  61.                 current->comm, task_pid_nr(current),  
  62.                 preempt_count());  
  63.   
  64.     acct_update_integrals(tsk);  
  65.   
  66.     group_dead = atomic_dec_and_test(&tsk->signal->live);  
  67.     if (group_dead) {  
  68.         hrtimer_cancel(&tsk->signal->real_timer);  
  69.         exit_itimers(tsk->signal);  
  70.         if (tsk->mm)  
  71.             setmax_mm_hiwater_rss(&tsk->signal->maxrss, tsk->mm);  
  72.     }  
  73.     acct_collect(code, group_dead);  
  74.     if (group_dead)  
  75.         tty_audit_exit();  
  76.     if (unlikely(tsk->audit_context))  
  77.         audit_free(tsk);  
  78.   
  79.     tsk->exit_code = code;  
  80.     taskstats_exit(tsk, group_dead);  
  81.   
  82.     exit_mm(tsk);  
  83.   
  84.     if (group_dead)  
  85.         acct_process();  
  86.     trace_sched_process_exit(tsk);  
  87.   
  88.     exit_sem(tsk);  
  89.     exit_files(tsk);  
  90.     exit_fs(tsk);  
  91.     check_stack_usage();  
  92.     exit_thread();  
  93.     cgroup_exit(tsk, 1);  
  94.   
  95.     if (group_dead && tsk->signal->leader)  
  96.         disassociate_ctty(1);  
  97.   
  98.     module_put(task_thread_info(tsk)->exec_domain->module);  
  99.   
  100.     proc_exit_connector(tsk);  
  101.   
  102.     /* 
  103.      * flush inherited counters to the parent - before the parent 
  104.      * gets woken up by child-exit notifications. 
  105.      */  
  106.     perf_event_exit_task(tsk);  
  107.   
  108.     exit_notify(tsk, group_dead);  
  109. #ifdef config_numa  
  110.     mpol_put(tsk->mempolicy);  
  111.     tsk->mempolicy = null;  
  112. #endif  
  113. #ifdef config_futex  
  114.     if (unlikely(current->pi_state_cache))  
  115.         kfree(current->pi_state_cache);  
  116. #endif  
  117.     /* 
  118.      * make sure we are holding no locks: 
  119.      */  
  120.     debug_check_no_locks_held(tsk);  
  121.     /* 
  122.      * we can do this unlocked here. the futex code uses this flag 
  123.      * just to verify whether the pi state cleanup has been done 
  124.      * or not. in the worst case it loops once more. 
  125.      */  
  126.     tsk->flags |= pf_exitpidone;  
  127.   
  128.     if (tsk->io_context)  
  129.         exit_io_context();  
  130.   
  131.     if (tsk->splice_pipe)  
  132.         __free_pipe_info(tsk->splice_pipe);  
  133.   
  134.     validate_creds_for_do_exit(tsk);  
  135.   
  136.     preempt_disable();  
  137.     exit_rcu();  
  138.     /* causes final put_task_struct in finish_task_switch(). */  
  139.     tsk->state = task_dead;  
  140.     schedule();  
  141.     bug();  
  142.     /* avoid "noreturn function does return".  */  
  143.     for (;;)  
  144.         cpu_relax();    /* for when bug is null */  
  145. }  
  146.   
  147. export_symbol_gpl(do_exit);  
    (1)为进程销毁做一系列准备。用set_fs设置user_ds。注意如果do_exit是因为当前进程出现不可预知的错误而被调用,这时get_fs()有可能得到的仍然是kernel_ds状态,因此我们要重置它为user_ds状态。还有一个可能原因是这可以防止mm_release()->clear_child_tid()写一个被用户控制的内核地址。
    (2)清除所有信号处理函数。exit_signals函数会设置pf_exiting标志来表明进程正在退出,并清除所有信息处理函数。内核的其他方面会利用pf_exiting来防止在进程被删除时还试图处理此进程。
    (3)清除一系列的进程资源。比如比如 exit_mm删除内存页、exit_files关闭所有打开的文件描述符,这会清理i/o缓存,如果缓存中有数据,就会将它们写入相应的文件,以防止文件数据的丢失。exit_fs清除当前目录关联的inode、exit_thread清除线程信息、等等。
    (4)发出退出通知。调用exit_notify执行一系列通知。例如通知父进程我正在退出。如下:
[cpp] 
  1. static void exit_notify(struct task_struct *tsk, int group_dead)  
  2. {  
  3.     int signal;  
  4.     void *cookie;  
  5.   
  6.     /* 
  7.      * this does two things: 
  8.      * 
  9.      * a.  make init inherit all the child processes 
  10.      * b.  check to see if any process groups have become orphaned 
  11.      *  as a result of our exiting, and if they have any stopped 
  12.      *  jobs, send them a sighup and then a sigcont.  (posix 3.2.2.2) 
  13.      */  
  14.     forget_original_parent(tsk);  
  15.     exit_task_namespaces(tsk);  
  16.   
  17.     write_lock_irq(&tasklist_lock);  
  18.     if (group_dead)  
  19.         kill_orphaned_pgrp(tsk->group_leader, null);  
  20.   
  21.     /* let father know we died 
  22.      * 
  23.      * thread signals are configurable, but you aren't going to use 
  24.      * that to send signals to arbitary processes. 
  25.      * that stops right now. 
  26.      * 
  27.      * if the parent exec id doesn't match the exec id we saved 
  28.      * when we started then we know the parent has changed security 
  29.      * domain. 
  30.      * 
  31.      * if our self_exec id doesn't match our parent_exec_id then 
  32.      * we have changed execution domain as these two values started 
  33.      * the same after a fork. 
  34.      */  
  35.     if (tsk->exit_signal != sigchld && !task_detached(tsk) &&  
  36.         (tsk->parent_exec_id != tsk->real_parent->self_exec_id ||  
  37.          tsk->self_exec_id != tsk->parent_exec_id))  
  38.         tsk->exit_signal = sigchld;  
  39.   
  40.     signal = tracehook_notify_death(tsk, &cookie, group_dead);  
  41.     if (signal >= 0)  
  42.         signal = do_notify_parent(tsk, signal);  
  43.   
  44.     tsk->exit_state = signal == death_reap ? exit_dead : exit_zombie;  
  45.   
  46.     /* mt-exec, de_thread() is waiting for us */  
  47.     if (thread_group_leader(tsk) &&  
  48.         tsk->signal->group_exit_task &&  
  49.         tsk->signal->notify_count < 0)  
  50.         wake_up_process(tsk->signal->group_exit_task);  
  51.   
  52.     write_unlock_irq(&tasklist_lock);  
  53.   
  54.     tracehook_report_death(tsk, signal, cookie, group_dead);  
  55.   
  56.     /* if the process is dead, release it - nobody will wait for it */  
  57.     if (signal == death_reap)  
  58.         release_task(tsk);  
  59. }  
    exit_notify将当前进程的所有子进程的父进程id设置为1(init),让init接管所有这些子进程。如果当前进程是某个进程组的组长,其销毁导致进程组变为“无领导状态“,则向每个组内进程发送挂起信号sighup,然后发送sigcont。这是遵循posix 3.2.2.2标准。接着向自己的父进程发送sigchld信号,然后调用do_notify_parent通知父进程。若返回death_reap(这个意思是不管是否有其他进程关心本进程的退出信息,自动完成进程退出和pcb销毁),就直接进入exit_dead状态,如果不是,则就需要变为exit_zombie状态。
    注意在最初父进程创建子进程时,如果调用了waitpid()等待子进程结束(表示它关心子进程的状态),子进程结束时父进程会处理它发来的sigchild信号。如果不调用wait(表示它不关心子进程的死活状态),则不会处理子进程的sigchild信号。参看./linux/kernel/signal.c:do_notify_parent(),代码如下:
[cpp] 
  1. int do_notify_parent(struct task_struct *tsk, int sig)  
  2. {  
  3.     struct siginfo info;  
  4.     unsigned long flags;  
  5.     struct sighand_struct *psig;  
  6.     int ret = sig;  
  7.   
  8.     bug_on(sig == -1);  
  9.   
  10.     /* do_notify_parent_cldstop should have been called instead.  */  
  11.     bug_on(task_is_stopped_or_traced(tsk));  
  12.   
  13.     bug_on(!task_ptrace(tsk) &&  
  14.            (tsk->group_leader != tsk || !thread_group_empty(tsk)));  
  15.   
  16.     info.si_signo = sig;  
  17.     info.si_errno = 0;  
  18.     /* 
  19.      * we are under tasklist_lock here so our parent is tied to 
  20.      * us and cannot exit and release its namespace. 
  21.      * 
  22.      * the only it can is to switch its nsproxy with sys_unshare, 
  23.      * bu uncharing pid namespaces is not allowed, so we'll always 
  24.      * see relevant namespace 
  25.      * 
  26.      * write_lock() currently calls preempt_disable() which is the 
  27.      * same as rcu_read_lock(), but according to oleg, this is not 
  28.      * correct to rely on this 
  29.      */  
  30.     rcu_read_lock();  
  31.     info.si_pid = task_pid_nr_ns(tsk, tsk->parent->nsproxy->pid_ns);  
  32.     info.si_uid = __task_cred(tsk)->uid;  
  33.     rcu_read_unlock();  
  34.   
  35.     info.si_utime = cputime_to_clock_t(cputime_add(tsk->utime,  
  36.                 tsk->signal->utime));  
  37.     info.si_stime = cputime_to_clock_t(cputime_add(tsk->stime,  
  38.                 tsk->signal->stime));  
  39.   
  40.     info.si_status = tsk->exit_code & 0x7f;  
  41.     if (tsk->exit_code & 0x80)  
  42.         info.si_code = cld_dumped;  
  43.     else if (tsk->exit_code & 0x7f)  
  44.         info.si_code = cld_killed;  
  45.     else {  
  46.         info.si_code = cld_exited;  
  47.         info.si_status = tsk->exit_code >> 8;  
  48.     }  
  49.   
  50.     psig = tsk->parent->sighand;  
  51.     spin_lock_irqsave(&psig->siglock, flags);  
  52.     if (!task_ptrace(tsk) && sig == sigchld &&  
  53.         (psig->action[sigchld-1].sa.sa_handler == sig_ign ||  
  54.          (psig->action[sigchld-1].sa.sa_flags & sa_nocldwait))) {  
  55.         /* 
  56.          * we are exiting and our parent doesn't care.  posix.1 
  57.          * defines special semantics for setting sigchld to sig_ign 
  58.          * or setting the sa_nocldwait flag: we should be reaped 
  59.          * automatically and not left for our parent's wait4 call. 
  60.          * rather than having the parent do it as a magic kind of 
  61.          * signal handler, we just set this to tell do_exit that we 
  62.          * can be cleaned up without becoming a zombie.  note that 
  63.          * we still call __wake_up_parent in this case, because a 
  64.          * blocked sys_wait4 might now return -echild. 
  65.          * 
  66.          * whether we send sigchld or not for sa_nocldwait 
  67.          * is implementation-defined: we do (if you don't want 
  68.          * it, just use sig_ign instead). 
  69.          */  
  70.         ret = tsk->exit_signal = -1;  
  71.         if (psig->action[sigchld-1].sa.sa_handler == sig_ign)  
  72.             sig = -1;  
  73.     }  
  74.     if (valid_signal(sig) && sig > 0)  
  75.         __group_send_sig_info(sig, &info, tsk->parent);  
  76.     __wake_up_parent(tsk, tsk->parent);  
  77.     spin_unlock_irqrestore(&psig->siglock, flags);  
  78.   
  79.     return ret;  
  80. }  
    我们可以看到,如果父进程显示指定对子进程的sigchld信号处理为sig_ign,或者标志为sa_nocldwait,则返回的是ret=-1,即death_reap(这个宏在./linux/include/tracehook.h中定义为-1),这时在exit_notify中子进程马上变为exit_dead,表示我已退出并且死亡,最后被后面的release_task回收,将不会再有进程等待我。否则返回值与传入的信号值相同,子进程变成exit_zombie,表示已退出但还没死。不管有没有处理sigchld,do_notify_parent最后都会用__wake_up_parent来唤醒正在等待的父进程。
    可见子进程在结束前不一定都需要经过一个exit_zombie过程。如果父进程调用了waitpid等待子进程,则会显示处理它发来的sigchild信号,子进程结束时会自我清理(在do_exit中自己用release_task清理);如果父进程没有调用waitpid等待子进程,则不会处理sigchld信号,子进程不会马上被清理,而是变成exit_zombie状态,成为著名的僵尸进程。还有一种特殊情形,如果子进程退出时父进程恰好正在睡眠(sleep),导致没来得急处理sigchld,子进程也会成为僵尸,只要父进程在醒来后能调用waitpid,也能清理僵尸子进程,因为wait系统调用内部有清理僵尸子进程的代码。因此,如果父进程一直没有调用waitpid,那么僵尸子进程就只能等到父进程退出时被init接管了。init进程会负责清理这些僵尸进程(init肯定会调用wait)。
    我们可以写个简单的程序来验证,父进程创建10个子进程,子进程sleep一段时间后退出。第一种情况,父进程只对1~9号子进程调用waitpid(),1~9号子进程都正常结束,而在父进程结束前,pids[0]为exit_zombie。第二种情况,父进程创建10个子进程后,sleep()一段时间,在这段时间内_exit()的子进程都成为exit_zombie。父进程sleep()结束后,依次调用waitpid(),子进程马上变为exit_dead被清理。
    为了更好地理解怎么清理僵尸进程,我们简要地分析一下wait系统调用。wait族的系统调用如waitpid,wait4等,最后都会进入./linux/kernel/exit.c:do_wait()内核例程,而后函数链为do_wait()--->do_wait_thread()--->wait_consider_task(),在这里,如果子进程在exit_notify中设置的tsk->exit_state为exit_dead,就返回0,即wait系统调用返回,说明子进程不是僵尸进程,会自己用release_task进行回收。如果它的exit_state是exit_zombie,进入wait_task_zombie()。在这里使用xchg尝试把它的exit_state设置为exit_dead,可见父进程的wait4调用会把子进程由exit_zombie设置为exit_dead。最后wait_task_zombie()在末尾调用release_task()清理这个僵尸进程。
    (5)设置销毁标志并调度新的进程。在do_exit的最后,用exit_io_context清除io上下文、preempt_disable禁用抢占,设置进程状态为task_dead,然后调用./linux/kernel/sched.c:schedule()来选择一个将要执行的新进程。注意进程在退出并回收之后,其位于调度器的进程列表中的进程描述符(pcb)并没有立即释放,必须在设置task_struct的state为task_dead之后,由schedule()中的finish_task_switch()--->put_task_struct()把它的pcb重新放回到free list(可用列表)中,这时pcb才算释放,然后切换到新的进程。
阅读(3771) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~
")); function link(t){ var href= $(t).attr('href'); href ="?url=" encodeuricomponent(location.href); $(t).attr('href',href); //setcookie("returnouturl", location.href, 60, "/"); }
网站地图