muti-thread & fork

来源:互联网 发布:工控软件下载 编辑:程序博客网 时间:2024/05/01 12:52

1 要点

fork会共用原来的代码段,对于数据段和堆栈进行“写时拷贝”, 对于内核全局变量应用,例如文件句柄进行+1。

因此fork会产生一个和原来进程占用内存一样的进程,注意只是和原来进程的内存模型一样,而不会产生和父进程一样的多线程进程,fork后的子进程会成为一个单线程进程,其他线程默认终止,这个单线程即是发生fork调用时的线程。


2  原型分析

在kernel/fork.c我们找到了fork函数原型:

long do_fork(unsigned long clone_flags,      unsigned long stack_start,      struct pt_regs *regs,      unsigned long stack_size,      int __user *parent_tidptr,      int __user *child_tidptr){...p = copy_process(clone_flags, stack_start, regs, stack_size, child_tidptr, NULL, trace);/* * Do this prior waking up the new thread - the thread pointer * might get invalid after that point, if the thread exits quickly. */...}
省略的部分为标志判断,和新进程任务调度代码,其核心工作都是由copy_process完成。

static struct 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){...retval = security_task_create(clone_flags);if (retval)goto fork_out;retval = -ENOMEM;p = dup_task_struct(current);if (!p)goto fork_out;ftrace_graph_init_task(p);rt_mutex_init_task(p);.../* Perform scheduler related setup. Assign this task to a CPU. */sched_fork(p);retval = perf_event_init_task(p);if (retval)goto bad_fork_cleanup_policy;retval = audit_alloc(p);if (retval)goto bad_fork_cleanup_policy;/* copy all the process information */retval = copy_semundo(clone_flags, p);if (retval)goto bad_fork_cleanup_audit;retval = copy_files(clone_flags, p);if (retval)goto bad_fork_cleanup_semundo;retval = copy_fs(clone_flags, p);if (retval)goto bad_fork_cleanup_files;retval = copy_sighand(clone_flags, p);if (retval)goto bad_fork_cleanup_fs;retval = copy_signal(clone_flags, p);if (retval)goto bad_fork_cleanup_sighand;retval = copy_mm(clone_flags, p);if (retval)goto bad_fork_cleanup_signal;retval = copy_namespaces(clone_flags, p);if (retval)goto bad_fork_cleanup_mm;retval = copy_io(clone_flags, p);if (retval)goto bad_fork_cleanup_namespaces;<strong>retval = copy_thread(clone_flags, stack_start, stack_size, p, regs);</strong>...}
为了结构清晰,省略了大量代码。

int copy_thread(unsigned long clone_flags, unsigned long sp,unsigned long unused,struct task_struct *p, struct pt_regs *regs){struct pt_regs *childregs;struct task_struct *tsk;int err;childregs = task_pt_regs(p);*childregs = *regs;childregs->ax = 0;childregs->sp = sp;p->thread.sp = (unsigned long) childregs;p->thread.sp0 = (unsigned long) (childregs+1);p->thread.ip = (unsigned long) ret_from_fork;task_user_gs(p) = get_user_gs(regs);p->fpu_counter = 0;p->thread.io_bitmap_ptr = NULL;tsk = current;err = -ENOMEM;memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP))) {p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr,IO_BITMAP_BYTES, GFP_KERNEL);if (!p->thread.io_bitmap_ptr) {p->thread.io_bitmap_max = 0;return -ENOMEM;}set_tsk_thread_flag(p, TIF_IO_BITMAP);}err = 0;/* * Set a new TLS for the child thread? */if (clone_flags & CLONE_SETTLS)err = do_set_thread_area(p, -1,(struct user_desc __user *)childregs->si, 0);if (err && p->thread.io_bitmap_ptr) {kfree(p->thread.io_bitmap_ptr);p->thread.io_bitmap_max = 0;}return err;}
copy_thread的主要工作室设置线程栈, tls, 寄存器等信息。

从上面可以看出,对于多线程fork,并不会产生一个多线程进程,只会产生一个和多线程占用内存一样大小的单线程进程,posix线程id即是父线程中的posix 线程id。


3 测试结论

#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <pthread.h>#include <sys/types.h>#include <sys/wait.h>#define __NR_gettid 186void *f1(){printf("tid:%ld\n", pthread_self());sleep(100000000);}int main(){int i = 0;pthread_t pth1[20]; while(i++<20){pthread_create(&pth1[i], NULL, f1, NULL);sleep(1);}printf("create thread finish!!!\n");sleep(10);int status;int ret = fork();if(ret == 0){printf("child: parent pid: %d, tid:%ld\n", getpid(), pthread_self());sleep(30);printf("clild exit.");return;}else if(ret > 0){printf("parent: parent pid: %d, tid:%ld\n", getpid(), pthread_self());waitpid(-1, &status, 0);}pause();}


由上图也可以看出,父进程有20个线程,子线程只有一个线程,但他们占用的内存一样大。

4 总结

多线程中调用fork并不会导致内存泄露,因为子进程退出后,所有资源由系统自动销毁,但是如果子进程进入死循环,则有可能导致资源不足。

另一方面,由于子进程复制父进程的内存及变量信息,会导致一些全局锁,信号量重复锁定的问题。所以尽量不要在多线程中调用fork,如果必须,在调用fork后立即调用exec覆盖子进程是一个不错的方案,对于无法立即执行exec的程序,需要调用pthread_atfork()进行各个资源的释放。



0 0
原创粉丝点击