當我重新閱讀《LDD3》時,我發現了一個曾經忽略的句子:「核心具有非常小的棧,它可能只有像一個4096位元組大小的頁那樣小。」針對這句話,我簡單地學習了一下進程的「核心棧」。
行程的「核心堆疊」是什麼? 在每個行程的生命週期中,必然會透過系統呼叫陷入核心。在執行系統呼叫陷入核心後,這些核心程式碼所使用的堆疊並不是原先用戶空間中的棧,而是一個核心空間的棧,也就是行程的「核心棧」。
例如,一個簡單的字元驅動程式實作了open()
方法。在這個驅動程式掛載後,應用程式透過glib函式庫呼叫Linux的open()
系統呼叫。當執行系統呼叫陷入核心後,處理器會轉換為特權模式(特定的轉換機制因處理器架構而異。例如,對於ARM,普通模式和使用者模式的堆疊指標(SP)是不同的暫存器)。此時,使用的堆疊指標就是核心棧指針,它指向核心為每個行程分配的核心棧空間。
核心堆疊的作用 個人理解是:在陷入核心後,系統呼叫中也存在函數呼叫和自動變量,這些都需要堆疊支援。使用者空間的堆疊顯然不安全,因此需要核心棧的支援。此外,核心堆疊也用於保存一些系統呼叫前的應用層資訊(例如,使用者空間堆疊指標和系統呼叫參數)。
核心堆疊與行程結構體的關聯 每個行程在建立時都會得到一個核心堆疊空間。核心堆疊和進程的對應關係是透過兩個結構體中的指標成員來完成的: (1)struct task_struct 在學習Linux進程管理時,必須學習的結構體。它在核心中代表了一個進程,並記錄了進程的所有狀態資訊。此結構體定義在Sched.h(include\linux)中。其中有一個成員void *stack
,它指向下面的核心堆疊結構體的「堆疊底部」。在系統運作時,巨集current
取得的是目前程序的struct task_struct
結構體。
(2)核心堆疊結構體union thread_union
1. union thread_union { 2. struct thread_info thread_info; 3. unsigned long stack[THREAD_SIZE/sizeof(long)]; 4. };
其中struct thread_info是記錄部分進程資訊的結構體,其中包括了進程上下文資訊:
1. /* 2. \* low level task data that entry.S needs immediate access to. 3. \* __switch_to() assumes cpu_context follows immediately after cpu_domain. 4. */ 5. struct thread_info { 6. unsigned long flags; /* low level flags */ 7. int preempt_count; /* 0 => preemptable, bug */ 8. mm_segment_t addr_limit; /* address limit */ 9. struct task_struct *task; /* main task structure */ 10. struct exec_domain *exec_domain; /* execution domain */ 11. __u32 cpu; /* cpu */ 12. __u32 cpu_domain; /* cpu domain */ 13. struct cpu_context_save cpu_context; /* cpu context */ 14. __u32 syscall; /* syscall number */ 15. __u8 used_cp[16]; /* thread used copro */ 16. unsigned long tp_value; 17. struct crunch_state crunchstate; 18. union fp_state fpstate __attribute__((aligned(8))); 19. union vfp_state vfpstate; 20. \#ifdef CONFIG_ARM_THUMBEE 21. unsigned long thumbee_state; /* ThumbEE Handler Base register */ 22. \#endif 23. struct restart_block restart_block; 24. };
關鍵是其中的task成員,指向的是所建立的進程的struct task_struct結構體
而其中的stack成員就是核心堆疊。從這裡可以看出核心堆疊空間和 thread_info是共用一塊空間的。如果核心堆疊溢出, thread_info就會被摧毀,系統崩潰了~~
#核心堆疊—struct thread_info—-struct task_struct三者的關係入下圖:
核心堆疊的產生
在行程被建立的時候,fork族的系統呼叫中會分別為核心堆疊和struct task_struct分配空間,呼叫過程是:
fork族的系統呼叫—>do_fork—>copy_process—>dup_task_struct
#在dup_task_struct函式中:
1. static struct task_struct *dup_task_struct(struct task_struct *orig) 2. { 3. struct task_struct *tsk; 4. struct thread_info *ti; 5. unsigned long *stackend; 6. 7. int err; 8. 9. prepare_to_copy(orig); 10. 11. **tsk = alloc_task_struct();** 12. if (!tsk) 13. return NULL; 14. 15. **ti = alloc_thread_info(tsk);** 16. if (!ti) { 17. free_task_struct(tsk); 18. return NULL; 19. } 20. 21. err = arch_dup_task_struct(tsk, orig); 22. if (err) 23. goto out; 24. 25. **tsk->stack = ti;** 26. 27. err = prop_local_init_single(&tsk->dirties); 28. if (err) 29. goto out; 30. 31. **setup_thread_stack(tsk, orig);** 32. ......
其中alloc_task_struct使用核心的slab分配器去為所要建立的程序分配struct task_struct的空間
而alloc_thread_info使用核心的夥伴系統去為要建立的程序分配核心堆疊(union thread_union )空間
#注意:
後面的tsk->stack = ti;語句,這就是關聯了struct task_struct和核心堆疊
而在setup_thread_stack(tsk, orig);中,關聯了核心堆疊與struct task_struct:
1. static inline void setup_thread_stack(struct task_struct *p, struct task_struct *org) 2. { 3. *task_thread_info(p) = *task_thread_info(org); 4. task_thread_info(p)->task = p; 5. }
核心堆疊的大小
#
由於是每一個行程都分配一個核心棧空間,所以不可能分配很大。這個大小是架構相關的,一般以頁為單位。其實也就是上面我們看到的THREAD_SIZE,這個數值一般是4K或8K。對於ARM構架,這個定義在Thread_info.h (arch\arm\include\asm),
1. \#define THREAD_SIZE_ORDER 1 2. \#define THREAD_SIZE 8192 3. \#define THREAD_START_SP (THREAD_SIZE - 8)
所以ARM的核心堆疊是8KB
在(核心)驅動程式設計時需要注意的問題:
由於堆疊空間的限制,在編寫的驅動(特別是被系統呼叫使用的底層函數)中要注意避免堆疊空間消耗較大的程式碼,例如遞歸演算法、局部自動變數定義的大小等等
以上是對Linux的進程內核堆疊的認識的詳細內容。更多資訊請關注PHP中文網其他相關文章!