Linux內核學習筆記二——進程
一 進程與線程
進程就是處於執行期的程序,包含了獨立地址空間,多個執行線程等資源。
線程是進程中活動的對象,每個線程都擁有獨立的程序計數器、進程棧和一組進程寄存器。
內核調度的對象是線程而不是進程。對Linux而言,線程是特殊的進程。
二 進程描述符及任務結構
內核使用雙向循環鏈表的任務隊列來存放進程,使用結構體task_struct來描述進程所有信息。
1 進程描述符task_struct
struct task_struct {}結構體相當大,大約1.7K字節。大概列出一些看看:
2 分配進程描述符
當進程由於中斷或系統調用從用戶態轉換到內核態時,進程所使用的棧也要從用戶棧切換到內核棧。
通過內核棧獲取棧尾thread_info,就可以獲取當前進程描述符task_struct。
每個進程的thread_info結構在他的內核棧的尾端分配。結構中task域中存放是指向該任務實際的task_struct。
內核處理進程就是通過進程描述符task_struct結構體對象來操作。所以操作進程要獲取當前正在運行的進程描述符。
通過thread_info的地址就可以找到task_struct地址;在不同的體系結構上計算thread_info的偏移地址不同。
/* linux-2.6.38.8/arch/arm/include/asm/current.h */ static inline struct task_struct *get_current(void) { return current_thread_info()->task; } #define current (get_current()) /* linux-2.6.38.8/arch/arm/include/asm/thread_info.h */ static inline struct thread_info *current_thread_info(void) { //棧指針 register unsigned long sp asm ("sp"); return (struct thread_info *)(sp & ~(THREAD_SIZE - 1)); }
3 進程的狀態
系統中的每個進程都必然處於五種進程狀態中的一種或進行切換。該域的值也必為下列五種狀態標誌之一:
TASK_RUNNING(運行)—進程是可執行的;它或者正在執行,或者在運行隊列中等待執行(運行隊列將會在第4章中討論)。
這是進程在用戶空間中執行的唯一可能的狀態;這種狀態也可以應用到內核空間中正在執行的進程。
TASK_INTERRUPTIBLE(可中斷)—進程正在睡眠(也就是說它被阻塞),等待某些條件的達成。一旦這些條件達成,
內核就會把進程狀態設置為運行。處於此狀態的進程也會因為接收到信號而提前被喚醒並隨時準備投入運行。
TASK_UNINTERRUPTIBLE(不可中斷)—除了就算是接收到信號也不會被喚醒或準備投入運行外,這個狀態與可打斷狀態相同。
這個狀態通常在進程必須在等待時不受幹擾或等待事件很快就會發生時出現。由於處於此狀態的任務對信號不做響應,
所以較之可中斷狀態,使用得較少。
__TASK_TRACED—被其他進程跟蹤的進程,例如通過ptrace對調試程序進行跟蹤。
__TASK_STOPPED(停止)—進程停止執行;進程沒有投入運行也不能投入運行。通常這種狀態發生在接收到SIGSTOP、
SIGTSTP、SIGTTIN、SIGTTOU等信號的時候。此外,在調試期間接收到任何信號,都會使進程進入這種狀態。
三 進程創建
fork:copy當前進程創建一個新的進程;
exec:讀取可執行文件並將其載入地址空間開始運行。
1 fork過程
創建進程都是通過調用do_fork函數完成,其中提供了很多參數標誌來表明進程創建的方式。
long do_fork(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) { struct task_struct *p; …… //創建進程 p = copy_process(clone_flags, stack_start, regs, stack_size, child_tidptr, NULL, trace); …… //將進程加入到運行隊列中 wake_up_new_task(p); }
copy_process裏面通過父進程創建子進程,並未執行:
task_struct *copy_process(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int __user *child_tidptr, struct pid *pid, int trace) { struct task_struct *p; //創建進程內核棧和進程描述符 p = dup_task_struct(current); //得到的進程與父進程內容完全一致,初始化新創建進程 …… return p; }
dup_task_struct根據父進程創建子進程內核棧和進程描述符:
static struct task_struct *dup_task_struct(struct task_struct *orig) { struct task_struct *tsk; struct thread_info *ti; int node = tsk_fork_get_node(orig); //創建進程描述符對象 tsk = alloc_task_struct_node(node);
//創建進程內核棧 thread_info ti = alloc_thread_info_node(tsk, node); //使子進程描述符和父進程一致 err = arch_dup_task_struct(tsk, orig); //進程描述符stack指向thread_info tsk->stack = ti; //使子進程thread_info內容與父進程一致但task指向子進程task_struct setup_thread_stack(tsk, orig); return tsk; }
創建進程copy_process之後並未執行,返回到do_fork中,將新創建進程加入到運行隊列中等待被執行。
四 線程在Linux中的實現與內核線程
線程機制提供了在同一個程序共享內存地址空間,文件等資源的一組線程。在Linux內核中把所有線程都當做進程來實現。
內核並沒有提供調度算法,或者數據結構來表征線程,而是作為與其他進程共享資源的進程;與其他系統不同。
內核線程與普通進程間的區別是:
內核線程沒有獨立的地址空間
只在內核空間運行,不會切換到用戶空間
五 進程終結
進程終結時內核釋放其所占有的資源,並告訴父進程,更新父子關系。調用exit終結進程,進程被終結時通常最後都要調用do_exit來處理。
void do_exit(long code) { //獲取當前運行進程 struct task_struct *tsk = current; …… //sets PF_EXITING exit_signals(tsk); //釋放task_struct的mm_struct內存 exit_mm(tsk); //退出接收IPC信號隊列 exit_sem(tsk); //進程名字空間 exit_shm(tsk); //文件描述符 exit_files(tsk); //文件系統 exit_fs(tsk); //資源釋放 exit_thread(); //向父進程發送信號 exit_notify(tsk, group_dead); …… //切換到其他進程 tsk->state = TASK_DEAD; tsk->flags |= PF_NOFREEZE; schedule(); …… }
Linux內核學習筆記二——進程