【Linux进程、线程、任务调度】一 Linux进程生命周期 僵尸进程的含义 停止状态与作业控制 内存泄漏的真实含义 task_struct以及task_struct之间的关系
- 學習交流加(可免費幫忙下載CSDN資源):
- 個人微信: liu1126137994
- 學習交流資源分享qq群1(已滿): 962535112
- 學習交流資源分享qq群2: 780902027
文章目錄
- 1、進程控制塊PCB
- 2、進程的生命周期
- 3、作業控制(cpulimit)
- 4、內存泄漏到底是什么
- 5、初見fork
- 6、總結
本篇文章主要記錄以下學習內容:
- Linux進程生命周期(就緒、運行、睡眠、停止、僵尸)
- 僵尸的含義
- 停止狀態與作業控制, cpulimit
- 內存泄漏的真實含義
- task_struct以及task_struct之間的關系
- 初見fork和僵尸
1、進程控制塊PCB
Task_struct (PCB) 通俗一點的說就是描述進程資源的結構體,也可以稱為進程描述符。在這個結構體中存放著這個進程所需要的所有資源的結構的描述。例如我們能想到的進程肯定有進程id,進程的內存管理,對文件的管理,對信號的管理等,那么PCB中就肯定存有類似于下面的結構:
其中PID的數量是有限的,在我自己的Linux系統中是32768
$ cat /proc/sys/kernel/pid_max 32768 所以我們不能無限制的創建進程。那么在Linux中Task_struct是如何被管理的呢?
形成樹
因為鏈表遍歷的開銷比較大,所以會在鏈表的基礎上,形成樹結構,這樣會使對進程描述符的遍歷的時間復雜度更低
形成哈希 :pid->task_struct
為了更加快捷的訪問到進程描述符,可以讓進程的pid作為索引,形成哈希結構,這樣在實現進程的調度算法時,效率會更高效。
2、進程的生命周期
上圖表示了進程的六種狀態,就緒態,運行態,深度睡眠態,淺睡眠態,停止態,僵尸態。
就緒態和運行態在數值上是相等的,都是由宏TASK_RUNNING定義。就緒態和運行太可以相互轉換,運行態可以到停止態(例如ctrl+z),停止態可以恢復到就緒態。
其中,就緒態,運行態,停止態很好理解,這里不再贅述。
- 深睡眠
進程處于睡眠態(調用sleep),等到資源到位,就可以被調度(變成就緒態TASK_RUNNING)。 - 淺睡眠
進程處于睡眠態(調用sleep),等到資源到位,或者收到信號,就可以被調度(變成就緒態TASK_RUNNING)。
正常的進程睡眠都是淺睡眠,但是內核中有一些進程處于睡眠態不希望被信號打斷,那么它就會處于深睡眠狀態。
- 僵尸態
資源已經釋放,沒有內存泄漏等!!!
但是 Task_struct還在,這樣的話,父進程可以根據子進程的Task_struct結構體存的退出碼,查出子進程的死因。
有內核代碼如下:
3、作業控制(cpulimit)
有時候我們的進程的CPU占用率非常高,為了使其他進程可以獲得CPU時間,我們可以使用一些手段降低進程的CPU占用率。
其中cpulimit是之前比較常用的一個命令,它利用間斷性的使進程處于停止態,從而降低進程的CPU占有率。
假設我的進程是一個死循環,且CPU占有率很高,則通過以下命令可以降低該使進程號為10111的進程的CPU占有率變為20%。
$ cpulimit -l 20 -p 10111cpulimit的原理:
4、內存泄漏到底是什么
- 內存泄漏不是進程死了內存沒釋放
如果進程死了(退出或者變成僵尸),它所占有的內存資源會瞬間全部釋放。
- 內存泄漏是進程活著,但隨著時間的推移,內存消耗越來越多
5、初見fork
1. 初識fork
看下面程序打印幾個hello?
int main() {fork();printf("hello\n");fork();printf("hello\n");while (1);return 0; }假設p1是main函數這個進程,進入函數后,fork產生一個子進程p2,p1和p2各打印一個hello,接著p1和p2又各fork(),分別又產生兩個hello,所以一共打印6個hello。
運行下面程序看如何打印:
#include <stdio.h> #include <sys/wait.h> #include <stdlib.h> #include <unistd.h>int main(){pid_t pid;pid = fork();if(pid==-1){ /* 創建不成功 */perror("Can't creat new process");exit(1);}else if(pid==0){ /* pid==0,子進程運行代碼 */printf("a\n");}else { /* 父進程運行代碼 */printf("b\n");}/* 父子進程都運行的代碼 */printf("c\n");while(1); }運行結果為:
- 結果分析:
fork()函數的返回值是返回兩次的,在父進程中返回子進程的pid,在子進程中返回0。借此我們可以在代碼中區分開父子進程運行的代碼。
進入函數后首先fork(),產生一個子進程,在子進程的進程空間的環境創建好之前,父進程就已經運行完并打印了b和c,然后子進程打印a和c。
2. 子死父清場(life_period.c)
#include <stdio.h> #include <sys/wait.h> #include <stdlib.h> #include <unistd.h>int main(void) {pid_t pid,wait_pid;int status;pid = fork();if (pid==-1) {perror("Cannot create new process");exit(1);} else if (pid==0) {printf("child process id: %ld\n", (long) getpid());pause();_exit(0);} else { #if 1 /* define 1 to make child process always a zomie */printf("ppid:%d\n", getpid());while(1); #endifdo {wait_pid=waitpid(pid, &status, WUNTRACED | WCONTINUED);if (wait_pid == -1) {perror("cannot using waitpid function");exit(1);}if (WIFEXITED(status))printf("child process exites, status=%d\n", WEXITSTATUS(status));if(WIFSIGNALED(status))printf("child process is killed by signal %d\n", WTERMSIG(status));if (WIFSTOPPED(status))printf("child process is stopped by signal %d\n", WSTOPSIG(status));if (WIFCONTINUED(status))printf("child process resume running....\n");} while (!WIFEXITED(status) && !WIFSIGNALED(status));exit(0);} }- 在if 1不改為if 0的情況下
編譯運行程序,殺死子進程,查看父進程的僵尸態
編譯運行:
$ gcc life_period.c $ ./a.out Child process id:6426另開一個終端先看父子進程的狀態:
然后殺死子進程:
再查看狀態:
可以看到,子進程(pid=6426)的狀態已經變味僵尸態
- 在if 1改為if 0的情況下
編譯運行程序,殺死子進程,查看父進程的僵尸態
編譯運行:
$ gcc life_period.c $ ./a.out Child process id:6430另開一個終端先看父子進程的狀態:
然后殺死子進程
$ kill -9 6430在第一個終端可以看到子進程被殺死的原因
然后父進程也退出。
可以看出父進程可以通過waitpid()函數回收子進程的task_struct結構。
6、總結
- 理解Linux進程的生命周期(六種狀態)
- 理解task_struct結構
- 理解僵尸進程(資源已經釋放,Task_struct結構還在)
- 理解內存泄漏的真實含義
- 理解fork與僵尸態
- 動手寫上述實驗代碼并自己編譯運行
學習探討加:
個人微信:liu1126137994
個人qq :1126137994
總結
以上是生活随笔為你收集整理的【Linux进程、线程、任务调度】一 Linux进程生命周期 僵尸进程的含义 停止状态与作业控制 内存泄漏的真实含义 task_struct以及task_struct之间的关系的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Caffe训练过程:test_iter
- 下一篇: 停用词过滤