聊聊 Linux 中進程與線程
進程
進程是一個具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合的一次運行活動。它是操作系統(tǒng)動態(tài)執(zhí)行的基本單元,在傳統(tǒng)的操作系統(tǒng)中,進程既是基本的分配單元,也是基本的執(zhí)行單元。進程的概念主要有兩點:第一,進程是一個實體。每一個進程都有它自己的地址空間,一般情況下,包括文本區(qū)域(text region)、數(shù)據(jù)區(qū)域(data region)和堆棧(stack region)。文本區(qū)域存儲處理器執(zhí)行的代碼;數(shù)據(jù)區(qū)域存儲變量和進程執(zhí)行期間使用的動態(tài)分配的內(nèi)存;堆棧區(qū)域存儲著活動過程調(diào)用的指令和本地變量。第二,進程是一個“執(zhí)行中的程序”。程序是一個沒有生命的實體,只有處理器賦予程序生命時(操作系統(tǒng)執(zhí)行之),它才能成為一個活動的實體,我們稱其為進程。
線程
線程是操作系統(tǒng)能夠進行運算調(diào)度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務(wù)。在Unix System V及SunOS中也被稱為輕量進程(lightweight processes),但輕量進程更多指內(nèi)核線程(kernel thread),而把用戶線程(user thread)稱為線程。進程與線程之間的關(guān)系 同一進程中的多條線程將共享該進程中的全部系統(tǒng)資源,如虛擬地址空間,文件描述符和信號處理等等。但同一進程中的多個線程有各自的調(diào)用棧(call stack),自己的寄存器環(huán)境(register context),自己的線程本地存儲(thread-local storage)。
linux中線程與進程
linux內(nèi)核中,進程與線程它們雖然都是任務(wù),但是應(yīng)該加以區(qū)分。其中,pid 是 process id,tgid 是 thread group ID。任何一個進程,如果只有主線程,那 pid 是自己,tgid 是自己,group_leader 指向的還是自己。但是,如果一個進程創(chuàng)建了其他線程,那就會有所變化了。線程有自己的 pid,tgid 就是進程的主線程的 pid,group_leader 指向的就是進程的主線程。所以有了 tgid,我們就知道 tast_struct 代表的是一個進程還是代表一個線程了。關(guān)系如下:圖片來源[1]
關(guān)于線程與進程的內(nèi)核參數(shù)
ulimit 限制,在 Linux 下執(zhí)行ulimit -a,你會看到 ulimit 對各種資源的限制。
其中的“max user processes”就是一個進程能創(chuàng)建的最大線程數(shù),我們可以修改這個參數(shù):
ulimit -u 66535
2.參數(shù)sys.kernel.threads-max限制。這個參數(shù)限制操作系統(tǒng)全局的線程數(shù),通過下面的命令可以查看它的值。查看threads-max的方法:
cat /proc/sys/kernel/threads-max
32768
修改這個值的方法:
#方法一,重啟后會失效
echo 65535 > /proc/sys/kernel/threads-max
#方法二,永久修改
echo "kernel.threads-max = 65535" >> /etc/sysctl.conf
3.參數(shù)sys.kernel.pid_max限制。這個參數(shù)限制操作系統(tǒng)全局的線程數(shù),通過下面的命令可以查看它的值。這里說一下32位操作系統(tǒng)這個值最大是32768不能修改,64位系統(tǒng)上pid_max最大值為2^22。Linux 內(nèi)核在初始化系統(tǒng)的時候,會根據(jù)機器 CPU 的數(shù)目來設(shè)置 pid_max 的值。比如說,如果機器中 CPU 數(shù)目小于等于 32,那么 pid_max 就會被設(shè)置為 32768(32K);如果機器中的 CPU 數(shù)目大于 32,那么 pid_max 就被設(shè)置為 N*1024 (N 就是 CPU 數(shù)目)。查看pid_max的方法:
cat /proc/sys/kernel/pid_max
32768
修改這個值的方法:
#方法一,重啟后會失效
echo 65535 > /proc/sys/kernel/pid_max
#方法二,永久修改
echo "kernel.pid_max = 65535" >> /etc/sysctl.conf
注意:一個線程數(shù)也會占用一個pid,所以threads-max須要小于等于pid_max。
容器線程數(shù)量的限制
對于 Linux 系統(tǒng)而言,容器就是一組進程的集合。如果容器中的應(yīng)用創(chuàng)建過多的進程或者出現(xiàn) bug,就會產(chǎn)生類似 fork bomb 的行為。這樣,不但會使同一個節(jié)點上的其他容器無法工作,還會讓宿主機本身也無法工作。所以對于每個容器來說,我們都需要限制它的最大進程數(shù)目,而這個功能由 pids Cgroup 這個子系統(tǒng)來完成。之前遇到過這樣一個問題,java應(yīng)用因為要處理很多定時任務(wù),一個定時任務(wù)拉起一個線程。但是由于代碼上的 bug ,沒有及時對線程進行回收,然后這個容器不斷產(chǎn)生線程,耗盡了宿主機的進程表空間,最終導(dǎo)致整臺linux上的服務(wù)報錯“java.lang.OutOfMemoryError: Unable to create native threads”,影響了其它的服務(wù)。創(chuàng)建進程出現(xiàn)“Resource temporarily unavailable”的報錯。這種問題除了讓開發(fā)人員修復(fù) bug 外,也需要在系統(tǒng)層面對線程數(shù)量進行限制。
cgroup
cgroup中對pid進行了隔離,通過更改docker/kubelet配置,可以限制pid總數(shù),從而達到限制線程總數(shù)的目的。
docker,容器啟動時設(shè)置 --pids-limit 參數(shù),限制容器級別pid總數(shù)
kubelet,開啟SupportPodPidsLimit特性,設(shè)置–pod-max-pids參數(shù),限制node每個pod的pid總數(shù)
原理如下:在一個容器建立之后,創(chuàng)建容器的服務(wù)會在 /sys/fs/cgroup/pids 下建立一個子目錄,就是一個控制組,控制組里最關(guān)鍵的一個文件就是 pids.max。kubelet或者docker向這個文件寫入數(shù)值,而這個值就是這個容器中允許的最大進程數(shù)目。Kubernetes 里面的每個節(jié)點都會運行一個叫做 Kubelet 的服務(wù),負責節(jié)點上容器的狀態(tài)和生命周期,比如創(chuàng)建和刪除容器。根據(jù) Kubernetes 的官方文檔 Process ID Limits And Reservations 內(nèi)容,可以設(shè)置 Kubelet 服務(wù)的 –pod-max-pids 配置選項,之后在該節(jié)點上創(chuàng)建的容器,最終都會使用 Cgroups pid 控制器限制容器的進程數(shù)量。
總結(jié)
linux中為了防止進程惡意使用資源,系統(tǒng)使用ulimit來限制進程的資源使用情況(包括文件描述符,線程數(shù),內(nèi)存大小等)。同樣地在容器化場景中,需要限制其系統(tǒng)資源的使用量。pid是計算機重要資源,所以需要在使用時,加以限制,以保證資源的合理利用。dockerd暫無默認的pid limit設(shè)置;k8s 限制線程數(shù),可通過在kubelet中開啟SupportPodPidsLimit特性,設(shè)置pod級別pid limit。
好了,今天的內(nèi)容就到這里。我是夏老師,祝你今天知識吃飽,我們下次再見。