进程创建

作者: ice_593e | 来源:发表于2019-02-21 19:46 被阅读0次

    目标

    在《进程入门01》中主要是从概念和理论上来描述进程,本章从fork()入手,从内核代码角度去观察一个进程的创建过程,更接地气的去感受真实世界里进程的具体形态。

    本文代码是基于linux-2.6.34版本。

    系统调用

    为了解释fork()系统调用从用户态发起到内核态响应的过程,简单概述一下Linux中系统调用的概念和原理。

    系统调用是内核向用户态程序提供的服务接口。用户态进程通过系统调用可以申请内核中一些资源和服务。内核提供系统调用的目的主要是为了隔离资源操作权限、保护系统稳定性。

    系统调用是通过软中断(中断号为0x80)来实现的,内核提前将所有系统调用的服务(函数)进行编号,如fork()对应的系统调用号为57。用户态程序使用系统调用时,根据接口约定指定好系统调用编号,设置好相关的参数,然后触发0x80中断陷入内核,中断响应处理函数根据系统调用编号来调用相应的服务例程代码,等处理函数返回后,用户态程序按约定获取返回值就可以了。

    注:通过0x80中断(int 0x80)触发调用内核服务的方式已经过时了,现在使用syscall/sysret指令(x86_64)和sysenter/sysexit指令(x86_32)来调用内核的函数,速度更快。

    x86_64的Linux内核,系统调用编号在<asm/unistd_64.h>文件中定义:

    ...
    #define __NR_clone 56
    #define __NR_fork 57
    #define __NR_vfork 58
    #define __NR_execve 59
    #define __NR_exit 60
    #define __NR_wait4 61
    #define __NR_kill 62
    ...
    

    内核为每个系统调用都设置了相应的处理入口函数。sys_fork()就是用来处理__NR_fork系统调用的入口函数。

    sys_fork()

    sys_fork()函数在<asm/syscalls.h>文件中声明,在kernel/process.c中实现。

    int sys_fork(struct pt_regs *regs)
    {
            return do_fork(SIGCHLD, regs->sp, regs, 0, NULL, NULL);
    }
    

    do_fork()完成了大部分创建的工作,在kernel/fork.c文件中实现。do_fork()函数主要调用copy_process()完成进程复制,并唤醒新的子进程让其投入运行。

    /*
     *  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.
     */
    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)
    {
            //......
            //创建子进程数据结构,并复制父进程数据
            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);
    
                    audit_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);
    
                    tracehook_report_clone_complete(trace, regs,
                                                    clone_flags, nr, p);
            } else {
                    nr = PTR_ERR(p);
            }
            return nr;
    }
    

    复制父进程数据

    copy_process()函数主要工作如下:

    1. 调用dup_task_struct()函数分配新的内核栈、thread_info结构和task_struct描述符,并复制父进程描述符和thread_info结构数据。
    2. 检查并确保新创建的子进程后,进程数量没有超过资源的限制。
    3. 初始化子进程描述符,与父进程区别分来。
    4. 调用sched_fork()函数,设置与调度相关的信息,将进程状态设置为TASK_WAKING
    5. 调用copy_flags()函数更新子进程task_struct的flags字段。
    6. 调用alloc_pid()函数为新的子进程分配PID
    7. 根据clone_flags参数标志,复制或共享父进程的文件描述符、文件系统、信号处理函数、地址空间、命名空间等。
    8. 最后,返回子进程的task_struct指针。
    /*
     * This creates a new process as a copy of the old one,
     * but does not actually start it yet.
     *
     * It copies the registers, and all the appropriate
     * parts of the process environment (as per the clone
     * flags). The actual kick-off is left to the caller.
     */
    static struct task_struct *copy_process(unsigned long clone_flags,
                                            unsigned long stack_start,
                                            struct pt_regs *regs,
                                            unsigned long stack_size,
                                            int __user *child_tidptr,
                                            struct pid *pid,
                                            int trace)
    {
            //...
            //创建新的进程描述符和thread_info,复制父进程描述符信息
            p = dup_task_struct(current);
            //检查进程数量是否超过限制
            if (nr_threads >= max_threads)
                    goto bad_fork_cleanup_count;
    
            //初始化子进程描述符,与父进程区分开
            p->did_exec = 0; 
            delayacct_tsk_init(p);  /* Must remain after dup_task_struct() */
            copy_flags(clone_flags, p);
            INIT_LIST_HEAD(&p->children);
            INIT_LIST_HEAD(&p->sibling);
            rcu_copy_process(p);
            p->vfork_done = NULL;
            spin_lock_init(&p->alloc_lock);
    
            init_sigpending(&p->pending);
            //...
    
            //初始化与调度相关的字段
            sched_fork(p, clone_flags);
    
            retval = perf_event_init_task(p);
            if (retval)
                    goto bad_fork_cleanup_policy;
    
            if ((retval = audit_alloc(p)))
                    goto bad_fork_cleanup_policy;
            /* copy all the process information */
            //如果CLONE_SYSVSEM置位,则使用父进程的System V信号量
            if ((retval = copy_semundo(clone_flags, p))) 
                    goto bad_fork_cleanup_audit;
            //如果CLONE_FILES置位,则共享父进程的文件描述符,否则创建新的files数组,其包含的信息与父进程相同
            if ((retval = copy_files(clone_flags, p))) 
                    goto bad_fork_cleanup_semundo;
            //如果CLONE_FS置位,则共享父进程的文件系统
            if ((retval = copy_fs(clone_flags, p))) 
                    goto bad_fork_cleanup_files;
            //如果CLONE_SIGHAND置位,则共享父进程的信号处理程序
            if ((retval = copy_sighand(clone_flags, p))) 
                    goto bad_fork_cleanup_fs;
            //如果CLONE_THREAD置位,则共享父进程信号处理相关数据信息
            if ((retval = copy_signal(clone_flags, p))) 
                    goto bad_fork_cleanup_sighand;
            //如果CLONE_VM置位,则共享父进程的内存地址空间
            if ((retval = copy_mm(clone_flags, p))) 
                    goto bad_fork_cleanup_signal;
            //命名空间处理刚好与前面的CLONE相反,如果设置了CLONE_NEWxyz相关的标志,则创建新的命名空间,否则共享父进程的命令空间
            if ((retval = copy_namespaces(clone_flags, p))) 
                    goto bad_fork_cleanup_mm;
            //如果CLONE_IO置位,则共享父进程的IO上下文信息
            if ((retval = copy_io(clone_flags, p))) 
                    goto bad_fork_cleanup_namespaces;
            //copy_thread()函数与特殊cpu体系结构相关,该函数主要处理进程使用的寄存器、进程切换相关的字段(主要是p->thread)
            retval = copy_thread(clone_flags, stack_start, stack_size, p, regs);
            if (retval)
                    goto bad_fork_cleanup_io;
    
            //在指定的PID命名空间中申请新PID
            if (pid != &init_struct_pid) {
                    pid = alloc_pid(p->nsproxy->pid_ns);
            }
    
            p->pid = pid_nr(pid);
            p->tgid = p->pid;
    
            if (likely(p->pid)) {
                    tracehook_finish_clone(p, clone_flags, trace);
    
                    if (thread_group_leader(p)) {
                            if (clone_flags & CLONE_NEWPID)
                                    p->nsproxy->pid_ns->child_reaper = p;
    
                            p->signal->leader_pid = pid;
                            tty_kref_put(p->signal->tty);
                            p->signal->tty = tty_kref_get(current->signal->tty);
                            attach_pid(p, PIDTYPE_PGID, task_pgrp(current));
                            attach_pid(p, PIDTYPE_SID, task_session(current));
                            list_add_tail(&p->sibling, &p->real_parent->children);
                            list_add_tail_rcu(&p->tasks, &init_task.tasks);
                            __get_cpu_var(process_counts)++;
                    }
                    attach_pid(p, PIDTYPE_PID, pid);
                    nr_threads++;
            }
    
            total_forks++;
            spin_unlock(&current->sighand->siglock);
            write_unlock_irq(&tasklist_lock);
            proc_fork_connector(p);
            cgroup_post_fork(p);
            perf_event_fork(p);
            return p;
            //......
    }
    
    int __attribute__((weak)) arch_dup_task_struct(struct task_struct *dst,
                                                   struct task_struct *src)
    {
            *dst = *src;
            return 0;
    }
    
    static struct task_struct *dup_task_struct(struct task_struct *orig)
    {
            //...
            //为子进程创建的进程描述符task_struct
            tsk = alloc_task_struct();
            //为子进程创建thread_info
            ti = alloc_thread_info(tsk);
            tsk->stack = ti;
            //复制父进程的描述符结构(字段复制)
            err = arch_dup_task_struct(tsk, orig);
            //复制父进程的thread_info结构(字段复制)
            setup_thread_stack(tsk, orig);
            //...
            return tsk;
    }
    

    唤醒子进程

    do_fork()函数调用copy_process()函数填充子进程相关数据结构后,调用wake_up_new_task()函数将子进程放入运行队列,唤醒子进程

    void wake_up_new_task(struct task_struct *p, unsigned long clone_flags)
    {
        unsigned long flags;
        struct rq *rq;
        int cpu __maybe_unused = get_cpu();
    
        rq = cpu_rq(cpu); //获取cpu对应的运行队列(每个cpu都维护一个运行队列)
        raw_spin_lock_irqsave(&rq->lock, flags);
    
        BUG_ON(p->state != TASK_WAKING);
        p->state = TASK_RUNNING; //设置进程状态为运行态
        update_rq_clock(rq);
        activate_task(rq, p, 0); //激活进程,将进程放入运行队列中
        trace_sched_wakeup_new(rq, p, 1);
        check_preempt_curr(rq, p, WF_FORK);
        task_rq_unlock(rq, &flags);
        put_cpu();
    }
    
    /*
     * activate_task - move a task to the runqueue.
     */
    static void activate_task(struct rq *rq, struct task_struct *p, int wakeup)
    {
        if (task_contributes_to_load(p))
            rq->nr_uninterruptible--;
    
        enqueue_task(rq, p, wakeup, false); //放入运行队列中
        inc_nr_running(rq);
    }
    
    static void
    enqueue_task(struct rq *rq, struct task_struct *p, int wakeup, bool head)
    {
        if (wakeup)
            p->se.start_runtime = p->se.sum_exec_runtime;
    
        sched_info_queued(p);
        p->sched_class->enqueue_task(rq, p, wakeup, head);
        p->se.on_rq = 1;
    }
    

    相关文章

      网友评论

        本文标题:进程创建

        本文链接:https://www.haomeiwen.com/subject/luwdyqtx.html