Chinaunix首页 | 论坛 | 博客
  • 博客访问: 11531
  • 博文数量: 4
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2013-11-07 13:23
文章分类
文章存档

2013年(4)

我的朋友

分类: LINUX

2013-11-07 13:26:04

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

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


  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中的一个联合体当中的:


  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的实现。



  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结构。注意,这里将当前进程描述符指针作为参数传递到此函数中。函数代码如下:
  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。代码如下:


  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执行一系列通知。例如通知父进程我正在退出。如下:
  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(),代码如下:
  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才算释放,然后切换到新的进程。
阅读(1286) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~