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