深入理解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)

總結:

    線程從進程棧分配空間,大小並不是固定的,如果分配空間大於進程棧空間,那麼直接運行時出現段錯誤。

 






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章