深入理解linux下进程和线程的空间分配,进程栈和线程栈的空间分配

最近学习了下linux下进程和线程空间的分配原理,觉得有必要坐下总结,


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

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

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

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

    (4)一般默认情况下,线程栈是在进程的堆中分配栈空间,每个线程拥有独立的栈空间,为了避免线程之间的栈空间踩踏,线程栈之间还会有以小块guardsize用来隔离保护各自的栈空间,一旦另一个线程踏入到这个隔离区,就会引发段错误。


下面是一个比较简单的多线程程序。程序如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main()
{
    int ret = 0;
    int i = 0;
    /* int stack_size = 128 * 1024; */

    pthread_attr_t attr;
    /* pthread_attr_setstacksize(&attr, stack_size); */

    for (i = 0; i < 3; i++) {
        ret = pthread_create(&thread[i], NULL, thread_func, NULL);
        if (ret) {
            perror("create");
            return -1;
        }
    }

    while (1) {
        ;
    }

    return 0;
}

上图是我的测试程序,我创建了3个线程。程序运行以后,我们可以通过/ proc/PID/task来看该程序有多少线程在运行: 

然后我们来看一下进程的地址空间。 /proc/PID/maps就是进程的地址空间。如下所示: 
 
可以看出,进程的地址空间从低到高依次是:进程代码段(标志含有x)、只读数据段、可读写数据段、堆、mmap区(文件映射和匿名映射,其中有文件名的行是文件映射),栈。

线程18438的栈:(0xb7570000-0xb6d70000)的值恰好是8M,线程栈默认大小是8M。(0xb6d70000-0xb6d6f000)的值是4K,这4K是保护页。

为什么这三个线程的栈都是8M?可以从ulimit命令来得出,这是进程的资源限制:

使用ulimit -a命令可以看出,进程资源限制中栈大小的限制是8194K,即8M.

那么,这个8M大小是不是可以更改的?以及后面会什么会有一个4K大小的保护页?这可以从glibc代码里面来获取答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1.
__pthread_create_2_1
    /* 这里分配线程栈 */
    ALLOCATE_STACK

2.
/* allocate_stack就是具体的分配线程栈的函数: */
allocate_stack
    /*如果没有设置线程栈大小,就使用默认值*/
    size = attr > stacksize ?: __default_stacksize;
  /* ... */
    /* Try to get a stack from the cache. */
    pd = get_cached_stack (&size, &mem);
    /* 如果没有从cache申请到,就要mmap申请一块内存 */
    if (pd == NULL){
        /* MAP_PRIVATE | MAP_ANONYMOUS: 私有匿名映射 */
        mmap (NULL, size, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
        /* 接着设置一个保护区,该区域的页表属性是PROT_NONE(Page can not be accessed) */
        mprotect(guard, guardsize, PROT_NONE);

对于设置为PROT_NONE的页,是不能访问的,那么访问到这个保护区时就出现错误,linux是靠这种机制来实现栈溢出保护的。

下面我们来调整线程栈:

  • 设置pthread_attr属性

可以看到此时的线程栈大小是: (0xb758f000-0xb756f000) = 128K.



测试进程栈和线程栈大小: 


查看线程栈大小:

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)

总结:

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

 






  • 11
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Linux中的进程线程有以下几个区别: 1. 数据结构:在Linux中,线程是通过进程模拟出来的,没有真正意义上的线程数据结构。而在Windows中,操作系统为线程创建了thread_struct数据结构,因此有真正意义上的线程。 2. 执行流:在Linux中,每个线程实体对应着操作系统下的一条执行流,通过PCB(task_struct)来模拟。而用户态下创建线程是通过线程库(pthread_struct)来进行管理。 3. 标识作用:在Linux中,轻量级进程ID(tid)对不同的线程起标识作用,操作系统在进行调度时使用tid。而进程ID(pid)对不同的进程起标识作用。在只有一个线程进程中,tid的值等于pid的值。 4. 线程私有部分:线程私有部分包括运行时、一组寄存器/硬件上下文/任务状态段等。 5. 多线程提高效率:多线程能够提高效率的原因是多核和单核环境下的不同。在多核环境下,多线程可以将庞大的任务分成若干份,并交给不同的线程进行处理,同时执行不同步骤的代码,从而提高效率。而在单核环境下,多线程并发执行,使用线程切换来提高整体代码的运行效率。 6. 进程线程的区别:进程是程序运行的实例,是系统分配资源的基本单位,拥有独立的地址空间线程进程中的一条执行流,是CPU调度的基本单位,共享同一地址空间创建和撤销进程的开销大于线程,不同进程间不会互相影响,而一个线程挂掉可以将整个进程挂掉。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值