在linux內核中進程以及線程(多線程也是通過一組輕量級進程實現的)都是通過task_struct結構體來描述的,我們稱它為進程描述符。而thread_info則是一個與進程描述符相關的小數據結構,它同進程的內核態棧stack存放在一個單獨為進程分配的內存區域。由於這個內存區域同時保存了thread_info和stack,所以使用了聯合體來定義,相關數據結構如下(基於4.4.87版本內核):
thread_union聯合體定義:
union thread_union { struct thread_info thread_info; unsigned long stack[THREAD_SIZE/sizeof(long)]; };
thread_info結構體定義:
struct thread_info { unsigned long flags; /* low level flags */ mm_segment_t addr_limit; /* address limit */ struct task_struct *task; /* main task structure */ int preempt_count; /* 0 => preemptable, <0 => bug */ int cpu; /* cpu */ };
task_struct的結構比較復雜,只列出部分成員變量,完整的可以在下面這個網站直接查看對應版本的內核代碼
struct task_struct { volatile long state; void *stack; //... #ifdef CONFIG_SMP int on_cpu; int wake_cpu; #endif int on_rq; //... #ifdef CONFIG_SCHED_INFO struct sched_info sched_info; #endif //... pid_t pid; pid_t tgid; //... };
用一副圖來表示:

這樣設計的好處就是,得到stack,thread_info或task_struct任意一個數據結構的地址,就可以很快得到另外兩個數據的地址。
我們可以通過crash工具在ubuntu系統上做個實驗,來窺視一下某個進程的進程描述符
如果通過crash分析內核數據結構,可參考:
這里以進程systemd進程為例,其pid=1
crash> task 1 PID: 1 TASK: ffff88007c898000 CPU: 1 COMMAND: "systemd" struct task_struct { state = 1, stack = 0xffff88007c894000, usage = { counter = 2 }, 。。。
可以看到systemd進程的task_struct結構體指針task=0xffff88007c898000
通過task->stack這個結構體成員即可定位到進程的內核棧地址 stack=0xffff88007c894000
另外從之前的圖可以看到,thread_info和stack處於同一地址空間,且thread_info在這段地址空間的最低地址處,而且這個地址空間是以THREAD_SIZE對齊的,所以只要將stack地址的最低N位變為0,即可得到thread_info的地址(2^N=THREAD_SIZE)
例如當THREAD_SZIE=8K時,systemd的thread_info地址就等於0xffff88007c894000&(~(0x1FFF)) = 0xffff88007c894000
crash> * thread_info 0xffff88007c894000 struct thread_info { task = 0xffff88007c898000, flags = 0, status = 0, cpu = 0, addr_limit = { seg = 140737488351232 }, sig_on_uaccess_error = 0, uaccess_err = 0 }
而通過thread_info->task這個成員變量,又能訪問到進程的task_struct結構體,這樣就形成了task_struct, thread_info,stack三者之間的關系網,知道其中任何一個,都可以快速的訪問到另外兩個,提高了數據存取的效率。