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

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

談到task_struct結構體,能夠說她是linux內核源碼中最複雜的一個結構體了,成員之多,佔用內存之大。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 內核提供了兩種方法將進程置爲睡眠狀態。less

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

  • 若是進程處於可中斷模式的睡眠狀態(經過將其狀態設置爲 TASK_INTERRUPTIBLE),那麼能夠經過顯式的喚醒呼叫(wakeup_process())或須要處理的信號來喚醒它。electron

  • 可是,若是進程處於非可中斷模式的睡眠狀態(經過將其狀態設置爲 TASK_UNINTERRUPTIBLE),那麼只能經過顯式的喚醒呼叫將其喚醒。除非萬不得已,不然咱們建議您將進程置爲可中斷睡眠模式,而不是不可中斷睡眠模式(好比說在設備 I/O 期間,處理信號很是困難時)。async

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

所以,懶惰的程序員可能比較喜歡將進程置爲不可中斷模式的睡眠狀態,由於信號不會喚醒這類任務。

但須要注意的一種狀況是,對不可中斷睡眠模式的進程的喚醒呼叫可能會因爲某些緣由不會發生,這會使進程沒法被終止,從而最終引起問題,由於唯一的解決方法就是重啓系統。一方面,您須要考慮一些細節,由於不這樣作會在內核端和用戶端引入 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的實現方式

<style> table th:nth-of-type(1){ width: 10%; } table th:nth-of-type(2){ width: 20% ; } </style>

架構 版本 定義連接 實現方式 思路解析
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 指向其所在進程組的領頭進程
相關文章
相關標籤/搜索