Linux 對進程的描述
進程是操作系統種調度的實體,對進程擁有資源的描述稱為進程控制塊(PCB, Process Contrl Block)。
通過 task_struct 描述進程
內核里,通過 task_struct 結構體來描述一個進程,稱為進程描述符 (process descriptor),它保存著支撐一個進程正常運行的所有信息。task_struct 結構體內容太多,這里只列出部分成員變量,感興趣的讀者可以去源碼 include/linux/sched.h頭文件查看。
- struct task_struct {
- #ifdef CONFIG_THREAD_INFO_IN_TASK
- /*
- * For reasons of header soup (see current_thread_info()), this
- * must be the first element of task_struct.
- */
- struct thread_info thread_info;
- #endif
- volatile long state;
- void *stack;
- ......
- struct mm_struct *mm;
- ......
- pid_t pid;
- ......
- struct task_struct *parent;
- ......
- char comm[TASK_COMM_LEN];
- ......
- struct files_struct *files;
- ......
- struct signal_struct *signal;
- }
task_struct 中的主要信息分類:
1.標示符:描述本進程的唯一標識符 pid,用來區別其他進程。
2.狀態:任務狀態,退出代碼,退出信號等
3.優先級:相對于其他進程的優先級
4.程序計數器:程序中即將被執行的下一條指令的地址
5.內存指針:包括程序代碼和進程相關數據的指針,還有和其他進程共享的內存塊的指針
6.上下文數據:進程執行時處理器的寄存器中的數據
7.I/O狀態信息:包括顯示的I/O請求,分配的進程I/O設備和進程使用的文件列表
8.記賬信息:可能包括處理器時間總和,使用的時鐘總和,時間限制,記帳號等
- struct thread_info thread_info: 進程被調度執行的信息
- volatile long state:-1是不運行的,=0是運行狀態,>0是停止狀態。下面是幾個比較重要的進程狀態以及它們之間的轉換流程。
- void *stack:指向內核棧的指針,內核通過 dup_task_struct 為每個進程都分配內核棧空間,并記錄在此。
- struct mm_struct *mm: 與進程地址空間相關的信息。
- pid_t pid: 進程標識符
- char comm[TASK_COMM_LEN]: 進程的名稱
- struct files_struct *files: 打開的文件表
- struct signal_struct *signal: 信號處理相關
task_struct, thread_info 和內核棧 sp 的關系
接著看下 thread_info 結構:
- struct thread_info {
- unsigned long flags; /* low level flags */
- mm_segment_t addr_limit; /* address limit */
- #ifdef CONFIG_ARM64_SW_TTBR0_PAN
- u64 ttbr0; /* saved TTBR0_EL1 */
- #endif
- union {
- u64 preempt_count; /* 0 => preemptible, <0 => bug */
- struct {
- #ifdef CONFIG_CPU_BIG_ENDIAN
- u32 need_resched;
- u32 count;
- #else
- u32 count;
- u32 need_resched;
- #endif
- } preempt;
- };
- #ifdef CONFIG_SHADOW_CALL_STACK
- void *scs_base;
- void *scs_sp;
- #endif
- };
接著再來看下內核棧的定義:
- 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)];
- };
當 CONFIG_THREAD_INFO_IN_TASK 這個配置打開的時候,則 thread_union 結構中只存在 stask 成員了。
內核在啟動的時候會在 head.S 里通過 __primary_switched 來做內核棧的初始化:
- SYM_FUNC_START_LOCAL(__primary_switched)
- adrp x4, init_thread_union
- add sp, x4, #THREAD_SIZE
- adr_l x5, init_task
- msr sp_el0, x5 // Save thread_info
將 init_thread_union 的地址保存到 x4,然后偏移 THREAD_SIZE 棧大小,用于初始化 sp。將 init_task 進程描述符地址賦值給 x5,并保存到 sp_el0。
下面再看下 init_thread_union 和 init_task 的定義:
- #include/linux/sched/task.h
- extern union thread_union init_thread_union;
- #init/init_task.c
- struct task_struct init_task
- __aligned(L1_CACHE_BYTES)
- = {
- #ifdef CONFIG_THREAD_INFO_IN_TASK
- .thread_info = INIT_THREAD_INFO(init_task),
- .stack_refcount = REFCOUNT_INIT(1),
- #endif
- .....
- };
故這三者的關系可以通過下圖描述:
如何獲取當前進程
內核中經常通過 current 宏來獲得當前進程對應的 struct task_sturct 結構,我們借助 current,結合上面介紹的內容,看下具體的實現。
- static __always_inline struct task_struct *get_current(void)
- {
- unsigned long sp_el0;
- asm ("mrs %0, sp_el0" : "=r" (sp_el0));
- return (struct task_struct *)sp_el0;
- }
- #define current get_current()
代碼比較簡單,可以看出通過讀取用戶空間棧指針寄存器 sp_el0 的值,然后將此值強轉成 task_struct 結構就可以獲得當前進程。(sp_el0 里存放的是 init_task,即 thread_info 地址,thread_info 又是在 task_sturct 的開始處,從而找到當前進程。)