http://blog./uid-20662820-id-5690021.html Author: Tony [email protected] Mon Mar 28 00:58:37 CST 2016 @CU瀚海書香
TOC \o "1-3" \h \z \u Linux線程(進(jìn)程)數(shù)限制.... PAGEREF _Toc447043843 \h 1 目錄.... PAGEREF _Toc447043844 \h 2 1. 問題來源.... PAGEREF _Toc447043845 \h 2 2. 初步原因分析和解決.... PAGEREF _Toc447043846 \h 2 3. Linux線程數(shù)的限制.... PAGEREF _Toc447043847 \h 3 3.1 應(yīng)用層測(cè)試代碼... PAGEREF _Toc447043848 \h 3 3.2 逆向分析... PAGEREF _Toc447043849 \h 4 3.3 正向分析... PAGEREF _Toc447043850 \h 6 3.3.1 內(nèi)存限制... PAGEREF _Toc447043851 \h 6 3.3.2 Threads-max參數(shù)限制... PAGEREF _Toc447043852 \h 6 3.3.3 Pid_max參數(shù)限制... PAGEREF _Toc447043853 \h 7 3.3.4 單進(jìn)程內(nèi)存限制... PAGEREF _Toc447043854 \h 7 4. 總結(jié).... PAGEREF _Toc447043855 \h 7 1. 問題來源
公司線上環(huán)境出現(xiàn)MQ不能接受消息的異常,,運(yùn)維和開發(fā)人員臨時(shí)切換另一臺(tái)服務(wù)器的MQ后恢復(fù),。同時(shí)運(yùn)維人員反饋在出現(xiàn)問題的服務(wù)器上很多基本的命令都不能運(yùn)行,出現(xiàn)如下錯(cuò)誤:
2. 初步原因分析和解決讓運(yùn)維的兄弟在服務(wù)上查看內(nèi)存,、CPU,、網(wǎng)絡(luò)、IO等基本信息都正常,。于是自己到運(yùn)維的服務(wù)器上看了一下,,下面是slabtop –s c的運(yùn)行結(jié)果,問題初步原因貌似出現(xiàn)了:
如果看到這個(gè)截圖你看不出什么異常的話,,下面的內(nèi)容你可能不感興趣,,哈哈。,。,。
task_struct是內(nèi)核對(duì)進(jìn)程的管理單位,通過slub(slab的升級(jí)版,,如果你對(duì)slub不了解也不影響下面的內(nèi)容,只要了解slab就行了)進(jìn)行節(jié)點(diǎn)的管理,,正常負(fù)載的服務(wù)不應(yīng)該出現(xiàn)task_struct的slub結(jié)構(gòu)體占用內(nèi)存最大的情況,,這說明這臺(tái)服務(wù)器上開啟了大量的進(jìn)程(Linux內(nèi)核態(tài)對(duì)進(jìn)程和線程都是一個(gè)單位,不要糾結(jié)這個(gè),,后面可能會(huì)進(jìn)程,、線程混用)。
通過這個(gè)信息,,兄弟們發(fā)現(xiàn)這臺(tái)服務(wù)器上有近3萬個(gè)線程,,同時(shí)也定位到出問題的網(wǎng)元(一個(gè)新同學(xué)的代碼沒有Review直接上線,里面有一個(gè)BUG觸發(fā)了異常創(chuàng)建大量線程),。
問題貌似到這里就結(jié)束了,,但是作為一個(gè)有情懷的程序員,這只是一個(gè)開始(哥的情懷白天都被繁瑣的工作磨沒了,,只能在這深夜獨(dú)享了,。,。。) 3. Linux線程數(shù)的限制3.1 應(yīng)用層測(cè)試代碼
#include
#include
#include
#include
#include
#define MEMSIZE (1024 * 1024 * 256)
void thread(void) { sleep(100); return; }
int main() { pthread_t id; int ret; int num = 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跟蹤,,發(fā)現(xiàn)問題出現(xiàn)在copy_process函數(shù),,那剩下的工作就是分析copy_process返回異常的原因了。 3.2 逆向分析這個(gè)時(shí)候逆向分析最簡(jiǎn)單直接,,可以直接定位到問題原因,。 首先通過strace分析,查找出問題的系統(tǒng)調(diào)用是clone函數(shù),。 SYS_clone--->do_fork--->copy_process,。內(nèi)核態(tài)函數(shù)的分析工具這次試用了systemtap,下面就是沒有任何美感的stap代碼了,,將就著看看吧
probe kernel.statement("*@kernel/fork.c:1184") { printf("In kernel/fork.c 1184\n"); } probe kernel.statement("*@kernel/fork.c:1197") { printf("In kernel/fork.c 1197\n"); }
probe kernel.statement("*@kernel/fork.c:1206") { printf("In kernel/fork.c 1206\n"); } probe kernel.statement("*@kernel/fork.c:1338") { printf("In kernel/fork.c 1338\n"); } probe kernel.statement("*@kernel/fork.c:1342") { printf("In kernel/fork.c 1342\n"); } probe kernel.statement("*@kernel/fork.c:1363") { printf("In kernel/fork.c 1363\n"); } probe kernel.statement("*@kernel/fork.c:1369") { printf("In kernel/fork.c 1369\n"); } probe kernel.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"); }
probe kernel.statement("*@kernel/pid.c:301") { printf("alloc_pid 301\n"); } probe kernel.statement("*@kernel/pid.c:312") { printf("alloc_pid 312\n"); } probe kernel.function("alloc_pid").return { printf("alloc_pid return %ld\n", check_null_pid($return)); }
發(fā)現(xiàn)問題出在alloc_pid失敗,,分析內(nèi)核代碼,這個(gè)受限于kernel.pid_max參數(shù),。
可以分配的線程數(shù)增加了幾百個(gè),,但是并沒有顯著增加,。 繼續(xù)通過strace跟蹤,這次發(fā)現(xiàn)問題出在了mprotect函數(shù)
這個(gè)問題是由于當(dāng)個(gè)線程的mmap個(gè)數(shù)限制,,受限于vm.max_map_count參數(shù),。
將參數(shù)調(diào)大到100000后,再次運(yùn)行,,線程數(shù)明顯增加了,。 其實(shí)這里面還有一個(gè)參數(shù) kernel.threads-max限制,由于系統(tǒng)默認(rèn)將這個(gè)參數(shù)設(shè)置為800000,,非常大,,所以這個(gè)參數(shù)的影響一直沒有保留出來。
后面又犯賤把相關(guān)的參數(shù)都設(shè)置成800000,,結(jié)果內(nèi)存耗盡,,系統(tǒng)直接沒響應(yīng)了。,。,。。 3.3 正向分析直接分析copy_process代碼 copy_process 3.3.1 內(nèi)存限制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” //這里也是一個(gè)坑,,docker這種基于cgroup的也會(huì)影響,,可能會(huì)因?yàn)榉峙浣oslub的內(nèi)存不夠用出現(xiàn)線程限制 具體函數(shù): alloc_pages---->__memcg_kmem_newpage_charge-->memcg_charge_kmem-->__res_counter_charge-->res_counter_charge_locked 3.3.2 Threads-max參數(shù)限制if (nr_threads >= max_threads) // threads-max 參數(shù)影響 3.3.3 Pid_max參數(shù)限制alloc_pid-->alloc_pidmap //pid_max參數(shù)影響
3.3.4 單進(jìn)程內(nèi)存限制單個(gè)進(jìn)程的線程數(shù),受限于vm.max_map_count限制 4. 總結(jié)/proc/sys/kernel/pid_max #操作系統(tǒng)線程數(shù)限制 /proc/sys/kernel/thread-max #操作系統(tǒng)線程數(shù) max_user_process(ulimit -u) #系統(tǒng)限制某用戶下最多可以運(yùn)行多少進(jìn)程或線程 /proc/sys/vm/max_map_count #單進(jìn)程mmap的限制會(huì)影響當(dāng)個(gè)進(jìn)程可創(chuàng)建的線程數(shù) /sys/fs/cgroup/memory/${cgroup}/memory.kmem #單個(gè)docker 內(nèi)核內(nèi)存的限制,可以影響task_struct等slab節(jié)點(diǎn)的申請(qǐng),,間接影響可創(chuàng)建的線程數(shù) |
|