大家好,本人被下面这个问题困扰了一段时间,最近似乎找到了答案。
这里和大家分享一下,可能对有相同困惑的同学有点帮助,同时也请各位帮忙看看错漏的地方。

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库,有兴趣的同学可以研究一下,好像还比较复杂。

分享地址:http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=2018590

分享一个关于pthread线程栈在mm_struct里面的分布问题的更多相关文章

  1. 分享一个Python脚本--统计redis key类型数据大小分布

    概述 今天主要介绍怎么统计redis key类型数据大小分布. 原理:使用redis命令: scan.pipline.type 和 debug object 来得到 redis key 信息. 脚本 ...

  2. Java性能分析之线程栈详解与性能分析

    Java性能分析之线程栈详解 Java性能分析迈不过去的一个关键点是线程栈,新的性能班级也讲到了JVM这一块,所以本篇文章对线程栈进行基础知识普及以及如何对线程栈进行性能分析. 基本概念 线程堆栈也称 ...

  3. 分享一个自制的 .net线程池

    扯淡 由于项目需求,需要开发一些程序去爬取一些网站的信息,算是小爬虫程序吧.爬网页这东西是要经过网络传输,如果程序运行起来串行执行请求爬取,会很慢,我想没人会这样做.为了提高爬取效率,必须使用多线程并 ...

  4. thread线程栈size及局部变量最大可分配size【转】

    转自:http://blog.csdn.net/sunny04/article/details/46805261 版权声明:本文为博主原创文章,未经博主允许不得转载. 进程是操作系统的最小资源管理单元 ...

  5. [并发并行]_[线程模型]_[Pthread线程使用模型之一管道Pipeline]

    场景 1.经常在Windows, MacOSX 开发C多线程程序的时候, 经常需要和线程打交道, 如果开发人员的数量不多时, 同时掌握Win32和pthread线程 并不是容易的事情, 而且使用Win ...

  6. Linux虚拟地址空间布局以及进程栈和线程栈总结【转】

    转自:http://www.cnblogs.com/xzzzh/p/6596982.html 原文链接:http://blog.csdn.net/freeelinux/article/details/ ...

  7. Linux虚拟地址空间布局以及进程栈和线程栈总结

    原文链接:http://blog.csdn.net/freeelinux/article/details/53782986[侵删] 本文转自多个博客,以及最后有我的总结.我没有单独从头到尾写一个总结的 ...

  8. 多线程编程之pthread线程深入理解

    不同的平台和操作系统上 进程和线程的实现机制不完全一致  但是一般来说线程栈都是独立的 只要得到地址就可以相互访问       Pthread是 POSIX threads 的简称,是POSIX的线程 ...

  9. 【C/C++多线程编程之五】pthread线程深入理解

    多线程编程之pthread线程深入理解       Pthread是 POSIX threads 的简称,是POSIX的线程标准.           前几篇博客已经能给你初步的多线程概念.在进一步学 ...

随机推荐

  1. Java学习第十七天

    1:登录注册案例(理解) 2:Set集合(理解) (1)Set集合的特点 无序,唯一 (2)HashSet集合(掌握) A:底层数据结构是哈希表(是一个元素为链表的数组) B:哈希表底层依赖两个方法: ...

  2. CVE-2018-7600-Drupal远程代码执行漏洞-Render API

    今天学习一下Drupal的另一个漏洞,由于渲染数组不当造成的漏洞 poc: url:http://localhost/drupal-8.5.0/user/register?element_parent ...

  3. Java Timer定时器原理

    做项目很多时候会用到定时任务,比如在深夜,流量较小的时候,做一些统计工作.早上定时发送邮件,更新数据库等.这里可以用Java的Timer或线程池实现.Timer可以实现,不过Timer存在一些问题.他 ...

  4. Node中使用mysql模块遇到的问题

    Node的mysql模块,本人的感受就是不好用,各种报错,各种坑,有一个问题困扰了我很久,也不知道是不是我使用的方式不对,不过后来用easymysql模块解决了,我才深信这是一个坑. 问题描述: 假设 ...

  5. 类的方法练习——定义MySQL类

    要求: 1.对象有id.host.port三个属性 2.定义工具create_id,在实例化时为每个对象随机生成id,保证id唯一 3.提供两种实例化方式,方式一:用户传入host和port 方式二: ...

  6. 转:HTML中让图片滚动的<marquee>标签的使用方法

    实例: <marquee id="affiche" align="left" behavior="scroll" bgcolor=&q ...

  7. 18_CGLib动态代理

    [概述] 已知JDK动态代理中的Proxy.newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)方法的 ...

  8. 【阿里云产品公测】PTS压力测试服务器性能

    作者:阿里云用户xsnjxjj 在PTS服务之前,经常使用webbench来对服务器进行压力测试,在看到阿里云PTS服务的介绍以后,深深的被PTS强大的功能所吸引     非常感谢阿里云团队给予的测试 ...

  9. 使用CKRule实现PVC配方计算

    1,PVC计算的基本原理 配方员设计好配方,再进行抽象提炼,会出现相对于软件而言可以理解的逻辑,如属性的概念,对厂企生成的PVC产品而言,一般都有产品大类名称,花纹,颜色,长度,宽度,厚度等概念,这对 ...

  10. Android NestedScrollView与RecyclerView嵌套,以及NestedScrollView不会滚动到屏幕顶部解决

    ①NestedScrollView与RecyclerView嵌套,导致滚动惯性消失 解决:mRecyclerView.setNestedScrollingEnabled(false); ②Nested ...