struct task_struct
就是PCB進程控制塊。struct task_struct
記錄了當前進程的父進程real_parent
、parent
。struct list_head children
記錄當前進程的子進程。struct list_head sibling
記錄當前進程的兄弟進程。fork
系統調用建立了一個子進程,子進程複製了父進程中全部的進程信息,包括內核堆棧、進程描述符等,子進程做爲一個獨立的進程也會被調度。fork
、vfork
、clone
系統調用和kernel_thread
內核函數均可以建立一個新進程,並且都是經過do_fork函數來建立進程的,只不過傳遞的參數不一樣。do_fork
主要完成了調用copy_process()
複製父進程信息、得到pid、調用wake_up_new_task將子進程加入調度器隊列等待得到分配CPU資源運行、經過clone_flags標誌作一些輔助工做。do_fork
代碼: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; // ... // 複製進程描述符,返回建立的task_struct的指針 p = copy_process(clone_flags, stack_start, stack_size, child_tidptr, NULL, trace); if (!IS_ERR(p)) { struct completion vfork; struct pid *pid; trace_sched_process_fork(current, p); // 取出task結構體內的pid pid = get_task_pid(p, PIDTYPE_PID); nr = pid_vnr(pid); if (clone_flags & CLONE_PARENT_SETTID) put_user(nr, parent_tidptr); // 若是使用的是vfork,那麼必須採用某種完成機制,確保父進程後運行 if (clone_flags & CLONE_VFORK) { p->vfork_done = &vfork; init_completion(&vfork); get_task_struct(p); } // 將子進程添加到調度器的隊列,使得子進程有機會得到CPU wake_up_new_task(p); // ... // 若是設置了 CLONE_VFORK 則將父進程插入等待隊列,並掛起父進程直到子進程釋放本身的內存空間 // 保證子進程優先於父進程運行 if (clone_flags & CLONE_VFORK) { if (!wait_for_vfork_done(p, &vfork)) ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid); } put_pid(pid); } else { nr = PTR_ERR(p); } return nr; }
copy_process
函數主要完成課調用dup_task_struct
複製當前進程(父進程)描述符task_struct、信息檢查、初始化、把進程狀態設置爲TASK_RUNNING(此時子進程置爲就緒態)、採用寫時複製技術逐一複製全部其餘進程資源、調用copy_thread
初始化子進程內核棧、設置子進程pid等。copy_process
代碼: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; ... retval = security_task_create(clone_flags);//安全性檢查 ... p = dup_task_struct(current); //複製PCB,爲子進程建立內核棧、進程描述符 ftrace_graph_init_task(p); ··· retval = -EAGAIN; // 檢查該用戶的進程數是否超過限制 if (atomic_read(&p->real_cred->user->processes) >= task_rlimit(p, RLIMIT_NPROC)) { // 檢查該用戶是否具備相關權限,不必定是root if (p->real_cred->user != INIT_USER && !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN)) goto bad_fork_free; } ... // 檢查進程數量是否超過 max_threads,後者取決於內存的大小 if (nr_threads >= max_threads) goto bad_fork_cleanup_count; if (!try_module_get(task_thread_info(p)->exec_domain->module)) goto bad_fork_cleanup_count; ... spin_lock_init(&p->alloc_lock); //初始化自旋鎖 init_sigpending(&p->pending); //初始化掛起信號 posix_cpu_timers_init(p); //初始化CPU定時器 ··· retval = sched_fork(clone_flags, p); //初始化新進程調度程序數據結構,把新進程的狀態設置爲TASK_RUNNING,並禁止內核搶佔 ... // 複製全部的進程信息 shm_init_task(p); retval = copy_semundo(clone_flags, p); ... retval = copy_files(clone_flags, p); ... retval = copy_fs(clone_flags, p); ... retval = copy_sighand(clone_flags, p); ... retval = copy_signal(clone_flags, p); ... retval = copy_mm(clone_flags, p); ... retval = copy_namespaces(clone_flags, p); ... retval = copy_io(clone_flags, p); ... retval = copy_thread(clone_flags, stack_start, stack_size, p);// 初始化子進程內核棧 ... //若傳進來的pid指針和全局結構體變量init_struct_pid的地址不相同,就要爲子進程分配新的pid if (pid != &init_struct_pid) { retval = -ENOMEM; pid = alloc_pid(p->nsproxy->pid_ns_for_children); if (!pid) goto bad_fork_cleanup_io; } ... p->pid = pid_nr(pid); //根據pid結構體中得到進程pid //若 clone_flags 包含 CLONE_THREAD標誌,說明子進程和父進程在同一個線程組 if (clone_flags & CLONE_THREAD) { p->exit_signal = -1; p->group_leader = current->group_leader; //線程組的leader設爲子進程的組leader p->tgid = current->tgid; //子進程繼承父進程的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; //子進程的組leader就是它本身 p->tgid = p->pid; //組號tgid是它本身的pid } ... if (likely(p->pid)) { ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace); init_task_pid(p, PIDTYPE_PID, pid); if (thread_group_leader(p)) { ... // 將子進程加入它所在組的哈希鏈表中 attach_pid(p, PIDTYPE_PGID); attach_pid(p, PIDTYPE_SID); __this_cpu_inc(process_counts); } else { ... } attach_pid(p, PIDTYPE_PID); nr_threads++; //增長系統中的進程數目 } ... return p; //返回被建立的子進程描述符指針P ... }
系統調用服務例程sys_clone, sys_fork, sys_vfork三者最終都是調用do_fork函數完成。
do_fork的參數與clone系統調用的參數相似,不過多了一個regs(內核棧保存的用戶模式寄存器).,實際上其餘的參數也都是用regs取的。html
- 具體實現的參數不一樣
- clone:
clone的API外衣, 把fn, arg壓入用戶棧中, 而後引起系統調用. 返回用戶模式後下一條指令就是fn.
sysclone: parent_tidptr, child_tidptr都傳到了 do_fork的參數中
sysclone: 檢查是否有新的棧, 若是沒有就用父進程的棧 (開始地址就是regs.esp)- fork, vfork:
服務例程就是直接調用do_fork, 不過參數稍加修改
clone_flags:
sys_fork: SIGCHLD, 0, 0, NULL, NULL, 0
sys_vfork: CLONE_VFORK | CLONE_VM | SIGCHLD, 0, 0, NULL, NULL, 0
用戶棧: 都是父進程的棧.
parent_tidptr, child_ctidptr都是NULL.
本次實驗中使用的
fork
命令是用sys_clone
系統調用實現的,所以斷點設置在sys_clone
。
本次實驗經過實踐,調試應按照如下順序進行。linux
fork
命令