多线程程序在内存中的分布

上一篇博客谈到了普通程序在内存中的分布,这篇博客谈谈文艺程序在内存中的分布。

执行环境

首先缕一下概念吧。在linux下,进程(process)和线程(thread)并没有什么区别,都是一种执行环境(context of execution, COE),在linux下统称为task。

每个执行环境都有自己的状态,包括CPU状态,内存映射状态,权限状态(uid,pid)和各种各样的通信状态(打开的文件描述符,管道,信号处理程序等)。

而线程和进程的不同在于线程这种执行环境之间共享了很多状态,至于共享哪些状态不取决于书上说了什么(书上只说了一点点),而是取决于创建这个执行环境的函数提供了哪些选择。

在linux下,线程通过clone这个函数创建,而这个函数和fork一样,其实是用来创建进程的,但是clone在创建进程的时候,会指定调用这个函数的进程和新创建的进程之间共享的内容,比如说pid,代码段,文件描述符等等,所以看上去好像一个进程里面有很多股进程在并行运行,这些并行运行的进程后来有了个新名字:线程。所以大家不必太在意他们叫什么名字,就好比给皇上吃的胡萝卜叫“宫廷胡萝卜”一样,换个名字都是唬人的,只要搞清楚实质就好了。

而我们创建新线程(进程)也可以不和老进程共享那些内容,比如不共享文件描述符,这样线程(子进程)可以打开的文件数就不会受到父进程的限制了(通常一个进程可以打开的文件数是有限制的,可以使用ulimit -n查看,一般是1024);再比如不共享pid,这样新线程也有自己的pid(进程id),从这也可以看出线程其实就是进程。

内存中

概念缕清楚了,后面的内容还是回到默认写法,大家看到线程、进程,心里清楚其实就是COE就好了。

多线程程序和普通程序在内存中的的不同主要表现在栈的不同,每个线程一个栈,所以线程的局部变量不会受其他线程影响。而.text, .data, .bss, 等部分各个线程是共享的,所以线程间通信非常简单,直接在这些共享的内存中存取就可以了,所谓祸福相依,线程内操作这些共享内存中的数据的时候,也需要非常小心,要确保只有当前线程在操作全局变量(通过锁或者其他一些操作)。

再就没什么好说的了,感觉上一篇博客已经讲的比较清楚了,最后就是一个示例程序了:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

void *thread_foo_func(void *);
void *thread_bar_func(void *);

int global = 4;

int main(){
  int local = 8;
  int foo, bar;
  pthread_t fthread, bthread;
  foo = pthread_create(&fthread, NULL, thread_foo_func, (void *)&local);
  bar = pthread_create(&bthread, NULL, thread_bar_func, (void *)&local);
  if (foo != 0 || bar != 0){
    printf("thread creation failed.\n");
    return -1;
  }

  foo = pthread_join(fthread, NULL);
  bar = pthread_join(bthread, NULL);
  if (foo != 0 || bar != 0){
    printf("thread join failed.\n");
    return -2;
  }

  char i;
  scanf("%c", &i);

  return 0;
}

void *thread_foo_func(void *arg){
  int foo_local = 16;
  printf("address of global %d: %x\n", global, &global);
  printf("address of main local %d: %x\n", *(int *)arg, arg);
  printf("address of foo local: %x\n", &foo_local);
}

void *thread_bar_func(void *arg){
  int bar_local = 32;
  printf("address of global %d: %x\n", global, &global);
  printf("address of main local %d: %x\n", *(int *)arg, arg);
  printf("address of bar local: %x\n", &bar_local);
}

程序运行结果如下:

address of global 4: 601040
address of main local 8: 619417d0
address of foo local: ddb36ebc
address of global 4: 601040
address of main local 8: 619417d0
address of bar local: dd335ebc

内存maps如下:

cat /proc/2806/maps 
00400000-00401000 r-xp 00000000 08:03 4080124                            /home/yuduo/Workspace/C/sandbox/threaded
00600000-00601000 r--p 00000000 08:03 4080124                            /home/yuduo/Workspace/C/sandbox/threaded
00601000-00602000 rw-p 00001000 08:03 4080124                            /home/yuduo/Workspace/C/sandbox/threaded
00699000-006ba000 rw-p 00000000 00:00 0                                  [heap]
7fe7dcb36000-7fe7dcb37000 ---p 00000000 00:00 0 
7fe7dcb37000-7fe7dd337000 rw-p 00000000 00:00 0 
7fe7dd337000-7fe7dd338000 ---p 00000000 00:00 0 
7fe7dd338000-7fe7ddb38000 rw-p 00000000 00:00 0 
7fe7ddb38000-7fe7ddccd000 r-xp 00000000 08:03 4460234                    /lib/x86_64-linux-gnu/libc-2.13.so
7fe7ddccd000-7fe7ddecc000 ---p 00195000 08:03 4460234                    /lib/x86_64-linux-gnu/libc-2.13.so
7fe7ddecc000-7fe7dded0000 r--p 00194000 08:03 4460234                    /lib/x86_64-linux-gnu/libc-2.13.so
7fe7dded0000-7fe7dded1000 rw-p 00198000 08:03 4460234                    /lib/x86_64-linux-gnu/libc-2.13.so
7fe7dded1000-7fe7dded7000 rw-p 00000000 00:00 0 
7fe7dded7000-7fe7ddeef000 r-xp 00000000 08:03 4460294                    /lib/x86_64-linux-gnu/libpthread-2.13.so
7fe7ddeef000-7fe7de0ee000 ---p 00018000 08:03 4460294                    /lib/x86_64-linux-gnu/libpthread-2.13.so
7fe7de0ee000-7fe7de0ef000 r--p 00017000 08:03 4460294                    /lib/x86_64-linux-gnu/libpthread-2.13.so
7fe7de0ef000-7fe7de0f0000 rw-p 00018000 08:03 4460294                    /lib/x86_64-linux-gnu/libpthread-2.13.so
7fe7de0f0000-7fe7de0f4000 rw-p 00000000 00:00 0 
7fe7de0f4000-7fe7de115000 r-xp 00000000 08:03 4460221                    /lib/x86_64-linux-gnu/ld-2.13.so
7fe7de2f2000-7fe7de2f5000 rw-p 00000000 00:00 0 
7fe7de310000-7fe7de314000 rw-p 00000000 00:00 0 
7fe7de314000-7fe7de315000 r--p 00020000 08:03 4460221                    /lib/x86_64-linux-gnu/ld-2.13.so
7fe7de315000-7fe7de317000 rw-p 00021000 08:03 4460221                    /lib/x86_64-linux-gnu/ld-2.13.so
7fff61923000-7fff61944000 rw-p 00000000 00:00 0                          [stack]
7fff619ff000-7fff61a00000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

global没什么问题,关键看看几个局部变量的内存的范围,main函数的局部变量地址是:619417d0,在main函数栈的范围内:7fff61923000-7fff61944000

foo的局部变量在foo的栈里面,内存范围是:7fe7dd338000-7fe7ddb38000。

bar的局部变量在bar的栈里面,内存范围是:7fe7dcb37000-7fe7dd337000。

顺便说一下,pthread库使用mmap函数为新线程创建栈空间,然后传给clone,大家可以参照pthread库的源码练练怎么自己创建线程。


参考文献:

  1.  http://www.kernel.org/doc/man-pages/index.html
  2. Implementing a Thread Library on Linux
  3. linus本人发的一个帖子,值得一看,猛击进入
  4. Professional Linux kernel architecture. Wolfgang Mauerer. 2008

你可能感兴趣的:(JOIN,thread,多线程,linux,null,library)