最近學習了下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 |
|
上圖是我的測試程序,我創建了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 |
|
對於設置爲PROT_NONE的頁,是不能訪問的,那麼訪問到這個保護區時就出現錯誤,linux是靠這種機制來實現棧溢出保護的。
下面我們來調整線程棧:
- 設置pthread_attr屬性
可以看到此時的線程棧大小是: (0xb758f000-0xb756f000) = 128K.
測試進程棧和線程棧大小:
查看線程棧大小:
可以看到默認情況下線程棧大小爲8192(8MB),可以使用ulimit -s xxx修改線程默認棧大小
(1)檢查線程棧默認大小(8KB)
線程執行2030次之後,出現段錯誤(2030*4K=8120K)
(2)修改棧大小,使用pthread_attr_setstack()
如上修改棧大小爲16MB,其中線程棧的空間從堆中進行分配
程序執行4063次後出現段錯誤(4063*4KB)
(3)創建兩個線程,使用默認棧大小執行
創建兩個線程,默認單個線程棧大小爲8M
執行結果1:程序執行4009次之後段錯誤(4009*4KB)
執行結果2:程序執行3380次之後段錯誤(3380*4KB)
總結:
兩個線程時,兩個線程棧的總和不是固定值,也不是線程棧的2倍
(3)不使用任何線程
執行結果1:程序執行2538次後段錯誤(2538*4KB)
執行結果2:程序執行2537次後段錯誤(2537*4KB)
總結:
進程的棧大小不是固定的,而是比線程棧大一些
(4)線程棧從進程棧中分配
執行結果1: 程序執行2536次後段錯誤(2536*4KB>8M)
執行結果2:程序執行2537次後段錯誤(2537*4KB>8M)
總結:
線程從進程棧分配空間,大小並不是固定的,如果分配空間大於進程棧空間,那麼直接運行時出現段錯誤。