標籤:
劉柳 + 原創作品轉載請註明出處 + 《Linux核心分析》MOOC課程+http://mooc.study.163.com/course/[email protected]
進程的描述ulk那個大圖 for task_struct
提綱挈領,看代碼前總覽
序言 進程式控制制塊PCB——task_struct
為了管理進程,核心必須對每個進程進行清晰的描述,進程描述符提供了核心所需瞭解的進程資訊。
struct task_struct資料結構很龐大
Linux進程的狀態與作業系統原理中的描述的進程狀態似乎有所不同,比如就緒狀態和運行狀態都是TASK_RUNNING,為什麼呢?
A66:取決於是否獲得cpu的控制權
進程的標示pid
每個進程唯一的標示
調度相關的
核心關鍵詞有:
runqueue
優先順序
搶佔
調度資訊
struct task_struct {... int on_rq; int prio, static_prio, normal_prio; unsigned int rt_priority; const struct sched_class *sched_class; struct sched_entity se; struct sched_rt_entity rt;... struct sched_dl_entity dl;... unsigned int policy; int nr_cpus_allowed; cpumask_t cpus_allowed;#ifdef CONFIG_PREEMPT_RCU int rcu_read_lock_nesting; union rcu_special rcu_read_unlock_special; struct list_head rcu_node_entry;#endif /* #ifdef CONFIG_PREEMPT_RCU */#ifdef CONFIG_TREE_PREEMPT_RCU struct rcu_node *rcu_blocked_node;#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */#ifdef CONFIG_TASKS_RCU unsigned long rcu_tasks_nvcsw; bool rcu_tasks_holdout; struct list_head rcu_tasks_holdout_list; int rcu_tasks_idle_cpu;#endif /* #ifdef CONFIG_TASKS_RCU */#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT) struct sched_info sched_info;#endif...}
所有進程鏈表struct list_head tasks;
核心的雙向迴圈鏈表的實現方法 - 一個更簡略的雙向迴圈鏈表
獨立操作
父子關係
程式建立的進程具有父子關係,在編程時往往需要引用這樣的父子關係。進程描述符中有幾個域用來表示這樣的關係
/* * 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 */
mm_struct
非本章重點,待後面章節展開,
linux進程的地址空間,是個有趣的話題
cpu相關
與我們menuos相關的,注意其中sp/ip指標。
struct thread_struct { /* Cached TLS descriptors: */ struct desc_struct tls_array[GDT_ENTRY_TLS_ENTRIES]; unsigned long sp0; unsigned long sp;#ifdef CONFIG_X86_32 unsigned long sysenter_cs;#else unsigned long usersp; /* Copy from PDA */ unsigned short es; unsigned short ds; unsigned short fsindex; unsigned short gsindex;#endif#ifdef CONFIG_X86_32 unsigned long ip;#endif...}
至此,400行左右的task_struct大致介紹到此
記憶體地區
? Linux為每個進程分配一個8KB大小的記憶體地區,用於存放該進程兩個不同的資料結構:Thread_info和進程的核心堆棧
? 進程處於核心態時使用,不同於使用者態堆棧,即PCB中指定了核心棧,那為什麼PCB中沒有使用者態堆棧?使用者態堆棧是怎麼設定的?
? 核心控制路徑所用的堆棧很少,因此對棧和Thread_info來說,8KB足夠了
? struct thread_struct thread;?//CPU-specific state of this task
? 檔案系統和檔案描述符
記憶體管理——進程的地址空間
不深入mmu
二. 進程的建立回顧
start_kernel —>
kernel_init —>
kthreadd
fork一個子進程的代碼
#include <stdio.h>#include <stdlib.h>#include <unistd.h>int main(int argc, char * argv[]){ int pid; /* fork another process */ pid = fork();//核心調用 if (pid < 0) { /* error occurred */ fprintf(stderr,"Fork Failed!"); exit(-1); } else if (pid == 0) { /* child process */ printf("This is Child Process!\n"); } else { /* parent process */ printf("This is Parent Process!\n"); /* parent will wait for the child to complete*/ wait(NULL); printf("Child Complete!\n"); }}
系統調用回顧
不僅僅是調用一個fork
int 0x80和iret的一個配合
試問fork進程的來源?我們來看下文
建立一個新進程在核心中的執行過程(sys_clone–>do_fork)
fork、vfork和clone三個系統調用都可以建立一個新進程,而且都是通過調用do_fork來實現進程的建立;
Linux通過複製父進程來建立一個新進程,那麼這就給我們理解這一個過程提供一個想象的架構:
複製一個PCB——task_struct
err = arch_dup_task_struct(tsk, orig);
要給新進程分配一個新的核心堆棧
ti = alloc_thread_info_node(tsk, node);tsk->stack = ti;setup_thread_stack(tsk, orig); //這裡只是複製thread_info,而非複製核心堆棧
要修改複製過來的進程資料,比如pid、進程鏈表等等都要改改吧,見copy_process內部。
從使用者態的代碼看fork();函數返回了兩次,即在父子進程中各返回一次,父進程從系統調用中返回比較容易理解,子進程從系統調用中返回,那它在系統調用處理過程中的哪裡開始執行的呢?這就涉及子進程的核心堆棧資料狀態和task_struct中thread記錄的sp和ip的一致性問題,這是在哪裡設定的?copy_thread in copy_process
進程資料的修改
*childregs = *current_pt_regs(); //複製核心堆棧childregs->ax = 0; //為什麼子進程的fork返回0,這裡就是原因!p->thread.sp = (unsigned long) childregs; //調度到子進程時的核心棧頂p->thread.ip = (unsigned long) ret_from_fork; //調度到子進程時的第一條指令地址
代碼情景分析
fork.c
do_fork –>copy_process–>dup_task_struct
dup_task_struct
{ alloc_task_sturct //slab管理 task_stuct... alloc_thread_info //keme_pages stack }
copy_thread
do_fork –>copy_process–>dup_task_struct
—>子進程初始化(很多)–>copy_thread
copy_thread{sp指標核心堆棧資料拷貝(重點展開)}
附圖
childregs->ax=0;
這裡看到傳回值(pid)賦值的位置
子進程啟動位置
本質原因是ret_from_fork
先展開pt_regs內容,這也是int指令和save_all壓到堆棧的內容
struct pt_regs { long ebx; long ecx; long edx; long esi; long edi; long ebp; long eax; int xds; int xes; int xfs; int xgs; long orig_eax; long eip; int xcs; long eflags; long esp; int xss;};
以系統調用位列,ax為調用號。
展開ret_from_fork
ENTRY(ret_from_fork) movi a4, schedule_tail callx4 a4 movi a4, do_syscall_trace_leave mov a6, a1 callx4 a4 j common_exception_returnENDPROC(ret_from_fork)...
展開syscall_exit
syscall_exit: LOCKDEP_SYS_EXIT DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don‘t miss an interrupt # setting need_resched or sigpending # between sampling and the iret TRACE_IRQS_OFF movl TI_flags(%ebp), %ecx testl $_TIF_ALLWORK_MASK, %ecx # current->work jne syscall_exit_workrestore_all: TRACE_IRQS_IRET
以後的流程其實就是上節課的內容啦
子進程返回使用者台之前會發生系統調用嗎?
個人認為,會。
原因從下面這個圖(ULK)就可以說明。
三.動手實驗
- rm menu
- git clone
- mv test_fork.c test.c
- make rootfs
now, enjoy the work see the “fork”
the information to debug
- sys_clone
- do_fork
- dup_task_struct
- copy_process
- copy_thread
- ret_from_fork (*)
the result of debugging
- copy_process alloc_thread_info arc_dup_task_struct setup_thread_stack- copy_thread childregs //ptype current_pt_regs()//前後堆棧變化 ip- ret_from_fork //step by step 不跟蹤schedule,continue- syscall_exit (直到這個位置)
todo
添加動手
其他
gdb調試器,堆棧可以即時修改嗎?
感謝:
圖片路徑問題
http://www.v2ex.com/t/57063
圖片路徑:C:/Users/Vanny/Downloads/MarkdownEditor/ 本地hard
注釋中的 條件編譯 被 錯誤解釋了
注釋的三個引號如何輸入啦
用戶端不支援 文法嵌套?比如注釋裡面加粗
還有回退功能有限,僅一步
還有左右的呼喚,編輯器的find..
如何讓#不顯示轉義
二級目錄
綠色版等等需要改進
Linux 進程管理淺析