免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 9360 | 回复: 2
打印 上一主题 下一主题

分享一个关于pthread线程栈在mm_struct里面的分布问题 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-11-09 15:01 |只看该作者 |倒序浏览
大家好,本人被下面这个问题困扰了一段时间,最近似乎找到了答案。
这里和大家分享一下,可能对有相同困惑的同学有点帮助,同时也请各位帮忙看看错漏的地方。

1================问题:
在使用pthread库创建两个线程时clone()被调用了两次,可以用strace 看到:
int
main()
{
...
        err=pthread_create(&tid, NULL, job, NULL);
        err=pthread_create(&tid1, NULL, job, NULL);
...
}

strace:
clone(Thread is running.
child_stack=0xb7efb4b4, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0xb7efbbd8, {entry_number:6, base_addr:0xb7efbb90, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}, child_tidptr=0xb7efbbd = 5104

clone(child_stack=0xb76fa4b4, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0xb76fabd8, {entry_number:6, base_addr:0xb76fab90, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}, child_tidptr=0xb76fabd = 5105

大家都知道clone()可以产生一个所谓的“轻量级进程”,也就是有独立的task_struct的,独立调度的东西。
再看看flag:CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID
这里对这些flag就不多说了对于我这个问题,最为重要的是CLONE_VM(共享内存描述符mm_struct和所有的页表)。
这点可以在copy_mm()里面看得到(在clone()中被调用):
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;
        }
...
}

这样我这里两个pthread和原来的main进程就共享同一个mm_struct了,如图1:

pthread线程之间的栈必定是独立的,不然不可能被独立调度,我的问题就在这里,3个task_struct 共用一个mm_struct,那么他们的栈应该怎么办?难道在一个栈里面?

2======================解答:
问题主要在两个函数上面,一个是clone()系统调用,一个是pthread_create()
man clone,看他的参数。参数列表中最为显眼的就是第二个参数void *child_stack,man里面是这样形容它的:
The  child_stack argument specifies the location of the stack used by the child process.  Since the child
and calling process may share memory, it is not possible for the child process to  execute  in  the  same
stack as the calling process.
调用clone()的时候是要自己提供子task的栈空间的,这个系统调用实在是太特别了。
在看看pthread的库是怎样给这个参数赋值的:
函数pthread_handle_create() (manager.c里面) 被调用来创建新线程,在这个函数里面调用了pthread_allocate_stack()
来分配栈空间:
static int pthread_allocate_stack(const pthread_attr_t *attr,
                                  pthread_descr default_new_thread,
                                  int pagesize,
                                  char ** out_new_thread,
                                  char ** out_new_thread_bottom,
                                  char ** out_guardaddr,
                                  size_t * out_guardsize,
                                  size_t * out_stacksize)
{
...
      map_addr = mmap(NULL, stacksize + guardsize,
                      PROT_READ | PROT_WRITE | PROT_EXEC,
                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
      if (map_addr == MAP_FAILED)
        /* No more memory available.  */
        return -1;
...
}

可以看到pthread库是通过调用mmap()来为新的线程创建栈空间的,可以在细看一下它的参数,它使用了flag
MAP_ANONYMOUS和MAP_PRIVATE,而且fd参数中用了-1,这样调用的结果是在进程空间中创建一个匿名的
线性区,这样就有了栈空间,而且这个空间也在原来的mm_struct里面,如图2:

光有空间不行,还要看看内核是怎样使用,我们来到copy_thread(),它在copy_process()中被调用(它们都在clone()里面被调用的):
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)
{
...
        retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
...
}

int copy_thread(int nr, 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;
...
}

这里可以清楚的看到,内核为这个用户线程初始化他未来的sp寄存器值,就是刚才mmap()返回的那个地址。所以结果
如图3:

附上pthread库,有兴趣的同学可以研究一下,好像还比较复杂。

[ 本帖最后由 pennyliang 于 2008-11-9 15:08 编辑 ]

论坛徽章:
0
2 [报告]
发表于 2008-11-10 22:02 |只看该作者
顶LZ一个。分析的很好,这种探索精神更好!

论坛徽章:
0
3 [报告]
发表于 2008-11-11 17:28 |只看该作者
呵呵,感谢zx_wing兄的鼓励。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP