我正在尝试分析这段代码,它使用pthreads和stacks:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NTHREADS 8
#define ARRAY_SIZE 500000
#define MEGEXTRA 1000000
pthread_attr_t attr;
void *Hello(void *threadid)
{
double A[ARRAY_SIZE];
int i;
long tid;
size_t mystacksize;
tid = (long)threadid;
sleep(3);
for (i=0; i<ARRAY_SIZE; i++)
{
A[i] = i * 1.0;
}
printf("%ld: Hello World! %f\n", tid, A[ARRAY_SIZE-1]);
pthread_attr_getstacksize (&attr, &mystacksize);
printf("%ld: Thread stack size = %li bytes \n", tid, mystacksize);
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
pthread_t threads[NTHREADS];
size_t stacksize;
int rc;
long t;
pthread_attr_init(&attr);
stacksize = ARRAY_SIZE*sizeof(double) + MEGEXTRA;
pthread_attr_setstacksize (&attr, stacksize);
pthread_attr_getstacksize (&attr, &stacksize);
printf("Thread stack size = %li bytes (hint, hint)\n",stacksize);
for(t=0;t<NTHREADS;t++){
rc = pthread_create(&threads[t], &attr, Hello, (void *)t);
if (rc){
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
printf("Created %ld threads.\n", t);
pthread_exit(NULL);
}
我搞不懂这部分
#define MEGEXTRA 1000000
(...)
stacksize = ARRAY_SIZE*sizeof(double) + MEGEXTRA;
pthread_attr_setstacksize (&attr, stacksize);
pthread_attr_getstacksize (&attr, &stacksize);
为什么我需要将这个megaxtra值添加到stacksize。我是说,为什么不加上这个值,程序segfaults。
最佳答案
无论何时创建pthread
,pthread库都必须为它分配一些堆栈空间。这不一定为堆栈空间分配物理内存,而是为堆栈分配虚拟地址空间。为线程分配的默认堆栈大小取决于实现,但如果要在堆栈上分配一个大数组(实际上所有C实现中都放置了自动存储类变量),则需要调整分配的空间以确保其足够大。
考虑:假设实现(在pthreads库中)决定为每个线程分配2MB的堆栈空间。然后在创建3个线程之后,虚拟内存映射可能如下所示(确切地址和其他详细信息当然会有所不同):
8060000-8080000 Thread 3 stack
8030000-8050000 Thread 2 stack
8000000-8020000 Thread 1 stack
7000000-8000000 Main thread stack
[...] Other program regions (program code, heap, initialized data, library code/data, etc)
有几件事要注意。堆叠向下生长。堆栈指针从已分配区域的顶部开始,当您通过调用子例程或为局部变量分配空间将内容推送到堆栈时,堆栈指针会减少。内核通常不会立即为堆栈分配实际的物理页。这将是浪费,因为你可能永远不会使用它们(和其他东西可能不得不驱逐出公羊这样做)。相反,将为区域中的每个页分配页映射条目,但标记为空。然后,当您试图写入每个页面时,您的程序将出现页面错误。内核通过为您分配一个物理页面、将其映射到正确的虚拟地址并更新页面映射条目(然后自动恢复您的程序,而无需知道这一切)来处理错误。
还要注意,堆栈区域不是立即相邻的。这是为了让内核能够区分什么时候由于走得太远而耗尽了虚拟地址空间。这就是在您的场景中导致分段冲突的原因:您已经从堆栈的底部展开,并前进到没有为其分配页映射项的空间中。
因此,当您使用
pthread_attr_setstacksize
时,您将告诉库和内核您确切地知道生成堆栈的大小,并相应地配置内存映射。但是,由于您只提供了足够的空间来精确地包含数组,因此您没有为调用线程函数所用的堆栈帧、其其他局部变量(tid
、i
、mystacksize
)或任何填充或其他本地堆栈使用留有任何空间。因此,这段代码的原始作者实际上是在说:“我需要确保每个线程中都有空间容纳我的大数组,然后为局部变量、调用堆栈帧和任何其他开销添加额外的
MEGEXTRA
字节。”,请注意,这只是分配虚拟地址空间,因此这样做并不浪费(在64位体系结构中,虚拟地址空间通常不是宝贵的资源)。在程序的实际运行中,您可能只使用了该额外空间中的一个或两个额外页面。还有一点需要注意:堆栈大小计算的第一部分(
ARRAY_SIZE*sizeof(double)
)等于400万。十六进制,即0x3D0900,它不是页面大小的倍数(通常是4K或0x1000)。使用这个数字的结果是不确定的。内核可能会将其扩展到下一个页面大小边界(0x3d10000),或者将其截断到上一个边界(0x3d0000),或者(根据linux手册页)返回错误。posix规范(http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setstacksize.html)指出
stacksize属性应定义为创建的线程堆栈分配的最小堆栈大小(字节)。
对于非页面对齐的大小没有任何说明,因此可以说,将大小扩展到下一个页面边界是唯一正确的行为。但是glibc似乎没有做这样的调整,linux内核实现似乎随后会截断所提供的大小。
无论如何,最好不要把这些东西剪得太近。在实际程序中预测实际的堆栈使用量充其量是很困难的。
关于c - 为什么我需要设置比实际大小更大的堆栈大小?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54297808/