线程栈的空间是开辟在那里的?
线程栈之间可以互访吗?
为什么在使用 pthread_attr_setstack
函数时,需要设置栈的大小,而进程 task_struct
的 mm_struct *mm
成员中却并没有却并没有 stack_size
这个成员项,怎么保存的栈大小呢?
虚拟地址空间
如图所示,具体可以参照 Linux 中的各种栈:进程栈 线程栈 内核栈 中断栈
进程栈
进程用户空间的管理在 task_struct
的 mm_struct *mm
成员中体现, mm
中的成员定义了用户空间的布局情况如图。
用户空间的栈起始于 STACK_TOP
, 如果设置了 PF_RANDOMIZE
,则起始点会减少一个小的随机量,经过随机处理后,进程栈的起始地址将存放在 mm->start_stack
中,可以通过 cat /proc/xxx/stat
查看。
栈从上而下扩展,而用于内存映射的区域起始于 mm->mmap_base
, mm->mmap_base
通过调用 mmap_base()
来初始化,为了确保栈不与 mmap 区域不发生冲突,两者之间设置了一个安全间隙。 mmap_base
函数源代码如下:
#define MIN_GAP (128*1024*1024)
#define MAX_GAP (TASK_SIZE/6*5)
static inline unsigned long mmap_base(struct mm_struct *mm)
{
unsigned long gap = current->signal->rlim[RLIMIT_STACK].rlim_cur; // rlim_cur 默认为 8388608,及 8M, 可以使用 getrlimit(RLIMIT_STACK, &limit) 查看
unsigned long random_factor = 0;
if (current->flags & PF_RANDOMIZE)
random_factor = get_random_int() % (1024*1024);
if (gap < MIN_GAP) // 通过 MIN_GAP 来保证,进程栈的大小至少为 128MB
gap = MIN_GAP;
else if (gap > MAX_GAP) // 栈的最大空间为 TASK_SIZE/6*5, 及 2.5G
gap = MAX_GAP;
return PAGE_ALIGN(TASK_SIZE - gap - random_factor); // 通过保留 random_factor 空间大小的间隙来防止栈溢出
}
进程栈位于虚拟地址空间的栈区,可以动态增长,上限为 ulimit -s
值
线程栈
线程包含了表示进程内执行环境必需的信息,其中包括进程中标示线程的线程 ID,一组寄存器值,栈,调度优先级和策略, 信号屏蔽字,errno 变量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本,程序的全局内存和堆内存,栈以及文件描述符,所以线程的 mm_struct *mm
指针变量和所属进程的 mm
指针变量相同。
在创建线程的时候,可以通过 pthread_attr_t
来初始化线程的属性,包括线程的栈布局信息,如栈起始地址 stackaddr
, 栈大小 stacksize
。 具体需要通过方法:
// stackaddr 指向为该线程开辟的空间,该空间可以使用 malloc 或者 mmap 来开辟,而不能来自进程的栈区。开辟的 stackaddr 所指向的动态空间需要自己负责释放。
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
当然也可将线程栈的空间管理交给系统,如果想改变系统默认的栈大小 8MB,可以通过
// stacksize 最小值为 16384,单位为字节
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
pthread
创建线程流程可以参考 pthread_create 源码分析
线程栈在 linux 下起始地址跟大小保存在 pthread_attr_t
中
typedef struct __pthread_attr_s
{
int __detachstate; // 分离状态
int __schedpolicy;// 调度策略
struct __sched_param __schedparam;
int __inheritsched;
int __scope;// 线程优先级的有效范围
size_t __guardsize;//
int __stackaddr_set;
void *__stackaddr;// 起始地址
size_t __stacksize;// 表示堆栈的大小。
}pthread_attr_t;
由此看来线程栈在所属进程的堆区,线程与其所属的进程共享进程的虚拟地址空间,所以线程栈之间可以互访