2006-06-07 16:42:00 liumangxiong 阅读数 3742

今天在CSDN里回了一个贴子,我按照理论上的栈大小可以有2G多的空间时,被另一个朋友指出我的错误,实际中栈只有2M,他指的是线程。可是在LINUX下进程和线程是没什么差别的。我就突然想起应该用一种什么方法来验证一下栈的大小,由GOOGLE帮忙,找到了后面的reference。

写一个hello, world程序

#include <stdio.h>
int main(int argc, char* argv[])
{
int buf[2*1024*1024];
printf("hello, world");
return 0;
}

buf的大小是2*1024*1024*4=8M。可以改变buf的大小,来验证栈的大小。我在的系统用的内核版本是2.4.20。在以上程序运行的时候出现了段错误。

References:   http://coolq.blogdriver.com/coolq/26282.html

2012-11-05 11:31:14 daniel_ice 阅读数 8292

注:本文所涉及的环境为Linux, 下文讨论的栈跟内核栈,没有任何的关系,关于内核栈,请参考《深入Linux内核架构》中的2.4.1 进程复制

这里有如下几个问题,线程栈的空间是开辟在那里的? 线程栈之间可以互访吗?为什么在使用pthread_attr_setstack函数时,需要设置栈的大小,而进程task_struct的 mm_struct *mm 成员中却并没有却并没有stack_size这个成员项,怎么保存的栈大小呢?

进程栈:

        进程用户空间的管理在task_struct 的mm_struct *mm成员中体现, mm中的成员定义了用户空间的布局情况如图一。 用户空间的栈起始于STACK_TOP, 如果设置了PF_RANDOMIZE,则起始点会减少一个小的随机量,每个体系结构都必须定义STACK_TOP, 大多数都设置为TASK_SIZE, 在32位机上该值为0XC0000000。经过随机处理后,进程栈的起始地址将存放在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空间大小的间隙来防止栈溢出
}

图 一 IA-32计算机上虚拟地址空间的布局

线程栈:

        线程包含了表示进程内执行环境必需的信息,其中包括进程中标示线程的线程ID,一组寄存器值,栈,调度优先级和策略, 信号屏蔽字,errno变量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本,程序的全局内存和堆内存,栈以及文件描述符,所以线程的mm_struct *mm指针变量和所属进程的mm指针变量相同。

       在创建线程的时候,可以通过pthread_attr_t来初始化线程的属性,包括线程的栈布局信息,如栈起始地址stackaddr, 栈大小stacksize。 具体需要通过方法

int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
// 注:stackaddr 指向为该线程开辟的空间,该空间可以使用malloc或者mmap来开辟,而不能来自进程的栈区。开辟的stackaddr所指向的动态空间需要自己负责释放。

当然也可将线程栈的空间管理交给系统,如果想改变系统默认的栈大小8MB,可以通过

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
// 注:stacksize最小值为16384,单位为字节

由上面的API接口,可以得到,线程栈的stacksize是保存在pthread_attr_t中的,可以通过人为的指定,也可以通过在创建线程的时候读取系统的配置文件来初始化stacksize,当初始化完栈的起始地址,和大小后,便可以通过

int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);

来初始化线程栈末尾之后用以避免栈溢出的缓冲区的大小,如果应用程序溢出到此缓冲区中,这个错误可能会导致 SIGSEGV 信号被发送给该线程, 从而造成段错误,缓冲区默认设置为PAGESIZE个字节因为线程的mm->start_stack和所属进程相同,所以线程栈的起始地址并没有存放在task_struct中,应该只是使用attr中的stackaddr,来初始化task_struct->thread-> sp(sp指向struct pt_regs对象,该结构体用于保存用户进程或者线程的寄存器现场)。

总结:线程栈的空间开辟在所属进程的堆区,线程与其所属的进程共享进程的用户空间,所以线程栈之间可以互访。线程栈的起始地址和大小存放在pthread_attr_t 中,栈的大小并不是用来判断栈是否越界,而是用来初始化避免栈溢出的缓冲区的大小(或者说安全间隙的大小)

ps: 文中如有错误的地方,请各位随时提出来,我将第一时间更改,谢谢。

2010-05-28 00:10:00 yjzl1911 阅读数 18524

线程应用程序最常见导致创建线程失败的原因是线程栈大小的设置。创建一个新的线程,默认情况下系统为线程栈预留了2MB的寻址空间。线程栈起始于进程虚拟 内存的高端地址,并向虚拟内存底端地址方向扩展。取决于线程本身的大小以及其它线程内存分配的情况,进程虚拟地址空间消耗过快可能导致创建线程失败。

这里有一个测试程序可以看到,Linux下最多可以创建多少个线程。

#include <pthread.h>

#include <stdio.h>

#include <string.h>

 

void *ThreadFunc()

{

    static int count = 1;

    printf ("Create thread %d/n", count);

    //pthread_detach (pthread_self());

    count++;

 

}

 

int main(char *argv[ ], int argc)

{

    int     err;

    pthread_t tid;

    while (1){

           err = pthread_create(&tid, NULL, ThreadFunc, NULL);

           if (err != 0){

               printf("can't create thread: %s/n", strerror(err));

           break;

           }  

    }

}

 

输出结果如下:

Create thread 303

Create thread 304

can't create thread: Cannot allocate memory

用 ulimit -s 可以查看到栈的默认大小为10240K

32位linux下的进程用户空间是3072M, 3072/10.24=300。为什么实际会比计算出来的多2个,这个原因还不太清楚。

可以在调用 pthread_create 的时候用 pthread_attr_getstacksize 设置栈的大小,或者直接用 ulimit -s 设置栈的大小。

如果修改上面的测试代码为

#include <pthread.h>

#include <stdio.h>

#include <string.h>

 

void *ThreadFunc()

{

    static int count = 1;

    printf ("Create thread %d/n", count);

    pthread_detach (pthread_self());

    count++;

 

}

 

int main(char *argv[ ], int argc)

{

    int     err;

    pthread_t tid;

    while (1){

           err = pthread_create(&tid, NULL, ThreadFunc, NULL);

           if (err != 0){

               printf("can't create thread: %s/n", strerror(err));

           break;

           }  

    }

}

那么得到的结果将是:

Create thread 560000

Create thread 560001

……………

这里用到了pthread_detach (pthread_self())来释放线程所占用的内存资源(线程内核对象task_struct和线程内核堆栈)。这样就可以创建更多的线程,而不会出现Cannot allocate memory的错误。

2017-04-17 15:11:42 sunny04 阅读数 564

总结:

    1、进程主线程的栈大小是在进程执行的时刻才能指定的,即不是在编译的时候决定的,也不是在链接的时候决定的 

    2、进程主线程的栈大小是随机确定的至少比线程栈要大,但是不到线程栈大小的2倍 (ulimit -s 查询到的是线程的栈大小,)

    3、线程栈大小是固定的,也就是ulimit -a 显示的值

   4.  线程的栈大小, 不是从进程主线程栈大小里面分配的。    也就是说 N个线程的栈大小 可以大于 进程主线程栈大小。

 

查看线程栈大小:

ulimit

可以看到默认情况下线程栈大小为8192(8MB),可以使用ulimit -s xxx修改线程默认栈大小

(1)检查线程栈默认大小(8KB)

stacksize

    线程执行2030次之后,出现段错误(2030*4K=8120K)

stacksize_error

 

(2)修改栈大小,使用pthread_attr_setstack()

setattribute

    如上修改栈大小为16MB,其中线程栈的空间从堆中进行分配

setattribute_error

   程序执行4063次后出现段错误(4063*4KB)

 

(3)创建两个线程,使用默认栈大小执行

twothread

    创建两个线程,默认单个线程栈大小为8M

twothread1_error

    执行结果1:程序执行4009次之后段错误(4009*4KB)

twothread2_error

    执行结果2:程序执行3380次之后段错误(3380*4KB)

总结:

        两个线程时,两个线程栈的总和不是固定值,也不是线程栈的2倍

 

(3)不使用任何线程

nothread

nothread1_error

执行结果1:程序执行2538次后段错误(2538*4KB)

nothread2_error

执行结果2:程序执行2537次后段错误(2537*4KB)

总结:

    进程的栈大小不是固定的,而是比线程栈大一些

 

(4)线程栈从进程栈中分配

getstacksize

getthreadsize1_error

执行结果1:   程序执行2536次后段错误(2536*4KB>8M)

getstacksize2_error

    执行结果2:程序执行2537次后段错误(2537*4KB>8M)

总结:

    线程从进程栈分配空间,大小并不是固定的,如果分配空间大于进程栈空间,那么直接运行时出现段错误。

 

关于进程栈和线程栈总结:

    (1)进程栈大小时执行时确定的,与编译链接无关

    (2)进程栈大小是随机确认的,至少比线程栈要大,但不会超过2倍

    (3)线程栈是固定大小的,可以使用ulimit -a 查看,使用ulimit -s 修改

2015-04-16 22:02:00 weixin_30867015 阅读数 7

 

Linux 进程栈和线程栈的区别

http://www.cnblogs.com/luosongchao/p/3680312.html

 

总结:线程栈的空间开辟在所属进程的堆区,线程与其所属的进程共享进程的用户空间,所以线程栈之间可以互访。线程栈的起始地址和大小存放在pthread_attr_t 中,栈的大小并不是用来判断栈是否越界,而是用来初始化避免栈溢出的缓冲区的大小(或者说安全间隙的大小)

 

进程内核栈、用户栈

 

1.进程的堆栈

     内核在创建进程的时候,在创建task_struct的同事,会为进程创建相应的堆栈。每个进程会有两个栈,一个用户栈,存在于用户空间,一个内核栈,存 在于内核空间。当进程在用户空间运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;当进程在内核空间时,cpu堆栈指针寄存器里面的内 容是内核栈空间地址,使用内核栈。

2.进程用户栈和内核栈的切换

    当进程因为中断或者系统调用而陷入内核态之行时,进程所使用的堆栈也要从用户栈转到内核栈。

    进程陷入内核态后,先把用户态堆栈的地址保存在内核栈之中,然后设置堆栈指针寄存器的内容为内核栈的地址,这样就完成了用户栈向内核栈的转换;当进程从内 核态恢复到用户态之行时,在内核态之行的最后将保存在内核栈里面的用户栈的地址恢复到堆栈指针寄存器即可。这样就实现了内核栈和用户栈的互转。

    那么,我们知道从内核转到用户态时用户栈的地址是在陷入内核的时候保存在内核栈里面的,但是在陷入内核的时候,我们是如何知道内核栈的地址的呢?

    关键在进程从用户态转到内核态的时候,进程的内核栈总是空的。这是因为,当进程在用户态运行时,使用的是用户栈,当进程陷入到内核态时,内 核栈保存进程在内核态运行的相关信心,但是一旦进程返回到用户态后,内核栈中保存的信息无效,会全部恢复,因此每次进程从用户态陷入内核的时候得到的内核 栈都是空的(为什么?)。所以在进程陷入内核的时候,直接把内核栈的栈顶地址给堆栈指针寄存器就可以了。

3.内核栈的实现

        内核栈在kernel-2.4和kernel-2.6里面的实现方式是不一样的。

 在kernel-2.4内核里面,内核栈的实现是:

 Union task_union {

                   Struct task_struct task;

                   Unsigned long stack[INIT_STACK_SIZE/sizeof(long)];

 };

 其中,INIT_STACK_SIZE的大小只能是8K。

     内核为每个进程分配task_struct结构体的时候,实际上分配两个连续的物理页面,底部用作task_struct结构体,结构上面的用作堆栈。使用current()宏能够访问当前正在运行的进程描述符。

 注意:这个时候task_struct结构是在内核栈里面的,内核栈的实际能用大小大概有7K。

 

内核栈在kernel-2.6里面的实现是(kernel-2.6.32):

 Union thread_union {

                   Struct thread_info thread_info;

                   Unsigned long stack[THREAD_SIZE/sizeof(long)];

 };

 其中THREAD_SIZE的大小可以是4K,也可以是8K,thread_info占52bytes。

     当内核栈为8K时,Thread_info在这块内存的起始地址,内核栈从堆栈末端向下增长。所以此时,kernel-2.6中的current宏是需要 更改的。要通过thread_info结构体中的task_struct域来获得于thread_info相关联的task。更详细的参考相应的 current宏的实现。

 struct thread_info {

                   struct task_struct *task;

                   struct exec_domain *exec_domain;

                   __u32 flags;

        __u32 status;

                   __u32 cpu;

                   …  ..

 };

 注意:此时的task_struct结构体已经不在内核栈空间里面了。

转载于:https://www.cnblogs.com/jingzhishen/p/4433437.html

Linux 进程空间介绍

博文 来自: Crazy__Programmer
没有更多推荐了,返回首页