关于linux系统如何实现fork的研究(二)【转】

简介: 转自:http://www.aichengxu.com/linux/7166015.htm 本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 引言   前一篇关于linux系统如何实现fork的研究(一)通过代码已经说明了从用户态怎么通过软中断实现调用系统调用clone函数,而clone函数的精华copy_process函数就在此篇文章中进行分析。

转自: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)。

【作者】 张昺华
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【facebook】 张昺华 zhangbinghua
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
目录
相关文章
|
3天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
18 3
|
3天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
16 2
|
13天前
|
Linux 应用服务中间件 Shell
linux系统服务二!
本文详细介绍了Linux系统的启动流程,包括CentOS 7的具体启动步骤,从BIOS自检到加载内核、启动systemd程序等。同时,文章还对比了CentOS 6和CentOS 7的启动流程,分析了启动过程中的耗时情况。接着,文章讲解了Linux的运行级别及其管理命令,systemd的基本概念、优势及常用命令,并提供了自定义systemd启动文件的示例。最后,文章介绍了单用户模式和救援模式的使用方法,包括如何找回忘记的密码和修复启动故障。
35 5
linux系统服务二!
|
13天前
|
Linux 应用服务中间件 Shell
linux系统服务!!!
本文详细介绍了Linux系统(以CentOS7为例)的启动流程,包括BIOS自检、读取MBR信息、加载Grub菜单、加载内核及驱动程序、启动systemd程序加载必要文件等五个主要步骤。同时,文章还对比了CentOS6和CentOS7的启动流程图,并分析了启动流程的耗时。此外,文中还讲解了Linux的运行级别、systemd的基本概念及其优势,以及如何使用systemd管理服务。最后,文章提供了单用户模式和救援模式的实战案例,帮助读者理解如何在系统启动出现问题时进行修复。
35 3
linux系统服务!!!
|
3天前
|
安全 网络协议 Linux
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。通过掌握 ping 命令,读者可以轻松测试网络连通性、诊断网络问题并提升网络管理能力。
18 3
|
6天前
|
安全 Linux 数据安全/隐私保护
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。本文介绍了使用 `ls -l` 和 `stat` 命令查找文件所有者的基本方法,以及通过文件路径、通配符和结合其他命令的高级技巧。还提供了实际案例分析和注意事项,帮助读者更好地掌握这一操作。
23 6
|
6天前
|
Linux
在 Linux 系统中,`find` 命令是一个强大的文件查找工具
在 Linux 系统中,`find` 命令是一个强大的文件查找工具。本文详细介绍了 `find` 命令的基本语法、常用选项和具体应用示例,帮助用户快速掌握如何根据文件名、类型、大小、修改时间等条件查找文件,并展示了如何结合逻辑运算符、正则表达式和排除特定目录等高级用法。
30 6
|
7天前
|
机器学习/深度学习 自然语言处理 Linux
Linux 中的机器学习:Whisper——自动语音识别系统
本文介绍了先进的自动语音识别系统 Whisper 在 Linux 环境中的应用。Whisper 基于深度学习和神经网络技术,支持多语言识别,具有高准确性和实时处理能力。文章详细讲解了在 Linux 中安装、配置和使用 Whisper 的步骤,以及其在语音助手、语音识别软件等领域的应用场景。
32 5
|
7天前
|
缓存 运维 监控
【运维必备知识】Linux系统平均负载与top、uptime命令详解
系统平均负载是衡量Linux服务器性能的关键指标之一。通过使用 `top`和 `uptime`命令,可以实时监控系统的负载情况,帮助运维人员及时发现并解决潜在问题。理解这些工具的输出和意义是确保系统稳定运行的基础。希望本文对Linux系统平均负载及相关命令的详细解析能帮助您更好地进行系统运维和性能优化。
25 3
|
7天前
|
监控 网络协议 算法
Linux内核优化:提升系统性能与稳定性的策略####
本文深入探讨了Linux操作系统内核的优化策略,旨在通过一系列技术手段和最佳实践,显著提升系统的性能、响应速度及稳定性。文章首先概述了Linux内核的核心组件及其在系统中的作用,随后详细阐述了内存管理、进程调度、文件系统优化、网络栈调整及并发控制等关键领域的优化方法。通过实际案例分析,展示了这些优化措施如何有效减少延迟、提高吞吐量,并增强系统的整体健壮性。最终,文章强调了持续监控、定期更新及合理配置对于维持Linux系统长期高效运行的重要性。 ####