• linux 内核源码 fork 解读


       前言,记得某一次开会的时候,学长学姐就说过让我们去看fork源码,结果一直没有时间去看(其实是懒),这不,正好碰上这次开进程的讲座,就在讲座之前看了一波源码,也算是了了一波自己阅读源码的心愿 。

      首先我们得基本了解一下,task_structthread_info结构是怎么一回事。

    1. linux中的PCB的实体(task_struct

    其实标题已经说的很清楚了。它就是我们常说的进程控制块。

    PCB通常记载进程之相关信息,包括:

    1. 进程状态:可以是new、ready、running、waiting或 blocked等。
    2. 程序计数器:接着要运行的指令地址。
    3. CPU寄存器:如累加器、变址寄存器、堆栈指针以及一般用途寄存器、状况代码等, 主要用途在于中断时暂时存储数据,以便稍后继续利用;其数量及类别因计算机体系结构有所差异。
    4. CPU排班法:优先级、排班队列等指针以及其他参数。
    5. 存储器管理:如标签页表等。
    6. 会计信息:如CPU与实际时间之使用数量、时限、账号、工作或进程号码。
    7. 输入输出状态:配置进程使用I/O设备,如磁带机。

    总言之,PCB如其名,内容不脱离各进程相关信息。

    内核使用双向循环链表的任务队列来存放进程,使用结构体task_struct来描述进程所有信息。

    1 进程描述符task_struct
    struct task_struct { }结构体相当大,大约1.7K字节。大概列出一些看看:

    struct task_struct
    {
        struct thread_info      thread_info; //必须是第一个元素
        //这个是进程的运行时状态,-1代表不可运行,0代表可运行,>0代表已停止。
        volatile long state;
        /* 
        flags是进程当前的状态标志,具体的如:
        0x00000002表示进程正在被创建; //通过宏定义实现
        0x00000004表示进程正准备退出; 
        0x00000040 表示此进程被fork出,但是并没有执行exec;
        0x00000400表示此进程由于其他进程发送相关信号而被杀死 。
        */
        unsigned int flags;
        void *stack;    //  指向内核栈的指针,通过他就可以找到thread_info
        //这个是进程号
        pid_t pid;
    
        //该结构体描述了虚拟内存的当前状态
        struct mm_struct *mm;
        ......
    };

    这里写图片描述

    2. thread_info 结构与内核栈

    当进程由于中断或系统调用从用户态转换到内核态时,进程所使用的栈也要从用户栈切换到内核栈

    内核空间就使用这个内核栈。因为内核控制路径使用很少的栈空间,所以只需要几千个字节的内核态堆栈。

    thread_info 就相当于进程在内核中的一个远方亲戚(内核中的PCB),各自都能通过一个指针指向对方。

    这里写图片描述

    struct thread_info {
        struct pcb_struct   pcb;        /* palcode state */
    
        struct task_struct  *task;      /* main task structure */
        unsigned int        flags;      /* low level flags */
        unsigned int        ieee_state; /* see fpu.h */
    
        mm_segment_t        addr_limit; /* thread address space */
        unsigned        cpu;        /* current CPU */
        int         preempt_count; /* 0 => preemptable, <0 => BUG */
        unsigned int        status;     /* thread-synchronous flags */
    
        int bpt_nsaved;
        unsigned long bpt_addr[2];      /* breakpoint handling  */
        unsigned int bpt_insn[2];
    };

    内核处理进程就是通过进程描述符task_struct结构体对象来操作。所以操作进程要获取当前正在运行的进程描述符。通过thread_info的地址就可以找到task_struct地址;在不同的体系结构上计算thread_info的偏移地址不同。

    比较两种结构:thread_info更加贴近于系统所处的体系结构,而task_struct比较抽象化,脱离体系而存在。

    3. 深入理解 fork

    (1)系统调用:

    这里写图片描述
    在 Linux 内核中,供用户创建进程的API调用有fork(),vfork(),clone() ,这三个函数的对应的系统调用是 sys_fork()、sys_clone()、sys_vfork()。

    这三个函数都是通过调用内核函数 do_fork() 来实现的,而现代linux内核do_fork()又调用了_do_fork( )函数,所以重点来了,我们只需要把关注点放在_do_fork( )函数即可

    (2)_do_fork() 函数

    long _do_fork(unsigned long clone_flags, 
            //从clone_flag参数标志来表明进程创建的方式
                  unsigned long stack_start,
                  unsigned long stack_size,
                  int __user *parent_tidptr,
                  int __user *child_tidptr,
                  unsigned long tls)
    {
        1. 检查参数 
        2. strct task_struct *p;
        3. p = copy_process(clone_flags, stack_start, stack_size,
                            child_tidptr, NULL, trace, tls);
        // 将进程插入运行队列,此时状态为TASK_RUNNING
        4.wake_up_new_task(p);
        5. return ? ;
    }

    (2)copy_process( )函数

    /*/*
     * This creates a new process as a copy of the old one,
     * but does not actually start it yet.
     * 根据clone_flags标志拷贝寄存器,以及其他进程环境
     * 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. 
     * 搞好的这个新的进程的启动由调用者完成启动 
     */
    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)
    {     struct task_struct *p;
           //创建进程内核栈和进程描述符
           p = dup_task_struct(current);
           //得到的进程与父进程内容完全一致,初始化新创建进程
          retval = copy_thread_tls(clone_flags, stack_start, stack_size, p, tls);
          //将寄存器%ax置为0,也是子进程pid返回0的原因 
          pid = alloc_pid(p->nsproxy->pid_ns_for_children); //分配新的 Pid 
           ……
           return p;
    }

    (3)dup_task_struct()函数

    /*为新进程创建新的内核堆栈(hread_info)和PCB(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);
    
           //创建进程描述符对象 
           tsk = alloc_task_struct_node(node);
           //创建进程内核栈 thread_info
           ti = alloc_thread_info_node(tsk, node);
           //使子进程描述符和父进程一致,为什么会一直
           err = arch_dup_task_struct(tsk, orig);
           //进程描述符stack指向thread_info
           tsk->stack = ti;
           //使子进程thread_info内容与父进程一致但task指向子进程task_struct
           setup_thread_stack(tsk, orig);
           return tsk;
    }
  • 相关阅读:
    Thinkphp M方法出错,D方法却可以
    Composer项目安装依赖包
    wamp httpd-vhosts.conf
    博客园报错 Mixed Content: The page at 'https://i.cnblogs.com/EditPosts.aspx?opt=1' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://upload.cnblogs.com/imageuploa
    Thinkphp js、css压缩类minify
    Thinkphp 不足之处
    Thinkphp 调试方法
    Lavavel 程序报错 MassAssignmentException in Model.php line 452: _token
    Laravel 安装mysql、表增加模拟数据、生成控制器
    Laravel 安装登录模块
  • 原文地址:https://www.cnblogs.com/Tattoo-Welkin/p/10335269.html
Copyright © 2020-2023  润新知