Linux中fork()函数的底层实现【转】-阿里云开发者社区

开发者社区> 开发与运维> 正文

Linux中fork()函数的底层实现【转】

简介: 转自:http://blog.csdn.net/duoru_xiong/article/details/76358812 1. fork(),vfork(),clone()的区别 这三个系统调用的底层都是通过do_fork()内核函数实现,只不过是通过对do_fork()传递的不同参数来实现不同的功能。

转自:http://blog.csdn.net/duoru_xiong/article/details/76358812

1. fork(),vfork(),clone()的区别

  • 这三个系统调用的底层都是通过do_fork()内核函数实现,只不过是通过对do_fork()传递的不同参数来实现不同的功能。
  • 其中参数clone_flags由两部分组成,其最低的字节为信号类型,用以规定子进程去世时应该向父进程发出的信号;第二部分是一些表示资源和特性的标志位,来标识对于父进程的资源是拷贝还是通过指针共享。
  • fork(),这些标志全为0,表示要全部拷贝;vfork(),则是父、子进程公用虚存空间;clone(),由调用者设定并作为参数传递。

所以,fork()创建进程;vfork()是创建的线程,它是创建进程的中间步骤,如若创建进程后会直接调用exec(),则可用vfork()来减少不必要的拷贝;clone()创建线程,可以是内核线程,也可以是用户线程。

2. fork()的底层实现

  • fork()是通过调用do_fork()内核函数来实现的,在do_fork()中,通过查找位图,为子进程分配新的pid参数,再通过copy_process()复制父进程的PCB以及资源,最后检查clone_flags中CLONE_STOPPED是否被设置,如若未设置,将子进程先于父进程插于运行队列并唤醒,fork()完成。
  • 在这些步骤中,copy_proceee()完成fork()大部分任务,其中dup_task_struct()完成子进程在内核分空间分配以及对父进程task_struct,thread_info的拷贝,并使这两部分互指;在检查一系列clone_flags之后确定拷贝父进程的哪些资源:

    if ((retval = copy_semundo(clone_flags, p)))
        goto bad_fork_cleanup_audit;
    if ((retval = copy_files(clone_flags, p)))
        goto bad_fork_cleanup_semundo;
    if ((retval = copy_fs(clone_flags, p)))
        goto bad_fork_cleanup_files;
    if ((retval = copy_sighand(clone_flags, p)))
        goto bad_fork_cleanup_fs;
    if ((retval = copy_signal(clone_flags, p)))
        goto bad_fork_cleanup_sighand;
    if ((retval = copy_mm(clone_flags, p)))
        goto bad_fork_cleanup_signal;
    if ((retval = copy_keys(clone_flags, p)))
        goto bad_fork_cleanup_mm;
    if ((retval = copy_namespace(clone_flags, p)))
        goto bad_fork_cleanup_keys;
    retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);

    在此之后,设置子进程的状态,各个字段,并将其插入进程链表,copy_process()完成任务。
    在以上拷贝资源的内核函数中,最为重要的是copy_mm()以及copy_thread(),copy_mm()完成的是对父进程虚存空间的拷贝,而copy_thread()通过eax寄存器将子进程返回值强制置为0并初始化子进程内核栈。
    copy_mm()内核函数:
    (1)先完成对子进程mm_struct的拷贝:

    struct mm_struct * mm, *oldmm;
    int retval;

    tsk->min_flt = tsk->maj_flt = 0;
    tsk->nvcsw = tsk->nivcsw = 0;

    tsk->mm = NULL;
    tsk->active_mm = NULL;


  (2)判断是否是内核线程:

    if (!oldmm)
        return 0;


  (3)通过clone_flags标志位确定是否需要拷贝虚存空间:


    if (clone_flags & CLONE_VM) {//有标志位,共享父进程空间
        atomic_inc(&oldmm->mm_users);
        mm = oldmm;
        spin_unlock_wait(&oldmm->page_table_lock);
        goto good_mm;
    }
    //没有CLONE_VM标志,就必须创建一个新的地址空间。必须要有地址空间,即使此时并没有分配内存。
    retval = -ENOMEM;
    //分配一个新的内存描述符。把它的地址存放在新进程的mm中。
    mm = allocate_mm();
    if (!mm)
        goto fail_nomem;
    //并从当前进程复制mm的内容。
    memcpy(mm, oldmm, sizeof(*mm));
    if (!mm_init(mm))
        goto fail_nomem;

    ......

    //dup_mmap不但复制了线程区和页表,也设置了mm的一些属性.
    //它也会改变父进程的私有,可写的页为只读的,以使写时复制机制生效。
    retval = dup_mmap(mm, oldmm);
    if (retval)
        goto free_pt;


  至此,copy_mm()对父进程资源拷贝完毕。

 

fork()底层流程图如下:
这里写图片描述

PS:终于码完…第一篇博客, 如若有什么错误,欢迎大家指正,请将错误发送至xiongduoru@163.com,谢谢~

【作者】张昺华
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【facebook】 张昺华 zhangbinghua
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章