偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

深入了解Linux內(nèi)核:task_struct結(jié)構(gòu)詳解

系統(tǒng) Linux
進(jìn)程終止的過程就像是一場(chǎng)演出的落幕,雖然看似簡單,但背后卻涉及到一系列復(fù)雜而有序的操作,而task_struct在這個(gè)過程中扮演著關(guān)鍵的角色 。

在 Linux 系統(tǒng)那如浩瀚宇宙般復(fù)雜又精妙的內(nèi)核世界里,隱藏著無數(shù)掌控全局的 “關(guān)鍵密碼”,而今天要帶大家認(rèn)識(shí)的 task_struct 結(jié)構(gòu),無疑是其中最為耀眼的一顆明星。當(dāng)你打開電腦,啟動(dòng) Linux 系統(tǒng),瞬間仿佛開啟了一場(chǎng)盛大的狂歡派對(duì),無數(shù)的進(jìn)程在幕后馬不停蹄地忙碌著,有的負(fù)責(zé)渲染精美的圖形界面,有的保障網(wǎng)絡(luò)連接順暢無阻,還有的默默守護(hù)著系統(tǒng)的安全防線。而這每一個(gè)進(jìn)程,它們的 “身世檔案”、“成長軌跡” 以及 “一舉一動(dòng)”,統(tǒng)統(tǒng)都被記錄在一個(gè)神奇的結(jié)構(gòu)體 ——task_struct 之中。

它就像是一位超級(jí)幕后管家,知曉進(jìn)程何時(shí)誕生,由哪個(gè)用戶啟動(dòng),占用了多少寶貴的系統(tǒng)資源,又該在何時(shí)退場(chǎng)謝幕。無論是深入探究系統(tǒng)性能瓶頸,精準(zhǔn)調(diào)試詭異的程序錯(cuò)誤,還是試圖理解 Linux 內(nèi)核如何有條不紊地調(diào)度千軍萬馬般的進(jìn)程,掌握 task_struct 結(jié)構(gòu),都如同握住了一把開啟內(nèi)核智慧寶庫的萬能鑰匙。此刻,就請(qǐng)緊跟我的腳步,一起深入剖析這個(gè) Linux 內(nèi)核中至關(guān)重要的 task_struct 結(jié)構(gòu),探尋進(jìn)程背后那些不為人知的精彩故事吧!

一、引言

在前文中,我們分析了內(nèi)核啟動(dòng)的整個(gè)過程以及系統(tǒng)調(diào)用的過程,從本文開始我們會(huì)介紹Linux系統(tǒng)各個(gè)重要的組成部分。這一切就從進(jìn)程和線程開始,在 Linux 里面,無論是進(jìn)程,還是線程,到了內(nèi)核里面,我們統(tǒng)一都叫任務(wù)(Task),由一個(gè)統(tǒng)一的結(jié)構(gòu) task_struct 進(jìn)行管理。

這個(gè)結(jié)構(gòu)非常復(fù)雜,本文將細(xì)細(xì)分析task_struct結(jié)構(gòu)。主要分析順序會(huì)按照該架構(gòu)體中的成員變量和函數(shù)的作用進(jìn)行分類,主要包括:

  • 任務(wù)ID
  • 親緣關(guān)系
  • 任務(wù)狀態(tài)
  • 任務(wù)權(quán)限
  • 運(yùn)行統(tǒng)計(jì)
  • 進(jìn)程調(diào)度
  • 信號(hào)處理
  • 內(nèi)存管理
  • 文件與文件系統(tǒng)
  • 內(nèi)核棧

二、Task_struct結(jié)構(gòu)

2.1 任務(wù)ID

任務(wù)ID是任務(wù)的唯一標(biāo)識(shí),在tast_struct中,主要涉及以下幾個(gè)ID

pid_t pid;
pid_t tgid;
struct task_struct *group_leader;

之所以有pid(process id),tgid(thread group ID)以及group_leader,是因?yàn)榫€程和進(jìn)程在內(nèi)核中是統(tǒng)一管理,視為相同的任務(wù)(task)。

任何一個(gè)進(jìn)程,如果只有主線程,那 pid 和tgid相同,group_leader 指向自己。但是,如果一個(gè)進(jìn)程創(chuàng)建了其他線程,那就會(huì)有所變化了。線程有自己的pid,tgid 就是進(jìn)程的主線程的 pid,group_leader 指向的進(jìn)程的主線程。因此根據(jù)pid和tgid是否相等我們可以判斷該任務(wù)是進(jìn)程還是線程。

2.2 親緣關(guān)系

除了0號(hào)進(jìn)程以外,其他進(jìn)程都是有父進(jìn)程的。全部進(jìn)程其實(shí)就是一顆進(jìn)程樹,相關(guān)成員變量如下所示:

struct task_struct __rcu *real_parent; /* real parent process */
struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
struct list_head children;      /* list of my children */
struct list_head sibling;       /* linkage in my parent's children list */
  • parent 指向其父進(jìn)程。當(dāng)它終止時(shí),必須向它的父進(jìn)程發(fā)送信號(hào)。
  • children 指向子進(jìn)程鏈表的頭部。鏈表中的所有元素都是它的子進(jìn)程。
  • sibling 用于把當(dāng)前進(jìn)程插入到兄弟鏈表中。

通常情況下,real_parent 和 parent 是一樣的,但是也會(huì)有另外的情況存在。例如,bash 創(chuàng)建一個(gè)進(jìn)程,那進(jìn)程的 parent 和 real_parent 就都是 bash。如果在 bash 上使用 GDB 來 debug 一個(gè)進(jìn)程,這個(gè)時(shí)候 GDB 是 parent,bash 是這個(gè)進(jìn)程的 real_parent。

2.3 任務(wù)狀態(tài)

任務(wù)狀態(tài)部分主要涉及以下變量

volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
int exit_state;
unsigned int flags;

其中狀態(tài)state通過設(shè)置比特位的方式來賦值,具體值在include/linux/sched.h中定義:

/* Used in tsk->state: */
#define TASK_RUNNING                    0
#define TASK_INTERRUPTIBLE              1
#define TASK_UNINTERRUPTIBLE            2
#define __TASK_STOPPED                  4
#define __TASK_TRACED                   8
/* Used in tsk->exit_state: */
#define EXIT_DEAD                       16
#define EXIT_ZOMBIE                     32
#define EXIT_TRACE                      (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_DEAD                       64
#define TASK_WAKEKILL                   128
#define TASK_WAKING                     256
#define TASK_PARKED                     512
#define TASK_NOLOAD                     1024
#define TASK_NEW                        2048
#define TASK_STATE_MAX                  4096

#define TASK_KILLABLE           (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)

TASK_RUNNING并不是說進(jìn)程正在運(yùn)行,而是表示進(jìn)程在時(shí)刻準(zhǔn)備運(yùn)行的狀態(tài)。當(dāng)處于這個(gè)狀態(tài)的進(jìn)程獲得時(shí)間片的時(shí)候,就是在運(yùn)行中;如果沒有獲得時(shí)間片,就說明它被其他進(jìn)程搶占了,在等待再次分配時(shí)間片。在運(yùn)行中的進(jìn)程,一旦要進(jìn)行一些 I/O 操作,需要等待 I/O 完畢,這個(gè)時(shí)候會(huì)釋放 CPU,進(jìn)入睡眠狀態(tài)。

在Linux中有兩種睡眠狀態(tài):

  • 一種是 TASK_INTERRUPTIBLE,可中斷的睡眠狀態(tài)。這是一種淺睡眠的狀態(tài),也就是說,雖然在睡眠,等待 I/O 完成,但是這個(gè)時(shí)候一個(gè)信號(hào)來的時(shí)候,進(jìn)程還是要被喚醒。只不過喚醒后,不是繼續(xù)剛才的操作,而是進(jìn)行信號(hào)處理。當(dāng)然程序員可以根據(jù)自己的意愿,來寫信號(hào)處理函數(shù),例如收到某些信號(hào),就放棄等待這個(gè) I/O 操作完成,直接退出;或者收到某些信息,繼續(xù)等待。
  • 另一種睡眠是 TASK_UNINTERRUPTIBLE,不可中斷的睡眠狀態(tài)。這是一種深度睡眠狀態(tài),不可被信號(hào)喚醒,只能死等 I/O 操作完成。一旦 I/O 操作因?yàn)樘厥庠虿荒芡瓿?,這個(gè)時(shí)候,誰也叫不醒這個(gè)進(jìn)程了。你可能會(huì)說,我 kill 它呢?別忘了,kill 本身也是一個(gè)信號(hào),既然這個(gè)狀態(tài)不可被信號(hào)喚醒,kill 信號(hào)也被忽略了。除非重啟電腦,沒有其他辦法。因此,這其實(shí)是一個(gè)比較危險(xiǎn)的事情,除非程序員極其有把握,不然還是不要設(shè)置成 TASK_UNINTERRUPTIBLE。
  • 于是,我們就有了一種新的進(jìn)程睡眠狀態(tài),TASK_KILLABLE,可以終止的新睡眠狀態(tài)。進(jìn)程處于這種狀態(tài)中,它的運(yùn)行原理類似 TASK_UNINTERRUPTIBLE,只不過可以響應(yīng)致命信號(hào)。由于TASK_WAKEKILL 用于在接收到致命信號(hào)時(shí)喚醒進(jìn)程,因此TASK_KILLABLE即在TASK_UNINTERUPTIBLE的基礎(chǔ)上增加一個(gè)TASK_WAKEKILL標(biāo)記位即可。

TASK_STOPPED是在進(jìn)程接收到 SIGSTOP、SIGTTIN、SIGTSTP或者 SIGTTOU 信號(hào)之后進(jìn)入該狀態(tài)。

TASK_TRACED 表示進(jìn)程被 debugger 等進(jìn)程監(jiān)視,進(jìn)程執(zhí)行被調(diào)試程序所停止。當(dāng)一個(gè)進(jìn)程被另外的進(jìn)程所監(jiān)視,每一個(gè)信號(hào)都會(huì)讓進(jìn)程進(jìn)入該狀態(tài)。

一旦一個(gè)進(jìn)程要結(jié)束,先進(jìn)入的是 EXIT_ZOMBIE 狀態(tài),但是這個(gè)時(shí)候它的父進(jìn)程還沒有使用wait() 等系統(tǒng)調(diào)用來獲知它的終止信息,此時(shí)進(jìn)程就成了僵尸進(jìn)程。EXIT_DEAD 是進(jìn)程的最終狀態(tài)。EXIT_ZOMBIE 和 EXIT_DEAD 也可以用于 exit_state。

上面的進(jìn)程狀態(tài)和進(jìn)程的運(yùn)行、調(diào)度有關(guān)系,還有其他的一些狀態(tài),我們稱為標(biāo)志。放在 flags字段中,這些字段都被定義成為宏,以 PF 開頭。

#define PF_EXITING    0x00000004
#define PF_VCPU      0x00000010
#define PF_FORKNOEXEC    0x00000040

PF_EXITING 表示正在退出。當(dāng)有這個(gè) flag 的時(shí)候,在函數(shù) find_alive_thread() 中,找活著的線程,遇到有這個(gè) flag 的,就直接跳過。

PF_VCPU 表示進(jìn)程運(yùn)行在虛擬 CPU 上。在函數(shù) account_system_time中,統(tǒng)計(jì)進(jìn)程的系統(tǒng)運(yùn)行時(shí)間,如果有這個(gè) flag,就調(diào)用 account_guest_time,按照客戶機(jī)的時(shí)間進(jìn)行統(tǒng)計(jì)。

PF_FORKNOEXEC 表示 fork 完了,還沒有 exec。在 _do_fork ()函數(shù)里面調(diào)用 copy_process(),這個(gè)時(shí)候把 flag 設(shè)置為 PF_FORKNOEXEC()。當(dāng) exec 中調(diào)用了 load_elf_binary() 的時(shí)候,又把這個(gè) flag 去掉。

圖片圖片

2.4 任務(wù)權(quán)限

任務(wù)權(quán)限主要包括以下兩個(gè)變量,real_cred是指可以操作本任務(wù)的對(duì)象,而red是指本任務(wù)可以操作的對(duì)象。

/* Objective and real subjective task credentials (COW): */
const struct cred __rcu         *real_cred;
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu         *cred;

cred定義如下所示:

struct cred {
......
    kuid_t          uid;            /* real UID of the task */
    kgid_t          gid;            /* real GID of the task */
    kuid_t          suid;           /* saved UID of the task */
    kgid_t          sgid;           /* saved GID of the task */
    kuid_t          euid;           /* effective UID of the task */
    kgid_t          egid;           /* effective GID of the task */
    kuid_t          fsuid;          /* UID for VFS ops */
    kgid_t          fsgid;          /* GID for VFS ops */
......
    kernel_cap_t    cap_inheritable; /* caps our children can inherit */
    kernel_cap_t    cap_permitted;  /* caps we're permitted */
    kernel_cap_t    cap_effective;  /* caps we can actually use */
    kernel_cap_t    cap_bset;       /* capability bounding set */
    kernel_cap_t    cap_ambient;    /* Ambient capability set */
......
} __randomize_layout;

從這里的定義可以看出,大部分是關(guān)于用戶和用戶所屬的用戶組信息。

  • uid和 gid,注釋是 real user/group id。一般情況下,誰啟動(dòng)的進(jìn)程,就是誰的 ID。但是權(quán)限審核的時(shí)候,往往不比較這兩個(gè),也就是說不大起作用。
  • euid 和 egid,注釋是 effective user/group id。一看這個(gè)名字,就知道這個(gè)是起“作用”的。當(dāng)這個(gè)進(jìn)程要操作消息隊(duì)列、共享內(nèi)存、信號(hào)量等對(duì)象的時(shí)候,其實(shí)就是在比較這個(gè)用戶和組是否有權(quán)限。
  • fsuid 和fsgid,也就是 filesystem user/group id。這個(gè)是對(duì)文件操作會(huì)審核的權(quán)限。

在Linux中,我們可以通過chmod u+s program命令更改更改euid和fsuid來獲取權(quán)限。

除了以用戶和用戶組控制權(quán)限,Linux 還有另一個(gè)機(jī)制就是 capabilities。

原來控制進(jìn)程的權(quán)限,要么是高權(quán)限的 root 用戶,要么是一般權(quán)限的普通用戶,這時(shí)候的問題是,root 用戶權(quán)限太大,而普通用戶權(quán)限太小。有時(shí)候一個(gè)普通用戶想做一點(diǎn)高權(quán)限的事情,必須給他整個(gè) root 的權(quán)限。這個(gè)太不安全了。于是,我們引入新的機(jī)制 capabilities,用位圖表示權(quán)限,在capability.h可以找到定義的權(quán)限。我這里列舉幾個(gè)。

#define CAP_CHOWN            0
#define CAP_KILL             5
#define CAP_NET_BIND_SERVICE 10
#define CAP_NET_RAW          13
#define CAP_SYS_MODULE       16
#define CAP_SYS_RAWIO        17
#define CAP_SYS_BOOT         22
#define CAP_SYS_TIME         25
#define CAP_AUDIT_READ          37
#define CAP_LAST_CAP         CAP_AUDIT_READ

對(duì)于普通用戶運(yùn)行的進(jìn)程,當(dāng)有這個(gè)權(quán)限的時(shí)候,就能做這些操作;沒有的時(shí)候,就不能做,這樣粒度要小很多。

2.5 運(yùn)行統(tǒng)計(jì)

運(yùn)行統(tǒng)計(jì)從宏觀來說也是一種狀態(tài)變量,但是和任務(wù)狀態(tài)不同,其存儲(chǔ)的主要是運(yùn)行時(shí)間相關(guān)的成員變量,具體如下所示

u64        utime;//用戶態(tài)消耗的CPU時(shí)間
u64        stime;//內(nèi)核態(tài)消耗的CPU時(shí)間
unsigned long      nvcsw;//自愿(voluntary)上下文切換計(jì)數(shù)
unsigned long      nivcsw;//非自愿(involuntary)上下文切換計(jì)數(shù)
u64        start_time;//進(jìn)程啟動(dòng)時(shí)間,不包含睡眠時(shí)間
u64        real_start_time;//進(jìn)程啟動(dòng)時(shí)間,包含睡眠時(shí)間

2.6 進(jìn)程調(diào)度

進(jìn)程調(diào)度部分較為復(fù)雜,會(huì)單獨(dú)拆分講解,這里先簡單羅列成員變量。

//是否在運(yùn)行隊(duì)列上
int        on_rq;
//優(yōu)先級(jí)
int        prio;
int        static_prio;
int        normal_prio;
unsigned int      rt_priority;
//調(diào)度器類
const struct sched_class  *sched_class;
//調(diào)度實(shí)體
struct sched_entity    se;
struct sched_rt_entity    rt;
struct sched_dl_entity    dl;
//調(diào)度策略
unsigned int      policy;
//可以使用哪些CPU
int        nr_cpus_allowed;
cpumask_t      cpus_allowed;
struct sched_info    sched_info;

2.7 信號(hào)處理

信號(hào)處理相關(guān)的數(shù)據(jù)結(jié)構(gòu)如下所示

/* Signal handlers: */
struct signal_struct    *signal;
struct sighand_struct    *sighand;
sigset_t      blocked;
sigset_t      real_blocked;
sigset_t      saved_sigmask;
struct sigpending    pending;
unsigned long      sas_ss_sp;
size_t        sas_ss_size;
unsigned int      sas_ss_flags;

這里將信號(hào)分為三類:

  • 阻塞暫不處理的信號(hào)(blocked)
  • 等待處理的信號(hào)(pending)
  • 正在通過信號(hào)處理函數(shù)處理的信號(hào)(sighand)

信號(hào)處理函數(shù)默認(rèn)使用用戶態(tài)的函數(shù)棧,當(dāng)然也可以開辟新的棧專門用于信號(hào)處理,這就是 sas_ss_xxx 這三個(gè)變量的作用。

2.8 內(nèi)存管理

內(nèi)存管理部分成員變量如下所示

struct mm_struct                *mm;
struct mm_struct                *active_mm;

由于內(nèi)存部分較為復(fù)雜,會(huì)放在后面單獨(dú)介紹,這里了先不做詳細(xì)說明。

2.9 文件與文件系統(tǒng)

文件系統(tǒng)部分也會(huì)在后面詳細(xì)說明,這里先簡單列舉成員變量

/* Filesystem information: */
struct fs_struct                *fs;
/* Open file information: */
struct files_struct             *files;

2.10 內(nèi)核棧

內(nèi)核棧相關(guān)的成員變量如下所示。為了介紹清楚其作用,我們需要從為什么需要內(nèi)核棧開始逐步討論。

struct thread_info    thread_info;
void  *stack;

當(dāng)進(jìn)程產(chǎn)生系統(tǒng)調(diào)用時(shí),會(huì)利用中斷陷入內(nèi)核態(tài)。而內(nèi)核態(tài)中也存在著各種函數(shù)的調(diào)用,因此我們需要有內(nèi)核態(tài)函數(shù)棧。Linux 給每個(gè) task 都分配了內(nèi)核棧。在 32 位系統(tǒng)上 arch/x86/include/asm/page_32_types.h,是這樣定義的:一個(gè) PAGE_SIZE是 4K,左移一位就是乘以 2,也就是 8K。

#define THREAD_SIZE_ORDER  1
#define THREAD_SIZE    (PAGE_SIZE << THREAD_SIZE_ORDER)

內(nèi)核棧在 64 位系統(tǒng)上 arch/x86/include/asm/page_64_types.h,是這樣定義的:在 PAGE_SIZE 的基礎(chǔ)上左移兩位,也即 16K,并且要求起始地址必須是 8192 的整數(shù)倍。

#ifdef CONFIG_KASAN
#define KASAN_STACK_ORDER 1
#else
#define KASAN_STACK_ORDER 0
#endif

#define THREAD_SIZE_ORDER  (2 + KASAN_STACK_ORDER)
#define THREAD_SIZE  (PAGE_SIZE << THREAD_SIZE_ORDER)

內(nèi)核棧的結(jié)構(gòu)如下所示,首先是預(yù)留的8個(gè)字節(jié),然后是存儲(chǔ)寄存器,最后存儲(chǔ)thread_info結(jié)構(gòu)體。

這個(gè)結(jié)構(gòu)是對(duì) task_struct 結(jié)構(gòu)的補(bǔ)充。因?yàn)?task_struct 結(jié)構(gòu)龐大但是通用,不同的體系結(jié)構(gòu)就需要保存不同的東西,所以往往與體系結(jié)構(gòu)有關(guān)的,都放在 thread_info 里面。在內(nèi)核代碼里面采用一個(gè) union將thread_info和stack 放在一起,在 include/linux/sched.h 中定義用以表示內(nèi)核棧。由代碼可見,這里根據(jù)架構(gòu)不同可能采用舊版的task_struct直接放在內(nèi)核棧,而新版的均采用thread_info,以節(jié)約空間。

union thread_union {
#ifndef CONFIG_ARCH_TASK_STRUCT_ON_STACK
    struct task_struct task;
#endif
#ifndef CONFIG_THREAD_INFO_IN_TASK
    struct thread_info thread_info;
#endif
    unsigned long stack[THREAD_SIZE/sizeof(long)];
};

另一個(gè)結(jié)構(gòu) pt_regs,定義如下。其中,32 位和 64 位的定義不一樣。

#ifdef __i386__
struct pt_regs {
  unsigned long bx;
  unsigned long cx;
  unsigned long dx;
  unsigned long si;
  unsigned long di;
  unsigned long bp;
  unsigned long ax;
  unsigned long ds;
  unsigned long es;
  unsigned long fs;
  unsigned long gs;
  unsigned long orig_ax;
  unsigned long ip;
  unsigned long cs;
  unsigned long flags;
  unsigned long sp;
  unsigned long ss;
};
#else 
struct pt_regs {
  unsigned long r15;
  unsigned long r14;
  unsigned long r13;
  unsigned long r12;
  unsigned long bp;
  unsigned long bx;
  unsigned long r11;
  unsigned long r10;
  unsigned long r9;
  unsigned long r8;
  unsigned long ax;
  unsigned long cx;
  unsigned long dx;
  unsigned long si;
  unsigned long di;
  unsigned long orig_ax;
  unsigned long ip;
  unsigned long cs;
  unsigned long flags;
  unsigned long sp;
  unsigned long ss;
/* top of stack page */
};
#endif

內(nèi)核棧和task_struct是可以互相查找的,而這里就需要用到task_struct中的兩個(gè)內(nèi)核棧相關(guān)成員變量了。

⑴通過task_struct查找內(nèi)核棧

如果有一個(gè) task_struct 的 stack 指針在手,即可通過下面的函數(shù)找到這個(gè)線程內(nèi)核棧:

static inline void *task_stack_page(const struct task_struct *task)
{
    return task->stack;
}

從 task_struct 如何得到相應(yīng)的 pt_regs 呢?我們可以通過下面的函數(shù),先從 task_struct找到內(nèi)核棧的開始位置。然后這個(gè)位置加上 THREAD_SIZE 就到了最后的位置,然后轉(zhuǎn)換為 struct pt_regs,再減一,就相當(dāng)于減少了一個(gè) pt_regs 的位置,就到了這個(gè)結(jié)構(gòu)的首地址。

/*
 * TOP_OF_KERNEL_STACK_PADDING reserves 8 bytes on top of the ring0 stack.
 * This is necessary to guarantee that the entire "struct pt_regs"
 * is accessible even if the CPU haven't stored the SS/ESP registers
 * on the stack (interrupt gate does not save these registers
 * when switching to the same priv ring).
 * Therefore beware: accessing the ss/esp fields of the
 * "struct pt_regs" is possible, but they may contain the
 * completely wrong values.
 */
#define task_pt_regs(task) \
({                  \
  unsigned long __ptr = (unsigned long)task_stack_page(task);  \
  __ptr += THREAD_SIZE - TOP_OF_KERNEL_STACK_PADDING;    \
  ((struct pt_regs *)__ptr) - 1;          \
})

這里面有一個(gè)TOP_OF_KERNEL_STACK_PADDING,這個(gè)的定義如下:

#ifdef CONFIG_X86_32
# ifdef CONFIG_VM86
#  define TOP_OF_KERNEL_STACK_PADDING 16
# else
#  define TOP_OF_KERNEL_STACK_PADDING 8
# endif
#else
# define TOP_OF_KERNEL_STACK_PADDING 0
#endif

也就是說,32 位機(jī)器上是 8,其他是 0。這是為什么呢?因?yàn)閴簵?pt_regs 有兩種情況。我們知道,CPU 用 ring 來區(qū)分權(quán)限,從而 Linux 可以區(qū)分內(nèi)核態(tài)和用戶態(tài)。因此,第一種情況,我們拿涉及從用戶態(tài)到內(nèi)核態(tài)的變化的系統(tǒng)調(diào)用來說。因?yàn)樯婕皺?quán)限的改變,會(huì)壓棧保存 SS、ESP 寄存器的,這兩個(gè)寄存器共占用 8 個(gè) byte。另一種情況是,不涉及權(quán)限的變化,就不會(huì)壓棧這 8 個(gè) byte。這樣就會(huì)使得兩種情況不兼容。如果沒有壓棧還訪問,就會(huì)報(bào)錯(cuò),所以還不如預(yù)留在這里,保證安全。在 64 位上,修改了這個(gè)問題,變成了定長的。

⑵通過內(nèi)核棧找task_struct

首先來看看thread_info的定義吧。下面所示為早期版本的thread_info和新版本thread_info的源碼

struct thread_info {
    struct task_struct  *task;    /* main task structure */
    __u32      flags;    /* low level flags */
    __u32      status;    /* thread synchronous flags */
    __u32      cpu;    /* current CPU */
    mm_segment_t    addr_limit;
    unsigned int    sig_on_uaccess_error:1;
    unsigned int    uaccess_err:1;  /* uaccess failed */
};
struct thread_info {
    unsigned long flags;          /* low level flags */
    unsigned long status;    /* thread synchronous flags */    
};

老版中采取current_thread_info()->task 來獲取task_struct。thread_info 的位置就是內(nèi)核棧的最高位置,減去 THREAD_SIZE,就到了 thread_info 的起始地址。

static inline struct thread_info *current_thread_info(void)
{
    return (struct thread_info *)(current_top_of_stack() - THREAD_SIZE);
}
  而新版本則采用了另一種current_thread_info

#include <asm/current.h>
#define current_thread_info() ((struct thread_info *)current)
#endif

那 current 又是什么呢?在 arch/x86/include/asm/current.h 中定義了。

struct task_struct;

DECLARE_PER_CPU(struct task_struct *, current_task);

static __always_inline struct task_struct *get_current(void)
{
    return this_cpu_read_stable(current_task);
}

#define current get_current

新的機(jī)制里面,每個(gè) CPU 運(yùn)行的 task_struct 不通過thread_info 獲取了,而是直接放在 Per CPU 變量里面了。多核情況下,CPU 是同時(shí)運(yùn)行的,但是它們共同使用其他的硬件資源的時(shí)候,我們需要解決多個(gè) CPU 之間的同步問題。Per CPU 變量是內(nèi)核中一種重要的同步機(jī)制。顧名思義,Per CPU 變量就是為每個(gè) CPU 構(gòu)造一個(gè)變量的副本,這樣多個(gè) CPU 各自操作自己的副本,互不干涉。比如,當(dāng)前進(jìn)程的變量 current_task 就被聲明為 Per CPU 變量。要使用 Per CPU 變量,首先要聲明這個(gè)變量,在 arch/x86/include/asm/current.h 中有:

DECLARE_PER_CPU(struct task_struct *, current_task);

然后是定義這個(gè)變量,在 arch/x86/kernel/cpu/common.c 中有:

DEFINE_PER_CPU(struct task_struct *, current_task) = &init_task;

也就是說,系統(tǒng)剛剛初始化的時(shí)候,current_task 都指向init_task。當(dāng)某個(gè) CPU 上的進(jìn)程進(jìn)行切換的時(shí)候,current_task 被修改為將要切換到的目標(biāo)進(jìn)程。例如,進(jìn)程切換函數(shù)__switch_to 就會(huì)改變 current_task。

__visible __notrace_funcgraph struct task_struct *
__switch_to(struct task_struct *prev_p, struct task_struct *next_p)
{
......
    this_cpu_write(current_task, next_p);
......
    return prev_p;
}

當(dāng)要獲取當(dāng)前的運(yùn)行中的 task_struct 的時(shí)候,就需要調(diào)用 this_cpu_read_stable 進(jìn)行讀取。

#define this_cpu_read_stable(var)       percpu_stable_op("mov", var)

通過這種方式,即可輕松地獲得task_struct的地址。

二、task_struct:進(jìn)程的“靈魂檔案”

2.1 定義與地位

在 Linux 內(nèi)核的代碼世界里,task_struct被精心定義為一個(gè)結(jié)構(gòu)體,其內(nèi)部成員眾多,宛如一個(gè)龐大而有序的信息倉庫 。在<linux/sched.h>頭文件中,我們能一窺它的定義全貌:

struct task_struct {
    volatile long state;          /* 進(jìn)程狀態(tài) */
    void *stack;                  /* 指向內(nèi)核棧的指針 */
    pid_t pid;                    /* 進(jìn)程ID */
    pid_t tgid;                   /* 線程組ID */
    struct task_struct *real_parent; /* 指向真正的父進(jìn)程 */
    struct task_struct *parent;   /* 指向接收SIGCHLD信號(hào)的父進(jìn)程 */
    struct list_head children;    /* 子進(jìn)程鏈表 */
    struct list_head sibling;     /* 兄弟進(jìn)程鏈表 */
    struct mm_struct *mm;         /* 指向內(nèi)存描述符 */
    struct mm_struct *active_mm;  /* 指向活躍的內(nèi)存描述符 */
    // 此處省略大量其他成員
};

task_struct就如同進(jìn)程的 “靈魂檔案”,從進(jìn)程誕生的那一刻起,它便如影隨形,詳細(xì)記錄著進(jìn)程的各種屬性和狀態(tài)信息。無論是進(jìn)程的唯一標(biāo)識(shí) —— 進(jìn)程 ID(PID),還是進(jìn)程所處的運(yùn)行狀態(tài)(如運(yùn)行、就緒、睡眠等),亦或是進(jìn)程與其他進(jìn)程之間的親屬關(guān)系(父進(jìn)程、子進(jìn)程、兄弟進(jìn)程等),以及進(jìn)程所占用的內(nèi)存資源、打開的文件描述符等關(guān)鍵信息,都被一一存儲(chǔ)在這個(gè)結(jié)構(gòu)體中??梢院敛豢鋸埖卣f,task_struct是進(jìn)程在 Linux 內(nèi)核中的 “代言人”,內(nèi)核正是通過對(duì)task_struct結(jié)構(gòu)體的管理和操作,實(shí)現(xiàn)了對(duì)進(jìn)程的創(chuàng)建、調(diào)度、終止等一系列生命周期的有效管控 。

2.2 內(nèi)存布局奧秘

在 Linux 內(nèi)核中,task_struct的內(nèi)存分配與內(nèi)核棧有著緊密的聯(lián)系 。通常情況下,內(nèi)核會(huì)為每個(gè)進(jìn)程分配一個(gè)大小固定的內(nèi)存區(qū)域,這個(gè)區(qū)域同時(shí)包含了task_struct結(jié)構(gòu)體和進(jìn)程的內(nèi)核棧。以常見的 x86 架構(gòu)為例,內(nèi)核會(huì)一次性分配兩個(gè)連續(xù)的物理頁面(每個(gè)頁面大小通常為 4KB,共 8KB)來存儲(chǔ)這兩部分內(nèi)容 。其中,task_struct結(jié)構(gòu)體大約占用底部的 1KB 空間,而剩余的 7KB 空間則用于存放進(jìn)程的內(nèi)核棧 。

這種內(nèi)存布局方式并非隨意為之,而是有著深刻的設(shè)計(jì)考量 。一方面,將task_struct與內(nèi)核棧放在一起,能夠減少內(nèi)存碎片的產(chǎn)生,提高內(nèi)存的使用效率 。當(dāng)進(jìn)程創(chuàng)建時(shí),一次性分配連續(xù)的內(nèi)存空間,避免了多次分配內(nèi)存可能導(dǎo)致的內(nèi)存碎片化問題 。另一方面,這種布局方式也方便了內(nèi)核在進(jìn)行進(jìn)程上下文切換時(shí)對(duì)相關(guān)信息的快速訪問 。在進(jìn)程上下文切換過程中,內(nèi)核需要保存和恢復(fù)進(jìn)程的各種狀態(tài)信息,包括 CPU 寄存器的值、堆棧指針等 。將task_struct與內(nèi)核棧相鄰放置,使得內(nèi)核能夠通過簡單的指針運(yùn)算,快速找到并操作這些關(guān)鍵信息,從而大大提高了進(jìn)程上下文切換的效率 。

例如,當(dāng)進(jìn)程從用戶態(tài)切換到內(nèi)核態(tài)時(shí),CPU 需要將當(dāng)前的堆棧指針切換到內(nèi)核棧的地址 。由于task_struct與內(nèi)核棧在內(nèi)存中是連續(xù)的,內(nèi)核可以根據(jù)task_struct中保存的棧指針信息,迅速定位到內(nèi)核棧的起始地址,完成堆棧指針的切換 。同樣,在進(jìn)程返回用戶態(tài)時(shí),內(nèi)核也能夠輕松地恢復(fù)用戶棧的地址和相關(guān)狀態(tài)信息 。這種緊密的內(nèi)存布局關(guān)系,就像是一場(chǎng)精心編排的舞蹈,task_struct與內(nèi)核棧相互配合,共同保障了進(jìn)程在 Linux 內(nèi)核中的高效運(yùn)行 。

2.3 task_struct 與系統(tǒng)調(diào)用

(1)以 fork 系統(tǒng)調(diào)用為例看 task_struct 的復(fù)制與初始化

在 Linux 中,fork 系統(tǒng)調(diào)用是創(chuàng)建新進(jìn)程的重要方式。當(dāng)執(zhí)行 fork 調(diào)用時(shí),內(nèi)核會(huì)為新創(chuàng)建的子進(jìn)程分配一個(gè)新的 task_struct 結(jié)構(gòu)體,這是整個(gè)創(chuàng)建子進(jìn)程流程中極為關(guān)鍵的起始步驟。

起初,內(nèi)核會(huì)盡可能多地將父進(jìn)程 task_struct 里的內(nèi)容復(fù)制到子進(jìn)程的 task_struct 中。不過,這里要注意并非所有內(nèi)容都會(huì)馬上被原樣復(fù)制,像內(nèi)存頁相關(guān)的部分,就會(huì)采用寫時(shí)拷貝(COW)機(jī)制來優(yōu)化性能,避免不必要的資源浪費(fèi)以及保證數(shù)據(jù)在后續(xù)使用時(shí)的獨(dú)立性和安全性。

例如,在一個(gè)文本編輯進(jìn)程執(zhí)行 fork 操作創(chuàng)建子進(jìn)程時(shí),父進(jìn)程 task_struct 中記錄的該文本編輯程序代碼段、數(shù)據(jù)段等相關(guān)內(nèi)存區(qū)域信息會(huì)先嘗試復(fù)制給子進(jìn)程,而那些具體的內(nèi)存頁面數(shù)據(jù)可能暫不實(shí)際復(fù)制,只是設(shè)置好寫時(shí)拷貝相關(guān)的機(jī)制,等到子進(jìn)程要對(duì)相應(yīng)內(nèi)存數(shù)據(jù)進(jìn)行修改操作時(shí),才會(huì)真正去復(fù)制一份屬于子進(jìn)程自己的數(shù)據(jù)副本,以保證父子進(jìn)程后續(xù)數(shù)據(jù)操作的互不干擾。

完成基本的復(fù)制后,內(nèi)核還需要進(jìn)行一系列必要的修改來確保子進(jìn)程能夠獨(dú)立于父進(jìn)程運(yùn)行。其中會(huì)設(shè)置子進(jìn)程的 PID(進(jìn)程 ID),使其擁有一個(gè)新的唯一的進(jìn)程 ID,而父進(jìn)程的 PID 保持不變;同時(shí),子進(jìn)程的 PPID(父進(jìn)程 ID)會(huì)被設(shè)置為調(diào)用 fork 的進(jìn)程的 PID。除此之外,像進(jìn)程組、會(huì)話等關(guān)系也會(huì)相應(yīng)地更新或初始化,信號(hào)處理器、文件描述符表等同樣要進(jìn)行適當(dāng)?shù)恼{(diào)整,以符合子進(jìn)程后續(xù)獨(dú)立運(yùn)行的需求。

(2)對(duì)進(jìn)程管理的影響與意義

task_struct 在 fork 系統(tǒng)調(diào)用創(chuàng)建子進(jìn)程時(shí)的這種復(fù)制與初始化機(jī)制,對(duì)進(jìn)程管理有著多方面重要的影響和意義。

從資源分配角度來看,通過寫時(shí)拷貝機(jī)制,在子進(jìn)程創(chuàng)建初期可以避免大量不必要的內(nèi)存數(shù)據(jù)復(fù)制開銷,多個(gè)子進(jìn)程可以在初期共享父進(jìn)程的內(nèi)存資源,只有當(dāng)真正需要修改數(shù)據(jù)時(shí)才各自分配獨(dú)立的內(nèi)存空間,這樣能更高效地利用系統(tǒng)內(nèi)存資源,尤其是在創(chuàng)建多個(gè)相似子進(jìn)程的場(chǎng)景下,能顯著節(jié)省內(nèi)存開銷,使得系統(tǒng)整體資源分配更加合理且靈活。

在進(jìn)程調(diào)度方面,新創(chuàng)建并初始化好的子進(jìn)程會(huì)被加入到內(nèi)核的調(diào)度隊(duì)列中(通常是就緒隊(duì)列),等待 CPU 的調(diào)度執(zhí)行。每個(gè)子進(jìn)程擁有獨(dú)立的 task_struct 結(jié)構(gòu)體,意味著調(diào)度器可以依據(jù)各個(gè)進(jìn)程(包括父子進(jìn)程以及不同的子進(jìn)程之間) task_struct 里記錄的不同狀態(tài)、優(yōu)先級(jí)等調(diào)度相關(guān)信息,來公平且合理地分配 CPU 時(shí)間片,確保系統(tǒng)中各個(gè)進(jìn)程都能有序地獲得執(zhí)行機(jī)會(huì),保障系統(tǒng)的并發(fā)處理能力和整體運(yùn)行效率。

從進(jìn)程獨(dú)立性和安全性來講,盡管子進(jìn)程是以父進(jìn)程為模板進(jìn)行 task_struct 的復(fù)制和初始化,但經(jīng)過修改關(guān)鍵標(biāo)識(shí)信息以及后續(xù)內(nèi)存數(shù)據(jù)寫時(shí)拷貝等操作后,子進(jìn)程能夠獨(dú)立運(yùn)行,不會(huì)因?yàn)樽陨淼牟僮鳎ū热缧薷膬?nèi)存數(shù)據(jù)、接收信號(hào)等)而影響到父進(jìn)程或者其他子進(jìn)程的正常運(yùn)行,保障了每個(gè)進(jìn)程在系統(tǒng)中的獨(dú)立性,同時(shí)也避免了因進(jìn)程間不合理的相互干擾而可能引發(fā)的安全問題,增強(qiáng)了整個(gè)系統(tǒng)進(jìn)程管理的穩(wěn)定性和安全性。

三、結(jié)構(gòu)成員深度剖析

3.1 進(jìn)程狀態(tài)標(biāo)識(shí)

在task_struct結(jié)構(gòu)體中,state和exit_state成員肩負(fù)著標(biāo)識(shí)進(jìn)程狀態(tài)的重要使命 。state成員通過一系列預(yù)定義的常量值,細(xì)致地描述了進(jìn)程當(dāng)前的運(yùn)行狀態(tài) 。其中,TASK_RUNNING狀態(tài)猶如賽道上蓄勢(shì)待發(fā)的選手,表示進(jìn)程要么正在 CPU 上全力奔跑(執(zhí)行),要么已經(jīng)站在起跑線上,時(shí)刻準(zhǔn)備著獲取 CPU 的青睞(就緒) 。當(dāng)我們?cè)谙到y(tǒng)中運(yùn)行一個(gè)簡單的計(jì)算程序時(shí),在程序執(zhí)行的過程中,該進(jìn)程就處于TASK_RUNNING狀態(tài) 。如果系統(tǒng)中同時(shí)存在多個(gè)處于TASK_RUNNING狀態(tài)的進(jìn)程,它們就會(huì)像一群渴望上場(chǎng)比賽的選手,等待著調(diào)度器按照一定的規(guī)則安排它們輪流在 CPU 這個(gè)賽道上奔跑 。

TASK_INTERRUPTIBLE狀態(tài)則像是一位暫時(shí)休息的選手,進(jìn)程處于可中斷的睡眠狀態(tài),它正在耐心等待某個(gè)特定事件的發(fā)生,比如等待讀取文件的數(shù)據(jù)、等待網(wǎng)絡(luò)請(qǐng)求的響應(yīng)等 。當(dāng)一個(gè)進(jìn)程發(fā)起文件讀取操作時(shí),由于磁盤 I/O 速度相對(duì)較慢,在數(shù)據(jù)讀取完成之前,進(jìn)程會(huì)進(jìn)入TASK_INTERRUPTIBLE狀態(tài),暫時(shí)讓出 CPU 資源,進(jìn)入睡眠狀態(tài) 。此時(shí),如果有信號(hào)傳來,就如同有人呼喊這位休息的選手,它會(huì)被喚醒,從睡眠狀態(tài)中蘇醒過來,加入到可運(yùn)行狀態(tài)的隊(duì)伍中,等待再次獲得 CPU 資源,繼續(xù)執(zhí)行后續(xù)的操作 。

與TASK_INTERRUPTIBLE狀態(tài)類似,TASK_UNINTERRUPTIBLE狀態(tài)下的進(jìn)程也處于睡眠狀態(tài),但它是深度睡眠,如同一位陷入沉睡的選手,不會(huì)被信號(hào)輕易喚醒,只有當(dāng)它所等待的特定事件完成時(shí),才會(huì)被喚醒 。在某些情況下,進(jìn)程可能會(huì)等待特定的硬件資源,比如等待磁盤設(shè)備完成初始化,此時(shí)進(jìn)程會(huì)進(jìn)入TASK_UNINTERRUPTIBLE狀態(tài),以確保在硬件資源準(zhǔn)備好之前,不會(huì)被其他因素干擾 。

__TASK_STOPPED狀態(tài)表示進(jìn)程被暫時(shí)喊停,處于停止執(zhí)行的狀態(tài),就像比賽中的選手因?yàn)槟承┨厥庠虮徊门幸髸和1荣?。通常,當(dāng)進(jìn)程接收到SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU等信號(hào)時(shí),就會(huì)進(jìn)入這種狀態(tài) 。例如,當(dāng)我們?cè)谡{(diào)試程序時(shí),使用調(diào)試工具向進(jìn)程發(fā)送SIGSTOP信號(hào),進(jìn)程就會(huì)停止執(zhí)行,方便我們進(jìn)行調(diào)試操作 。

__TASK_TRACED狀態(tài)則表示進(jìn)程正在被像調(diào)試器這樣的 “裁判助手” 密切監(jiān)視著,進(jìn)程的一舉一動(dòng)都在監(jiān)控之下 。在調(diào)試程序時(shí),調(diào)試器會(huì)將進(jìn)程設(shè)置為__TASK_TRACED狀態(tài),以便實(shí)時(shí)獲取進(jìn)程的運(yùn)行信息,幫助開發(fā)者找出程序中的問題 。

而exit_state成員主要用于記錄進(jìn)程在終止階段的相關(guān)狀態(tài) 。EXIT_ZOMBIE狀態(tài)意味著進(jìn)程已經(jīng)完成了它的使命,執(zhí)行被終止,但它的 “后事” 還未處理完畢,父進(jìn)程還沒有使用wait()等系統(tǒng)調(diào)用來獲取它的終止信息 。就像一位選手完成了比賽,但還沒有和教練(父進(jìn)程)進(jìn)行最后的交接 。EXIT_DEAD狀態(tài)則表示進(jìn)程已經(jīng)徹底結(jié)束,所有的資源都已被釋放,如同選手已經(jīng)離開賽場(chǎng),一切都已塵埃落定 。

進(jìn)程狀態(tài)的轉(zhuǎn)換就像一場(chǎng)精心編排的舞蹈,隨著系統(tǒng)中各種事件的發(fā)生而有序進(jìn)行 。當(dāng)一個(gè)處于TASK_INTERRUPTIBLE狀態(tài)的進(jìn)程等待的事件完成時(shí),它會(huì)像被喚醒的選手一樣,從睡眠狀態(tài)轉(zhuǎn)換為TASK_RUNNING狀態(tài),重新獲得執(zhí)行的機(jī)會(huì) 。當(dāng)進(jìn)程接收到終止信號(hào)時(shí),它會(huì)從當(dāng)前狀態(tài)轉(zhuǎn)換為EXIT_ZOMBIE狀態(tài),等待父進(jìn)程的處理 。父進(jìn)程調(diào)用wait()系統(tǒng)調(diào)用后,進(jìn)程才會(huì)最終進(jìn)入EXIT_DEAD狀態(tài),完成它的整個(gè)生命周期 。

3.2 身份標(biāo)識(shí)

pid和tgid作為task_struct結(jié)構(gòu)體中的身份標(biāo)識(shí)成員,在進(jìn)程和線程的識(shí)別與管理中扮演著至關(guān)重要的角色 。pid,即進(jìn)程 ID,是系統(tǒng)為每個(gè)進(jìn)程分配的獨(dú)一無二的 “身份證號(hào)碼”,它就像班級(jí)里每個(gè)學(xué)生的學(xué)號(hào),用于唯一標(biāo)識(shí)一個(gè)進(jìn)程 。在 Linux 系統(tǒng)中,pid是一個(gè)整型數(shù)值,從 1 開始依次遞增,每個(gè)新創(chuàng)建的進(jìn)程都會(huì)被賦予一個(gè)比之前進(jìn)程pid更大的唯一值 。當(dāng)我們?cè)谙到y(tǒng)中運(yùn)行多個(gè)程序時(shí),通過pid可以準(zhǔn)確地區(qū)分不同的進(jìn)程,操作系統(tǒng)也能夠根據(jù)pid對(duì)進(jìn)程進(jìn)行各種操作,如發(fā)送信號(hào)、終止進(jìn)程等 。例如,使用kill命令時(shí),就需要指定進(jìn)程的pid來向該進(jìn)程發(fā)送終止信號(hào) 。

tgid,即線程組 ID,與線程組的概念緊密相連 。在 Linux 系統(tǒng)中,線程被視為輕量級(jí)進(jìn)程,它們共享同一進(jìn)程的資源,如內(nèi)存空間、文件描述符等 。線程組是由一個(gè)或多個(gè)線程組成的集合,這些線程共同協(xié)作完成特定的任務(wù) 。tgid用于標(biāo)識(shí)線程組,同一線程組中的所有線程都擁有相同的tgid,它就像一個(gè)團(tuán)隊(duì)的標(biāo)志,將同一組的線程緊密聯(lián)系在一起 。對(duì)于只有主線程的進(jìn)程來說,pid和tgid的值是相等的,因?yàn)榇藭r(shí)進(jìn)程就是一個(gè)單一的線程組,主線程既是進(jìn)程的代表,也是線程組的唯一成員 。但當(dāng)一個(gè)進(jìn)程創(chuàng)建了多個(gè)線程時(shí),情況就有所不同了 。每個(gè)線程都有自己獨(dú)立的pid,就像團(tuán)隊(duì)中的每個(gè)成員都有自己的個(gè)性標(biāo)識(shí),但它們都共享同一個(gè)tgid,以表明它們屬于同一個(gè)線程組 。

這種進(jìn)程和線程的標(biāo)識(shí)機(jī)制,為操作系統(tǒng)的高效管理提供了有力支持 。在多線程編程中,通過pid和tgid,操作系統(tǒng)能夠清晰地識(shí)別每個(gè)線程的身份,合理地分配 CPU 資源,確保線程組內(nèi)的線程能夠協(xié)同工作,同時(shí)也能對(duì)不同線程組的進(jìn)程進(jìn)行有效的調(diào)度和管理 。例如,在一個(gè)多線程的網(wǎng)絡(luò)服務(wù)器程序中,主線程負(fù)責(zé)監(jiān)聽網(wǎng)絡(luò)連接,而多個(gè)工作線程負(fù)責(zé)處理接收到的請(qǐng)求 。通過pid和tgid,操作系統(tǒng)可以準(zhǔn)確地調(diào)度這些線程,保證服務(wù)器能夠高效地處理大量并發(fā)請(qǐng)求 。

3.3 優(yōu)先級(jí)與調(diào)度

在task_struct結(jié)構(gòu)體中,prio、static_prio、normal_prio和rt_priority等成員在進(jìn)程調(diào)度的舞臺(tái)上扮演著關(guān)鍵角色,它們共同決定了進(jìn)程在 CPU 資源競(jìng)爭中的優(yōu)先級(jí)順序 。

static_prio是進(jìn)程的靜態(tài)優(yōu)先級(jí),它就像一個(gè)學(xué)生的基礎(chǔ)成績,在進(jìn)程創(chuàng)建時(shí)就被確定下來,并且在進(jìn)程的生命周期中相對(duì)穩(wěn)定 。靜態(tài)優(yōu)先級(jí)的值越小,代表進(jìn)程的優(yōu)先級(jí)越高 。對(duì)于實(shí)時(shí)進(jìn)程,其靜態(tài)優(yōu)先級(jí)范圍通常是 0 - 99,而普通進(jìn)程的靜態(tài)優(yōu)先級(jí)范圍是 100 - 139 。例如,一個(gè)用于實(shí)時(shí)視頻播放的進(jìn)程,為了保證視頻播放的流暢性,可能會(huì)被賦予較低的靜態(tài)優(yōu)先級(jí),以確保它能夠優(yōu)先獲得 CPU 資源 。靜態(tài)優(yōu)先級(jí)可以通過nice()或者setpriority等系統(tǒng)調(diào)用在用戶空間進(jìn)行修改,新創(chuàng)建的進(jìn)程會(huì)繼承父進(jìn)程的靜態(tài)優(yōu)先級(jí) 。

rt_priority表示進(jìn)程的實(shí)時(shí)優(yōu)先級(jí),主要用于實(shí)時(shí)進(jìn)程 。實(shí)時(shí)進(jìn)程對(duì)時(shí)間的要求非常嚴(yán)格,需要在規(guī)定的時(shí)間內(nèi)完成任務(wù) 。實(shí)時(shí)優(yōu)先級(jí)的范圍是 1 - 99,值越大表示優(yōu)先級(jí)越高 。在一個(gè)工業(yè)控制系統(tǒng)中,用于控制生產(chǎn)設(shè)備的實(shí)時(shí)進(jìn)程,其rt_priority可能會(huì)被設(shè)置得較高,以確保能夠及時(shí)響應(yīng)設(shè)備的各種信號(hào),保證生產(chǎn)的正常進(jìn)行 。普通進(jìn)程的rt_priority通常為 0 。

normal_prio是歸一化優(yōu)先級(jí),它是根據(jù)靜態(tài)優(yōu)先級(jí)、實(shí)時(shí)優(yōu)先級(jí)和調(diào)度策略綜合計(jì)算得出的 。調(diào)度器在進(jìn)行調(diào)度決策時(shí),會(huì)參考?xì)w一化優(yōu)先級(jí)來確定進(jìn)程的執(zhí)行順序 。對(duì)于普通進(jìn)程,其歸一化優(yōu)先級(jí)通常就是靜態(tài)優(yōu)先級(jí);而對(duì)于實(shí)時(shí)進(jìn)程,歸一化優(yōu)先級(jí)則與實(shí)時(shí)優(yōu)先級(jí)相關(guān) 。

prio是進(jìn)程的動(dòng)態(tài)優(yōu)先級(jí),它是調(diào)度器實(shí)際用于調(diào)度的優(yōu)先級(jí) 。動(dòng)態(tài)優(yōu)先級(jí)在運(yùn)行時(shí)可以根據(jù)進(jìn)程的運(yùn)行情況進(jìn)行調(diào)整 。例如,當(dāng)一個(gè)進(jìn)程長時(shí)間占用 CPU 資源時(shí),調(diào)度器可能會(huì)降低它的動(dòng)態(tài)優(yōu)先級(jí),以便讓其他進(jìn)程也有機(jī)會(huì)獲得 CPU 資源 。相反,當(dāng)一個(gè)進(jìn)程處于等待狀態(tài),等待某個(gè)資源的時(shí)間較長時(shí),調(diào)度器可能會(huì)適當(dāng)提高它的動(dòng)態(tài)優(yōu)先級(jí),以提高系統(tǒng)的整體性能 。

不同的調(diào)度策略對(duì)這些優(yōu)先級(jí)成員有著不同的運(yùn)用方式 。在完全公平調(diào)度(CFS)策略下,調(diào)度器會(huì)根據(jù)進(jìn)程的虛擬運(yùn)行時(shí)間(vruntime)和歸一化優(yōu)先級(jí)來分配 CPU 時(shí)間,確保每個(gè)進(jìn)程都能公平地獲得 CPU 資源 。而在實(shí)時(shí)調(diào)度策略中,如SCHED_FIFO(先進(jìn)先出調(diào)度)和SCHED_RR(時(shí)間片輪轉(zhuǎn)調(diào)度),實(shí)時(shí)優(yōu)先級(jí)rt_priority起著關(guān)鍵作用,高優(yōu)先級(jí)的實(shí)時(shí)進(jìn)程會(huì)優(yōu)先獲得 CPU 資源,并且在沒有更高優(yōu)先級(jí)實(shí)時(shí)進(jìn)程的情況下,會(huì)一直占用 CPU,直到完成任務(wù)或者主動(dòng)讓出 CPU 。

3.4 內(nèi)存管理指針

在task_struct結(jié)構(gòu)體中,mm和active_mm成員猶如進(jìn)程內(nèi)存管理世界的 “導(dǎo)航儀”,在進(jìn)程用戶空間內(nèi)存管理的復(fù)雜旅程中發(fā)揮著關(guān)鍵作用 。

mm指針指向一個(gè)mm_struct結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體就像是進(jìn)程內(nèi)存世界的 “大管家”,詳細(xì)記錄了進(jìn)程用戶空間的內(nèi)存布局和管理信息 。它包含了進(jìn)程的代碼段、數(shù)據(jù)段、堆、棧等內(nèi)存區(qū)域的映射信息,以及頁表、虛擬內(nèi)存區(qū)域(VMA)列表等重要數(shù)據(jù) 。當(dāng)一個(gè)進(jìn)程運(yùn)行時(shí),它所需要的代碼和數(shù)據(jù)都存儲(chǔ)在這些內(nèi)存區(qū)域中 。例如,一個(gè) C 語言程序在運(yùn)行時(shí),程序的可執(zhí)行代碼會(huì)存儲(chǔ)在代碼段,全局變量和靜態(tài)變量會(huì)存儲(chǔ)在數(shù)據(jù)段,動(dòng)態(tài)分配的內(nèi)存會(huì)在堆中進(jìn)行管理,而函數(shù)調(diào)用時(shí)的局部變量和參數(shù)則會(huì)存儲(chǔ)在棧中 。mm_struct結(jié)構(gòu)體通過對(duì)這些內(nèi)存區(qū)域的有效管理,確保進(jìn)程能夠正確地訪問和使用內(nèi)存資源 。

active_mm主要用于處理內(nèi)核線程的內(nèi)存管理問題 。對(duì)于普通用戶進(jìn)程來說,active_mm通常指向與該進(jìn)程關(guān)聯(lián)的mm_struct,就像一個(gè)專屬的內(nèi)存管理助手,時(shí)刻為進(jìn)程提供內(nèi)存管理服務(wù) 。然而,內(nèi)核線程的情況有所不同 。內(nèi)核線程只在內(nèi)核空間中運(yùn)行,不需要訪問用戶空間內(nèi)存,因此它們通常沒有自己獨(dú)立的mm_struct 。在這種情況下,active_mm會(huì)指向最后一個(gè)運(yùn)行在該 CPU 上的用戶進(jìn)程的mm_struct 。這就好比內(nèi)核線程在內(nèi)存管理方面沒有自己的 “家”,但它可以借用最后一個(gè)在該 CPU 上運(yùn)行的用戶進(jìn)程的 “家” 來進(jìn)行一些必要的內(nèi)存操作 。例如,當(dāng)內(nèi)核線程執(zhí)行某些需要訪問內(nèi)存的操作時(shí),它可以通過active_mm找到合適的內(nèi)存上下文,從而確保內(nèi)存操作的正確執(zhí)行 。

這種內(nèi)存管理機(jī)制,既保證了普通進(jìn)程能夠高效地管理和使用自己的用戶空間內(nèi)存,又巧妙地解決了內(nèi)核線程在內(nèi)存管理方面的特殊需求,使得整個(gè)系統(tǒng)的內(nèi)存管理更加靈活和高效 。在系統(tǒng)中同時(shí)運(yùn)行多個(gè)進(jìn)程和內(nèi)核線程的情況下,通過mm和active_mm的協(xié)同工作,能夠有條不紊地進(jìn)行內(nèi)存分配、回收和訪問控制,為系統(tǒng)的穩(wěn)定運(yùn)行提供了堅(jiān)實(shí)的保障 。

3.5 親屬關(guān)系指針

在task_struct結(jié)構(gòu)體中,real_parent、parent、children和sibling等成員如同一張無形的關(guān)系網(wǎng),清晰地描繪了進(jìn)程間的親屬關(guān)系,在進(jìn)程管理和信號(hào)傳遞的舞臺(tái)上發(fā)揮著不可或缺的作用 。

real_parent指針指向進(jìn)程真正的父進(jìn)程,就像孩子指向自己的親生父母 。在正常情況下,當(dāng)一個(gè)進(jìn)程被創(chuàng)建時(shí),它的real_parent會(huì)指向創(chuàng)建它的父進(jìn)程 。例如,當(dāng)我們?cè)诮K端中通過命令行啟動(dòng)一個(gè)新的進(jìn)程時(shí),這個(gè)新進(jìn)程的real_parent就是終端進(jìn)程 。然而,在某些特殊情況下,如使用調(diào)試工具(如 GDB)調(diào)試進(jìn)程時(shí),情況會(huì)有所不同 。假設(shè)在 bash 中使用 GDB 來調(diào)試一個(gè)進(jìn)程,此時(shí)進(jìn)程的parent是 GDB,因?yàn)?GDB 負(fù)責(zé)監(jiān)控和控制進(jìn)程的執(zhí)行;而real_parent仍然是 bash,因?yàn)?bash 是最初創(chuàng)建該進(jìn)程的父進(jìn)程 。這種區(qū)分在進(jìn)程管理和信號(hào)傳遞中非常重要,它確保了進(jìn)程能夠正確地繼承父進(jìn)程的資源和屬性,并且在需要時(shí)能夠向正確的父進(jìn)程發(fā)送信號(hào) 。

parent指針同樣指向父進(jìn)程,但它主要用于接收SIGCHLD信號(hào)和wait4()系統(tǒng)調(diào)用的報(bào)告 。當(dāng)一個(gè)進(jìn)程終止時(shí),它會(huì)向自己的parent發(fā)送SIGCHLD信號(hào),通知父進(jìn)程自己的狀態(tài)發(fā)生了變化 。父進(jìn)程可以通過wait4()系統(tǒng)調(diào)用來獲取子進(jìn)程的終止信息,如子進(jìn)程的退出狀態(tài)、資源使用情況等 。這就像孩子完成任務(wù)后向家長匯報(bào)情況,家長可以根據(jù)這些信息進(jìn)行相應(yīng)的處理 。

children是一個(gè)鏈表頭,它將所有屬于該進(jìn)程的子進(jìn)程串聯(lián)在一起,形成了一個(gè)家族樹 。鏈表中的每個(gè)元素都是該進(jìn)程的子進(jìn)程,通過children鏈表,父進(jìn)程可以方便地管理和訪問自己的子進(jìn)程 。例如,父進(jìn)程可以遍歷children鏈表,對(duì)每個(gè)子進(jìn)程進(jìn)行資源分配、狀態(tài)查詢等操作 。

sibling指針則用于將當(dāng)前進(jìn)程插入到兄弟進(jìn)程鏈表中,它就像連接兄弟姐妹之間的紐帶 。擁有同一父進(jìn)程的所有進(jìn)程互為兄弟進(jìn)程,它們通過sibling指針相互關(guān)聯(lián) 。通過這個(gè)鏈表,進(jìn)程可以快速找到自己的兄弟進(jìn)程,實(shí)現(xiàn)進(jìn)程間的協(xié)作和通信 。例如,在一個(gè)多進(jìn)程的應(yīng)用程序中,兄弟進(jìn)程之間可能需要共享某些資源或者傳遞數(shù)據(jù),通過sibling鏈表,它們可以方便地找到彼此并進(jìn)行交互 。

在進(jìn)程管理和信號(hào)傳遞中,這些親屬關(guān)系指針起著至關(guān)重要的作用 。當(dāng)一個(gè)進(jìn)程接收到信號(hào)時(shí),它會(huì)根據(jù)自己的親屬關(guān)系將信號(hào)傳遞給合適的進(jìn)程 。例如,當(dāng)父進(jìn)程接收到SIGCHLD信號(hào)時(shí),它可以通過children鏈表找到對(duì)應(yīng)的子進(jìn)程,并進(jìn)行相應(yīng)的處理 。這種基于親屬關(guān)系的信號(hào)傳遞機(jī)制,確保了信號(hào)能夠準(zhǔn)確地到達(dá)目標(biāo)進(jìn)程,提高了系統(tǒng)的響應(yīng)速度和穩(wěn)定性 。

3.6 時(shí)間與統(tǒng)計(jì)信息

在task_struct結(jié)構(gòu)體中,utime、stime、start_time等時(shí)間相關(guān)成員,以及nvcsw、nivcsw等統(tǒng)計(jì)信息成員,就像一個(gè)個(gè)精準(zhǔn)的記錄員,詳細(xì)地記錄著進(jìn)程的時(shí)間開銷和運(yùn)行統(tǒng)計(jì)信息,為系統(tǒng)的性能分析和進(jìn)程管理提供了重要的數(shù)據(jù)支持 。

utime表示進(jìn)程在用戶態(tài)下消耗的時(shí)間,就像運(yùn)動(dòng)員在比賽中實(shí)際奔跑的時(shí)間 。它記錄了進(jìn)程執(zhí)行用戶代碼所花費(fèi)的時(shí)間,這個(gè)時(shí)間不包括進(jìn)程在系統(tǒng)調(diào)用和內(nèi)核態(tài)下的時(shí)間 。例如,一個(gè)計(jì)算密集型的進(jìn)程在進(jìn)行復(fù)雜的數(shù)學(xué)運(yùn)算時(shí),utime會(huì)隨著運(yùn)算的進(jìn)行而不斷增加 。通過統(tǒng)計(jì)utime,我們可以了解進(jìn)程在用戶態(tài)下的執(zhí)行效率,判斷進(jìn)程是否存在性能瓶頸 。

stime則記錄了進(jìn)程在內(nèi)核態(tài)下消耗的時(shí)間,如同運(yùn)動(dòng)員在比賽中準(zhǔn)備和調(diào)整的時(shí)間 。當(dāng)進(jìn)程進(jìn)行系統(tǒng)調(diào)用,如讀取文件、分配內(nèi)存等操作時(shí),會(huì)進(jìn)入內(nèi)核態(tài),stime會(huì)統(tǒng)計(jì)這部分時(shí)間 。系統(tǒng)調(diào)用通常涉及到內(nèi)核資源的訪問和管理,stime的統(tǒng)計(jì)可以幫助我們了解進(jìn)程對(duì)內(nèi)核資源的使用情況,評(píng)估系統(tǒng)調(diào)用的開銷 。

start_time記錄了進(jìn)程的啟動(dòng)時(shí)間,就像比賽的開始時(shí)間 。它是一個(gè)時(shí)間戳,表示進(jìn)程從創(chuàng)建到開始執(zhí)行的時(shí)間點(diǎn) 。通過start_time,我們可以計(jì)算進(jìn)程的運(yùn)行時(shí)長,了解進(jìn)程在系統(tǒng)中的存活時(shí)間 。在系統(tǒng)性能分析中,運(yùn)行時(shí)長是一個(gè)重要的指標(biāo),它可以幫助我們判斷進(jìn)程是否長時(shí)間占用系統(tǒng)資源,是否需要進(jìn)行優(yōu)化 。

nvcsw和nivcsw屬于統(tǒng)計(jì)信息成員,分別表示自愿上下文切換次數(shù)和非自愿上下文切換次數(shù) 。自愿上下文切換是指進(jìn)程主動(dòng)放棄 CPU,例如進(jìn)程在等待 I/O 操作完成時(shí),會(huì)主動(dòng)讓出 CPU,此時(shí)nvcsw會(huì)增加 。這就像運(yùn)動(dòng)員在比賽中主動(dòng)休息,調(diào)整狀態(tài) 。非自愿上下文切換則是指進(jìn)程被調(diào)度器強(qiáng)制剝奪 CPU,例如當(dāng)有更高優(yōu)先級(jí)的進(jìn)程需要運(yùn)行時(shí),當(dāng)前進(jìn)程會(huì)被切換出去,nivcsw會(huì)增加 。這就像運(yùn)動(dòng)員在比賽中被裁判要求暫停,讓其他選手上場(chǎng) 。通過統(tǒng)計(jì)這兩個(gè)值,我們可以了解進(jìn)程在 CPU 競(jìng)爭中的表現(xiàn),評(píng)估調(diào)度器的性能 。如果一個(gè)進(jìn)程的nivcsw過高,可能意味著系統(tǒng)中存在競(jìng)爭激烈的情況,需要進(jìn)一步優(yōu)化調(diào)度策略 。

四、task_struct與進(jìn)程管理的“化學(xué)反應(yīng)”

4.1 進(jìn)程創(chuàng)建

在 Linux 系統(tǒng)中,進(jìn)程的創(chuàng)建如同生命的誕生,充滿了奇妙的過程,而fork()系統(tǒng)調(diào)用則是這個(gè)過程的關(guān)鍵 “催化劑” 。當(dāng)fork()系統(tǒng)調(diào)用被觸發(fā)時(shí),一場(chǎng)精心編排的 “復(fù)制” 大戲便拉開了帷幕 。內(nèi)核首先會(huì)在內(nèi)存中為新的子進(jìn)程精心分配一個(gè)全新的task_struct結(jié)構(gòu)體,就像為新生命準(zhǔn)備了一個(gè)獨(dú)特的 “生命檔案” 。這個(gè)新的task_struct結(jié)構(gòu)體就像是一張白紙,等待著被賦予各種關(guān)鍵信息 。

接下來,子進(jìn)程開始從父進(jìn)程那里繼承一系列重要的信息 。子進(jìn)程會(huì)繼承父進(jìn)程的進(jìn)程狀態(tài),就像孩子繼承了父母的某些特質(zhì) 。如果父進(jìn)程處于運(yùn)行狀態(tài),子進(jìn)程在創(chuàng)建初期也會(huì)繼承這個(gè)狀態(tài),等待著被調(diào)度執(zhí)行 。在進(jìn)程的親屬關(guān)系方面,子進(jìn)程的real_parent和parent指針都會(huì)指向父進(jìn)程,就像孩子與父母之間建立了緊密的聯(lián)系 。這種親屬關(guān)系的繼承確保了子進(jìn)程能夠正確地融入進(jìn)程家族樹,在需要時(shí)能夠向父進(jìn)程尋求支持和資源 。

在內(nèi)存管理方面,子進(jìn)程會(huì)與父進(jìn)程共享內(nèi)存資源 。它們共享同一內(nèi)存描述符mm_struct,這意味著它們?cè)谟脩艨臻g中看到的內(nèi)存布局是相同的 。這就好比兩個(gè)孩子住在同一所房子里,共享著房子里的各種設(shè)施 。不過,這種共享是基于寫時(shí)拷貝(Copy - On - Write,COW)機(jī)制的 。在初始階段,子進(jìn)程和父進(jìn)程共享內(nèi)存頁面,但當(dāng)其中任何一方試圖對(duì)共享頁面進(jìn)行寫操作時(shí),系統(tǒng)會(huì)為寫操作的一方分配新的物理頁面,將共享頁面的內(nèi)容復(fù)制到新頁面中,然后進(jìn)行寫操作 。這樣,既保證了內(nèi)存資源的高效利用,又確保了子進(jìn)程和父進(jìn)程在內(nèi)存操作上的獨(dú)立性 。

文件描述符表也會(huì)被子進(jìn)程繼承 。這意味著父進(jìn)程打開的文件,子進(jìn)程同樣可以訪問 。如果父進(jìn)程打開了一個(gè)日志文件用于記錄信息,子進(jìn)程也能夠讀取和寫入這個(gè)文件 。這為父子進(jìn)程之間的協(xié)作提供了便利,它們可以通過共享的文件進(jìn)行數(shù)據(jù)傳遞和同步 。

例如,在一個(gè)多進(jìn)程的服務(wù)器程序中,父進(jìn)程負(fù)責(zé)監(jiān)聽網(wǎng)絡(luò)端口,接受客戶端的連接請(qǐng)求 。當(dāng)有新的連接到來時(shí),父進(jìn)程通過fork()創(chuàng)建子進(jìn)程,子進(jìn)程繼承了父進(jìn)程的網(wǎng)絡(luò)連接文件描述符,從而可以獨(dú)立地處理與客戶端的通信 。在這個(gè)過程中,子進(jìn)程的task_struct結(jié)構(gòu)體從父進(jìn)程那里繼承了必要的信息,使得子進(jìn)程能夠順利地開始它的 “生命旅程”,與父進(jìn)程協(xié)同工作,共同完成服務(wù)器的任務(wù) 。

4.2 進(jìn)程調(diào)度

進(jìn)程調(diào)度在 Linux 系統(tǒng)中就像是一場(chǎng)激烈的資源爭奪賽,而task_struct結(jié)構(gòu)體則是這場(chǎng)比賽中的關(guān)鍵 “情報(bào)站”,為調(diào)度器提供了豐富的信息,幫助調(diào)度器做出合理的決策 。調(diào)度器在選擇下一個(gè)要執(zhí)行的進(jìn)程時(shí),會(huì)仔細(xì)參考task_struct中的優(yōu)先級(jí)信息 。進(jìn)程的優(yōu)先級(jí)就像運(yùn)動(dòng)員的比賽排名,優(yōu)先級(jí)高的進(jìn)程會(huì)優(yōu)先獲得 CPU 資源 。實(shí)時(shí)進(jìn)程的優(yōu)先級(jí)通常高于普通進(jìn)程,這是因?yàn)閷?shí)時(shí)進(jìn)程對(duì)時(shí)間的要求非常嚴(yán)格,需要在規(guī)定的時(shí)間內(nèi)完成任務(wù) 。在一個(gè)實(shí)時(shí)視頻監(jiān)控系統(tǒng)中,用于處理視頻流的進(jìn)程會(huì)被賦予較高的優(yōu)先級(jí),以確保視頻的實(shí)時(shí)性和流暢性 。

進(jìn)程的狀態(tài)也是調(diào)度器關(guān)注的重要信息 。處于TASK_RUNNING狀態(tài)的進(jìn)程,如同已經(jīng)站在起跑線上的運(yùn)動(dòng)員,時(shí)刻準(zhǔn)備著獲取 CPU 資源,進(jìn)入執(zhí)行狀態(tài) 。而處于TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE狀態(tài)的進(jìn)程,則像是在休息或等待特定資源的運(yùn)動(dòng)員,調(diào)度器會(huì)暫時(shí)跳過它們,將 CPU 資源分配給更有執(zhí)行條件的進(jìn)程 。

當(dāng)進(jìn)行上下文切換時(shí),task_struct的作用更是不可或缺 。上下文切換就像是運(yùn)動(dòng)員在比賽中途進(jìn)行換人,新上場(chǎng)的運(yùn)動(dòng)員需要迅速適應(yīng)比賽環(huán)境 。在這個(gè)過程中,task_struct保存了進(jìn)程的各種上下文信息,包括 CPU 寄存器的值、堆棧指針等 。當(dāng)一個(gè)進(jìn)程被切換出去時(shí),內(nèi)核會(huì)將該進(jìn)程的 CPU 寄存器的值等上下文信息保存到它的task_struct中 。當(dāng)這個(gè)進(jìn)程再次被調(diào)度執(zhí)行時(shí),內(nèi)核會(huì)從它的task_struct中讀取這些上下文信息,恢復(fù) CPU 寄存器的值和堆棧指針,使得進(jìn)程能夠繼續(xù)從上次中斷的地方執(zhí)行 。這就像運(yùn)動(dòng)員在比賽中換人后,新上場(chǎng)的運(yùn)動(dòng)員能夠迅速了解比賽的進(jìn)展情況,繼續(xù)完成比賽 。

在多核心 CPU 的系統(tǒng)中,調(diào)度器還會(huì)根據(jù)task_struct中的cpus_allowed等信息,決定將進(jìn)程分配到哪個(gè) CPU 核心上執(zhí)行 。這個(gè)信息就像是運(yùn)動(dòng)員的參賽場(chǎng)地選擇,調(diào)度器會(huì)根據(jù)進(jìn)程的需求和 CPU 核心的負(fù)載情況,合理地安排進(jìn)程在不同的 CPU 核心上運(yùn)行,以提高系統(tǒng)的整體性能 。

4.3 進(jìn)程終止

進(jìn)程終止的過程就像是一場(chǎng)演出的落幕,雖然看似簡單,但背后卻涉及到一系列復(fù)雜而有序的操作,而task_struct在這個(gè)過程中扮演著關(guān)鍵的角色 。當(dāng)一個(gè)進(jìn)程完成了它的使命,準(zhǔn)備終止時(shí),內(nèi)核會(huì)首先對(duì)task_struct中的相關(guān)信息進(jìn)行處理 。進(jìn)程會(huì)釋放它所占用的各種資源,這就像是演員在演出結(jié)束后歸還借用的道具 。例如,進(jìn)程會(huì)關(guān)閉它打開的文件描述符,釋放文件鎖,將文件資源歸還給系統(tǒng) 。在內(nèi)存管理方面,進(jìn)程會(huì)釋放它所占用的內(nèi)存空間,包括堆內(nèi)存、棧內(nèi)存等 。對(duì)于使用了動(dòng)態(tài)內(nèi)存分配的進(jìn)程,如通過malloc()函數(shù)分配的內(nèi)存,在進(jìn)程終止時(shí),這些內(nèi)存會(huì)被回收,以避免內(nèi)存泄漏 。

進(jìn)程的狀態(tài)也會(huì)被更新 。它會(huì)從當(dāng)前的運(yùn)行狀態(tài)轉(zhuǎn)換為EXIT_ZOMBIE狀態(tài),就像演員從舞臺(tái)上退下,進(jìn)入了一種等待善后處理的狀態(tài) 。在EXIT_ZOMBIE狀態(tài)下,進(jìn)程雖然已經(jīng)停止執(zhí)行,但它的task_struct結(jié)構(gòu)體仍然存在于系統(tǒng)中,因?yàn)樗€需要向父進(jìn)程傳遞一些重要的信息,如進(jìn)程的退出狀態(tài)、資源使用情況等 。父進(jìn)程可以通過wait()或waitpid()等系統(tǒng)調(diào)用來獲取這些信息 。這就像演出結(jié)束后,演員需要向?qū)а輩R報(bào)演出的情況 。

當(dāng)父進(jìn)程調(diào)用wait()或waitpid()時(shí),內(nèi)核會(huì)處理子進(jìn)程的task_struct 。內(nèi)核會(huì)讀取子進(jìn)程的退出狀態(tài),這個(gè)狀態(tài)信息就像是演員的演出評(píng)價(jià),父進(jìn)程可以根據(jù)這個(gè)狀態(tài)了解子進(jìn)程的執(zhí)行結(jié)果 。內(nèi)核會(huì)回收子進(jìn)程的task_struct結(jié)構(gòu)體以及其他相關(guān)資源,將它們從系統(tǒng)中徹底移除 。這就像是演出結(jié)束后,清理舞臺(tái)和道具,為下一場(chǎng)演出做好準(zhǔn)備 。

如果父進(jìn)程沒有及時(shí)調(diào)用wait()來獲取子進(jìn)程的終止信息,子進(jìn)程就會(huì)一直處于EXIT_ZOMBIE狀態(tài),成為一個(gè) “僵尸進(jìn)程” 。僵尸進(jìn)程雖然不占用CPU等執(zhí)行資源,但它會(huì)占用系統(tǒng)的內(nèi)存等資源,就像廢棄的道具占用著倉庫的空間 。長時(shí)間存在大量僵尸進(jìn)程可能會(huì)導(dǎo)致系統(tǒng)資源的浪費(fèi),影響系統(tǒng)的性能 。因此,在編寫多進(jìn)程程序時(shí),父進(jìn)程需要及時(shí)處理子進(jìn)程的終止信息,避免僵尸進(jìn)程的產(chǎn)生 。

五、案例實(shí)戰(zhàn)分析

為了更直觀地感受task_struct在實(shí)際中的應(yīng)用,我們通過一個(gè)簡單的內(nèi)核模塊代碼示例來一探究竟 。下面是一段用于讀取所有進(jìn)程的task_struct結(jié)構(gòu)信息的內(nèi)核模塊代碼:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/sched/signal.h>
#include <linux/init.h>

static int __init hello_init(void) {
    struct task_struct *pp;
    printk("for_each_process begin\n");
    for_each_process(pp) {
        printk(KERN_INFO "process_info pid:%i comm:%s flags:%i", pp->pid, pp->comm, pp->flags);
    }
    return 0;
}

static void __exit hello_exit(void) {
    printk("for_each_process end!\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

在這段代碼中,我們首先包含了必要的頭文件,這些頭文件為我們提供了與內(nèi)核交互所需的各種定義和函數(shù)聲明 。hello_init函數(shù)是內(nèi)核模塊的初始化函數(shù),當(dāng)模塊被加載到內(nèi)核中時(shí),這個(gè)函數(shù)會(huì)被執(zhí)行 。在函數(shù)內(nèi)部,我們使用for_each_process宏來遍歷系統(tǒng)中的所有進(jìn)程 。這個(gè)宏就像是一個(gè)向?qū)?,帶領(lǐng)我們逐一訪問系統(tǒng)中的每個(gè)進(jìn)程 。對(duì)于每個(gè)遍歷到的進(jìn)程,我們通過printk函數(shù)打印出其pid(進(jìn)程 ID)、comm(進(jìn)程名稱)和flags(進(jìn)程標(biāo)志)等信息 。pid就像進(jìn)程的身份證號(hào)碼,獨(dú)一無二地標(biāo)識(shí)著每個(gè)進(jìn)程;comm則是進(jìn)程的名字,讓我們能夠直觀地了解進(jìn)程的用途;flags包含了進(jìn)程的各種狀態(tài)標(biāo)志為我們提供了關(guān)于進(jìn)程狀態(tài)的重要線索 。

hello_exit函數(shù)是內(nèi)核模塊的退出函數(shù),當(dāng)模塊從內(nèi)核中卸載時(shí),這個(gè)函數(shù)會(huì)被執(zhí)行 。在函數(shù)中,我們使用printk函數(shù)打印出模塊卸載的提示信息 。

通過這個(gè)內(nèi)核模塊,我們可以清晰地看到系統(tǒng)中每個(gè)進(jìn)程的一些關(guān)鍵信息,這些信息都來自于task_struct結(jié)構(gòu)體 。這就好比我們打開了一個(gè)進(jìn)程信息的寶藏庫,通過task_struct結(jié)構(gòu)體,我們能夠獲取到進(jìn)程的各種詳細(xì)信息,從而更好地了解系統(tǒng)中進(jìn)程的運(yùn)行狀態(tài)和行為 。

責(zé)任編輯:武曉燕 來源: 深度Linux
相關(guān)推薦

2010-09-27 09:31:42

JVM內(nèi)存結(jié)構(gòu)

2019-05-07 10:03:47

Linux系統(tǒng)發(fā)行版

2023-04-19 08:13:02

EpollLinux

2009-12-23 17:50:07

Linux網(wǎng)絡(luò)命令

2010-06-23 20:31:54

2010-07-13 09:36:25

2010-11-19 16:22:14

Oracle事務(wù)

2009-08-25 16:27:10

Mscomm控件

2020-09-21 09:53:04

FlexCSS開發(fā)

2022-08-26 13:48:40

EPUBLinux

2017-01-19 19:24:29

Linux重定向

2020-07-20 06:35:55

BashLinux

2009-12-09 14:03:18

2010-01-06 17:18:54

Linux常用軟件

2011-07-18 15:08:34

2022-06-03 10:09:32

威脅檢測(cè)軟件

2010-11-15 11:40:44

Oracle表空間

2018-06-22 13:05:02

前端JavaScript引擎

2021-01-19 12:00:39

前端監(jiān)控代碼

2021-04-28 10:13:58

zookeeperZNode核心原理
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)