標籤(空格分隔): 20135328陳都node
陳都 原創做品轉載請註明出處 《Linux內核分析》MOOC課程 http://mooc.study.163.com/course/USTC-1000029000linux
最核心的是進程管理編程
將信號、進程間通訊、內存管理和文件系統聯繫起來數組
爲了管理進程,內核必須對每一個進程進行清晰的描述,進程描述符提供了內核所需瞭解的進程信息。
struct task_struct數據結構很龐大
Linux進程的狀態與操做系統原理中的描述的進程狀態彷佛有所不一樣,好比就緒狀態和運行狀態都是TASK_RUNNING,爲何呢?
進程的標示pid
全部進程鏈表struct list_head tasks;
內核的雙向循環鏈表的實現方法 - 一個更簡略的雙向循環鏈表
程序建立的進程具備父子關係,在編程時每每須要引用這樣的父子關係。進程描述符中有幾個域用來表示這樣的關係
Linux爲每一個進程分配一個8KB大小的內存區域,用於存放該進程兩個不一樣的數據結構:Thread_info和進程的內核堆棧
進程處於內核態時使用,不一樣於用戶態堆棧,即PCB中指定了內核棧,那爲何PCB中沒有用戶態堆棧?用戶態堆棧是怎麼設定的?
內核控制路徑所用的堆棧不多,所以對棧和Thread_info來講,8KB足夠了
struct thread_struct thread; //CPU-specific state of this task
文件系統和文件描述符
內存管理——進程的地址空間數據結構
分析:框架
pid_t pid又叫進程標識符,惟一地標識進程 list_head tasks即進程鏈表 ——雙向循環鏈表連接起了全部的進程,也表示了父子、兄弟等進程關係 struct mm_struct 指的是進程地址空間,涉及到內存管理(對於X86而言,一共有4G的地址空間) thread_struct thread 與CPU相關的狀態結構體 struct *file表示打開的文件鏈表 Linux爲每一個進程分配一個8KB大小的內存區域,用於存放該進程兩個不一樣的數據結構:Thread_info和進程的內核堆棧
Linux進程的狀態與操做系統原理中的描述的進程狀態有所不一樣,好比就緒狀態和運行狀態都是TASK_RUNNING
通常操做系統原理中描述的進程狀態有就緒態,運行態,阻塞態,可是在實際內核進程管理中是不同的。函數
struct task_struct數據結構很龐大學習
道生一(start_kernel....cpu_idle),一輩子二(kernel_init和kthreadd),二生三(即前面0、1和2三個進程),三生萬物(1號進程是全部用戶態進程的祖先,0號進程是全部內核線程的祖先),新內核的核心代碼已經優化的至關乾淨,都符合中國傳統文化精神了優化
0號進程,是代碼寫死的,1號進程複製0號進程PCB,再修改,再加載可執行程序。
this
系統調用進程建立過程:
iret與int 0x80指令對應,一個是彈出寄存器值,一個是壓入寄存器的值
若是將系統調用類比於fork();那麼就至關於系統調用建立了一個子進程,而後子進程返回以後將在內核態運行,而返回到父進程後仍然在用戶態運行。
進程的父子關係直觀圖:
do_fork
fork代碼:fork、vfork和clone這三個函數最終都是經過do_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) //pid == 0和下面的else都會被執行到(一個是在父進程中即pid ==0的狀況,一個是在子進程中,即pid不等於0) { /* child process */pid=0時 if和else都會執行 fork系統調用在父進程和子進程各返回一次 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"); } }
建立新進程的框架do_fork:dup_thread複製父進程的PCB
long do_fork(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) { struct task_struct *p; int trace = 0; long nr; p = copy_process(clone_flags, stack_start, stack_size, child_tidptr, NULL, trace); }
copy_process:進程建立的關鍵,修改複製的PCB以適應子進程的特色,也就是子進程的初始化
static struct task_struct *copy_process(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *child_tidptr, struct pid *pid, int trace) { int retval; struct task_struct *p; // 分配一個新的task_struct p = dup_task_struct(current); // 檢查該用戶的進程數是否超過限制 if (atomic_read(&p->real_cred->user->processes) >= task_rlimit(p, RLIMIT_NPROC)) { // 檢查該用戶是否具備相關權限 if (p->real_cred->user != INIT_USER && !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN)) goto bad_fork_free; } retval = -EAGAIN; // 檢查進程數量是否超過 max_threads if (nr_threads >= max_threads) goto bad_fork_cleanup_count; // 初始化自旋鎖,掛起信號,定時器 retval = sched_fork(clone_flags, p); // 初始化子進程的內核棧 retval = copy_thread(clone_flags, stack_start, stack_size, p); if (retval) goto bad_fork_cleanup_io; if (pid != &init_struct_pid) { retval = -ENOMEM; // 這裏爲子進程分配了新的pid號 pid = alloc_pid(p->nsproxy->pid_ns_for_children); if (!pid) goto bad_fork_cleanup_io; } /* ok, now we should be set up.. */ // 設置子進程的pid p->pid = pid_nr(pid); // 若是是建立線程 if (clone_flags & CLONE_THREAD) { p->exit_signal = -1; // 線程組的leader設置爲當前線程的leader p->group_leader = current->group_leader; // tgid是當前線程組的id,也就是main進程的pid p->tgid = current->tgid; } else { if (clone_flags & CLONE_PARENT) p->exit_signal = current->group_leader->exit_signal; else p->exit_signal = (clone_flags & CSIGNAL); // 建立的是進程,本身是一個單獨的線程組 p->group_leader = p; // tgid和pid相同 p->tgid = p->pid; } if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) { //同一線程組內的全部線程、進程共享父進程 p->real_parent = current->real_parent; p->parent_exec_id = current->parent_exec_id; } else { // 若是是建立進程,當前進程就是子進程的父進程 p->real_parent = current; p->parent_exec_id = current->self_exec_id; }
dup_ task_ struct
copy_thread:
int copy_thread(unsigned long clone_flags, unsigned long sp, unsigned long arg, struct task_struct *p) { struct pt_regs *childregs = task_pt_regs(p); struct task_struct *tsk; int err; // 若是是建立的內核線程 if (unlikely(p->flags & PF_KTHREAD)) { /* kernel thread */ memset(childregs, 0, sizeof(struct pt_regs)); // 內核線程開始執行的位置 p->thread.ip = (unsigned long) ret_from_kernel_thread; task_user_gs(p) = __KERNEL_STACK_CANARY; childregs->ds = __USER_DS; childregs->es = __USER_DS; childregs->fs = __KERNEL_PERCPU; childregs->bx = sp; /* function */ childregs->bp = arg; childregs->orig_ax = -1; childregs->cs = __KERNEL_CS | get_kernel_rpl(); childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED; p->thread.io_bitmap_ptr = NULL; return 0; } // 複製內核堆棧,並非所有,只是regs結構體(內核堆棧棧底的程序) *childregs = *current_pt_regs(); childregs->ax = 0; if (sp) childregs->sp = sp; // 子進程從ret_from_fork開始執行 p->thread.ip = (unsigned long) ret_from_fork;//調度到子進程時的第一條指令地址,也就是說返回的就是子進程的空間了 task_user_gs(p) = get_user_gs(current_pt_regs()); return err; }
#ifdef CONFIG_SMP //條件編譯,多處理器會用到 struct llist_node wake_entry; int on_cpu; struct task_struct *last_wakee; unsigned long wakee_flips; unsigned long wakee_flip_decay_ts; int wake_cpu; #endif 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 list_head tasks; //進程鏈表 #ifdef CONFIG_SMP struct plist_node pushable_tasks; struct rb_node pushable_dl_tasks; #endif
fork、vfork和clone三個系統調用均可以建立一個新進程,並且都是經過調用do_fork來實現進程的建立;
$ err = arch_dup_task_struct(tsk, orig); //在這個函數複製父進程的數據結構
$ ti = alloc_thread_info_node(tsk, node); $ tsk->stack = ti; //複製內核堆棧 $ setup_thread_stack(tsk, orig); //這裏只是複製thread_info,而非複製內核堆棧
$ *childregs = *current_pt_regs(); //複製內核堆棧 $ childregs->ax = 0; //爲何子進程的fork返回0,這裏就是緣由 $ p->thread.sp = (unsigned long) childregs; //調度到子進程時的內核棧頂 $ p->thread.ip = (unsigned long) ret_from_fork; //調度到子進程時的第一條指令地址
fork()函數被調用一次,但返回兩次
新進程如何開始的關鍵:
copy_thread()中:
p->thread.ip = (unsigned long) ret_from_fork; //調度到子進程時的第一條指令地址
將子進程的ip設置爲ret_ form _ fork的首地址,所以子進程是從ret_ from_ fork開始執行的。
在設置子進程的ip以前:
p->thread.sp = (unsigned long) childregs; //調度到子進程時的內核棧頂
*childregs = *current_ pt_ regs();
將父進程的regs參數賦值到子進程的內核堆棧,*childregs的類型爲pt_regs,其中存放了SAVE ALL中壓入棧的參數。