干貨 | 一文搞懂Linux系統(tǒng)下進(jìn)程
點(diǎn)擊上方“君子黎”,選擇“置頂/星標(biāo)公眾號(hào)”
干貨福利,第一時(shí)間送達(dá)! 1. 進(jìn)程概念
所謂進(jìn)程(Process),是指運(yùn)行中的程序,它是程序的一個(gè)運(yùn)行實(shí)例。對(duì)于同一個(gè)程序,當(dāng)我們分別運(yùn)行兩次時(shí)候,操作系統(tǒng)會(huì)創(chuàng)建兩個(gè)不同的進(jìn)程。注意,程序不等同于進(jìn)程,進(jìn)程是正在執(zhí)行中的程序以及相關(guān)資源(如程序計(jì)數(shù)器PC、打開(kāi)的文件描述符、掛起的信號(hào)、處理器狀態(tài)、寄存器狀態(tài)、內(nèi)核數(shù)據(jù)、臨時(shí)數(shù)據(jù)堆棧等)的總稱,而程序只是存儲(chǔ)在某種介質(zhì)上(如磁盤)的一組機(jī)器機(jī)器代碼指令和數(shù)據(jù)。
對(duì)于進(jìn)程與程序之間的差異,其示意圖如下所示。
2. 進(jìn)程數(shù)據(jù)結(jié)構(gòu)
Linux操作系統(tǒng)為了便于管理進(jìn)程,在linux/sched.h文件中定義了名為task_struct的數(shù)據(jù)結(jié)構(gòu),該結(jié)構(gòu)包含了每一個(gè)運(yùn)行中的具體進(jìn)程所需的所有信息,比如進(jìn)程優(yōu)先級(jí)、進(jìn)程的狀態(tài)、進(jìn)程PID、文件系統(tǒng)信息等等。對(duì)于task_struct數(shù)據(jù)結(jié)構(gòu),又稱為“進(jìn)程描述符(Process Descriptor)” 。Linux把每個(gè)進(jìn)程(task_struct)都放在內(nèi)核中的雙向循環(huán)鏈接中,該雙向循環(huán)鏈表又被稱為“任務(wù)隊(duì)列(Task List)”。
在Linux 0.01版本中,該結(jié)構(gòu)體(task_struct)僅有幾十個(gè)成員,閱讀起來(lái)十分方便。
struct task_struct {
/* these are hardcoded - don't touch */
long state; /* -1 unrunnable, 0 runnable, >0 stopped */
long counter;
long priority;
long signal;
fn_ptr sig_restorer;
fn_ptr sig_fn[32];
/* various fields */
int exit_code;
unsigned long end_code,end_data,brk,start_stack;
long pid,father,pgrp,session,leader;
unsigned short uid,euid,suid;
unsigned short gid,egid,sgid;
long alarm;
long utime,stime,cutime,cstime,start_time;
unsigned short used_math;
/* file system info */
int tty; /* -1 if no tty, so it must be signed */
unsigned short umask;
struct m_inode * pwd;
struct m_inode * root;
unsigned long close_on_exec;
struct file * filp[NR_OPEN];
/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
struct desc_struct ldt[3];
/* tss for this task */
struct tss_struct tss;
};
但是到了5.4.3版本及之后,該結(jié)構(gòu)體大小已經(jīng)到了KB級(jí)別了,由此可見(jiàn)從0.01版本到5.4.3版本間新增了許多的特性和功能。

2.1 進(jìn)程描述符PID
前面提到過(guò),task_struct數(shù)據(jù)結(jié)構(gòu)描述了每個(gè)進(jìn)程的詳細(xì)完整信息。在該數(shù)據(jù)結(jié)構(gòu)中,有一個(gè)最為顯眼的成員變量,即pid。它是Linux內(nèi)核用來(lái)識(shí)別各進(jìn)程的唯一標(biāo)識(shí),稱為“進(jìn)程標(biāo)識(shí)值(Process Identificaion Value, PID)”。在0.01內(nèi)核版本中,它是long int類型,但是之后,其類型統(tǒng)一適配為pid_t類型。pid_t是unsigned int的一個(gè)別名。
typedef signed int pid_t;
pid_t pid;
從某種層面上來(lái)說(shuō),成員pid的數(shù)據(jù)類型取值范圍表明了系統(tǒng)中允許同時(shí)運(yùn)行(存在)的進(jìn)程的最大數(shù)量,因?yàn)檫M(jìn)程PID不能為負(fù)數(shù)。盡管unsigned int類型的取值范圍達(dá)到了0~4294967295,但是實(shí)際情況中,基于硬件技術(shù)和資源限制等緣故,操作系統(tǒng)上不可能同時(shí)運(yùn)行這么多數(shù)量的進(jìn)程。
可通過(guò)查看/proc/sys/kernel/pid_max文件以得知當(dāng)前設(shè)備環(huán)境能夠支持的同時(shí)運(yùn)行的最大進(jìn)程數(shù)量限制。某些情況下,可能通過(guò)修改該文件配置值,以提高進(jìn)程的并行數(shù)量。

2.1.1 進(jìn)程資源限制
由于系統(tǒng)資源有限,因此,內(nèi)核必須嚴(yán)格把控并記錄每一個(gè)運(yùn)行中的進(jìn)程的資源(包括內(nèi)存、CPU、文件句柄等)詳細(xì)使用情況,并且給每一個(gè)進(jìn)程一個(gè)默認(rèn)限制閾值。這是保證每個(gè)進(jìn)程得以在操作系統(tǒng)上完美運(yùn)行的前提。在task_struct數(shù)據(jù)結(jié)構(gòu)中,有一個(gè)數(shù)據(jù)成員signal,該成員是一個(gè)struct signal_struc類型。該數(shù)據(jù)類型中成員變量(數(shù)組)rlim用來(lái)記錄當(dāng)前進(jìn)程的資源限制。
//記錄各進(jìn)程中的16個(gè)屬性資源.
#define RLIM_NLIMITS 16
typedef unsigned long __kernel_ulong_t;
struct rlimit {
__kernel_ulong_t rlim_cur;
__kernel_ulong_t rlim_max;
};
struct signal_struct {
struct rlimit rlim[RLIM_NLIMITS];
};
struct task_struct{
/* Signal handlers: */
struct signal_struct *signal;
};
signal成員和rlim成員間的關(guān)聯(lián)如下圖:
從signal_struct 類型聲明中的rlim成員可知,該成員是一個(gè)擁有16個(gè)元素大小的數(shù)組,這意味著操作系統(tǒng)對(duì)每個(gè)進(jìn)程有16個(gè)資源限制。對(duì)于struct rlimit數(shù)據(jù)類型,共聲明了兩個(gè)成員,分別是:rlim_cur和rlim_max。其中rlim_cur是軟限制(Soft Limit),rlim_max是硬限制(Hard Limit)。
軟限制與硬限制
1) 硬限制是指由超級(jí)用戶/root設(shè)置的對(duì)用戶的最大限制。該值在/etc/security/limits.conf配置文件中進(jìn)行設(shè)置,將其視為上限或是天花板。
2) 軟限制是內(nèi)核對(duì)相應(yīng)資源強(qiáng)制執(zhí)行的值。硬限制充當(dāng)軟限制的上限;非特權(quán)進(jìn)程可以將其軟限制設(shè)置為從0到硬限制的范圍內(nèi)的值,并且(不可逆地)降低其硬限制。
下面是這16個(gè)特定于進(jìn)程的資源限制的說(shuō)明。它們定義于include/uapi/asm-generic/resource.h文件中,其中各選項(xiàng)的具體含義如下:
#define RLIMIT_CPU 0 // 按毫秒計(jì)算的最大CPU時(shí)間
#define RLIMIT_FSIZE 1 // 允許的最大文件長(zhǎng)度
#define RLIMIT_DATA 2 // 數(shù)據(jù)段的最大長(zhǎng)度
#define RLIMIT_STACK 3 // (用戶狀態(tài))棧的最大長(zhǎng)度
#define RLIMIT_CORE 4 // core轉(zhuǎn)存文件的最大長(zhǎng)度
#define RLIMIT_RSS 5 // 常駐內(nèi)存的最大尺寸(進(jìn)程使用頁(yè)幀的最大數(shù)目)
#define RLIMIT_NPROC 6 // 進(jìn)程UID用戶可以打開(kāi)的進(jìn)程的最大數(shù)量
#define RLIMIT_NOFILE 7 // 允許打開(kāi)文件的最大數(shù)量
#define RLIMIT_MEMLOCK 8 // 不可換出頁(yè)的最大數(shù)量
#define RLIMIT_AS 9 // 進(jìn)程占用的虛擬地址空間的最大尺寸
#define RLIMIT_LOCKS 10 // 文件鎖的最大數(shù)目
#define RLIMIT_SIGPENDING 11 // 待決信號(hào)的最大數(shù)量
#define RLIMIT_MSGQUEUE 12 // 信息隊(duì)列的最大數(shù)目
#define RLIMIT_NICE 13 // 非實(shí)時(shí)進(jìn)程的優(yōu)先級(jí)(和調(diào)度有關(guān), 0-39 for nice level 19 .. -20)
#define RLIMIT_RTPRIO 14 // 最大的實(shí)時(shí)優(yōu)先級(jí)
#define RLIMIT_RTTIME 15 /* 指定在不進(jìn)行阻塞系統(tǒng)調(diào)用的情況下,根據(jù)實(shí)時(shí)調(diào)度策略調(diào)度的進(jìn)程可能消耗的CPU時(shí)間的限制(以微秒為單位) */
Linux內(nèi)核在/proc文件系統(tǒng)中,對(duì)每一個(gè)運(yùn)行著的進(jìn)程,都會(huì)創(chuàng)建一個(gè)相應(yīng)的資源限制詳情的文件。因此通過(guò)查看/proc/self/limits文件可得知當(dāng)前進(jìn)程的資源限制。查看指定進(jìn)程的資源限制,只需將self換為對(duì)應(yīng)的進(jìn)程PID,即:/proc/PID/limits。比如查看PID為141(cat /proc/7/limits)的進(jìn)程的資源限制

Linux系統(tǒng)提供了getrlimits()和setrlimit()兩個(gè)系統(tǒng)調(diào)用函數(shù),它們分別用來(lái)獲取、設(shè)置資源限制。
#include <sys/time.h>
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
int prlimit(pid_t pid, int resource, const struct rlimit *new_limit,
struct rlimit *old_limit);
除了使用這兩個(gè)系統(tǒng)函數(shù)外,還可以通過(guò)修改上面提到的系統(tǒng)配置文件/etc/security/limits.conf來(lái)動(dòng)態(tài)更改資源限制。在該配置文件中,每一行均以“#<domain> <type> <item> <value>”的組成形式,描述了用戶的限制。其中#<domain> 可以是用戶名或組名,也可以是通配符*;<type> 表明是軟限制,還是硬限制。<item>表明要限制的條目(即上面16種資源限制之一);<value>則是該限制條目的值。
////vim /etc/security/limits.conf
#<domain> <type> <item> <value>
#* soft core 0
#* hard rss 10000
#@student hard nproc 20
#@faculty soft nproc 20
#@faculty hard nproc 50
#ftp hard nproc 0
#@student - maxlogins 4
# End of file
* soft nofile 131072
* hard nofile 131072
此外,還可以通過(guò)命令行方式來(lái)進(jìn)行資源限制的獲取與設(shè)置。該命令是ulimit,它的使用方式如下(方括號(hào)的數(shù)字用以表明ulimit支持的選項(xiàng)參數(shù))。
ulimit: usage: ulimit [-SHacdefilmnpqrstuvx] [limit]
比如查看當(dāng)前系統(tǒng)的軟限制:ulimit -S -a;查看當(dāng)前系統(tǒng)的硬限制:ulimit -H -a。為一個(gè)變量設(shè)置指定的軟限制:ulimit -S [option] [number],其中[option]是各限制的縮寫,通過(guò)ulimit -a可以看到各限制條目的縮寫,即下面括號(hào)中的-c、-d、-e、-f等。
[root@center-controller-7f4b9fbffc-t5wnh CenterController]# ulimit -a
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 255136
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1048576
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) unlimited
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
為一個(gè)限制條目設(shè)置特定的硬限制:ulimit -H [option] [number]。
2.2 進(jìn)程運(yùn)行狀態(tài)
操作系統(tǒng)上的進(jìn)程并非總是能夠立刻執(zhí)行,有時(shí)它需要等待內(nèi)核調(diào)度的切換,以及資源的分配才能夠運(yùn)行。對(duì)于被加載器加載到內(nèi)存并開(kāi)始運(yùn)行的每一個(gè)進(jìn)程,都將有一個(gè)對(duì)應(yīng)的狀態(tài)標(biāo)志,且這些狀態(tài)標(biāo)志是互斥的,即同一個(gè)時(shí)刻,進(jìn)程只能存在一個(gè)狀態(tài)(State)。這些狀態(tài)可以是下圖中的之一。
該狀態(tài)由task_struct數(shù)據(jù)類型中的state成員表示。
struct task_struct {
/* -1 unrunnable, 0 runnable, >0 stopped: */
volatile long state;
};
對(duì)于0.01版本中Linux的進(jìn)程,其狀態(tài)共有以下5種,它們分別是:運(yùn)行狀態(tài)(TASK_RUNNING)、可中斷睡眠狀態(tài)(TASK_INTERRUPTIBLE)、不可中斷睡眠狀態(tài)(TASK_UNINTERRUPTIBLE)、僵尸狀態(tài)(TASK_ZOMBIE)和暫停狀態(tài)(TASK_STOPPED)。這些狀態(tài)定義于linux/sched.h文件中。
//linux/sched.h 0.01 version
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_ZOMBIE 3
#define TASK_STOPPED 4
對(duì)于TASK_RUNNING、TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE、TASK_ZOMBIE和TASK_STOPPED狀態(tài),它們的含義分別如下:
1) 可運(yùn)行狀態(tài)(TASK_RUNNING)
進(jìn)程是可執(zhí)行的。該進(jìn)程可能正在執(zhí)行,或在運(yùn)行隊(duì)列中等待被執(zhí)行。該狀態(tài)是進(jìn)程在用戶空間中執(zhí)行的唯一可能狀態(tài)。
2) 可中斷的等待狀態(tài)(TASK_INTERRUPTIBLE)
進(jìn)展正在睡眠(被阻塞,或是被掛起),直到某個(gè)條件到達(dá)。喚醒進(jìn)程的條件可以是:產(chǎn)生一個(gè)硬件中斷,釋放其正在等待的系統(tǒng)資源;亦或是傳遞一個(gè)信號(hào)。這時(shí)進(jìn)程的狀態(tài)將會(huì)切換到TASK_RUNNING。
3) 不可中斷的等待狀態(tài)(TASK_UNINTERRUPTIBLE)
與“可中斷的等待狀態(tài)”類似。但是有一個(gè)不同地方是即使把信號(hào)傳遞到一個(gè)處于TASK_UNINTERRUPTIBLE狀態(tài)中的進(jìn)程,也不會(huì)改變?cè)撨M(jìn)程的狀態(tài)。相比于“可中斷的等待狀態(tài)(TASK_INTERRUPTIBLE)”,使用場(chǎng)景相對(duì)較少,但在某些驅(qū)動(dòng)程序中應(yīng)用較為廣泛。
4) 暫停狀態(tài)(TASK_STOPPED)
進(jìn)程的執(zhí)行被暫停。比如當(dāng)進(jìn)程收到SIGSTOP、SIGTSTP、SIGTTIN或是SIGTTOU信號(hào),或是在調(diào)試期間收到任何信息,都會(huì)使進(jìn)程進(jìn)入這種狀態(tài)。
5) 僵尸狀態(tài)(TASK_ZOMBIE)
進(jìn)程的執(zhí)行已經(jīng)終止,但是其父進(jìn)程還沒(méi)有回收(通過(guò)wait()或waitpid())該進(jìn)程所占用的某些系統(tǒng)資源。
這5個(gè)狀態(tài)之間在滿足某種條件之下,能夠相互地進(jìn)行轉(zhuǎn)換。
在Linux 5.4.3版本的中,進(jìn)程的狀態(tài)已經(jīng)添加了很多,如下所示:
/* Used in tsk->state: */
#define TASK_RUNNING 0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002
#define __TASK_STOPPED 0x0004
#define __TASK_TRACED 0x0008
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x0010
#define EXIT_ZOMBIE 0x0020
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_PARKED 0x0040
#define TASK_DEAD 0x0080
#define TASK_WAKEKILL 0x0100
#define TASK_WAKING 0x0200
#define TASK_NOLOAD 0x0400
#define TASK_NEW 0x0800
#define TASK_STATE_MAX 0x1000
這里對(duì)新增的__TASK_TRACED、和TASK_DEAD狀態(tài)進(jìn)行簡(jiǎn)要的補(bǔ)充說(shuō)明。
1) 跟蹤狀態(tài) (__TASK_TRACED)
被其他進(jìn)程跟蹤的進(jìn)程。如通過(guò)ptrace 對(duì)調(diào)試程序進(jìn)行跟蹤。__TASK_TRACED本身不是進(jìn)程狀態(tài),它主要用于區(qū)分常規(guī)的進(jìn)程。
2) 僵尸撤銷狀態(tài)(TASK_DEAD)
對(duì)于TASK_DEAD狀態(tài),《深入Linux內(nèi)核》一書中有說(shuō)道,“它是指wait()系統(tǒng)調(diào)用已發(fā)出,而進(jìn)程完全從系統(tǒng)中移除之前的狀態(tài)。只有多個(gè)線程同時(shí)對(duì)同一個(gè)進(jìn)程發(fā)出wait()調(diào)用時(shí)候,該狀態(tài)才有意義。”
2.2.1 進(jìn)程狀態(tài)的縮寫標(biāo)志
在2.3節(jié)里詳細(xì)描述了Linux系統(tǒng)上進(jìn)程的可呈現(xiàn)狀態(tài)和轉(zhuǎn)換。通常使用top來(lái)查看進(jìn)程列表時(shí)候,其狀態(tài)列都僅顯示一個(gè)縮寫標(biāo)志,比如R、S、D等,下面將一一列出通常進(jìn)程狀態(tài)的縮寫。

R是可執(zhí)行狀態(tài)(TASK_RUNNING)的狀態(tài)標(biāo)志;S是可中斷的睡眠狀態(tài)(TASK_INTERRUPTIBLE)狀態(tài)標(biāo)志;D是不可中斷睡眠狀態(tài)(TASK_UNINTERRUPTIBLE)的狀態(tài)標(biāo)志;T是暫停狀態(tài)(TASK_STOPPED)或跟蹤狀態(tài)(TASK_TRACED)的標(biāo)志;Z是僵尸進(jìn)程(TASK_ZOMBIE)的狀態(tài)標(biāo)志。
2.3 查找指定進(jìn)程PID
對(duì)于運(yùn)行中的進(jìn)程,可以使用以下幾種方式來(lái)快速查看其進(jìn)程的PID。
(1) ps假如當(dāng)前系統(tǒng)上面運(yùn)行著進(jìn)程A,那么使用ps命令查看該進(jìn)程的PID時(shí),結(jié)合管道、grep命令方式,即: ps -axu|grep A ,即可得到進(jìn)程A的PID。

可以看到該進(jìn)程的PID是4007。
(2) pidof除了使用ps來(lái)查看進(jìn)程的PID外,還可以使用“pidof 進(jìn)程名”的方式來(lái)查看。如下:

(3) pgrep pgrep命令是使用通過(guò)grep命令管道傳輸?shù)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 100, 65);">ps命令的快捷方式。它使用名稱或定義的模式搜索特進(jìn)程的所有匹配項(xiàng)。其命令語(yǔ)法為:pgrep <options> <pattern>。常用的選項(xiàng)參數(shù)如下:
-d, --delimiter <string> specify output delimiter
-l, --list-name list PID and process name
-a, --list-full list PID and full command line
. . . . . . //省略若干選項(xiàng)參數(shù)
-v, --inverse negates the matching
-w, --lightweight list all TID
-c, --count count of matching processes
比如查看kubelet進(jìn)程的PID,并且列出該PID對(duì)應(yīng)的進(jìn)程名,則使用pgrep -l kubelet。

附:可以使用命令"pstree -p PID"、"ps -ejH"、"ps -aux --forest"或"ps axjf"以樹(shù)(tree)的形式打印進(jìn)程。

2.4 查找指定PID的線程
獲取操作系統(tǒng)上有關(guān)線程的信息,可以使用“ps -eLf”和“ps axms”命令。若想查看某個(gè)進(jìn)程下的線程,則可使用“ps -T -p PID”,其中參數(shù)-T可以開(kāi)啟線程;或使用“top -H -p PID”方式。也可以直接使用pstree -p PID命令以樹(shù)形方式打印指定PID下的所有線程列表信息,不過(guò)該方式不會(huì)列出線程名。

2.5 kill指定PID進(jìn)程
kill掉一個(gè)正在運(yùn)行的進(jìn)程,最常規(guī)的方法是先ps找出該進(jìn)程的PID,然后再使用kill()命令向該進(jìn)程PID發(fā)送信號(hào)以達(dá)到終止進(jìn)程的目的。其實(shí),有一個(gè)命令可以幫我們省去ps查找進(jìn)程PID的過(guò)程。這個(gè)命令是pkill(此外,還有killall),該命令也是與kill命令一起使用ps命令的快捷方式。pkill命令用于根據(jù)進(jìn)程PID或名稱向指定進(jìn)程發(fā)送信號(hào)。
pkill命令的語(yǔ)法格式是:pkill [options] <pattern>。該命令常見(jiàn)的選項(xiàng)參數(shù)有:
-<sig>, --signal <sig> signal to send (either number or name)
-e, --echo display what is killed
-c, --count count of matching processes
. . . . . . //省略
-f, --full use full process name to match
-g, --pgroup <PGID,...> match listed process group IDs
-P, --parent <PPID,...> match only child processes of the given parent
比如有一個(gè)進(jìn)程A,現(xiàn)想要使用STGSTOP信號(hào)停止該進(jìn)程,則可以:pkill -19 A。使用kill -l命令可以查看所有信號(hào)及對(duì)應(yīng)的序號(hào)。
[root@node1 ~]# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
3. 僵尸進(jìn)程
對(duì)于僵尸進(jìn)程,維基上面是這樣描述的:
在Unix和類Unix計(jì)算機(jī)操作系統(tǒng)上,僵尸進(jìn)程或失效進(jìn)程是已完成執(zhí)行(通過(guò)exit()系統(tǒng)調(diào)用)但在進(jìn)程表(Process Table)中仍有條目的進(jìn)程:它是處于“終止?fàn)顟B(tài)”的進(jìn)程。這發(fā)生在子進(jìn)程中,其中仍然需要該條目以允許父進(jìn)程讀取其子進(jìn)程的退出狀態(tài);一旦通過(guò)wait()或waitpid()系統(tǒng)調(diào)用讀取退出狀態(tài),則僵尸進(jìn)程的條目將從進(jìn)程表中刪除,并被收割。子進(jìn)程從資源表中刪除之前總數(shù)首先變成僵尸進(jìn)程。大多數(shù)情況下,在正常的系統(tǒng)操作中,僵尸進(jìn)程會(huì)立即被它們的父進(jìn)程wait,然后被系統(tǒng)收割。長(zhǎng)時(shí)間處于僵尸狀態(tài)(TASK_ZOMBIE)的進(jìn)程通常是一個(gè)錯(cuò)誤,且會(huì)導(dǎo)致資源泄露。
進(jìn)程終止后,有些信息對(duì)于父進(jìn)程和內(nèi)核是很有用的,比如進(jìn)程PID、進(jìn)程退出狀態(tài)、進(jìn)程運(yùn)行的CPU時(shí)間等。不允許類UNIX內(nèi)核在進(jìn)程一終止后就丟棄包含在進(jìn)程描述符字段中的數(shù)據(jù)。只有父進(jìn)程發(fā)出來(lái)wait()或waitpid()等系統(tǒng)調(diào)用之后才可以。
注:進(jìn)程使用exit()系統(tǒng)調(diào)用終止時(shí),分配給該進(jìn)程的所有內(nèi)存和資源都將被釋放。但是進(jìn)程表中的條目仍然可用,比如PID、進(jìn)程表項(xiàng)等。
3.1 僵尸進(jìn)程產(chǎn)生
從上面對(duì)僵尸進(jìn)程的定義可知它發(fā)生在子進(jìn)程中。其具體的產(chǎn)生過(guò)程是:父進(jìn)程調(diào)用fork()創(chuàng)建子進(jìn)程后,子進(jìn)程一直運(yùn)行直到其終止。此時(shí),它將立即從內(nèi)存中移除,但是其PID仍然保留在內(nèi)存中(盡管PID占用的空間并不大)。這時(shí)子進(jìn)程的狀態(tài)成為EXIT_ZOMBIE,并向其父進(jìn)程發(fā)送SIGCHLD信號(hào)。正常情況下,該進(jìn)程的父進(jìn)程此時(shí)會(huì)調(diào)用wait或waitpid系統(tǒng)函數(shù)來(lái)獲取子進(jìn)程的退出狀態(tài)及相關(guān)信息,并于wait()/waitpid()之后,僵尸進(jìn)程將完全徹底地從內(nèi)存中移除。但如果因?yàn)榇a編寫缺陷或是其他的一些因素影響,導(dǎo)致父進(jìn)程未調(diào)用wait()/waitpid()系統(tǒng)函數(shù),則該進(jìn)程將一直成為僵尸進(jìn)程。
僵尸進(jìn)程存在于其終止一直到父進(jìn)程調(diào)用wait()/waitpid()系統(tǒng)函數(shù)這個(gè)時(shí)間段期間。
僵尸進(jìn)程的創(chuàng)建與終止過(guò)程可參考下圖。

為了更深一步理解僵尸進(jìn)程的產(chǎn)生、終止過(guò)程,現(xiàn)編寫一個(gè)demo來(lái)進(jìn)行演示說(shuō)明。在該示例中,子進(jìn)程打印PID之后便結(jié)束,而父進(jìn)程也沒(méi)有調(diào)用wait()或是waitpid()來(lái)回收子進(jìn)程的系統(tǒng)資源,直到睡眠2min之后,整個(gè)進(jìn)程結(jié)束,此時(shí)進(jìn)程的所有資源將交接給init進(jìn)程來(lái)進(jìn)行回收。
// file: zombie.c, gcc zombie.c -o zombie
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid = 0;
pid = fork();
if(0 == pid){
//打印進(jìn)程PID之后,調(diào)用exit()系統(tǒng)函數(shù)結(jié)束.
printf("Child PID is %ld\n", (long)getpid());
exit(0);
}else if(0 < pid){
puts("Parent process . . .");
sleep(120);
//不回收子進(jìn)程的資源.
//wait(NULL);
}else{
perror("fork");
exit(-1);
}
return 0;
}
可看到(在2min時(shí)間段內(nèi))zombie進(jìn)程處于僵尸狀態(tài)(EXIT_ZOMBIE)。而該僵尸進(jìn)程(PID為466910)的父進(jìn)程(PPID是466909)則處于“可中斷的睡眠狀態(tài)”(左下窗口2)。

3.2 僵尸進(jìn)程危害
盡管僵尸進(jìn)程不占用任何資源,但是它們會(huì)保留其進(jìn)程PID,即進(jìn)程描述符(PID)將永久占據(jù)著RAM。若有大量的僵尸進(jìn)程存在,那么所有可用的進(jìn)程ID都將被獨(dú)占,會(huì)導(dǎo)致其他進(jìn)程沒(méi)有可用的ID。此外,在較重負(fù)載下,會(huì)給系統(tǒng)帶來(lái)重大的問(wèn)題。
3.3 殺死僵尸進(jìn)程
僵尸進(jìn)程可以通過(guò)使用kill命令向其父進(jìn)程發(fā)送SIGCHLD信號(hào)來(lái)終止。該信號(hào)將通知父進(jìn)程調(diào)用wait()系統(tǒng)函數(shù)來(lái)回收該僵尸進(jìn)程。即:kill -s SIGCHLD PID,這里PID是父進(jìn)程的ID。
若要找僵尸進(jìn)程的父進(jìn)程,可以使用命令:ps -o ppid = -p PID,這里的PID是子進(jìn)程的進(jìn)程ID。當(dāng)然也可以使用ps -aux|grep 進(jìn)程名的方式。
4. 進(jìn)程與線程
4.1 線程概念
進(jìn)程是具有一定獨(dú)立功能的程序在數(shù)據(jù)集上的動(dòng)態(tài)執(zhí)行過(guò)程,它是操作系統(tǒng)中資源分配和調(diào)度的獨(dú)立單元,是應(yīng)用程序的載體,每個(gè)進(jìn)程都有自己獨(dú)立的內(nèi)存空間,每個(gè)進(jìn)程內(nèi)存地址彼此隔離。進(jìn)程一般由程序(Program)、數(shù)據(jù)集指令(Data Collection)和進(jìn)程控制塊(Process Control Blok, PCB)組成。其中程序用來(lái)描述要完成的功能,是控制進(jìn)程執(zhí)行的指令集;而數(shù)據(jù)集是程序執(zhí)行過(guò)程中所需要的數(shù)據(jù)和工作區(qū)域;進(jìn)程控制塊則包含進(jìn)程的描述信息,并且控制信息是進(jìn)程存在的唯一標(biāo)志。
4.2 線程表示形式
線程是進(jìn)程執(zhí)行操作的最小單位,也是處理器調(diào)度和分派的基本單位,它是進(jìn)程的一個(gè)實(shí)體。一個(gè)進(jìn)程可以有一個(gè)或多個(gè)線程,各線程間共享其所在進(jìn)程的內(nèi)存空間。一個(gè)標(biāo)準(zhǔn)線程是由線程ID、程序計(jì)算機(jī)PC、寄存器和堆棧組成。
4.3 兩者區(qū)別
一個(gè)線程只能屬于一個(gè)進(jìn)程,但是一個(gè)進(jìn)程可以有多個(gè)線程,且至少有一個(gè)線程。線程是進(jìn)程中最小的執(zhí)行單位,是調(diào)度的最小單位。而進(jìn)程是資源的基本單位。進(jìn)程和線程都可以并非,但是創(chuàng)建、銷毀一個(gè)進(jìn)程比創(chuàng)建、銷毀一個(gè)線程的開(kāi)銷要大很多,并且進(jìn)程是擁有資源的獨(dú)立單位,而一個(gè)線程卻僅擁有很少的系統(tǒng)資源。最后,進(jìn)程和線程在內(nèi)核中都是由task_struct數(shù)據(jù)類型表示。
- END -
1. 進(jìn)程概念
所謂進(jìn)程(Process),是指運(yùn)行中的程序,它是程序的一個(gè)運(yùn)行實(shí)例。對(duì)于同一個(gè)程序,當(dāng)我們分別運(yùn)行兩次時(shí)候,操作系統(tǒng)會(huì)創(chuàng)建兩個(gè)不同的進(jìn)程。注意,程序不等同于進(jìn)程,進(jìn)程是正在執(zhí)行中的程序以及相關(guān)資源(如程序計(jì)數(shù)器PC、打開(kāi)的文件描述符、掛起的信號(hào)、處理器狀態(tài)、寄存器狀態(tài)、內(nèi)核數(shù)據(jù)、臨時(shí)數(shù)據(jù)堆棧等)的總稱,而程序只是存儲(chǔ)在某種介質(zhì)上(如磁盤)的一組機(jī)器機(jī)器代碼指令和數(shù)據(jù)。
對(duì)于進(jìn)程與程序之間的差異,其示意圖如下所示。
2. 進(jìn)程數(shù)據(jù)結(jié)構(gòu)
Linux操作系統(tǒng)為了便于管理進(jìn)程,在linux/sched.h文件中定義了名為task_struct的數(shù)據(jù)結(jié)構(gòu),該結(jié)構(gòu)包含了每一個(gè)運(yùn)行中的具體進(jìn)程所需的所有信息,比如進(jìn)程優(yōu)先級(jí)、進(jìn)程的狀態(tài)、進(jìn)程PID、文件系統(tǒng)信息等等。對(duì)于task_struct數(shù)據(jù)結(jié)構(gòu),又稱為“進(jìn)程描述符(Process Descriptor)” 。Linux把每個(gè)進(jìn)程(task_struct)都放在內(nèi)核中的雙向循環(huán)鏈接中,該雙向循環(huán)鏈表又被稱為“任務(wù)隊(duì)列(Task List)”。
在Linux 0.01版本中,該結(jié)構(gòu)體(task_struct)僅有幾十個(gè)成員,閱讀起來(lái)十分方便。
struct task_struct {
/* these are hardcoded - don't touch */
long state; /* -1 unrunnable, 0 runnable, >0 stopped */
long counter;
long priority;
long signal;
fn_ptr sig_restorer;
fn_ptr sig_fn[32];
/* various fields */
int exit_code;
unsigned long end_code,end_data,brk,start_stack;
long pid,father,pgrp,session,leader;
unsigned short uid,euid,suid;
unsigned short gid,egid,sgid;
long alarm;
long utime,stime,cutime,cstime,start_time;
unsigned short used_math;
/* file system info */
int tty; /* -1 if no tty, so it must be signed */
unsigned short umask;
struct m_inode * pwd;
struct m_inode * root;
unsigned long close_on_exec;
struct file * filp[NR_OPEN];
/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
struct desc_struct ldt[3];
/* tss for this task */
struct tss_struct tss;
};
但是到了5.4.3版本及之后,該結(jié)構(gòu)體大小已經(jīng)到了KB級(jí)別了,由此可見(jiàn)從0.01版本到5.4.3版本間新增了許多的特性和功能。

2.1 進(jìn)程描述符PID
前面提到過(guò),task_struct數(shù)據(jù)結(jié)構(gòu)描述了每個(gè)進(jìn)程的詳細(xì)完整信息。在該數(shù)據(jù)結(jié)構(gòu)中,有一個(gè)最為顯眼的成員變量,即pid。它是Linux內(nèi)核用來(lái)識(shí)別各進(jìn)程的唯一標(biāo)識(shí),稱為“進(jìn)程標(biāo)識(shí)值(Process Identificaion Value, PID)”。在0.01內(nèi)核版本中,它是long int類型,但是之后,其類型統(tǒng)一適配為pid_t類型。pid_t是unsigned int的一個(gè)別名。
typedef signed int pid_t;
pid_t pid;
從某種層面上來(lái)說(shuō),成員pid的數(shù)據(jù)類型取值范圍表明了系統(tǒng)中允許同時(shí)運(yùn)行(存在)的進(jìn)程的最大數(shù)量,因?yàn)檫M(jìn)程PID不能為負(fù)數(shù)。盡管unsigned int類型的取值范圍達(dá)到了0~4294967295,但是實(shí)際情況中,基于硬件技術(shù)和資源限制等緣故,操作系統(tǒng)上不可能同時(shí)運(yùn)行這么多數(shù)量的進(jìn)程。
可通過(guò)查看/proc/sys/kernel/pid_max文件以得知當(dāng)前設(shè)備環(huán)境能夠支持的同時(shí)運(yùn)行的最大進(jìn)程數(shù)量限制。某些情況下,可能通過(guò)修改該文件配置值,以提高進(jìn)程的并行數(shù)量。

2.1.1 進(jìn)程資源限制
由于系統(tǒng)資源有限,因此,內(nèi)核必須嚴(yán)格把控并記錄每一個(gè)運(yùn)行中的進(jìn)程的資源(包括內(nèi)存、CPU、文件句柄等)詳細(xì)使用情況,并且給每一個(gè)進(jìn)程一個(gè)默認(rèn)限制閾值。這是保證每個(gè)進(jìn)程得以在操作系統(tǒng)上完美運(yùn)行的前提。在task_struct數(shù)據(jù)結(jié)構(gòu)中,有一個(gè)數(shù)據(jù)成員signal,該成員是一個(gè)struct signal_struc類型。該數(shù)據(jù)類型中成員變量(數(shù)組)rlim用來(lái)記錄當(dāng)前進(jìn)程的資源限制。
//記錄各進(jìn)程中的16個(gè)屬性資源.
#define RLIM_NLIMITS 16
typedef unsigned long __kernel_ulong_t;
struct rlimit {
__kernel_ulong_t rlim_cur;
__kernel_ulong_t rlim_max;
};
struct signal_struct {
struct rlimit rlim[RLIM_NLIMITS];
};
struct task_struct{
/* Signal handlers: */
struct signal_struct *signal;
};
signal成員和rlim成員間的關(guān)聯(lián)如下圖:
從signal_struct 類型聲明中的rlim成員可知,該成員是一個(gè)擁有16個(gè)元素大小的數(shù)組,這意味著操作系統(tǒng)對(duì)每個(gè)進(jìn)程有16個(gè)資源限制。對(duì)于struct rlimit數(shù)據(jù)類型,共聲明了兩個(gè)成員,分別是:rlim_cur和rlim_max。其中rlim_cur是軟限制(Soft Limit),rlim_max是硬限制(Hard Limit)。
軟限制與硬限制
1) 硬限制是指由超級(jí)用戶/root設(shè)置的對(duì)用戶的最大限制。該值在
/etc/security/limits.conf配置文件中進(jìn)行設(shè)置,將其視為上限或是天花板。2) 軟限制是內(nèi)核對(duì)相應(yīng)資源強(qiáng)制執(zhí)行的值。硬限制充當(dāng)軟限制的上限;非特權(quán)進(jìn)程可以將其軟限制設(shè)置為從0到硬限制的范圍內(nèi)的值,并且(不可逆地)降低其硬限制。
下面是這16個(gè)特定于進(jìn)程的資源限制的說(shuō)明。它們定義于include/uapi/asm-generic/resource.h文件中,其中各選項(xiàng)的具體含義如下:
#define RLIMIT_CPU 0 // 按毫秒計(jì)算的最大CPU時(shí)間
#define RLIMIT_FSIZE 1 // 允許的最大文件長(zhǎng)度
#define RLIMIT_DATA 2 // 數(shù)據(jù)段的最大長(zhǎng)度
#define RLIMIT_STACK 3 // (用戶狀態(tài))棧的最大長(zhǎng)度
#define RLIMIT_CORE 4 // core轉(zhuǎn)存文件的最大長(zhǎng)度
#define RLIMIT_RSS 5 // 常駐內(nèi)存的最大尺寸(進(jìn)程使用頁(yè)幀的最大數(shù)目)
#define RLIMIT_NPROC 6 // 進(jìn)程UID用戶可以打開(kāi)的進(jìn)程的最大數(shù)量
#define RLIMIT_NOFILE 7 // 允許打開(kāi)文件的最大數(shù)量
#define RLIMIT_MEMLOCK 8 // 不可換出頁(yè)的最大數(shù)量
#define RLIMIT_AS 9 // 進(jìn)程占用的虛擬地址空間的最大尺寸
#define RLIMIT_LOCKS 10 // 文件鎖的最大數(shù)目
#define RLIMIT_SIGPENDING 11 // 待決信號(hào)的最大數(shù)量
#define RLIMIT_MSGQUEUE 12 // 信息隊(duì)列的最大數(shù)目
#define RLIMIT_NICE 13 // 非實(shí)時(shí)進(jìn)程的優(yōu)先級(jí)(和調(diào)度有關(guān), 0-39 for nice level 19 .. -20)
#define RLIMIT_RTPRIO 14 // 最大的實(shí)時(shí)優(yōu)先級(jí)
#define RLIMIT_RTTIME 15 /* 指定在不進(jìn)行阻塞系統(tǒng)調(diào)用的情況下,根據(jù)實(shí)時(shí)調(diào)度策略調(diào)度的進(jìn)程可能消耗的CPU時(shí)間的限制(以微秒為單位) */
Linux內(nèi)核在/proc文件系統(tǒng)中,對(duì)每一個(gè)運(yùn)行著的進(jìn)程,都會(huì)創(chuàng)建一個(gè)相應(yīng)的資源限制詳情的文件。因此通過(guò)查看/proc/self/limits文件可得知當(dāng)前進(jìn)程的資源限制。查看指定進(jìn)程的資源限制,只需將self換為對(duì)應(yīng)的進(jìn)程PID,即:/proc/PID/limits。比如查看PID為141(cat /proc/7/limits)的進(jìn)程的資源限制

Linux系統(tǒng)提供了getrlimits()和setrlimit()兩個(gè)系統(tǒng)調(diào)用函數(shù),它們分別用來(lái)獲取、設(shè)置資源限制。
#include <sys/time.h>
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
int prlimit(pid_t pid, int resource, const struct rlimit *new_limit,
struct rlimit *old_limit);
除了使用這兩個(gè)系統(tǒng)函數(shù)外,還可以通過(guò)修改上面提到的系統(tǒng)配置文件/etc/security/limits.conf來(lái)動(dòng)態(tài)更改資源限制。在該配置文件中,每一行均以“#<domain> <type> <item> <value>”的組成形式,描述了用戶的限制。其中#<domain> 可以是用戶名或組名,也可以是通配符*;<type> 表明是軟限制,還是硬限制。<item>表明要限制的條目(即上面16種資源限制之一);<value>則是該限制條目的值。
////vim /etc/security/limits.conf
#<domain> <type> <item> <value>
#* soft core 0
#* hard rss 10000
#@student hard nproc 20
#@faculty soft nproc 20
#@faculty hard nproc 50
#ftp hard nproc 0
#@student - maxlogins 4
# End of file
* soft nofile 131072
* hard nofile 131072
此外,還可以通過(guò)命令行方式來(lái)進(jìn)行資源限制的獲取與設(shè)置。該命令是ulimit,它的使用方式如下(方括號(hào)的數(shù)字用以表明ulimit支持的選項(xiàng)參數(shù))。
ulimit: usage: ulimit [-SHacdefilmnpqrstuvx] [limit]
比如查看當(dāng)前系統(tǒng)的軟限制:ulimit -S -a;查看當(dāng)前系統(tǒng)的硬限制:ulimit -H -a。為一個(gè)變量設(shè)置指定的軟限制:ulimit -S [option] [number],其中[option]是各限制的縮寫,通過(guò)ulimit -a可以看到各限制條目的縮寫,即下面括號(hào)中的-c、-d、-e、-f等。
[root@center-controller-7f4b9fbffc-t5wnh CenterController]# ulimit -a
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 255136
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1048576
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) unlimited
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
為一個(gè)限制條目設(shè)置特定的硬限制:ulimit -H [option] [number]。
2.2 進(jìn)程運(yùn)行狀態(tài)
操作系統(tǒng)上的進(jìn)程并非總是能夠立刻執(zhí)行,有時(shí)它需要等待內(nèi)核調(diào)度的切換,以及資源的分配才能夠運(yùn)行。對(duì)于被加載器加載到內(nèi)存并開(kāi)始運(yùn)行的每一個(gè)進(jìn)程,都將有一個(gè)對(duì)應(yīng)的狀態(tài)標(biāo)志,且這些狀態(tài)標(biāo)志是互斥的,即同一個(gè)時(shí)刻,進(jìn)程只能存在一個(gè)狀態(tài)(State)。這些狀態(tài)可以是下圖中的之一。
該狀態(tài)由task_struct數(shù)據(jù)類型中的state成員表示。
struct task_struct {
/* -1 unrunnable, 0 runnable, >0 stopped: */
volatile long state;
};
對(duì)于0.01版本中Linux的進(jìn)程,其狀態(tài)共有以下5種,它們分別是:運(yùn)行狀態(tài)(TASK_RUNNING)、可中斷睡眠狀態(tài)(TASK_INTERRUPTIBLE)、不可中斷睡眠狀態(tài)(TASK_UNINTERRUPTIBLE)、僵尸狀態(tài)(TASK_ZOMBIE)和暫停狀態(tài)(TASK_STOPPED)。這些狀態(tài)定義于linux/sched.h文件中。
//linux/sched.h 0.01 version
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_ZOMBIE 3
#define TASK_STOPPED 4
對(duì)于TASK_RUNNING、TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE、TASK_ZOMBIE和TASK_STOPPED狀態(tài),它們的含義分別如下:
1) 可運(yùn)行狀態(tài)(TASK_RUNNING)
進(jìn)程是可執(zhí)行的。該進(jìn)程可能正在執(zhí)行,或在運(yùn)行隊(duì)列中等待被執(zhí)行。該狀態(tài)是進(jìn)程在用戶空間中執(zhí)行的唯一可能狀態(tài)。
2) 可中斷的等待狀態(tài)(TASK_INTERRUPTIBLE)
進(jìn)展正在睡眠(被阻塞,或是被掛起),直到某個(gè)條件到達(dá)。喚醒進(jìn)程的條件可以是:產(chǎn)生一個(gè)硬件中斷,釋放其正在等待的系統(tǒng)資源;亦或是傳遞一個(gè)信號(hào)。這時(shí)進(jìn)程的狀態(tài)將會(huì)切換到TASK_RUNNING。
3) 不可中斷的等待狀態(tài)(TASK_UNINTERRUPTIBLE)
與“可中斷的等待狀態(tài)”類似。但是有一個(gè)不同地方是即使把信號(hào)傳遞到一個(gè)處于TASK_UNINTERRUPTIBLE狀態(tài)中的進(jìn)程,也不會(huì)改變?cè)撨M(jìn)程的狀態(tài)。相比于“可中斷的等待狀態(tài)(TASK_INTERRUPTIBLE)”,使用場(chǎng)景相對(duì)較少,但在某些驅(qū)動(dòng)程序中應(yīng)用較為廣泛。
4) 暫停狀態(tài)(TASK_STOPPED)
進(jìn)程的執(zhí)行被暫停。比如當(dāng)進(jìn)程收到SIGSTOP、SIGTSTP、SIGTTIN或是SIGTTOU信號(hào),或是在調(diào)試期間收到任何信息,都會(huì)使進(jìn)程進(jìn)入這種狀態(tài)。
5) 僵尸狀態(tài)(TASK_ZOMBIE)
進(jìn)程的執(zhí)行已經(jīng)終止,但是其父進(jìn)程還沒(méi)有回收(通過(guò)wait()或waitpid())該進(jìn)程所占用的某些系統(tǒng)資源。
這5個(gè)狀態(tài)之間在滿足某種條件之下,能夠相互地進(jìn)行轉(zhuǎn)換。
在Linux 5.4.3版本的中,進(jìn)程的狀態(tài)已經(jīng)添加了很多,如下所示:
/* Used in tsk->state: */
#define TASK_RUNNING 0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002
#define __TASK_STOPPED 0x0004
#define __TASK_TRACED 0x0008
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x0010
#define EXIT_ZOMBIE 0x0020
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_PARKED 0x0040
#define TASK_DEAD 0x0080
#define TASK_WAKEKILL 0x0100
#define TASK_WAKING 0x0200
#define TASK_NOLOAD 0x0400
#define TASK_NEW 0x0800
#define TASK_STATE_MAX 0x1000
這里對(duì)新增的__TASK_TRACED、和TASK_DEAD狀態(tài)進(jìn)行簡(jiǎn)要的補(bǔ)充說(shuō)明。
1) 跟蹤狀態(tài) (__TASK_TRACED)
被其他進(jìn)程跟蹤的進(jìn)程。如通過(guò)ptrace 對(duì)調(diào)試程序進(jìn)行跟蹤。__TASK_TRACED本身不是進(jìn)程狀態(tài),它主要用于區(qū)分常規(guī)的進(jìn)程。
2) 僵尸撤銷狀態(tài)(TASK_DEAD)
對(duì)于TASK_DEAD狀態(tài),《深入Linux內(nèi)核》一書中有說(shuō)道,“它是指wait()系統(tǒng)調(diào)用已發(fā)出,而進(jìn)程完全從系統(tǒng)中移除之前的狀態(tài)。只有多個(gè)線程同時(shí)對(duì)同一個(gè)進(jìn)程發(fā)出wait()調(diào)用時(shí)候,該狀態(tài)才有意義。”
2.2.1 進(jìn)程狀態(tài)的縮寫標(biāo)志
在2.3節(jié)里詳細(xì)描述了Linux系統(tǒng)上進(jìn)程的可呈現(xiàn)狀態(tài)和轉(zhuǎn)換。通常使用top來(lái)查看進(jìn)程列表時(shí)候,其狀態(tài)列都僅顯示一個(gè)縮寫標(biāo)志,比如R、S、D等,下面將一一列出通常進(jìn)程狀態(tài)的縮寫。

R是可執(zhí)行狀態(tài)(TASK_RUNNING)的狀態(tài)標(biāo)志;S是可中斷的睡眠狀態(tài)(TASK_INTERRUPTIBLE)狀態(tài)標(biāo)志;D是不可中斷睡眠狀態(tài)(TASK_UNINTERRUPTIBLE)的狀態(tài)標(biāo)志;T是暫停狀態(tài)(TASK_STOPPED)或跟蹤狀態(tài)(TASK_TRACED)的標(biāo)志;Z是僵尸進(jìn)程(TASK_ZOMBIE)的狀態(tài)標(biāo)志。
2.3 查找指定進(jìn)程PID
對(duì)于運(yùn)行中的進(jìn)程,可以使用以下幾種方式來(lái)快速查看其進(jìn)程的PID。
(1) ps假如當(dāng)前系統(tǒng)上面運(yùn)行著進(jìn)程A,那么使用ps命令查看該進(jìn)程的PID時(shí),結(jié)合管道、grep命令方式,即: ps -axu|grep A ,即可得到進(jìn)程A的PID。

可以看到該進(jìn)程的PID是4007。
(2) pidof除了使用ps來(lái)查看進(jìn)程的PID外,還可以使用“pidof 進(jìn)程名”的方式來(lái)查看。如下:

(3) pgrep pgrep命令是使用通過(guò)grep命令管道傳輸?shù)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 100, 65);">ps命令的快捷方式。它使用名稱或定義的模式搜索特進(jìn)程的所有匹配項(xiàng)。其命令語(yǔ)法為:pgrep <options> <pattern>。常用的選項(xiàng)參數(shù)如下:
-d, --delimiter <string> specify output delimiter
-l, --list-name list PID and process name
-a, --list-full list PID and full command line
. . . . . . //省略若干選項(xiàng)參數(shù)
-v, --inverse negates the matching
-w, --lightweight list all TID
-c, --count count of matching processes
比如查看kubelet進(jìn)程的PID,并且列出該PID對(duì)應(yīng)的進(jìn)程名,則使用pgrep -l kubelet。

附:可以使用命令"pstree -p PID"、"ps -ejH"、"ps -aux --forest"或"ps axjf"以樹(shù)(tree)的形式打印進(jìn)程。

2.4 查找指定PID的線程
獲取操作系統(tǒng)上有關(guān)線程的信息,可以使用“ps -eLf”和“ps axms”命令。若想查看某個(gè)進(jìn)程下的線程,則可使用“ps -T -p PID”,其中參數(shù)-T可以開(kāi)啟線程;或使用“top -H -p PID”方式。也可以直接使用pstree -p PID命令以樹(shù)形方式打印指定PID下的所有線程列表信息,不過(guò)該方式不會(huì)列出線程名。

2.5 kill指定PID進(jìn)程
kill掉一個(gè)正在運(yùn)行的進(jìn)程,最常規(guī)的方法是先ps找出該進(jìn)程的PID,然后再使用kill()命令向該進(jìn)程PID發(fā)送信號(hào)以達(dá)到終止進(jìn)程的目的。其實(shí),有一個(gè)命令可以幫我們省去ps查找進(jìn)程PID的過(guò)程。這個(gè)命令是pkill(此外,還有killall),該命令也是與kill命令一起使用ps命令的快捷方式。pkill命令用于根據(jù)進(jìn)程PID或名稱向指定進(jìn)程發(fā)送信號(hào)。
pkill命令的語(yǔ)法格式是:pkill [options] <pattern>。該命令常見(jiàn)的選項(xiàng)參數(shù)有:
-<sig>, --signal <sig> signal to send (either number or name)
-e, --echo display what is killed
-c, --count count of matching processes
. . . . . . //省略
-f, --full use full process name to match
-g, --pgroup <PGID,...> match listed process group IDs
-P, --parent <PPID,...> match only child processes of the given parent
比如有一個(gè)進(jìn)程A,現(xiàn)想要使用STGSTOP信號(hào)停止該進(jìn)程,則可以:pkill -19 A。使用kill -l命令可以查看所有信號(hào)及對(duì)應(yīng)的序號(hào)。
[root@node1 ~]# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
3. 僵尸進(jìn)程
對(duì)于僵尸進(jìn)程,維基上面是這樣描述的:
在Unix和類Unix計(jì)算機(jī)操作系統(tǒng)上,僵尸進(jìn)程或失效進(jìn)程是已完成執(zhí)行(通過(guò)
exit()系統(tǒng)調(diào)用)但在進(jìn)程表(Process Table)中仍有條目的進(jìn)程:它是處于“終止?fàn)顟B(tài)”的進(jìn)程。這發(fā)生在子進(jìn)程中,其中仍然需要該條目以允許父進(jìn)程讀取其子進(jìn)程的退出狀態(tài);一旦通過(guò)wait()或waitpid()系統(tǒng)調(diào)用讀取退出狀態(tài),則僵尸進(jìn)程的條目將從進(jìn)程表中刪除,并被收割。子進(jìn)程從資源表中刪除之前總數(shù)首先變成僵尸進(jìn)程。大多數(shù)情況下,在正常的系統(tǒng)操作中,僵尸進(jìn)程會(huì)立即被它們的父進(jìn)程wait,然后被系統(tǒng)收割。長(zhǎng)時(shí)間處于僵尸狀態(tài)(TASK_ZOMBIE)的進(jìn)程通常是一個(gè)錯(cuò)誤,且會(huì)導(dǎo)致資源泄露。
進(jìn)程終止后,有些信息對(duì)于父進(jìn)程和內(nèi)核是很有用的,比如進(jìn)程PID、進(jìn)程退出狀態(tài)、進(jìn)程運(yùn)行的CPU時(shí)間等。不允許類UNIX內(nèi)核在進(jìn)程一終止后就丟棄包含在進(jìn)程描述符字段中的數(shù)據(jù)。只有父進(jìn)程發(fā)出來(lái)wait()或waitpid()等系統(tǒng)調(diào)用之后才可以。
注:進(jìn)程使用exit()系統(tǒng)調(diào)用終止時(shí),分配給該進(jìn)程的所有內(nèi)存和資源都將被釋放。但是進(jìn)程表中的條目仍然可用,比如PID、進(jìn)程表項(xiàng)等。
3.1 僵尸進(jìn)程產(chǎn)生
從上面對(duì)僵尸進(jìn)程的定義可知它發(fā)生在子進(jìn)程中。其具體的產(chǎn)生過(guò)程是:父進(jìn)程調(diào)用fork()創(chuàng)建子進(jìn)程后,子進(jìn)程一直運(yùn)行直到其終止。此時(shí),它將立即從內(nèi)存中移除,但是其PID仍然保留在內(nèi)存中(盡管PID占用的空間并不大)。這時(shí)子進(jìn)程的狀態(tài)成為EXIT_ZOMBIE,并向其父進(jìn)程發(fā)送SIGCHLD信號(hào)。正常情況下,該進(jìn)程的父進(jìn)程此時(shí)會(huì)調(diào)用wait或waitpid系統(tǒng)函數(shù)來(lái)獲取子進(jìn)程的退出狀態(tài)及相關(guān)信息,并于wait()/waitpid()之后,僵尸進(jìn)程將完全徹底地從內(nèi)存中移除。但如果因?yàn)榇a編寫缺陷或是其他的一些因素影響,導(dǎo)致父進(jìn)程未調(diào)用wait()/waitpid()系統(tǒng)函數(shù),則該進(jìn)程將一直成為僵尸進(jìn)程。
僵尸進(jìn)程存在于其終止一直到父進(jìn)程調(diào)用
wait()/waitpid()系統(tǒng)函數(shù)這個(gè)時(shí)間段期間。
僵尸進(jìn)程的創(chuàng)建與終止過(guò)程可參考下圖。

為了更深一步理解僵尸進(jìn)程的產(chǎn)生、終止過(guò)程,現(xiàn)編寫一個(gè)demo來(lái)進(jìn)行演示說(shuō)明。在該示例中,子進(jìn)程打印PID之后便結(jié)束,而父進(jìn)程也沒(méi)有調(diào)用wait()或是waitpid()來(lái)回收子進(jìn)程的系統(tǒng)資源,直到睡眠2min之后,整個(gè)進(jìn)程結(jié)束,此時(shí)進(jìn)程的所有資源將交接給init進(jìn)程來(lái)進(jìn)行回收。
// file: zombie.c, gcc zombie.c -o zombie
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid = 0;
pid = fork();
if(0 == pid){
//打印進(jìn)程PID之后,調(diào)用exit()系統(tǒng)函數(shù)結(jié)束.
printf("Child PID is %ld\n", (long)getpid());
exit(0);
}else if(0 < pid){
puts("Parent process . . .");
sleep(120);
//不回收子進(jìn)程的資源.
//wait(NULL);
}else{
perror("fork");
exit(-1);
}
return 0;
}
可看到(在2min時(shí)間段內(nèi))zombie進(jìn)程處于僵尸狀態(tài)(EXIT_ZOMBIE)。而該僵尸進(jìn)程(PID為466910)的父進(jìn)程(PPID是466909)則處于“可中斷的睡眠狀態(tài)”(左下窗口2)。

3.2 僵尸進(jìn)程危害
盡管僵尸進(jìn)程不占用任何資源,但是它們會(huì)保留其進(jìn)程PID,即進(jìn)程描述符(PID)將永久占據(jù)著RAM。若有大量的僵尸進(jìn)程存在,那么所有可用的進(jìn)程ID都將被獨(dú)占,會(huì)導(dǎo)致其他進(jìn)程沒(méi)有可用的ID。此外,在較重負(fù)載下,會(huì)給系統(tǒng)帶來(lái)重大的問(wèn)題。
3.3 殺死僵尸進(jìn)程
僵尸進(jìn)程可以通過(guò)使用kill命令向其父進(jìn)程發(fā)送SIGCHLD信號(hào)來(lái)終止。該信號(hào)將通知父進(jìn)程調(diào)用wait()系統(tǒng)函數(shù)來(lái)回收該僵尸進(jìn)程。即:kill -s SIGCHLD PID,這里PID是父進(jìn)程的ID。
若要找僵尸進(jìn)程的父進(jìn)程,可以使用命令:ps -o ppid = -p PID,這里的PID是子進(jìn)程的進(jìn)程ID。當(dāng)然也可以使用ps -aux|grep 進(jìn)程名的方式。
4. 進(jìn)程與線程
4.1 線程概念
進(jìn)程是具有一定獨(dú)立功能的程序在數(shù)據(jù)集上的動(dòng)態(tài)執(zhí)行過(guò)程,它是操作系統(tǒng)中資源分配和調(diào)度的獨(dú)立單元,是應(yīng)用程序的載體,每個(gè)進(jìn)程都有自己獨(dú)立的內(nèi)存空間,每個(gè)進(jìn)程內(nèi)存地址彼此隔離。進(jìn)程一般由程序(Program)、數(shù)據(jù)集指令(Data Collection)和進(jìn)程控制塊(Process Control Blok, PCB)組成。其中程序用來(lái)描述要完成的功能,是控制進(jìn)程執(zhí)行的指令集;而數(shù)據(jù)集是程序執(zhí)行過(guò)程中所需要的數(shù)據(jù)和工作區(qū)域;進(jìn)程控制塊則包含進(jìn)程的描述信息,并且控制信息是進(jìn)程存在的唯一標(biāo)志。
4.2 線程表示形式
線程是進(jìn)程執(zhí)行操作的最小單位,也是處理器調(diào)度和分派的基本單位,它是進(jìn)程的一個(gè)實(shí)體。一個(gè)進(jìn)程可以有一個(gè)或多個(gè)線程,各線程間共享其所在進(jìn)程的內(nèi)存空間。一個(gè)標(biāo)準(zhǔn)線程是由線程ID、程序計(jì)算機(jī)PC、寄存器和堆棧組成。
4.3 兩者區(qū)別
一個(gè)線程只能屬于一個(gè)進(jìn)程,但是一個(gè)進(jìn)程可以有多個(gè)線程,且至少有一個(gè)線程。線程是進(jìn)程中最小的執(zhí)行單位,是調(diào)度的最小單位。而進(jìn)程是資源的基本單位。進(jìn)程和線程都可以并非,但是創(chuàng)建、銷毀一個(gè)進(jìn)程比創(chuàng)建、銷毀一個(gè)線程的開(kāi)銷要大很多,并且進(jìn)程是擁有資源的獨(dú)立單位,而一個(gè)線程卻僅擁有很少的系統(tǒng)資源。最后,進(jìn)程和線程在內(nèi)核中都是由task_struct數(shù)據(jù)類型表示。
