UNIX家族的操作系統(tǒng)里面都用進程的概念足画,進程就是一個程序運行的實體(instance)。這個概念當(dāng)年大學(xué)里面學(xué)《操作系統(tǒng)原理》的時候怎么也搞不懂(清華大學(xué)出版社出版结耀,屠立德著)砂竖。
直到后面讀研究生開始做課題么介,寫代碼才知道到底是怎么回事罪治。
Linux延用了UNIX的設(shè)計思想丽声,繼續(xù)使用進程這個概念(后面的線程也是用進程來實現(xiàn)的,所以叫做輕量級進程规阀,LWP)恒序。進程在系統(tǒng)中有不同的狀態(tài),進程就是在各個狀態(tài)之間來回切換谁撼,從而完成設(shè)計的功能。操作系統(tǒng)內(nèi)部為每一個進程提供了一個進程描述符,這個結(jié)構(gòu)龐大而且復(fù)雜厉碟,用來描述進程的信息喊巍,例如打開的文件,調(diào)度信息箍鼓,父子進程關(guān)系等等崭参,是操作系統(tǒng)管理進程的核心數(shù)據(jù)結(jié)構(gòu),Linux里面是struct task_struct款咖。
系統(tǒng)里面的進程因為父子關(guān)系而形成一個樹形結(jié)構(gòu)何暮。整個系統(tǒng)啟動過程中第一個用戶態(tài)的進程是Init進程,叫做1號進程铐殃,它是整個樹形結(jié)構(gòu)的根海洼,所有進程都是它的子孫后代。Init是系統(tǒng)的管理進程富腊,包含很多功能坏逢,其中一個功能就是“垃圾回收”。
在操作系統(tǒng)原理中會提到“僵尸進程”赘被,什么是僵尸進程是整?就是一個進程在結(jié)束運行的時候它的主體已經(jīng)結(jié)束,但是內(nèi)核當(dāng)中的進程描述符還沒有被回收(它的“殼”還在民假,但是“靈魂”沒有了)浮入。為什么它的進程描述符沒有被回收呢?
因為一個進程結(jié)束運行的時候羊异,是需要通知它的父進程對其進程描述符進行回收(它自己都死了事秀,沒法回收自己)。而如果它的父進程忙于別的事情而不去主動回收該進程的描述符球化,就會導(dǎo)致系統(tǒng)出現(xiàn)“僵尸進程”秽晚。而這個通知和回收的過程是通過信號SIGCHLD和wait來完成的。具體可以參考Linux進程編程方面的文章或者書籍筒愚。
下面是一個例子赴蝇。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
int main(int argc, char **argv)
{
pid_t pid, ppid = 0;
printf("Argv[0] = %s, length = %ld\n", argv[0], strlen(argv[0]));
strncpy(argv[0], "testaa", strlen(argv[0]));
pid = fork();
if (pid > 0) {
printf("Parent: pid = %d, ppid = %d\n", getpid(), getppid());
while(1) {
sleep(1);
}
} else if (pid == 0) {
strncpy(argv[0], "taowtest", strlen(argv[0]));
printf("Child: pid = %d, ppid = %d\n", getpid(), getppid());
exit(123);
}
return 0;
}
運行結(jié)果為 (運行環(huán)境是Ubuntu 18.04 X86_64, VMware的虛擬機)
Parent: pid = 561, ppid = 31574
Child: pid = 562, ppid = 561
/test/kermod# ps ax | grep -e 'testaa\|taow'
561 pts/0 S 0:00 testaa
562 pts/0 Z 0:00 [taowtest] <defunct>
“僵尸進程”在ps命令下面顯示的狀態(tài)是Z,而且不接受kill -9 pid去退出巢掺。
那么如果一個進程A的父進程P先于它退出句伶,那么A在退出的時候誰來回收A的進程描述符呢?這個就是init進程的工作陆淀。當(dāng)一個進程的父進程(生父)退出之后考余,這個父進程下面的子進程都成為init(養(yǎng)父)的子進程了。init進程會周期的調(diào)用wait()來回收其子進程的進程描述符轧苫。
所以楚堤,要想真正“清除”僵尸進程,需要殺掉(kill)它的父進程(生父,不是養(yǎng)父init進程)身冬。
有沒有別的辦法來做這個事情呢衅胀?有,只要我們能寫和插入kernel module就可以干這個事情酥筝。
下面這個代碼是模擬內(nèi)核當(dāng)中父進程回收子進程資源的邏輯來完成對僵尸進程的“過繼”和“清除”滚躯,而不需要殺死其父進程。簡單來說就是把一個父進程活著的僵尸進程直接交給init進程使其對它進行回收嘿歌。
代碼參考前面掸掏。父進程啟動把自己名字改為testaa,然后啟動子進程宙帝,子進程改名為taowtest丧凤,并把自己變成僵尸進程。最后插入kernel module茄唐,其找到Zombie狀態(tài)息裸,并且名字是taowtest的進程之后對其進行處理,把它交給init進程沪编,并通知init進程對其進行回收呼盆。
下面是找到名為taowtest的僵尸進程的參考代碼。
for_each_process( task ) {
? ? if ((task->pid == 0) || (task->pid == 1)) {
? ? ? ? pr_info("PID %d, comm = %s\n", task->pid, task->comm);
? ? ? ? p = task;
? ? ? ? continue;
? ? }
? ? if (strstr(task->comm, "taowtest")) {
? ? ? ? pr_info("Got : %d, %s, ppid=%d, exit_state = %d\n",
? ? ? ? ? ? ? ? task->pid, task->comm, task->parent->pid, task->exit_state);
? ? ? ? if (task->exit_state == EXIT_ZOMBIE) {
? ? ? ? ? ? 蚁廓。访圃。。相嵌。腿时。
? ? ? ? ? ? pr_info("Reaped Zombie process %d\n", task->pid);
? ? ? ? ? ? 。饭宾。批糟。。看铆。
? ? ? ? }
? ? }
}
模塊加載之后的內(nèi)核log顯示如下徽鼎,
[250532.186292] LOADING MODULE
[250532.186294] PID 1, comm = systemd
[250532.186380] Got : 561, taowtest, ppid=31574, exit_state = 0
[250532.186382] Got : 562, taowtest, ppid=561, exit_state = 32
[250532.187181] Reaped Zombie process 562
此時,再看ps -ax的輸出弹惦,已經(jīng)找不到taowtest了否淤,而此時testaa仍然還在。
/test/kermod# ps ax | grep 'testaa\|taow'
561 pts/0 S 0:00 testaa
577 pts/0 S+ 0:00 grep --color=auto testaa\|taow
/test/kermod#
以上是一種回收僵尸進程的方法棠隐,還有另一種方法有空再分析吧石抡。
總之,這是個有趣的實驗助泽,有助于搞清楚Linux系統(tǒng)中進程之間關(guān)系啰扛,進程回收嚎京,信號處理,以及init進程等等很多方面侠讯。而且它可以解決不殺死父進程的情況下回收清理大量僵尸進程的場景和需求挖藏。
后續(xù)會陸陸續(xù)續(xù)把一千多篇關(guān)于Linux暑刃,X86厢漩,VT-X,KVM岩臣,服務(wù)器溜嗜,嵌入式系統(tǒng)等有關(guān)的東西整理出來。
歡迎轉(zhuǎn)載架谎,轉(zhuǎn)載請標明出處炸宵。Thanks