1. 程式人生 > >關於linux系統如何實現fork的研究(二)【轉】

關於linux系統如何實現fork的研究(二)【轉】

默認 flag atomic val lan sin 用戶權限 內存地址 判斷

轉自:http://www.aichengxu.com/linux/7166015.htm

本文為原創,轉載請註明:http://www.cnblogs.com/tolimit/


引言

  前一篇關於linux系統如何實現fork的研究(一)通過代碼已經說明了從用戶態怎麽通過軟中斷實現調用系統調用clone函數,而clone函數的精華copy_process函數就在此篇文章中進行分析。我們知道,在linux系統中,應用層可以創建子進程和子線程(輕量級進程)兩種程序分支結構。而對於linux內核而且,並不詳細區分子進程和子線程(輕量級進程)的區別,他們都使用的是task_struct結構(此結構極其復雜,包含非常多的數據結構),而不同的是子進程和子線程的task_struct初始化結果不同。task_struct結構是一個進程或線程的標識和存在的憑證,調度程序就是通過task_struct結構來區分不同的進程(線程)。裏面包含了進程(線程)所有需要用到的結構(內存描述符,文件描述符,信號描述符,信號處理函數,調度優先級等)。而我們知道,一個進程(線程)不止有自己的task_struck結構,還必須有一個自己的內核棧,當執行進程切換時,部分進程上下文會保存於其進程的內核棧中,而中斷發生時的中斷上下文也會保存於正在持續的進程內核棧中。在copy_process函數中內核棧的初始化導致了fork()的兩次返回值不同(之後會說明)。當然,copy_process還涉及到許多操作,比如新進程(線程)的安全檢測,pid的分配,關系調整(父子進程、進程組關系,命名空間關系等),內存結構的初始化等等,這些我們在之後的代碼中慢慢道來。


copy_process

技術分享圖片

1 /* 代碼目錄:linux源碼/kernel/Fork.c */
  2 
  3 static struct task_struct *copy_process(unsigned long clone_flags,
  4                     unsigned long stack_start,
  5                     unsigned long stack_size,
  6                     int __user *child_tidptr,
  7                     struct pid *pid,
  8                     int trace)
  9 {
 10     int retval;
 11     struct task_struct *p;
 12 
 13     /* CLONE_FS 不能與 CLONE_NEWNS 或 CLONE_NEWUSER 同時設置 */
 14     if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
 15         return ERR_PTR(-EINVAL);
 16 
 17     if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
 18         return ERR_PTR(-EINVAL);
 19 
 20     /* 創建線程時線程之間要共享信號處理函數 */
 21     if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
 22         return ERR_PTR(-EINVAL);
 23 
 24     /* 
 25      * 父子進程共享信號處理函數時必須共享內存地址空間
 26      * 這就是為什麽書上寫的fork出來的父子進程有其獨立的信號處理函數,因為他們的內存地址空間不同
 27      */
 28     if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
 29         return ERR_PTR(-EINVAL);
 30 
 31     /*
 32      * 防止參數init進程的兄弟進程
 33      * 只有init進程的 signal->flags & SIGNAL_UNKILLABLE 為真
 34      * 因為當進程退出時實際上是成為了僵屍進程(zombie),而要通過init進程將它回收,而如果此進程為init的兄弟進程,則沒辦法將其回收
 35      */
 36     if ((clone_flags & CLONE_PARENT) &&
 37                 current->signal->flags & SIGNAL_UNKILLABLE)
 38         return ERR_PTR(-EINVAL);
 39 
 40     /* 如果新的進程將會有新的用戶空間或者pid,則不能讓它共享父進程的線程組或者信號處理或者父進程 */
 41     if (clone_flags & CLONE_SIGHAND) {
 42         if ((clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) ||
 43             (task_active_pid_ns(current) !=
 44                 current->nsproxy->pid_ns_for_children))
 45             return ERR_PTR(-EINVAL);
 46     }
 47 
 48     /* 附加安全檢查 */
 49     retval = security_task_create(clone_flags);
 50     if (retval)
 51         goto fork_out;
 52 
 53     retval = -ENOMEM;
 54     /* 為新進程分配struct task_struct內存和內核棧內存 */
 55     p = dup_task_struct(current);
 56     if (!p)
 57         goto fork_out;
 58 
 59     /* ftrace是用於內核性能分析和跟蹤的 */
 60     ftrace_graph_init_task(p);
 61 
 62     /* futex初始化,其用於SYSTEM V IPC,具體可見 http://blog.chinaunix.net/uid-7295895-id-3011238.html
*/ 63 rt_mutex_init_task(p); 64 65 #ifdef CONFIG_PROVE_LOCKING 66 DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled); 67 DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled); 68 #endif 69 retval = -EAGAIN; 70 /* 檢查 tsk->signal->rlim[RLIMIT_NPROC].rlim_cur是否小於等於用戶所擁有的進程數,rlim結構體表示相關資源的最大值 */ 71 if (atomic_read(&p->real_cred->user->processes) >= task_rlimit(p, RLIMIT_NPROC)) { 72 /* INIT_USER是root權限。檢查父進程是否有root權限 */ 73 if (p->real_cred->user != INIT_USER && !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN)) 74 goto bad_fork_free; 75 } 76 current->flags &= ~PF_NPROC_EXCEEDED; 77 78 /* 將父進程的cred復制到子進程的real_cred和cred。struct cred用於安全操作的結構 */ 79 retval = copy_creds(p, clone_flags); 80 if (retval < 0) 81 goto bad_fork_free; 82 83 retval = -EAGAIN; 84 /* 進程數量是否超出系統允許最大進程數量,最大進程數量跟內存有關,一般原則是所有的進程內核棧(默認8K)加起來不超過總內存的1/8,可通過/proc/sys/kernel/threads-max改寫此值 */ 85 if (nr_threads >= max_threads) 86 goto bad_fork_cleanup_count; 87 88 /* 如果實現新進程的執行域和可執行格式的內核函數都包含在內核模塊中,則遞增其使用計數 */ 89 if (!try_module_get(task_thread_info(p)->exec_domain->module)) 90 goto bad_fork_cleanup_count; 91 92 delayacct_tsk_init(p); /* Must remain after dup_task_struct() */ 93 94 /* 清除 PF_SUPERPRIV(表示進程使用了超級用戶權限) 和 PF_WQ_WORKER(使用了工作隊列) */ 95 p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER); 96 /* 設置 PF_FORKNOEXEC 表明此子進程還沒有進行 execve() 系統調用 */ 97 p->flags |= PF_FORKNOEXEC; 98 99 /* 初始化子進程的子進程鏈表和兄弟進程鏈表為空 */ 100 INIT_LIST_HEAD(&p->children); 101 INIT_LIST_HEAD(&p->sibling); 102 /* 見 http://www.ibm.com/developerworks/cn/linux/l-rcu/
*/ 103 rcu_copy_process(p); 104 p->vfork_done = NULL; 105 /* 初始化分配鎖,此鎖用於保護分配內存,文件,文件系統等操作 */ 106 spin_lock_init(&p->alloc_lock); 107 108 /* 信號列表初始化,此列表保存被掛起的信號 */ 109 init_sigpending(&p->pending); 110 111 /* 代碼執行時間變量都置為0 */ 112 p->utime = p->stime = p->gtime = 0; 113 p->utimescaled = p->stimescaled = 0; 114 #ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE 115 p->prev_cputime.utime = p->prev_cputime.stime = 0; 116 #endif 117 #ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN 118 seqlock_init(&p->vtime_seqlock); 119 p->vtime_snap = 0; 120 p->vtime_snap_whence = VTIME_SLEEPING; 121 #endif 122 123 #if defined(SPLIT_RSS_COUNTING) 124 memset(&p->rss_stat, 0, sizeof(p->rss_stat)); 125 #endif 126 /* 此變量一般用於epoll和select,從父進程復制過來 */ 127 p->default_timer_slack_ns = current->timer_slack_ns; 128 129 /* 初始化進程IO計數結構 */ 130 task_io_accounting_init(&p->ioac); 131 acct_clear_integrals(p); 132 133 /* 初始化cputime_expires結構 */ 134 posix_cpu_timers_init(p); 135 136 /* 設置進程創建時間 */ 137 p->start_time = ktime_get_ns(); 138 p->real_start_time = ktime_get_boot_ns(); 139 140 /* io_context 和 audit_context 置空 */ 141 p->io_context = NULL; 142 p->audit_context = NULL; 143 /* 如果創建的是線程,因為需要修改到當前進程的描述符,會先上鎖 */ 144 if (clone_flags & CLONE_THREAD) 145 threadgroup_change_begin(current); 146 cgroup_fork(p); 147 #ifdef CONFIG_NUMA 148 p->mempolicy = mpol_dup(p->mempolicy); 149 if (IS_ERR(p->mempolicy)) { 150 retval = PTR_ERR(p->mempolicy); 151 p->mempolicy = NULL; 152 goto bad_fork_cleanup_threadgroup_lock; 153 } 154 #endif 155 #ifdef CONFIG_CPUSETS 156 p->cpuset_mem_spread_rotor = NUMA_NO_NODE; 157 p->cpuset_slab_spread_rotor = NUMA_NO_NODE; 158 seqcount_init(&p->mems_allowed_seq); 159 #endif 160 #ifdef CONFIG_TRACE_IRQFLAGS 161 p->irq_events = 0; 162 p->hardirqs_enabled = 0; 163 p->hardirq_enable_ip = 0; 164 p->hardirq_enable_event = 0; 165 p->hardirq_disable_ip = _THIS_IP_; 166 p->hardirq_disable_event = 0; 167 p->softirqs_enabled = 1; 168 p->softirq_enable_ip = _THIS_IP_; 169 p->softirq_enable_event = 0; 170 p->softirq_disable_ip = 0; 171 p->softirq_disable_event = 0; 172 p->hardirq_context = 0; 173 p->softirq_context = 0; 174 #endif 175 #ifdef CONFIG_LOCKDEP 176 p->lockdep_depth = 0; /* no locks held yet */ 177 p->curr_chain_key = 0; 178 p->lockdep_recursion = 0; 179 #endif 180 181 #ifdef CONFIG_DEBUG_MUTEXES 182 p->blocked_on = NULL; /* not blocked yet */ 183 #endif 184 #ifdef CONFIG_BCACHE 185 p->sequential_io = 0; 186 p->sequential_io_avg = 0; 187 #endif 188 189 190 /* 初始化子進程的調度優先級和策略,在此並沒有將此進程加入到運行隊列,在copy_process返回之後加入 */ 191 retval = sched_fork(clone_flags, p); 192 if (retval) 193 goto bad_fork_cleanup_policy; 194 195 /* perf event是一個性能調優工具,具體見 http://blog.sina.com.cn/s/blog_98822316010122ex.html */ 196 retval = perf_event_init_task(p); 197 if (retval) 198 goto bad_fork_cleanup_policy; 199 retval = audit_alloc(p); 200 if (retval) 201 goto bad_fork_cleanup_perf; 202 /* 初始化 p->sysvshm.shm_clist 鏈表頭 */ 203 shm_init_task(p); 204 205 /* copy_semundo, copy_files, copy_fs, copy_sighand, copy_signal, copy_mm, copy_namespaces, copy_io都是根據clone_flags從父進程做相應的復制 */ 206 retval = copy_semundo(clone_flags, p); 207 if (retval) 208 goto bad_fork_cleanup_audit; 209 retval = copy_files(clone_flags, p); 210 if (retval) 211 goto bad_fork_cleanup_semundo; 212 retval = copy_fs(clone_flags, p); 213 if (retval) 214 goto bad_fork_cleanup_files; 215 /* 判斷是否設置 CLONE_SIGHAND ,如果是(線程必須為是),增加父進行的sighand引用計數,如果否(創建的必定是子進程),將父線程的sighand_struct復制到子進程中 */ 216 retval = copy_sighand(clone_flags, p); 217 if (retval) 218 goto bad_fork_cleanup_fs; 219 /* 如果創建的是線程,直接返回0,如果創建的是進程,則會將父進程的信號屏蔽和安排復制到子進程中 */ 220 retval = copy_signal(clone_flags, p); 221 if (retval) 222 goto bad_fork_cleanup_sighand; 223 /* 224 * 如果是進程,則將父進程的mm_struct結構復制到子進程中,然後修改當中屬於子進程有別於父進程的信息(如頁目錄) 225 * 如果是線程,則將子線程的mm指針和active_mm指針都指向父進程的mm指針所指結構。 226 */ 227 retval = copy_mm(clone_flags, p); 228 if (retval) 229 goto bad_fork_cleanup_signal; 230 retval = copy_namespaces(clone_flags, p); 231 if (retval) 232 goto bad_fork_cleanup_mm; 233 retval = copy_io(clone_flags, p); 234 if (retval) 235 goto bad_fork_cleanup_namespaces; 236 237 /* 238 * 初始化子進程內核棧和thread_struct結構體 239 * 當進程切換時,進程的硬件上下文一般保存於三個地方: tss_struct(保存進程內核棧地址,I/O許可權限位),thread_struct(大部分非通用寄存器),進程內核棧(通用寄存器) 240 * copy_thread函數會將父進程的thread_struct和內核棧數據復制到子進程中,並將子進程的返回值置為0(x86返回值保存在eax中,arm保存在r0中,即把eax或者r0所在的內核棧數據置為0) 241 * copy_thread函數還會將子進程的eip寄存器值設置為ret_from_fork()的地址,即當子進程首次被調用就立即執行系統調用clone返回。 242 * 所以應用層調用fork()函數後,子進程返回0,父進程返回子進程ID(返回子進程ID在之後代碼中會實現) 243 */ 244 retval = copy_thread(clone_flags, stack_start, stack_size, p); 245 if (retval) 246 goto bad_fork_cleanup_io; 247 248 /* 判斷是不是init進程 */ 249 if (pid != &init_struct_pid) { 250 retval = -ENOMEM; 251 /* 分配pid */ 252 pid = alloc_pid(p->nsproxy->pid_ns_for_children); 253 if (!pid) 254 goto bad_fork_cleanup_io; 255 } 256 257 /* 如果設置了CLONE_CHILD_SETTID則將task_struct中的set_child_tid指向用戶空間的child_tidptr,否則置空 */ 258 p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL; 259 /* 如果設置了CLONE_CHILD_CLEARTID則將task_struct中的clear_child_tid指向用戶空間的child_tidptr,否則置空 */ 260 p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr : NULL; 261 262 #ifdef CONFIG_BLOCK 263 p->plug = NULL; 264 #endif 265 #ifdef CONFIG_FUTEX 266 p->robust_list = NULL; 267 #ifdef CONFIG_COMPAT 268 p->compat_robust_list = NULL; 269 #endif 270 INIT_LIST_HEAD(&p->pi_state_list); 271 p->pi_state_cache = NULL; 272 #endif 273 /* 274 * 如果共享VM或者vfork創建,信號棧清空 275 */ 276 if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM) 277 p->sas_ss_sp = p->sas_ss_size = 0; 278 279 /* 280 * 系統調用跟蹤時應該禁止單步執行 281 */ 282 user_disable_single_step(p); 283 clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE); 284 #ifdef TIF_SYSCALL_EMU 285 clear_tsk_thread_flag(p, TIF_SYSCALL_EMU); 286 #endif 287 clear_all_latency_tracing(p); 288 289 290 /* 將子進程的PID設置為分配的PID在全局namespace中分配的值,在不同namespace中進程的PID不同,而p->pid保存的是全局的namespace中所分配的PID */ 291 p->pid = pid_nr(pid); 292 if (clone_flags & CLONE_THREAD) { 293 /* 創建的是線程 */ 294 p->exit_signal = -1; 295 /* 線程組的所有線程的group_leader都一致 */ 296 p->group_leader = current->group_leader; 297 /* 線程組的所有線程的tgid都一致,使用getpid返回的就是tgid */ 298 p->tgid = current->tgid; 299 } else { 300 /* 創建的是子進程 */ 301 if (clone_flags & CLONE_PARENT) 302 p->exit_signal = current->group_leader->exit_signal; 303 else 304 p->exit_signal = (clone_flags & CSIGNAL); 305 p->group_leader = p; 306 /* tgid與pid一致,所以當創建子線程時,tgid與主線程的一致 */ 307 p->tgid = p->pid; 308 } 309 310 /* 初始化頁框中臟頁數量為0 */ 311 p->nr_dirtied = 0; 312 /* 初始化臟頁數量臨界值,當臟頁數量到達臨界值時,會調用balance_dirty_pages()將臟頁寫入磁盤 */ 313 p->nr_dirtied_pause = 128 >> (PAGE_SHIFT - 10); 314 /* 將臟頁寫入磁盤的開始時間 */ 315 p->dirty_paused_when = 0; 316 317 p->pdeath_signal = 0; 318 /* 初始化線程組鏈表為空 */ 319 INIT_LIST_HEAD(&p->thread_group); 320 p->task_works = NULL; 321 322 323 /* 到此系統中已經存在此進程(線程),但是它還不能夠執行,需要等待父進程對其處理,這裏會上鎖 */ 324 write_lock_irq(&tasklist_lock); 325 326 if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) { 327 /* 創建的是兄弟進程或者相同線程組線程 */ 328 /* 其父進程為父進程的父進程 */ 329 p->real_parent = current->real_parent; 330 /* 其父進程執行域為父進程的父進程執行域 */ 331 p->parent_exec_id = current->parent_exec_id; 332 } else { 333 /* 創建的是子進程 */ 334 /* 父進程為父進程 */ 335 p->real_parent = current; 336 /* 父進程的執行域為父進程的執行域 */ 337 p->parent_exec_id = current->self_exec_id; 338 } 339 340 /* 當前進程信號處理上鎖,這裏應該是禁止了信號處理 */ 341 spin_lock(¤t->sighand->siglock); 342 343 /* 344 * seccomp與系統安全有關,具體見 http://note.sdo.com/u/634687868481358385/NoteContent/M5cEN~kkf9BFnM4og00239 345 */ 346 copy_seccomp(p); 347 348 /* 349 * 在fork之前,進程組和會話信號都需要送到父親結點,而在fork之後,這些信號需要送到父親和孩子結點。 350 * 如果我們在將新進程添加到進程組的過程中出現一個信號,而這個掛起信號會導致當前進程退出(current),我們的子進程就不能夠被kill或者退出了 351 * 所以這裏要檢測父進程有沒有信號被掛起。 352 */ 353 recalc_sigpending(); 354 if (signal_pending(current)) { 355 /* 包含有掛起進程,錯誤 */ 356 spin_unlock(¤t->sighand->siglock); 357 write_unlock_irq(&tasklist_lock); 358 retval = -ERESTARTNOINTR; 359 goto bad_fork_free_pid; 360 } 361 362 if (likely(p->pid)) { 363 /* 如果子進程需要跟蹤,就將 current->parent 賦值給 tsk->parent ,並將子進程插入調試程序的跟蹤鏈表中 */ 364 ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace); 365 366 /* p->pids[PIDTYPE_PID].pid = pid; */ 367 init_task_pid(p, PIDTYPE_PID, pid); 368 369 /* 如果是子進程(其實就是判斷 p->exit_signal 是否大於等於0,創建的是線程的話,exit_signal的值為-1) */ 370 if (thread_group_leader(p)) { 371 /* p->pids[PIDTYPE_PGID].pid = current->group_leader->pids[PIDTYPE_PGID].pid; PGID為進程組ID,所以直接復制父進程的pgid */ 372 init_task_pid(p, PIDTYPE_PGID, task_pgrp(current)); 373 /* p->pids[PIDTYPE_SID].pid = current->group_leader->pids[PIDTYPE_SID].pid; SID為會話組ID,當沒有使用setsid()時,子進程的sid與父進程一致 */ 374 init_task_pid(p, PIDTYPE_SID, task_session(current)); 375 376 /* return pid->numbers[pid->level].nr == 1; 判斷新進程是否處於一個新創建的namespace中(新進程所在的新namespace中的pid會為1,以此判斷) */ 377 if (is_child_reaper(pid)) { 378 /* 將當前namespace的init進程設置為此新進程 */ 379 ns_of_pid(pid)->child_reaper = p; 380 p->signal->flags |= SIGNAL_UNKILLABLE; 381 } 382 383 p->signal->leader_pid = pid; 384 p->signal->tty = tty_kref_get(current->signal->tty); 385 386 /* 將此進程添加到父進程的子進程鏈表 */ 387 list_add_tail(&p->sibling, &p->real_parent->children); 388 /* 將此進程task_struct加入到task鏈表中 */ 389 list_add_tail_rcu(&p->tasks, &init_task.tasks); 390 /* 將新進程描述符的pgid結構插入pgid_hash */ 391 attach_pid(p, PIDTYPE_PGID); 392 /* 將新進程描述符的sid結構插入sid_hash */ 393 attach_pid(p, PIDTYPE_SID); 394 /* 當前cpu進程數量加1 */ 395 __this_cpu_inc(process_counts); 396 } else { 397 /* 創建的是線程,這裏的處理導致了線程會共享信號 */ 398 current->signal->nr_threads++; 399 atomic_inc(¤t->signal->live); 400 atomic_inc(¤t->signal->sigcnt); 401 /* 將新線程的thread_group結點加入到線程組的領頭線程的thread_group鏈表中 */ 402 list_add_tail_rcu(&p->thread_group, 403 &p->group_leader->thread_group); 404 /* 將新線程的thread_node結點加入的新線程的signal->thread_head中 */ 405 list_add_tail_rcu(&p->thread_node, 406 &p->signal->thread_head); 407 } 408 /* 將新進程描述符的pid結構插入pid_hash */ 409 attach_pid(p, PIDTYPE_PID); 410 /* 當前系統進程數加1 */ 411 nr_threads++; 412 } 413 414 /* 已創建的進程數量加1 */ 415 total_forks++; 416 /* 釋放當前進程信號處理鎖 */ 417 spin_unlock(¤t->sighand->siglock); 418 syscall_tracepoint_update(p); 419 /* 釋放tasklist_lock鎖 */ 420 write_unlock_irq(&tasklist_lock); 421 422 /* 將新進程與proc文件系統進行關聯 */ 423 proc_fork_connector(p); 424 cgroup_post_fork(p); 425 /* 如果創建的是線程,釋放此鎖 */ 426 if (clone_flags & CLONE_THREAD) 427 threadgroup_change_end(current); 428 perf_event_fork(p); 429 430 trace_task_newtask(p, clone_flags); 431 uprobe_copy_process(p, clone_flags); 432 433 /* 返回新進程的task_struct結構 */ 434 return p; 435 436 /* 以下為執行期間的錯誤處理 */ 437 bad_fork_free_pid: 438 if (pid != &init_struct_pid) 439 free_pid(pid); 440 bad_fork_cleanup_io: 441 if (p->io_context) 442 exit_io_context(p); 443 bad_fork_cleanup_namespaces: 444 exit_task_namespaces(p); 445 bad_fork_cleanup_mm: 446 if (p->mm) 447 mmput(p->mm); 448 bad_fork_cleanup_signal: 449 if (!(clone_flags & CLONE_THREAD)) 450 free_signal_struct(p->signal); 451 bad_fork_cleanup_sighand: 452 __cleanup_sighand(p->sighand); 453 bad_fork_cleanup_fs: 454 exit_fs(p); /* blocking */ 455 bad_fork_cleanup_files: 456 exit_files(p); /* blocking */ 457 bad_fork_cleanup_semundo: 458 exit_sem(p); 459 bad_fork_cleanup_audit: 460 audit_free(p); 461 bad_fork_cleanup_perf: 462 perf_event_free_task(p); 463 bad_fork_cleanup_policy: 464 #ifdef CONFIG_NUMA 465 mpol_put(p->mempolicy); 466 bad_fork_cleanup_threadgroup_lock: 467 #endif 468 if (clone_flags & CLONE_THREAD) 469 threadgroup_change_end(current); 470 delayacct_tsk_free(p); 471 module_put(task_thread_info(p)->exec_domain->module); 472 bad_fork_cleanup_count: 473 atomic_dec(&p->cred->user->processes); 474 exit_creds(p); 475 bad_fork_free: 476 free_task(p); 477 fork_out: 478 return ERR_PTR(retval); 479 }


技術分享圖片


流程圖

技術分享圖片



小結

  copy_process作為do_fork的主心骨,其流程並不復雜,只是每一步調用的初始化函數都非常精妙,涉及到大量的內知識和代碼,這裏為了篇幅著想就不繼續往細節分析了,會在之後的文章中慢慢補全其中的知識和自己的理解。整篇文章讀下來,其實copy_process的核心就是初始化task_struct結構體供新進程(線程)使用,並為其分配獨有的pid,最後將其加入到運行隊列中。而至於為什麽應用層調用fork()會進行兩次返回,原理就是在內核棧中,在copy_thread函數中父進程將其內核棧復制到子進程中,把子進程被調度後執行的第一條語句設置為do_fork()返回,並把保存返回值的寄存器值(一般返回值保存在eax(ARM是r0),而這些通用寄存器值保存在內核棧中,當調用後會進行進程切換,會把這些保存於內核棧的寄存器值還原到寄存器中)置為0,所以子進程的返回值為0,而父進程會繼續執行copy_thread函數之後的初始化,最後返回子進程的pid(實際上是tgid)。

關於linux系統如何實現fork的研究(二)【轉】