Linux進程描述符task_struct結構體詳解--Linux進程的管理與調度(一)【轉】

来源:https://www.cnblogs.com/linhaostudy/archive/2018/08/31/9568775.html
-Advertisement-
Play Games

Linux內核通過一個被稱為進程描述符的 結構體來管理進程,這個結構體包含了一個進程所需的所有信息。它定義在 文件中。 談到 結構體,可以說她是linux內核源碼中最複雜的一個結構體了,成員之多,占用記憶體之大。 進程狀態 5個互斥狀態 狀態 | 描述 |: : TASK_RUNNING | 表示進程 ...


Linux內核通過一個被稱為進程描述符的task_struct結構體來管理進程,這個結構體包含了一個進程所需的所有信息。它定義在include/linux/sched.h文件中。

談到task_struct結構體,可以說她是linux內核源碼中最複雜的一個結構體了,成員之多,占用記憶體之大。

進程狀態

 /*
  * Task state bitmask. NOTE! These bits are also
  * encoded in fs/proc/array.c: get_task_state().
  *
  * We have two separate sets of flags: task->state
  * is about runnability, while task->exit_state are
  * about the task exiting. Confusing, but this way
  * modifying one set can't modify the other one by
  * mistake.
  */
 #define TASK_RUNNING            0
 #define TASK_INTERRUPTIBLE      1
 #define TASK_UNINTERRUPTIBLE    2
 #define __TASK_STOPPED          4
 #define __TASK_TRACED           8

/* in tsk->exit_state */
 #define EXIT_DEAD               16
 #define EXIT_ZOMBIE             32
 #define EXIT_TRACE              (EXIT_ZOMBIE | EXIT_DEAD)

/* in tsk->state again */
 #define TASK_DEAD               64
 #define TASK_WAKEKILL           128    /** wake on signals that are deadly **/
 #define TASK_WAKING             256
 #define TASK_PARKED             512
 #define TASK_NOLOAD             1024
 #define TASK_STATE_MAX          2048

 /* Convenience macros for the sake of set_task_state */
#define TASK_KILLABLE           (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_STOPPED            (TASK_WAKEKILL | __TASK_STOPPED)
#define TASK_TRACED             (TASK_WAKEKILL | __TASK_TRACED)

5個互斥狀態

狀態 描述
TASK_RUNNING 表示進程要麼正在執行,要麼正要準備執行(已經就緒),正在等待cpu時間片的調度
TASK_INTERRUPTIBLE 進程因為等待一些條件而被掛起(阻塞)而所處的狀態。這些條件主要包括:硬中斷、資源、一些信號……,一旦等待的條件成立,進程就會從該狀態(阻塞)迅速轉化成為就緒狀態TASK_RUNNING
TASK_UNINTERRUPTIBLE 意義與TASK_INTERRUPTIBLE類似,除了不能通過接受一個信號來喚醒以外,對於處於TASK_UNINTERRUPIBLE狀態的進程,哪怕我們傳遞一個信號或者有一個外部中斷都不能喚醒他們。只有它所等待的資源可用的時候,他才會被喚醒。這個標誌很少用,但是並不代表沒有任何用處,其實他的作用非常大,特別是對於驅動刺探相關的硬體過程很重要,這個刺探過程不能被一些其他的東西給中斷,否則就會讓進城進入不可預測的狀態
TASK_STOPPED 進程被停止執行,當進程接收到SIGSTOP、SIGTTIN、SIGTSTP或者SIGTTOU信號之後就會進入該狀態
TASK_TRACED 表示進程被debugger等進程監視,進程執行被調試程式所停止,當一個進程被另外的進程所監視,每一個信號都會讓進城進入該狀態

2個終止狀態

其實還有兩個附加的進程狀態既可以被添加到state域中,又可以被添加到exit_state域中。只有當進程終止的時候,才會達到這兩種狀態.

/* task state */
int exit_state;
int exit_code, exit_signal;
狀態 描述
EXIT_ZOMBIE 進程的執行被終止,但是其父進程還沒有使用wait()等系統調用來獲知它的終止信息,此時進程成為僵屍進程
EXIT_DEAD 進程的最終狀態

int exit_code, exit_signal;我們會在後面進程介紹

新增睡眠狀態

如前所述,進程狀態 TASK_UNINTERRUPTIBLE 和 TASK_INTERRUPTIBLE 都是睡眠狀態。現在,我們來看看內核如何將進程置為睡眠狀態。

內核如何將進程置為睡眠狀態

Linux 內核提供了兩種方法將進程置為睡眠狀態。

將進程置為睡眠狀態的普通方法是將進程狀態設置為 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 並調用調度程式的 schedule() 函數。這樣會將進程從 CPU 運行隊列中移除。

  • 如果進程處於可中斷模式的睡眠狀態(通過將其狀態設置為 TASK_INTERRUPTIBLE),那麼可以通過顯式的喚醒呼叫(wakeup_process())或需要處理的信號來喚醒它。

  • 但是,如果進程處於非可中斷模式的睡眠狀態(通過將其狀態設置為 TASK_UNINTERRUPTIBLE),那麼只能通過顯式的喚醒呼叫將其喚醒。除非萬不得已,否則我們建議您將進程置為可中斷睡眠模式,而不是不可中斷睡眠模式(比如說在設備 I/O 期間,處理信號非常困難時)。

當處於可中斷睡眠模式的任務接收到信號時,它需要處理該信號(除非它已被屏弊),離開之前正在處理的任務(此處需要清除代碼),並將 -EINTR 返回給用戶空間。再一次,檢查這些返回代碼和採取適當操作的工作將由程式員完成。

因此,懶惰的程式員可能比較喜歡將進程置為不可中斷模式的睡眠狀態,因為信號不會喚醒這類任務。

但需要註意的一種情況是,對不可中斷睡眠模式的進程的喚醒呼叫可能會由於某些原因不會發生,這會使進程無法被終止,從而最終引發問題,因為惟一的解決方法就是重啟系統。一方面,您需要考慮一些細節,因為不這樣做會在內核端和用戶端引入 bug。另一方面,您可能會生成永遠不會停止的進程(被阻塞且無法終止的進程)。

現在,我們在內核中實現了一種新的睡眠方法

Linux Kernel 2.6.25 引入了一種新的進程睡眠狀態,

狀態 描述
TASK_KILLABLE 當進程處於這種可以終止的新睡眠狀態中,它的運行原理類似於 TASK_UNINTERRUPTIBLE,只不過可以響應致命信號

它定義如下:

#define TASK_WAKEKILL           128 /** wake on signals that are deadly **/

/* Convenience macros for the sake of set_task_state */
#define TASK_KILLABLE           (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_STOPPED            (TASK_WAKEKILL | __TASK_STOPPED)
#define TASK_TRACED             (TASK_WAKEKILL | __TASK_TRACED)

換句話說,TASK_UNINTERRUPTIBLE + TASK_WAKEKILL = TASK_KILLABLE。

而TASK_WAKEKILL 用於在接收到致命信號時喚醒進程

新的睡眠狀態允許 TASK_UNINTERRUPTIBLE 響應致命信號

進程狀態的切換過程和原因大致如下圖

進程標識符(PID)

pid_t pid;  
pid_t tgid;  

Unix系統通過pid來標識進程,linux把不同的pid與系統中每個進程或輕量級線程關聯,而unix程式員希望同一組線程具有共同的pid,遵照這個標準linux引入線程組的概念。一個線程組所有線程與領頭線程具有相同的pid,存入tgid欄位,getpid()返回當前進程的tgid值而不是pid的值。

在CONFIG_BASE_SMALL配置為0的情況下,PID的取值範圍是0到32767,即系統中的進程數最大為32768個。

#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)  

在Linux系統中,一個線程組中的所有線程使用和該線程組的領頭線程(該組中的第一個輕量級進程)相同的PID,並被存放在tgid成員中。只有線程組的領頭線程的pid成員才會被設置為與tgid相同的值。註意,getpid()系統調用返回的是當前進程的tgid值而不是pid值。

進程內核棧

void *stack;  

內核棧與線程描述符

對每個進程,Linux內核都把兩個不同的數據結構緊湊的存放在一個單獨為進程分配的記憶體區域中;

  • 一個是內核態的進程堆棧
  • 另一個是緊挨著進程描述符的小數據結構thread_info,叫做線程描述符。

Linux把thread_info(線程描述符)和內核態的線程堆棧存放在一起,這塊區域通常是8192K(占兩個頁框),其實地址必須是8192的整數倍。

在linux/arch/x86/include/asm/page_32_types.h中,

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

出於效率考慮,內核讓這8K空間占據連續的兩個頁框並讓第一個頁框的起始地址是213的倍數。

內核態的進程訪問處於內核數據段的棧,這個棧不同於用戶態的進程所用的棧。

用戶態進程所用的棧,是在進程線性地址空間中;

而內核棧是當進程從用戶空間進入內核空間時,特權級發生變化,需要切換堆棧,那麼內核空間中使用的就是這個內核棧。因為內核控制路徑使用很少的棧空間,所以只需要幾千個位元組的內核態堆棧。

需要註意的是,內核態堆棧僅用於內核常式,Linux內核另外為中斷提供了單獨的硬中斷棧和軟中斷棧

下圖中顯示了在物理記憶體中存放兩種數據結構的方式。線程描述符駐留與這個記憶體區的開始,而棧頂末端向下增長。 下圖摘自ULK3,進程內核棧與進程描述符的關係如下圖:

但是較新的內核代碼中,進程描述符task_struct結構中沒有直接指向thread_info結構的指針,而是用一個void指針類型的成員表示,然後通過類型轉換來訪問thread_info結構。

相關代碼在include/linux/sched.h中

#define task_thread_info(task)  ((struct thread_info *)(task)->stack)

在這個圖中,esp寄存器是CPU棧指針,用來存放棧頂單元的地址。在80x86系統中,棧起始於頂端,並朝著這個記憶體區開始的方向增長。從用戶態剛切換到內核態以後,進程的內核棧總是空的。因此,esp寄存器指向這個棧的頂端。一旦數據寫入堆棧,esp的值就遞減。

內核棧數據結構描述thread_info和thread_union

thread_info是體繫結構相關的,結構的定義在thread_info.h中

Linux內核中使用一個聯合體來表示一個進程的線程描述符和內核棧:

union thread_union
{
    struct thread_info thread_info;
    unsigned long stack[THREAD_SIZE/sizeof(long)];
};

獲取當前在CPU上正在運行進程的thread_info

下麵來說說如何通過esp棧指針來獲取當前在CPU上正在運行進程的thread_info結構。

實際上,上面提到,thread_info結構和內核態堆棧是緊密結合在一起的,占據兩個頁框的物理記憶體空間。而且,這兩個頁框的起始起始地址是213對齊的。

早期的版本中,不需要對64位處理器的支持,所以,內核通過簡單的屏蔽掉esp的低13位有效位就可以獲得thread_info結構的基地址了。

我們在下麵對比了,獲取正在運行的進程的thread_info的實現方式

架構 版本 定義鏈接 實現方式 思路解析
x86 3.14 current_thread_info(void) return (struct thread_info *)(sp & ~(THREAD_SIZE - 1)); 屏蔽了esp的低十三位,最終得到的是thread_info的地址
x86 3.15 current_thread_info(void) ti = (void *)(this_cpu_read_stable(kernel_stack) + KERNEL_STACK_OFFSET - THREAD_SIZE);
x86 4.1 current_thread_info(void) (struct thread_info *)(current_top_of_stack() - THREAD_SIZE);

早期版本
當前的棧指針(current_stack_pointer == sp)就是esp,
THREAD_SIZE為8K,二進位的表示為0000 0000 0000 0000 0010 0000 0000 0000。
~(THREAD_SIZE-1)的結果剛好為1111 1111 1111 1111 1110 0000 0000 0000,第十三位是全為零,也就是剛好屏蔽了esp的低十三位,最終得到的是thread_info的地址。

進程最常用的是進程描述符結構task_struct而不是thread_info結構的地址。為了獲取當前CPU上運行進程的task_struct結構,內核提供了current巨集,由於task_struct *task在thread_info的起始位置,該巨集本質上等價於current_thread_info()->task,在include/asm-generic/current.h中定義:

#define get_current() (current_thread_info()->task)
#define current get_current()

分配和銷毀thread_info

進程通過alloc_thread_info_node函數分配它的內核棧,通過free_thread_info函數釋放所分配的內核棧。

# if THREAD_SIZE >= PAGE_SIZE
static struct thread_info *alloc_thread_info_node(struct task_struct *tsk,
                          int node)
{
    struct page *page = alloc_kmem_pages_node(node, THREADINFO_GFP,
                          THREAD_SIZE_ORDER);

    return page ? page_address(page) : NULL;
}

static inline void free_thread_info(struct thread_info *ti)
{
    free_kmem_pages((unsigned long)ti, THREAD_SIZE_ORDER);
}
# else
static struct kmem_cache *thread_info_cache;

static struct thread_info *alloc_thread_info_node(struct task_struct *tsk,
                          int node)
{
    return kmem_cache_alloc_node(thread_info_cache, THREADINFO_GFP, node);
}

static void free_thread_info(struct thread_info *ti)
{
    kmem_cache_free(thread_info_cache, ti);
}

進程標記

unsigned int flags; /* per process flags, defined below */  

反應進程狀態的信息,但不是運行狀態,用於內核識別進程當前的狀態,以備下一步操作

flags成員的可能取值如下,這些巨集以PF(ProcessFlag)開頭

參見
http://lxr.free-electrons.com/source/include/linux/sched.h?v4.5#L2083
例如
PF_FORKNOEXEC 進程剛創建,但還沒執行。
PF_SUPERPRIV 超級用戶特權。
PF_DUMPCORE dumped core。
PF_SIGNALED 進程被信號(signal)殺出。
PF_EXITING 進程開始關閉。

/*
* Per process flags
*/
#define PF_EXITING      0x00000004      /* getting shut down */
#define PF_EXITPIDONE   0x00000008      /* pi exit done on shut down */
#define PF_VCPU         0x00000010      /* I'm a virtual CPU */
#define PF_WQ_WORKER    0x00000020      /* I'm a workqueue worker */
#define PF_FORKNOEXEC   0x00000040      /* forked but didn't exec */
#define PF_MCE_PROCESS  0x00000080      /* process policy on mce errors */
#define PF_SUPERPRIV    0x00000100      /* used super-user privileges */
#define PF_DUMPCORE     0x00000200      /* dumped core */
#define PF_SIGNALED     0x00000400      /* killed by a signal */
#define PF_MEMALLOC     0x00000800      /* Allocating memory */
#define PF_NPROC_EXCEEDED 0x00001000    /* set_user noticed that RLIMIT_NPROC was exceeded */
#define PF_USED_MATH    0x00002000      /* if unset the fpu must be initialized before use */
#define PF_USED_ASYNC   0x00004000      /* used async_schedule*(), used by module init */
#define PF_NOFREEZE     0x00008000      /* this thread should not be frozen */
#define PF_FROZEN       0x00010000      /* frozen for system suspend */
#define PF_FSTRANS      0x00020000      /* inside a filesystem transaction */
#define PF_KSWAPD       0x00040000      /* I am kswapd */
#define PF_MEMALLOC_NOIO 0x00080000     /* Allocating memory without IO involved */
#define PF_LESS_THROTTLE 0x00100000     /* Throttle me less: I clean memory */
#define PF_KTHREAD      0x00200000      /* I am a kernel thread */
#define PF_RANDOMIZE    0x00400000      /* randomize virtual address space */
#define PF_SWAPWRITE    0x00800000      /* Allowed to write to swap */
#define PF_NO_SETAFFINITY 0x04000000    /* Userland is not allowed to meddle with cpus_allowed */
#define PF_MCE_EARLY    0x08000000      /* Early kill for mce process policy */
#define PF_MUTEX_TESTER 0x20000000      /* Thread belongs to the rt mutex tester */
#define PF_FREEZER_SKIP 0x40000000      /* Freezer should not count it as freezable */
#define PF_SUSPEND_TASK 0x80000000      /* this thread called freeze_processes and should not be frozen */

表示進程親屬關係的成員

/*
 * pointers to (original) parent process, youngest child, younger sibling,
 * older sibling, respectively.  (p->father can be replaced with
 * p->real_parent->pid)
 */
struct task_struct __rcu *real_parent; /* real parent process */
struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
/*
 * children/sibling forms the list of my natural children
 */
struct list_head children;      /* list of my children */
struct list_head sibling;       /* linkage in my parent's children list */
struct task_struct *group_leader;       /* threadgroup leader */

在Linux系統中,所有進程之間都有著直接或間接地聯繫,每個進程都有其父進程,也可能有零個或多個子進程。擁有同一父進程的所有進程具有兄弟關係。

欄位 描述
real_parent 指向其父進程,如果創建它的父進程不再存在,則指向PID為1的init進程
parent 指向其父進程,當它終止時,必須向它的父進程發送信號。它的值通常與real_parent相同
children 表示鏈表的頭部,鏈表中的所有元素都是它的子進程
sibling 用於把當前進程插入到兄弟鏈表中
group_leader 指向其所在進程組的領頭進程

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 概述... 2 2. ServerSuperIO.Core跨平臺開發環境... 2 3. ServerSuperIO.Core特點... 2 4. ServerSuperIO.Core與ServerSuperIO區別... 2 5. 嵌入式應用... 2 6. 上位機應用... 2 7. 雲服 ...
  • 在使用WPF開發的時候就不免會遇到需要兩個視窗間進行傳值操作,當然多視窗間傳值的方法有很多種,本文介紹的是使用委托實現多視窗間的傳值。 在上代碼之前呢,先簡單介紹一下什麼是C#中的委托(如果只想瞭解如何傳值可以略過這部分)在網路上有很多對於委托的介紹和講解,經過我的學習和總結加上了一點我自己的理解, ...
  • 一.數據類型 值類型 引用類型 數組 類(自定義類) 字元串 介面 Object 委托 指針類型 官方給出的說明 在指針類型中的 * 之前指定的類型被稱為“referrent 類型”。 以下任一類型均可為 referrent 類型: 任何整型類型:sbyte、byte、short、ushort、in ...
  • 2018-08-30 直接調用瀏覽器的列印方法 1、列印按鈕 <a href="#" target="_self" onclick="printme()">列印</a> 2、js 2、js //列印 function printme() { $.messager.confirm('確認', '確認打 ...
  • Hadoop 中文編碼相關問題 -- mapreduce程式處理GBK編碼數據並輸出GBK編碼數據 Hadoop 中文編碼相關問題 -- mapreduce程式處理GBK編碼數據並輸出GBK編碼數據 Hadoop 中文編碼相關問題 -- mapreduce程式處理GBK編碼數據並輸出GBK編碼數據 ...
  • 作者:依樂祝 原文地址:https://www.cnblogs.com/yilezhu/p/9557375.html 簡單的說Ocelot是一個用.NET Core實現並且開源的API網關技術。 可能你又要問了,什麼是API網關技術呢?Ocelot又有什麼特別呢?我們又該如何集成到我們的asp.ne ...
  • 優先順序 欄位 | 描述 | static_prio | 用於保存靜態優先順序,可以通過nice系統調用來進行修改 rt_priority | 用於保存實時優先順序 normal_prio | 它的值取決於靜態優先順序和調度策略 prio | 用於保存動態優先順序 實時優先順序範圍是0到MAX_RT_PRIO ...
  • 在Linux中,有很多命令或工具查看記憶體使用情況,今天我們來看看如何查看進程消耗、占用的記憶體情況,Linux的記憶體管理和相關概念要比Windows複雜一些。在此之前,我們需要瞭解一下Linux系統下麵有關記憶體的專用名詞和專業術語概念: 物理記憶體和虛擬記憶體 物理記憶體:就是系統硬體提供的記憶體大小,是真正... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...