Linux进程线程源码浅析

Posted ty_laurel

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux进程线程源码浅析相关的知识,希望对你有一定的参考价值。

内核版本3.13

概述

       Linux内核中,进程通过数据结构task_struct(也称为进程描述符) 被表示成任务(task),不像其他的操作系统会区别进程、轻量级进程和线程(下边就统称进程吧),Linux系统用 task_struct 数据结构来表示所有的执行上下文。对于每一个进程,一个类型为task_struct的进程描述符始终存在于内存中。它包含了内核管理全部进程所需的重要信息,如调度参数、已打开的文件描述符列表等。进程描述符从进程被创建开始就一直存在于内核堆栈之中。 
       Linu对进程标识符(PID) 和任务标识符(TID)进行了区分。这两个分量都存储在任务数据结构task_struct 中。当调用clone函数创建一个新进程而不需要和旧进程共享任何信息时,会设置一个新的PID,否则,任务得到一个新的任务标识符TID,但是PID不变。这样一来,一个进程中所有的线程都会拥有与该进程中第一个线程相同的PID。

创建过程介绍

        创建一个新进程会为其创建要给新的进程描述符和用户空间,然后从父进程复制大量的内容,如子进程被赋予一个PID,并建立它的内存映射,同时它也被赋予了访问属于父进程文件的权利。然后,它的寄存器内容被初始化并准备运行。 
       当系统调用fork执行的时候,调用fork函数的进程陷入内核并且创建一个task_struct结构和其他相关的数据结构,如内核堆栈和thread_info结构。这个结构位于进程堆栈栈底固定偏移量的地方,包含一些进程参数,以及进程描述符的地址。把进程描述符的地址存储在一个固定的地方,使得Linux系统只需要进行很少的有效操作就可以找到一个运行中进程的task_struct。 
       进程描述符的主要内容根据父进程的进程描述符来填充。Linux系统只需要寻找一个可用的PID,更新进程标识符散列表的表项使之指向新的任务数据结构即可。如果散列表发生冲突,相同键值的进程描述符会被组成链表 。它会把task_struct 结构中的一些分量设置为指向任务数组中相应进程的前一/后一进程的指针。 
       理论上,现在就应该为子进程分配数据段、堆栈段,并且对父进程的段进行复制,因为fork函数意味着父、子进程之间不共享内存。其中如果代码段是只读的,可以复制也可以共享。然后,子进程就可以运行了。 但是,实际上复制内存的代价相当的昂贵,所以现代Linux系统都使用了欺骗手段。在最开始主要依赖于父进程来创建子进程用户空间,在创建的过程中所做的工作仅仅是建立mm_struct结构、vm_area_struct结构以及页目录和页表,并没有真正地复制一个物理页面。它们赋予子进程属于它自己的页表,但是这些页表都指向父进程的页面,同时把这些页面标记成只读。当子进程试图向某一页面中写入数据的时候,它会收到写保护的错误。内核发现子进程的写入行为之后,会为子进程分配一个该页面的新副本,并将这个副本标记为可读、可写,即就是为子进程分配一个对应的物理页面。通过这种方式,使得只有需要写入数据的页面才会被复制。这种机制称为写时复制机制(Copy-on-Write)。它所带来的好处就是不需要在内存中维护同一个程序的两个副本,从而节省了内存RAM。

一步一步源码分析

linux中创建进程和线程一般都是使用fork()和pthread_create(),接下来就可以对其分别使用strace命令进行追踪,确定其系统调用函数。

 
  
   //fork创建进程strace追踪
  
  
   tiany@tiany-desktop:~/program/C/pthread$ strace ./fork.o
  
  
   ......
  
  
   clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,child_tidptr=0x7fb1e8fe7a10) = 3599
  
  
    
  
  
    
  
  
   //pthread_create创建线程
  
  
   tiany@tiany-desktop:~/program/C/pthread$ strace ./pthread_create.o
  
  
   ……
  
  
   clone(xchild_stack=0x7f683d393fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|  CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f683d3949d0, tls=0x7f683d394700, child_tidptr=0x7f683d3949d0) = 3878
  
 

       由strace结果可以看到,无论是fork创建进程还是pthread_create创建线程,最终都是使用系统调用clone来实现的。两者主要就是参数不一致,特别是clone_flags标志。接下来就进入内核进行深入的分析吧。先看下刚刚提到的clone_flags标志,如下。 

 
  
   /*
  
  
    * cloning flags:
  
  
    */
  
  
   #define CSIGNAL     0x000000ff  /* signal mask to be sent at exit */
  
  
   #define CLONE_VM    0x00000100  /* set if VM shared between processes */
  
  
   #define CLONE_FS    0x00000200  /* set if fs info shared between processes */
  
  
   #define CLONE_FILES 0x00000400  /* set if open files shared between processes */
  
  
   #define CLONE_SIGHAND   0x00000800  /* set if signal handlers and blocked signals shared */
  
  
   #define CLONE_PTRACE    0x00002000  /* set if we want to let tracing continue on the child too */
  
  
   #define CLONE_VFORK 0x00004000  /* set if the parent wants the child to wake it up on mm_release */
  
  
   #define CLONE_PARENT    0x00008000  /* set if we want to have the same parent as the cloner */
  
  
   #define CLONE_THREAD    0x00010000  /* Same thread group? */
  
  
   #define CLONE_NEWNS 0x00020000  /* New namespace group? */
  
  
   #define CLONE_SYSVSEM   0x00040000  /* share system V SEM_UNDO semantics */
  
  
   #define CLONE_SETTLS    0x00080000  /* create a new TLS for the child */
  
  
   #define CLONE_PARENT_SETTID 0x00100000  /* set the TID in the parent */
  
  
   #define CLONE_CHILD_CLEARTID    0x00200000  /* clear the TID in the child */
  
  
   #define CLONE_DETACHED      0x00400000  /* Unused, ignored */
  
  
   #define CLONE_UNTRACED      0x00800000  /* set if the tracing process can't force CLONE_PTRACE on this clone */
  
  
   #define CLONE_CHILD_SETTID  0x01000000  /* set the TID in the child */
  
  
   /* 0x02000000 was previously the unused CLONE_STOPPED (Start in stopped state)
  
  
      and is now available for re-use. */
  
  
   #define CLONE_NEWUTS        0x04000000  /* New utsname group? */
  
  
   #define CLONE_NEWIPC        0x08000000  /* New ipcs */
  
  
   #define CLONE_NEWUSER       0x10000000  /* New user namespace */
  
  
   #define CLONE_NEWPID        0x20000000  /* New pid namespace */
  
  
   #define CLONE_NEWNET        0x40000000  /* New network namespace */
  
  
   #define CLONE_IO        0x80000000  /* Clone io context */
  
 

 
这些flag在创建进程线程时是非常重要的,通过这些标志一般就基本上可以确定创建的是进程还是线程。接下来就真正进入内核,看看fork、vfork、clone等函数的实现,如下:

 
  
   //fork.c
    
  
  
   #ifdef __ARCH_WANT_SYS_CLONE
  
  
   #ifdef CONFIG_CLONE_BACKWARDS
  
  
   SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
  
  
            int __user *, parent_tidptr,
  
  
            int, tls_val,
  
  
            int __user *, child_tidptr)
  
  
   #elif defined(CONFIG_CLONE_BACKWARDS2)
  
  
   SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,
  
  
            int __user *, parent_tidptr,
  
  
            int __user *, child_tidptr,
  
  
            int, tls_val)
  
  
   #elif defined(CONFIG_CLONE_BACKWARDS3)
  
  
   SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,
  
  
           int, stack_size,
  
  
           int __user *, parent_tidptr,
  
  
           int __user *, child_tidptr,
  
  
           int, tls_val)
  
  
   #else
  
  
   SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
  
  
            int __user *, parent_tidptr,
  
  
            int __user *, child_tidptr,
  
  
            int, tls_val)
  
  
   #endif
  
  
   
  
  
       return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
  
  
   
  
  
   #endif
  
 

do_fork函数

实际上边的那些函数最终都是调用do_fork()函数来实现的。

 
  
   /*
  
  
    这是fork的主程序。复制进程,如果需要的话,并等待它使用VM完成
  
  
    @clone_flags: 低字节指定子进程结束时发送到父进程的信号代码(通常是SIGCHILD),高位保存了其他的标志flags,如CLONE_VM
  
  
    @stack_start: 用户态下,栈的起始地址
  
  
    @stack_size: 为未使用(被设置为0)
  
  
    @stack_parent_tidptr: 用户态下父进程的TID指针
  
  
    @stack_child_tidptr: 用户态下子进程的TID指针
  
  
    */
  
  
   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;
  
  
    
  
  
       /*
  
  
        * 
   确定是否以及哪些事件向追踪者报告。 当从kernel_thread或CLONE_UNTRACED被调用被显式请求时,没有事件被报告; 否则,报告是否启用了分支类型的事件
  
  
        * 下边的if语句部分主要是对参数clone_flag组合的正确性进行检查,因为标志需要遵循一定的规则,若不符合,则返回错误代码
  
  
        */
  
  
       if (!(clone_flags & CLONE_UNTRACED)) 
  
  
           if (clone_flags & CLONE_VFORK)
  
  
               trace = PTRACE_EVENT_VFORK;
  
  
           else if ((clone_flags & CSIGNAL) != SIGCHLD)
  
  
               trace = PTRACE_EVENT_CLONE;
  
  
           else
  
  
               trace = PTRACE_EVENT_FORK;
  
  
    
  
  
           if (likely(!ptrace_event_enabled(current, trace)))
  
  
               trace = 0;
  
  
       
  
  
    
  
  
       p = copy_process(clone_flags浅析Linux线程调度

Linux 线程浅析

flask 源码浅析(flask 如何处理请求(多线程,多进程,IO多路复用))

浅析Linux下进程的调度策略与优先级

浅析Java的Thread.join函数

Nginx线程池浅析