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

本文爲原創,轉載請註明: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的分配,關係調整(父子進程、進程組關係,命名空間關係等),內存結構的初始化等等,這些咱們在以後的代碼中慢慢道來。html

 

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(&current->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(&current->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(&current->signal->live);
400             atomic_inc(&current->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(&current->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),而這些通用寄存器值保存在內核棧中,當父進程調用後copy_thread函數會進行進程切換,會把這些保存於內核棧的寄存器值還原到寄存器中)置爲0,因此子進程的返回值爲0,而父進程會繼續執行copy_thread函數以後的初始化,最後返回子進程的pid(其實是tgid)。node

 

線程獨佔和共享的資源

線程獨有的內容包括:linux

  • PID(進程的全部子線程的tgid是同樣的)
  • 寄存器組的值
  • 線程的棧(包括內核棧)
  • 錯誤返回碼
  • 線程的信號屏蔽碼

線程與父進程共享的內容包括:安全

  • 代碼段
  • 進程的公有數據(利用這些共享的數據,線程很容易的實現相互之間的通信)
  • 打開的文件描述符
  • 信號的處理函數
  • 進程的當前目錄
  • 進程用戶 ID 與進程組 ID
相關文章
相關標籤/搜索