AI智能
改变未来

Linux进程(三):进程的创建

Linux进程(三):进程的创建

  • fork
  • copy-on-write(COW)
  • vfork
  • clone
    • clone, fork, vfork区别与联系
    • referfence

    fork

    ​   

    fork

    用来创建一个进程,当我们的进程执行到了

    fork

    的时候,系统将为我们复制一份进程资源,并且两个进程都将从

    fork

    函数返回。

    ​   从内核的调度层面来说,只要一个进程存在

    task_struct

    ,那么该进程就可以被调度。所以在我们的父进程把子进程

    fork

    出来的时刻,父进程会将该进程内的资源拷贝给子进程。

    ​   当一个进程刚刚被创建出来的时候执行的是一个

    copy

    ,但是任何秀海都将造成父子进程资源的分类,如:

    chroot

    open

    、写

    memory

    mmap

    sigaction

    等。

    ​   

    task_struct

    中,文件资源、信号资源等资源的分裂都比较好实现,唯一困难的就是内存资源的分裂,因为我们需要探测是谁在写哪一块内存内容,且对于fork来讲,有一个很讨厌的东西叫exec系列的系统调用,它会勾引子进程另起炉灶。如果创建子进程就要内存拷贝的的话,一执行exec,辛辛苦苦拷贝的内存又被完全放弃了。由于fork()后会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,处于效率考虑,linux中引入了“写时复制技术-Copy-On-Write”。接下来就是内存资源分裂的写时分裂技术。

    copy-on-write(COW)

    ​   在最开始时,父进程的虚拟地址为

    virt1

    ,物理地址为

    phy1

    ,此时进程根据

    MMU

    可通过虚拟地址查询到物理地址并且获取内存中的数据。且当前内存数据段的权限为

    R+W

    ​   当父进程通过

    fork

    创建出子进程时,此时子进程的虚拟地址以及物理地址都和父进程的相同,但是Linux将页表当中这一页所对应的访问权限变成了

    RD-ONLY

    只读权限。

    ​   若子进程或父进程在某个需要修改内存中的数据时,CPU一旦往一块

    RD-ONLY

    权限的内存页中写数据,将会收到一个

    page fault

    (缺页中断)。假设此刻子进程需要向这块内存中写入数据,CPU收到中断之后将分配一块新的物理内存给子进程,此时子进程将得到一片新的物理地址(如上图中的

    phy2

    ),然后Linux内核会将之前的

    phy1

    中的内容拷贝到

    phy2

    中去,然后修改子进程的页表,使得子进程的虚拟地址

    virt1

    指向新的物理地址

    phy2

    此时,父子进程的虚拟地址都是相同的,但指向的物理地址则是各自不同的。

    ​   在此之后,Linux内核将两个进程的页表的访问权限都改成

    R+W

    ,父子进程实现内存分裂。

    ​   

    copy-on-write

    技术严重依赖于CPU中的MMU(memory management unit)。若CPU中没有MMU,则

    fork

    是不能工作的。

    ​   在Linux2.6之前(2.6版本之后Linux系统支持了无MMU的CPU),在没有MMU的CPU中是不可能执行

    copy-on-write

    的,所以在这样的CPU中去跑Linux时是没有

    fork

    的,只有

    vfork


    vfork

    ​   

    vfork

    fork

    的其中一点区别主要在于**

    vfork

    在调用之后将阻塞父进程,直到子进程调用

    _exit

    exec

    这两个系统调用。**同时

    vfork

    fork

    还有一点区别:内存分裂技术不同。

    ​   在执行

    vfork

    时,父进程的

    mm_struct

    不再对拷给子进程,而是子进程的

    task_struct

    中的

    mm

    指针直接指向父进程

    mm_struct

    指向的结构体。

    可用以下代码做一个小测试:

    int data = 10;int child_process(){printf(\"Child process %d, data %d\\n\",getpid(),data);//注意:执行data=20这行代码的代价时Linux内核其实是做了一系列操作://首先,由于此块内存时只读的,所以将发生缺页中断//发生缺页中断后Linux内核将申请一块新的内存//申请完内存后内核把该进程的虚拟地址指向新的物理地址,并把老的物理页中的内容拷贝到新的物理页中//拷完之后Linux把父子进程中的内存访问权限都改为R+W,并把pc指针再次指向data=20执行data = 20;printf(\"Child process %d, data %d\\n\",getpid(),data);_exit(0);}int main(int argc, char* argv[]){if(vfork()==0) {child_process();}else{sleep(1);printf(\"Parent process %d, data %d\\n\",getpid(), data);}}

      这个程序的输出为:

    在此处,

    vfork

    相当于

    clone

    函数将flags标志设置为

    CLONE_VM

    CLONE_VFORK

    ,接下来我们就介绍更加强大的函数:

    clone

    clone

    ​   

    clone

    是Linux为创建线程设计的(虽然也可以用

    clone

    创建进程)。所以可以说

    clone

    是fork的升级版本,不仅可以创建进程或者线程,还可以指定创建新的命名空间(namespace)、有选择的继承父进程的内存、甚至可以将创建出来的进程变成父进程的兄弟进程等等。

    ​   

    clone

    函数功能强大,带了众多参数,它提供了一个非常灵活自由的常见进程的方法。因此由他创建的进程要比前面2种方法要复杂。

    clone

    可以让你有选择性的继承父进程的资源,你可以选择想

    vfork

    一样和父进程共享一个虚存空间,从而使创造的是线程,你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系。先有必要说下这个函数的结构:

    int clone(int (*fn)(void*), void *child_stack, int flags, void *arg);

    fn

    为函数指针,此指针指向一个函数体,即想要创建进程的静态程序(我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本\”, );

    `child_stack`为给子进程分配系统堆栈的指针(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块`task_struct`的值);

    arg

    就是传给子进程的参数一般为(0);

    flags

    为要复制资源的标志,描述你需要从父进程继承那些资源(是资源复制还是共享,在这里设置参数:

    下面是

    flags

    可以取的值:

    参数标志 含义
    CLONE_PARENT
    创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
    CLONE_FS
    子进程与父进程共享相同的文件系统,包括root、当前目录、

    umask
    CLONE_FILES
    子进程与父进程共享相同的文件描述符(file descriptor)表
    CLONE_NEWNS
    为子进程创建新的命名空间
    CLONE_SIGHAND
    子进程与父进程共享相同的信号处理(signal handler)表
    CLONE_PTRACE
    若父进程被trace,子进程也被trace,继续调试子进程
    CLONE_VFORK
    父进程被挂起,直至子进程释放虚拟内存资源
    CLONE_VM
    子进程与父进程运行于相同的内存空间
    CLONE_PID
    子进程在创建时PID与父进程一致
    CLONE_THREAD
    Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群
    CLONE_IDLETASK

    PID

    设置为0(只供idle进程使用)

    CLONE_SETTID
    将TID会写至用户空间
    CLONE_SITTLS
    为子进程创建新的TLS
    CLONE_SYSVSEM
    父子进程共享System V SEM_UNDO语义
    CLONE_UNTRACE
    防止跟踪进程在子进程上强制执行

    CLONE_PTRACE
    CLONE_STOP

    TASK_STOPPED

    状态开始子进程

    ​   当我们使用

    pthread_create

    创建线程时,本质上也是通过调用Linux系统中的

    clone()

    函数,但此时

    pthread_create

    函数调用

    clone

    时会将子进程的各种资源指针全部设为和父进程相同的指针:

    clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0)

    ​   上面的代码产生的结果和调用

    fork()

    差不多,只是父子俩共享地址空间、文件系统资源、文件描述符和信号处理程序。换个说法就是,新建的进程和它的父进程就是流行的所谓线程。这种方式其实就是LWP的实现。

    clone, fork, vfork区别与联系

      系统调用服务例程

    sys_clone

    sys_fork

    sys_vfork

    三者最终都是调用

    do_fork

    函数完成.

      

    do_fork

    的参数与

    clone

    系统调用的参数类似, 不过多了一个regs(内核栈保存的用户模式寄存器). 实际上其他的参数也都是用regs取的。

      三者具体调用do_fork时的参数不同。

    referfence

    http://blog.csdn.net/gogokongyin/article/details/51334773

    赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » Linux进程(三):进程的创建