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;
}

猜你喜欢

转载自blog.csdn.net/liushengxi_root/article/details/81332740