fork vfork clone 的比较 (待完成)

本文针对4.9.30内核版本,详细分析了fork, vfork, clone三个API在创建子进程时的资源复制与管理差异。fork会复制所有资源,可能导致系统波动;vfork不复制内存但可能导致句柄泄漏;而clone在某些情况下导致加锁失败。通过示例代码和内存使用情况展示了它们的工作原理。" 115743265,9278951,Linux上Maven项目部署Tomcat及热部署攻略,"['Linux', 'Java', 'Web开发', 'Maven', 'Tomcat']

最近项目团队中遇到了这几个API的问题,用某个都解决了一部分问题,

又引入了新问题(例如用fork在句柄多的进程里面会导致系统缓慢, vfork导致了句柄泄漏,clone又导致了后面加锁失败),因此这里仔细分析以下。

离开了内核版本讨论这个问题,都是不准确的,因此,本次分析是针对4.9.30版本内核,如果是其它版本,会指出。 编译&文档 编辑环境是ubuntu 16.04.

首先 对象用户的API: fork  vfork  clone 在内核中调用的都是 do_fork




SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
        return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
#else
        /* can not support in nommu mode */
        return -EINVAL;
#endif
}


SYSCALL_DEFINE0(vfork)
{
         return _do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
                        0, NULL, NULL, 0);
}



SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
                 int __user *, parent_tidptr,
                 int __user *, child_tidptr,
                 unsigned long, tls)
#endif
{
        return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls);
}

fork:

fork的参数是SIGCHLD,表示在子进程exit时,会发送SIGCHLD信号给父进程。 fork会复制父进程的所有资源,包括: 父进程的堆,代码段,栈, 文件句柄,信号等所有资源。 这个过程,在资源比较多的场景下,会对系统产生波动。 一段fork的代码可以证明这点

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>

#define ALLOC_MEM_SIZE 20 * 1024 * 1024 //20M 

int globalVar = 6666;

int main(int argc, char * argv []) 
{
    int localVar = 8888;

    char * s = (char*)malloc(ALLOC_MEM_SIZE );    
    memset(s, 0, ALLOC_MEM_SIZE);//make VIRT -> RSS
 
    FILE * fp = fopen("1.txt", "w+");
    if(!fp)printf("fopen error\n");
    
    struct timeval startTime;
    struct timeval endTime;
    long costTime;

    gettimeofday(&startTime,NULL);

    pid_t id = fork(); 

    if(id < 0) printf("fork error\n");

    if(id == 0)
    {   
        gettimeofday(&endTime,NULL);
        costTime = (endTime.tv_sec - startTime.tv_sec)*1000*1000 + endTime.tv_usec - startTime.tv_usec;    
        printf("fork: costTime = %ld\n", costTime);
        globalVar++;
        localVar++;
        printf("fork: child,  pid = %d, globalVar = %d, &globalVar = %p, localVar = %d, &localVar = %p\n", 
                                            getpid(), globalVar, &globalVar, localVar, &localVar);
        sleep(1000);
    }
    else
    {
        sleep(1);

        printf("fork: parent, pid = %d, globalVar = %d, &globalVar = %p, localVar = %d, &localVar = %p\n",
                                            getpid(), globalVar, &globalVar, localVar, &localVar);
        sleep(1000);
    }

    return 0;
}

从上图可以看出,子进程继承了父进程全局变量(全局数据区) 也继承了父进程的局部变量(stack), 变量的虚拟内存地址虽然一样,但是都是在各自的虚存空间中,也指向不同的物理内存。 试图开启地址随机化(echo 0 >/proc/sys/kernel/randomize_va_space ),让子进程的变量虚拟地址和父进程不一致,但是没有区别,还没理解。



上图可以看见,1.txt这个文件句柄被子进程给继承了。


从上图可以看出, 子进程拷贝了父进程的内存,虚存VIRT一样大小,实际内存(RES)也接近。


下面代码是_do_fork的

/*
 *  Ok, this is the main fork-routine.
 *
 * It copies the process, and if successful kick-starts
 * it and waits for it to finish using the VM if required.
 */
long _do_fork(unsigned long clone_flags,
              unsigned long stack_start,
              unsigned long stack_size,
              int __user *parent_tidptr,
              int __user *child_tidptr,
              unsigned long tls)
{
        struct task_struct *p;
        int trace = 0;
        long nr;

        /*
         * Determine whether and which event to report to ptracer.  When
         * called from kernel_thread or CLONE_UNTRACED is explicitly
         * requested, no event is reported; otherwise, report if the event
         * for the type of forking is enabled.
         */
        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, stack_start, stack_size,
                         child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
        add_latent_entropy();
        /*
         * Do this prior waking up the new thread - the thread pointer
         * might get invalid after that point, if the thread exits quickly.
         */
        if (!IS_ERR(p)) {
                struct completion vfork;
                struct pid *pid;

                trace_sched_process_fork(current, p);

                pid = get_task_pid(p, PIDTYPE_PID);
                nr = pid_vnr(pid);

                if (clone_flags & CLONE_PARENT_SETTID)
                        put_user(nr, parent_tidptr);

                if (clone_flags & CLONE_VFORK) {
                        p->vfork_done = &vfork;
                        init_completion(&vfork);
                        get_task_struct(p);
                }

                wake_up_new_task(p);

                /* forking complete and child started to run, tell ptracer */
                if (unlikely(trace))
                        ptrace_event_pid(trace, pid);

                if (clone_flags & CLONE_VFORK) {
                        if (!wait_for_vfork_done(p, &vfork))
                                ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
                }

                put_pid(pid);
        } else {
                nr = PTR_ERR(p);
        }
        return nr;
}

static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
{
        ......

        if (clone_flags & CLONE_VM) {
                atomic_inc(&oldmm->mm_users);
                mm = oldmm;
                goto good_mm;
        }

        retval = -ENOMEM;
        mm = dup_mm(tsk);
        if (!mm)
               goto fail_nomem;

good_mm:
        tsk->mm = mm;
        tsk->active_mm = mm;
        return 0;

fail_nomem:
        return retval;
}

从上面的代码,可以看出 vfork 传入了 CLONE_VM 参数,在这里,就不会在拷贝内存了(dup_mm)。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值