Linux執行緒(程式)數限制
TOC \o "1-3" \h \z \u Linux執行緒(程式)數限制.... PAGEREF _Toc447043843 \h 1
目錄.... PAGEREF _Toc447043844 \h 2
1. 問題來源.... PAGEREF _Toc447043845 \h 2
2. 初步原因分析和解決.... PAGEREF _Toc447043846 \h 2
3. Linux執行緒數的限制.... PAGEREF _Toc447043847 \h 3
3.1 應用層測試程式碼... PAGEREF _Toc447043848 \h 3
3.2 逆向分析... PAGEREF _Toc447043849 \h 4
3.3 正向分析... PAGEREF _Toc447043850 \h 6
3.3.1 記憶體限制... PAGEREF _Toc447043851 \h 6
3.3.2 Threads-max引數限制... PAGEREF _Toc447043852 \h 6
3.3.3 Pid_max引數限制... PAGEREF _Toc447043853 \h 7
3.3.4 單程式記憶體限制... PAGEREF _Toc447043854 \h 7
4. 總結.... PAGEREF _Toc447043855 \h 7
1. 問題來源
公司線上環境出現MQ不能接受訊息的異常,運維和開發人員臨時切換另一臺伺服器的MQ後恢復。同時運維人員反饋在出現問題的伺服器上很多基本的命令都不能執行,出現如下錯誤:
2. 初步原因分析和解決
讓運維的兄弟在服務上檢視記憶體、CPU、網路、IO等基本資訊都正常。於是自己到運維的伺服器上看了一下,下面是slabtop –s c的執行結果,問題初步原因貌似出現了:
如果看到這個截圖你看不出什麼異常的話,下面的內容你可能不感興趣,哈哈。。。
task_struct是核心對程式的管理單位,通過slub(slab的升級版,如果你對slub不瞭解也不影響下面的內容,只要瞭解slab就行了)進行節點的管理,正常負載的服務不應該出現task_struct的slub結構體佔用記憶體最大的情況,這說明這臺伺服器上開啟了大量的程式(Linux核心態對程式和執行緒都是一個單位,不要糾結這個,後面可能會程式、執行緒混用)。
通過這個資訊,兄弟們發現這臺伺服器上有近3萬個執行緒,同時也定位到出問題的網元(一個新同學的程式碼沒有Review直接上線,裡面有一個BUG觸發了異常建立大量執行緒)。
問題貌似到這裡就結束了,但是作為一個有情懷的程式設計師,這只是一個開始(哥的情懷白天都被繁瑣的工作磨沒了,只能在這深夜獨享了。。。)
3. Linux執行緒數的限制
3.1 應用層測試程式碼
#include
#include
#include
#include
#include
#define MEMSIZE (1024 * 1024 * 256)
void thread(void)
{
sleep(100);
return;
}
int main()
{
pthread_t id;
intret;
intnum = 0;
while(1) {
ret = pthread_create(&id, NULL,(void*)thread, NULL);
++num;
if (ret != 0)
break;
}
printf("pthread_create fail with ret=%d, total num=%d\n", ret,num);
sleep(100);
return 0;
}
通過strace跟蹤,發現問題出現在copy_process函式,那剩下的工作就是分析copy_process返回異常的原因了。
3.2 逆向分析
這個時候逆向分析最簡單直接,可以直接定位到問題原因。
首先通過strace分析,查詢出問題的系統呼叫是clone函式。
SYS_clone--->do_fork--->copy_process。核心態函式的分析工具這次試用了systemtap,下面就是沒有任何美感的stap程式碼了,將就著看看吧
probe kernel.statement("*@kernel/fork.c:1184")
{
printf("In kernel/fork.c 1184\n");
}
probekernel.statement("*@kernel/fork.c:1197")
{
printf("In kernel/fork.c 1197\n");
}
probekernel.statement("*@kernel/fork.c:1206")
{
printf("In kernel/fork.c 1206\n");
}
probekernel.statement("*@kernel/fork.c:1338")
{
printf("In kernel/fork.c 1338\n");
}
probekernel.statement("*@kernel/fork.c:1342")
{
printf("In kernel/fork.c 1342\n");
}
probekernel.statement("*@kernel/fork.c:1363")
{
printf("Inkernel/fork.c 1363\n");
}
probekernel.statement("*@kernel/fork.c:1369")
{
printf("In kernel/fork.c 1369\n");
}
probekernel.statement("*@kernel/fork.c:1373")
{
printf("In kernel/fork.c 1373\n");
}
probe kernel.function("copy_process").return
{
printf("copy_process return %d\n", $return)
}
function check_null_pid:long(addr:long)
%{
struct pid *p;
p =(struct pid*)THIS->l_addr;
if (p== NULL)
THIS->__retvalue = 0;
else
THIS->__retvalue = 1;
%}
probe kernel.function("alloc_pid")
{
printf("alloc_pid init\n");
}
probekernel.statement("*@kernel/pid.c:301")
{
printf("alloc_pid 301\n");
}
probekernel.statement("*@kernel/pid.c:312")
{
printf("alloc_pid312\n");
}
probe kernel.function("alloc_pid").return
{
printf("alloc_pid return %ld\n", check_null_pid($return));
}
發現問題出在alloc_pid失敗,分析核心程式碼,這個受限於kernel.pid_max引數。
將引數調大到100000後,再次執行。
可以分配的執行緒數增加了幾百個,但是並沒有顯著增加。
繼續通過strace跟蹤,這次發現問題出在了mprotect函式
這個問題是由於當個執行緒的mmap個數限制,受限於vm.max_map_count引數。
將引數調大到100000後,再次執行,執行緒數明顯增加了。
其實這裡面還有一個引數 kernel.threads-max限制,由於系統預設將這個引數設定為800000,非常大,所以這個引數的影響一直沒有保留出來。
後面又犯賤把相關的引數都設定成800000,結果記憶體耗盡,系統直接沒響應了。。。。
3.3 正向分析
直接分析copy_process程式碼
copy_process
3.3.1 記憶體限制
dup_task_struct-->alloc_task_struct_node/alloc_thread_info_node/arch_dup_task_struct-->kmme_cache_alloc_node(slub.c)-->slab_alloc_node-->
“CONFIG_MEMCG_KMEM”//這裡也是一個坑,docker這種基於cgroup的也會影響,可能會因為分配給slub的記憶體不夠用出現執行緒限制
具體函式:
alloc_pages---->__memcg_kmem_newpage_charge-->memcg_charge_kmem-->__res_counter_charge-->res_counter_charge_locked
3.3.2 Threads-max引數限制
if(nr_threads >= max_threads) // threads-max 引數影響
3.3.3 Pid_max引數限制
alloc_pid-->alloc_pidmap //pid_max引數影響
3.3.4 單程式記憶體限制
單個程式的執行緒數,受限於vm.max_map_count限制
4. 總結
/proc/sys/kernel/pid_max #作業系統執行緒數限制
/proc/sys/kernel/thread-max #作業系統執行緒數
max_user_process(ulimit -u) #系統限制某使用者下最多可以執行多少程式或執行緒
/proc/sys/vm/max_map_count #單程式mmap的限制會影響當個程式可建立的執行緒數
/sys/fs/cgroup/memory/${cgroup}/memory.kmem#單個docker 核心記憶體的限制,可以影響task_struct等slab節點的申請,間接影響可建立的執行緒數相關文章
- Linux 執行緒(程式)數限制分析Linux執行緒
- .net使用Task多執行緒執行任務 .net限制執行緒數量執行緒
- linux 檢視 程式 執行緒數Linux執行緒
- 限制程式執行例項數 (轉)
- Linux每程式執行緒數問題處理Linux執行緒
- 多執行緒------執行緒與程式/執行緒排程/建立執行緒執行緒
- 程式執行緒篇——程式執行緒基礎執行緒
- Linux程式多執行緒入門Linux執行緒
- linux 多執行緒程式設計Linux執行緒程式設計
- 如何查詢一個程式下面的執行緒數(程式和執行緒區別)執行緒
- 程式-程式-執行緒執行緒
- 4、Linux多執行緒,執行緒同步(2)Linux執行緒
- linux執行緒-sysconf系統變數Linux執行緒變數
- [短文速讀 -5] 多執行緒程式設計引子:程式、執行緒、執行緒安全執行緒程式設計
- Linux 多執行緒程式設計(不限Linux)Linux執行緒程式設計
- Linux程式執行緒學習筆記Linux執行緒筆記
- 簡述Linux 中程式與執行緒Linux執行緒
- Linux執行緒與程式的區別Linux執行緒
- 程式和執行緒的區別(Linux)執行緒Linux
- Linux程式與執行緒的區別Linux執行緒
- Linux中檢視程式的多執行緒Linux執行緒
- Linux核心同步,程式,執行緒同步薦Linux執行緒
- 程式執行緒篇——執行緒切換(下)執行緒
- 執行緒、執行緒與程式、ULT與KLT執行緒
- 程式執行緒篇——執行緒切換(上)執行緒
- 多執行緒-程式和執行緒的概述執行緒
- 執行緒(一)——執行緒,執行緒池,Task概念+程式碼實踐執行緒
- Centos檢視程式的執行緒數量CentOS執行緒
- 執行緒池中的最大執行緒數、核心執行緒數和佇列大小的合理設定執行緒佇列
- Linux中程式與程式、執行緒的區別!Linux執行緒
- 多執行緒(2)-執行緒同步條件變數執行緒變數
- linux-執行緒Linux執行緒
- Linux檢視系統cpu個數、核心數、執行緒數Linux執行緒
- java 最大執行緒數Java執行緒
- Android程式框架:執行緒與執行緒池Android框架執行緒
- Java多執行緒1:程式與執行緒概述Java執行緒
- 程式與執行緒執行緒
- 執行緒與程式執行緒