为什么我需要设置比实际应该更大的堆栈大小?
Why do I need to set bigger stacksize than it actually should be?
我正在尝试分析这段代码,它使用了 pthreads 和堆栈:
#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);
为什么我需要将这个 MEGEXTRA 值添加到 stacksize。我的意思是,为什么不添加这个值,程序就会出现段错误。
无论何时创建 pthread
,pthread 库都必须为其分配一些堆栈 space。那不一定为栈space分配物理内存,它为栈分配虚拟地址space。为线程分配的默认堆栈大小取决于实现,但是如果您要在堆栈上分配一个大数组(automatic 存储 class 变量放置在几乎所有 C 实现中),您需要调整分配的 space 以确保它足够大。
考虑:假设实现(在 pthreads 库中)决定默认为每个线程分配 2MB 的堆栈 space。然后在创建 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)
有几点需要注意。堆栈向下增长。堆栈指针从分配区域的顶部开始,当您通过调用子例程或为局部变量分配 space 将内容压入堆栈时,堆栈指针会减少。内核通常 而不是 立即为您的堆栈分配实际的物理页面。那会很浪费,因为您可能永远不会使用它们(并且可能必须从 RAM 中驱逐其他东西才能做到这一点)。相反,为区域中的每个页面分配 页面映射条目 ,但标记为空。然后,当您尝试写入每个页面时,您的程序将出现页面错误。内核通过为您分配一个物理页面来处理故障,将其映射到正确的虚拟地址并更新页面映射条目(然后自动恢复您的程序,而无需您注意任何这些)。
另请注意,堆栈区域并非立即连续。这是为了让内核可以区分您何时因走得太远而耗尽了虚拟地址 space。 那 是导致您的场景中的分段违规的原因:您已经离开堆栈底部并前进到 space,其中没有分配页面映射条目。
因此,当您使用 pthread_attr_setstacksize
时,您是在告诉库和内核您确切知道堆栈的大小并相应地配置内存映射。但是因为你只提供了足够的 space 到 正好 包含数组,所以你没有为用于调用你的线程函数的堆栈帧或其他线程函数留出任何空间局部变量(tid
、i
、mystacksize
),或用于任何填充或其他局部堆栈使用。
所以,这段代码的原作者本质上是在说:"I need to ensure there is room in each thread for my big array and then throw in an additional MEGEXTRA
bytes for local variables, the calling stack frame and any other overhead." 再次注意,这只是分配 虚拟地址 space 所以这不是浪费这样做(虚拟地址 space 通常不是 64 位架构上的宝贵资源)。在程序的实际 运行 中,您可能只 使用 附加 space.
的一两个附加页面
还有一点要注意:堆栈大小计算的第一部分 (ARRAY_SIZE*sizeof(double)
) 等于 400 万。在十六进制中,即 0x3D0900,它是 而不是 页面大小(通常为 4K 或 0x1000)的倍数。使用该数字的结果是不确定的。内核可能会将其扩展到下一个页面大小边界 (0x3d10000),或者它可能会截断到前一个边界 (0x3d0000),或者(根据 linux 手册页)它可能 return 一个错误。
posix 规范 (http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setstacksize.html) 说
The stacksize attribute shall define the minimum stack size (in bytes) allocated for the created threads stack.
并没有提及非页面对齐大小,因此可以说,将大小扩展到下一页边界是唯一 正确的 行为。但是 glibc 似乎没有做这样的调整,linux 内核实现似乎会截断提供的大小。
无论如何,最好不要把这些东西切得太近。在实际程序中预测实际的确切堆栈使用情况充其量是困难的。
我正在尝试分析这段代码,它使用了 pthreads 和堆栈:
#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);
为什么我需要将这个 MEGEXTRA 值添加到 stacksize。我的意思是,为什么不添加这个值,程序就会出现段错误。
无论何时创建 pthread
,pthread 库都必须为其分配一些堆栈 space。那不一定为栈space分配物理内存,它为栈分配虚拟地址space。为线程分配的默认堆栈大小取决于实现,但是如果您要在堆栈上分配一个大数组(automatic 存储 class 变量放置在几乎所有 C 实现中),您需要调整分配的 space 以确保它足够大。
考虑:假设实现(在 pthreads 库中)决定默认为每个线程分配 2MB 的堆栈 space。然后在创建 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)
有几点需要注意。堆栈向下增长。堆栈指针从分配区域的顶部开始,当您通过调用子例程或为局部变量分配 space 将内容压入堆栈时,堆栈指针会减少。内核通常 而不是 立即为您的堆栈分配实际的物理页面。那会很浪费,因为您可能永远不会使用它们(并且可能必须从 RAM 中驱逐其他东西才能做到这一点)。相反,为区域中的每个页面分配 页面映射条目 ,但标记为空。然后,当您尝试写入每个页面时,您的程序将出现页面错误。内核通过为您分配一个物理页面来处理故障,将其映射到正确的虚拟地址并更新页面映射条目(然后自动恢复您的程序,而无需您注意任何这些)。
另请注意,堆栈区域并非立即连续。这是为了让内核可以区分您何时因走得太远而耗尽了虚拟地址 space。 那 是导致您的场景中的分段违规的原因:您已经离开堆栈底部并前进到 space,其中没有分配页面映射条目。
因此,当您使用 pthread_attr_setstacksize
时,您是在告诉库和内核您确切知道堆栈的大小并相应地配置内存映射。但是因为你只提供了足够的 space 到 正好 包含数组,所以你没有为用于调用你的线程函数的堆栈帧或其他线程函数留出任何空间局部变量(tid
、i
、mystacksize
),或用于任何填充或其他局部堆栈使用。
所以,这段代码的原作者本质上是在说:"I need to ensure there is room in each thread for my big array and then throw in an additional MEGEXTRA
bytes for local variables, the calling stack frame and any other overhead." 再次注意,这只是分配 虚拟地址 space 所以这不是浪费这样做(虚拟地址 space 通常不是 64 位架构上的宝贵资源)。在程序的实际 运行 中,您可能只 使用 附加 space.
还有一点要注意:堆栈大小计算的第一部分 (ARRAY_SIZE*sizeof(double)
) 等于 400 万。在十六进制中,即 0x3D0900,它是 而不是 页面大小(通常为 4K 或 0x1000)的倍数。使用该数字的结果是不确定的。内核可能会将其扩展到下一个页面大小边界 (0x3d10000),或者它可能会截断到前一个边界 (0x3d0000),或者(根据 linux 手册页)它可能 return 一个错误。
posix 规范 (http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setstacksize.html) 说
The stacksize attribute shall define the minimum stack size (in bytes) allocated for the created threads stack.
并没有提及非页面对齐大小,因此可以说,将大小扩展到下一页边界是唯一 正确的 行为。但是 glibc 似乎没有做这样的调整,linux 内核实现似乎会截断提供的大小。
无论如何,最好不要把这些东西切得太近。在实际程序中预测实际的确切堆栈使用情况充其量是困难的。