• 2019-2020-1 20199312《Linux内核原理与分析》第七周作业


    进程的描述

    • 操作系统内核实现操作系统的三大管理功能

      • 进程管理(内核中最核心的功能)
      • 内存管理
      • 文件系统
    • 在操作系统中,我们通过进程控制块PCB描述进程。

    • 为了管理进程,内核必须对每个进程进行清晰的描述,进程描述符提供了内核所需了解的进程信息。

    • 用数据结构struct task_struct 来描述进程(涉及内容过于庞杂)

    • 进程描述符的示意图

    • Linux内核管理的进程状态转换图

    • 开始实验,更新menu

    • 对重要sys_clone等处各设置断点
    (gdb)b sys_clone
    (gdb)b do_fork
    (gdb)b copy_process
    (gdb)b dup_task_struct
    (gdb)b copy_thread
    (gdb)b ret_from_fork
    

    • 断点常用命令合集
    • 开始调试



    do_fork函数

    long do_fork(unsigned long clone_flags,
              unsigned long stack_start,
              unsigned long stack_size,
              int __user *parent_tidptr,
              int __user *child_tidptr)
    {
        struct task_struct *p;
        int trace = 0;
        long nr;
    
        // ...
        
        // 复制进程描述符,返回创建的task_struct的指针
        p = copy_process(clone_flags, stack_start, stack_size,
                 child_tidptr, NULL, trace);
    
        if (!IS_ERR(p)) {
            struct completion vfork;
            struct pid *pid;
    
            trace_sched_process_fork(current, p);
    
            // 取出task结构体内的pid
            pid = get_task_pid(p, PIDTYPE_PID);
            nr = pid_vnr(pid);
    
            if (clone_flags & CLONE_PARENT_SETTID)
                put_user(nr, parent_tidptr);
    
            // 如果使用的是vfork,那么必须采用某种完成机制,确保父进程后运行
            if (clone_flags & CLONE_VFORK) {
                p->vfork_done = &vfork;
                init_completion(&vfork);
                get_task_struct(p);
            }
    
            // 将子进程添加到调度器的队列,使得子进程有机会获得CPU
            wake_up_new_task(p);
    
            // ...
    
            // 如果设置了 CLONE_VFORK 则将父进程插入等待队列,并挂起父进程直到子进程释放自己的内存空间
            // 保证子进程优先于父进程运行
            if (clone_flags & CLONE_VFORK) {
                if (!wait_for_vfork_done(p, &vfork))
                    ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
            }
    
            put_pid(pid);
        } else {
            nr = PTR_ERR(p);
        }
        return nr;
    }
    

    调用copy_process,将当期进程复制一份出来为子进程,并且为子进程设置相应地上下文信息。初始化vfork的完成处理信息(如果是vfork调用)调用wake_up_new_task,将子进程放入调度器的队列中,此时的子进程就可以被调度进程选中,得以运行。

    • copy_process的部分代码:
    static struct task_struct *copy_process(unsigned long clone_flags,
                        unsigned long stack_start,
                        unsigned long stack_size,
                        int __user *child_tidptr,
                        struct pid *pid,
                        int trace)
    {
        int retval;
        struct task_struct *p;
        ...
        retval = security_task_create(clone_flags);//安全性检查
        ...
        p = dup_task_struct(current);   //复制PCB,为子进程创建内核栈、进程描述符
        ftrace_graph_init_task(p);
        ···
        
        retval = -EAGAIN;
        // 检查该用户的进程数是否超过限制
        if (atomic_read(&p->real_cred->user->processes) >=
                task_rlimit(p, RLIMIT_NPROC)) {
            // 检查该用户是否具有相关权限,不一定是root
            if (p->real_cred->user != INIT_USER &&
                !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
                goto bad_fork_free;
        }
        ...
        // 检查进程数量是否超过 max_threads,后者取决于内存的大小
        if (nr_threads >= max_threads)
            goto bad_fork_cleanup_count;
    
        if (!try_module_get(task_thread_info(p)->exec_domain->module))
            goto bad_fork_cleanup_count;
        ...
        spin_lock_init(&p->alloc_lock);          //初始化自旋锁
        init_sigpending(&p->pending);           //初始化挂起信号 
        posix_cpu_timers_init(p);               //初始化CPU定时器
        ···
        retval = sched_fork(clone_flags, p);  //初始化新进程调度程序数据结构,把新进程的状态设置为TASK_RUNNING,并禁止内核抢占
        ...
        // 复制所有的进程信息
        shm_init_task(p);
        retval = copy_semundo(clone_flags, p);
        ...
        retval = copy_files(clone_flags, p);
        ...
        retval = copy_fs(clone_flags, p);
        ...
        retval = copy_sighand(clone_flags, p);
        ...
        retval = copy_signal(clone_flags, p);
        ...
        retval = copy_mm(clone_flags, p);
        ...
        retval = copy_namespaces(clone_flags, p);
        ...
        retval = copy_io(clone_flags, p);
        ...
        retval = copy_thread(clone_flags, stack_start, stack_size, p);// 初始化子进程内核栈
        ...
        //若传进来的pid指针和全局结构体变量init_struct_pid的地址不相同,就要为子进程分配新的pid
        if (pid != &init_struct_pid) {
            retval = -ENOMEM;
            pid = alloc_pid(p->nsproxy->pid_ns_for_children);
            if (!pid)
                goto bad_fork_cleanup_io;
        }
    
        ...
        p->pid = pid_nr(pid);    //根据pid结构体中获得进程pid
        //若 clone_flags 包含 CLONE_THREAD标志,说明子进程和父进程在同一个线程组
        if (clone_flags & CLONE_THREAD) {
            p->exit_signal = -1;
            p->group_leader = current->group_leader; //线程组的leader设为子进程的组leader
            p->tgid = current->tgid;       //子进程继承父进程的tgid
        } else {
            if (clone_flags & CLONE_PARENT)
                p->exit_signal = current->group_leader->exit_signal;
            else
                p->exit_signal = (clone_flags & CSIGNAL);
            p->group_leader = p;          //子进程的组leader就是它自己
            
           
            p->tgid = p->pid;        //组号tgid是它自己的pid
        }
    
        ...
        
        if (likely(p->pid)) {
            ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace);
    
            init_task_pid(p, PIDTYPE_PID, pid);
            if (thread_group_leader(p)) {
                ...
                // 将子进程加入它所在组的哈希链表中
                attach_pid(p, PIDTYPE_PGID);
                attach_pid(p, PIDTYPE_SID);
                __this_cpu_inc(process_counts);
            } else {
                ...
            }
            attach_pid(p, PIDTYPE_PID);
            nr_threads++;     //增加系统中的进程数目
        }
        ...
        return p;             //返回被创建的子进程描述符指针P
        ...
    }
    

    创建进程描述符以及子进程所需要的其他所有数据结构,为子进程准备运行环境。调用dup_task_struct复制一份task_struct结构体,作为子进程的进程描述符。复制所有的进程信息。调用copy_thread,设置子进程的堆栈信息,为子进程分配一个pid。

    • dup_task_struct的代码:
    static struct task_struct *dup_task_struct(struct task_struct *orig)
    {
        struct task_struct *tsk;
        struct thread_info *ti;
        int node = tsk_fork_get_node(orig);
        int err;
    
        // 分配一个task_struct结点
        tsk = alloc_task_struct_node(node);
        if (!tsk)
            return NULL;
    
        // 分配一个thread_info结点,其实内部分配了一个union,包含进程的内核栈
        // 此时ti的值为栈底,在x86下为union的高地址处。
        ti = alloc_thread_info_node(tsk, node);
        if (!ti)
            goto free_tsk;
    
        err = arch_dup_task_struct(tsk, orig);
        if (err)
            goto free_ti;
    
        // 将栈底的值赋给新结点的stack
        tsk->stack = ti;
        
        ...
    
        /*
         * One for us, one for whoever does the "release_task()" (usually
         * parent)
         */
        // 将进程描述符的使用计数器置为2
        atomic_set(&tsk->usage, 2);
    #ifdef CONFIG_BLK_DEV_IO_TRACE
        tsk->btrace_seq = 0;
    #endif
        tsk->splice_pipe = NULL;
        tsk->task_frag.page = NULL;
    
        account_kernel_stack(ti, 1);
    
        // 返回新申请的结点
        return tsk;
    
    free_ti:
        free_thread_info(ti);
    free_tsk:
        free_task_struct(tsk);
        return NULL;
    }
    

    先调用alloc_task_struct_node分配一个task_struct结构体。调用alloc_thread_info_node,分配了一个union。这里分配了一个thread_info结构体,还分配了一个stack数组。返回值为ti,实际上就是栈底。tsk->stack = ti将栈底的地址赋给task的stack变量。最后为子进程分配了内核栈空间。执行完dup_task_struct之后,子进程和父进程的task结构体,除了stack指针之外,完全相同

    总结

    进程的创建过程大致是复制进程描述符、一一复制其他进程资源(采用分时复制技术)、分配子进程的内核堆栈并对内核堆栈关键信息进行初始化。

  • 相关阅读:
    DOM-DOMTree-查找

    用sql语句操作数据库
    数据查询基础
    【案例】城市地址三级联动
    【案例】城市地址二级联动
    【案例】高仿百度注册页
    【案例】自由运动小球
    【案例】使用上下左右键控制元素的移动
    事件
  • 原文地址:https://www.cnblogs.com/banpingcu/p/11788621.html
Copyright © 2020-2023  润新知