asmlinkage int sys_fork(struct pt_regs regs)
{
return do_fork(SIGCHLD, regs.esp, ®s, 0, NULL, NULL);
}
这个是linux 上所有的包括线程进程的创建最终那个调用用的函数
如果是一个喜欢看小说的 就知道所谓的道生一 一生二 三生万物 如果把一个程序一个程序比喻成为一件一件事物 那么如何做到三生万物 就是这个函数了
在电脑里重要性不亚于人类的繁殖
在这里我一步一步剖析也做一部分的简要的例子
do_fork(SIGCHLD, regs.esp, ®s, 0, NULL, NULL);原型:
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
/**
* 负责处理clone,fork,vfork系统调用。
* clone_flags-与clone的flag参数相同
* stack_start-与clone的child_stack相同
* regs-指向通用寄存器的值。是在从用户态切换到内核态时被保存到内核态堆栈中的。
* stack_size-未使用,总是为0
* parent_tidptr,child_tidptr-clone中对应参数ptid,ctid相同
*/
这里先看第一个参数也就是负责处理是clone还是fork,vfork的参数;在本文里只讨论fork
不过我提一下 clone 这个函数pthread_cread 最终调用的 也就是创建线程的时候用的函数 所以在很多的书上都有一句叫做什么是线程和进程
在linux操作系统上本质上都是一样的;调用的都是同一个函数 然而具体详细一点当然传递的参数不同那么具体也就不同
再解析一下传递的这个宏也就是信号 在《深入理解操作系统》里面SIGCHLD对应的事件就是一个子进程停止或者终止的意思
第二个参数是regs.esp 这个表示的是栈的栈顶指针
第三个参数是®s 这个表示的是把寄存器传进去
接下来都是0 0 0 这个先不考虑;接下来我直接一步一步走
可以先思考一下进程的建立需要什么?这个函数就是需要什么就给什么;
struct task_struct *p; 这个表示的先创建一个task_struct 的指针 分析一下这个是什么 task_struct 是一个pcb(如果有兴趣百度进程控制块) 一个进程的建立必要条件
int trace = 0;
long pid = alloc_pidmap();这个是分配pid的一种方式现在只要知道每个进程都需要有直接的pid 同时也可以开始思考为什么pid有2个返回值;现在返回的pid是分配好的pid
也就是子进程的pid
if (pid < 0)
return -EAGAIN;//如果pid是小于0的那么可以直接返回还创建什么进程每个pid都是要大于0的
/**
* 如果父进程正在被跟踪,就检查debugger程序是否想跟踪子进程.并且子进程不是内核进程(CLONE_UNTRACED未设置)
* 那么就设置CLONE_PTRACE标志.
*/
if (unlikely(current->ptrace)) {//unlikely 这个其实是一种提升性能的一种方法
trace = fork_traceflag (clone_flags);
if (trace)
clone_flags |= CLONE_PTRACE;
}这个可以不用管因为在我们clone_flags=17;
/**
* copy_process复制进程描述符.如果所有必须的资源都是可用的,该函数返回刚创建的task_struct描述符的地址.
* 这是创建进程的关键步骤.
*/
p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);
这一步是关键的一步我们的很大篇幅都在这个上面
static task_t *copy_process(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
int pid)
参数和之前的是一样的加了一个之前分配好了的pid 返回的是一个pcb 被之前的p接收
int retval;
struct task_struct *p = NULL;//也是一个pcb的指针
/**
* 如果CLONE_NEWNS和CLONE_FS标志都被设置,返回错误
*/
if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return ERR_PTR(-EINVAL);
/*
* Thread groups must share signals as well, and detached threads
* can only be started up within the thread group.
*/
/**
* CLONE_THREAD标志被设置,并且CLONE_SIGHAND没有设置。
* (同一线程组中的轻量级进程必须共享信号)
*/
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL);
/*
* Shared signal handlers imply shared VM. By way of the above,
* thread groups also imply shared VM. Blocking this case allows
* for various simplifications in other code.
*/
/**
* CLONE_SIGHAND被设置,但是CLONE_VM没有设置。
* (共享信号处理程序的轻量级进程也必须共享内存描述符)
*/
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
return ERR_PTR(-EINVAL);
总结上面都是线程上的一些检验参数的东西所以说现在不用怎么管
/**
* 通过调用security_task_create以及稍后调用security_task_alloc执行所有附加的安全检查。
* LINUX2.6提供扩展安全性的钩子函数,与传统unix相比,它具有更加强壮的安全模型。
*/
retval = security_task_create(clone_flags);
也是为了一些安全性的考虑
if (retval)
goto fork_out;
retval = -ENOMEM;
/**
* 调用dup_task_struct为子进程获取进程描述符。
*/
p = dup_task_struct(current);
我们来分析一下如何获取子进程的进程描述符 current 表示的是当前进程的pcb的指针
static struct task_struct *dup_task_struct(struct task_struct *orig)
struct task_struct *tsk;先建立一个pcb
struct thread_info *ti; 再建立一个thread_info
/**
* prepare_to_copy中会调用unlazy_fpu。
* 它把FPU、MMX和SSE/SSE2寄存器的内容保存到父进程的thread_info结构中。
* 稍后,dup_task_struct将把这些值复制到子进程的thread_info中。
*/
prepare_to_copy(orig);//本身task_struct 本身不是硬件上的东西 所以还需要保存一些有些进程需要的硬件上的东西
/**
* alloc_task_struct宏为新进程获取进程描述符,并将描述符保存到tsk局部变量中。
*/
tsk = alloc_task_struct();
if (!tsk)
return NULL;
如果将这个alloc_task_struct();展开其实就是一个在内存上分配的一个本地的高速缓存对象 每个cpu都会有用了一个数组表示
如果不够就会扩大1个对象 每次都是判断cpu上是否存有缓存空闲对象如果有就直接分配这个对象没有就扩大可以简单的认为分配内存
参考一个讨论(http://bbs.chinaunix.net/thread-4085238-1-1.html)
/**
* alloc_thread_info宏获取一块空闲内存区,用来存放新进程的thread_info结构和内核栈。
* 这块内存区字段的大小是8KB或者4KB。
*/
ti = alloc_thread_info(tsk);
if (!ti) {
free_task_struct(tsk);
return NULL;
}
alloc_thread_info申请内存大小
综合上 我个人认为就是一个索引 alloc_task_struct 这个指向了thread_info
/**
* 将current进程描述符的内容复制到tsk所指向的task_struct结构中,然后把tsk_thread_info置为ti
* 将current进程的thread_info内容复制给ti指向的结构中,并将ti_task置为tsk.
*/
*ti = *orig->thread_info;
*tsk = *orig;
tsk->thread_info = ti;
ti->task = tsk;
如图:
这样就拷贝好了而且cpu也能进行调度了
/* One for us, one for whoever does the "release_task()" (usually parent) */
/**
* 把新进程描述符的使用计数器usage设置为2,用来表示描述符正在被使用而且其相应的进程处于活动状态。
* 进程状态既不是EXIT_ZOMBIE,也不是EXIT_DEAD
*/
atomic_set(&tsk->usage,2);
return tsk;
这样dup_task_struct(struct task_struct *orig)也就完成了对子进程的一个拷贝
现在唯一不同的就是在task_struct 有自己的地址 thread_info 也有自己的地址
返回回去
p = dup_task_struct(current);//这个就是之前的分析的代码
if (!p)
goto fork_out;
/**
* 检查存放在current->sigal->rlim[RLIMIT_NPROC].rlim_cur中的限制值,是否小于或者等于用户所拥有的进程数。
* 如果是,则返回错误码。当然,有root权限除外。
* p->user表示进程的拥有者,p->user->processes表示进程拥有者当前进程数
* xie.baoyou注:此处比较是用>=而不是>
*/
retval = -EAGAIN;
if (atomic_read(&p->user->processes) >=
p->signal->rlim[RLIMIT_NPROC].rlim_cur) {
/**
* 当然,用户有root权限就另当别论了
*/
if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) &&
p->user != &root_user)
goto bad_fork_free;
}//这个是说明普通的用户创建的进程数是有上限的 当然进程即使是root用户也不可能无限多肯定会有上限
/**
* 递增user结构的使用计数器
*/
atomic_inc(&p->user->__count);
/**
* 增加用户拥有的进程计数。
*/
atomic_inc(&p->user->processes);
get_group_info(p->group_info);//这个表示得到组信息 什么叫做组 就是有在结束进程的时候 kill -9 pid 这个pid是一个正数 进程组就是一系列的进程 比如一个
游戏有很多进程你要结束这个游戏结束进程组就可以了 结束进程组就是 kill -9 负pid
* 检查系统中的进程数量(nr_threads)是否超过max_threads
* max_threads的缺省值是由系统内存容量决定的。总的原则是:所有的thread_info描述符和内核栈所占用的空间
* 不能超过物理内存的1/8。不过,系统管理可以通过写/proc/sys/kernel/thread-max文件来改变这个值。
*/
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;
/** * 如果新进程的执行域和可招待格式的内核函数都包含在内核中模块中, * 就递增它们的使用计数器。 */ if (!try_module_get(p->thread_info->exec_domain->module)) goto bad_fork_cleanup_count;
if (p->binfmt && !try_module_get(p->binfmt->module)) goto bad_fork_cleanup_put_domain;
/** * 设置几个与进程状态相关的关键字段。 */
/** * did_exec是进程发出的execve系统调用的次数,初始为0 */ p->did_exec = 0; /** * 更新从父进程复制到tsk_flags字段中的一些标志。 * 首先清除PF_SUPERPRIV。该标志表示进程是否使用了某种超级用户权限。 * 然后设置PF_FORKNOEXEC标志。它表示子进程还没有发出execve系统调用。 */ copy_flags(clone_flags, p); /** * 保存新进程的pid值。 */ p->pid = pid;//这一步就是为什么我们的 fork pid会有2个值的原因; retval = -EFAULT; /** * 如果CLONE_PARENT_SETTID标志被设置,就将子进程的PID复制到参数parent_tidptr指向的用户态变量中。 * xie.baoyou:想想我们常常调用的pid = fork()语句吧。 */ if (clone_flags & CLONE_PARENT_SETTID) if (put_user(p->pid, parent_tidptr)) goto bad_fork_cleanup;
p->proc_dentry = NULL;//我猜测应该是一个控制缓存的一个东西
/** * 初始化子进程描述符中的list_head数据结构和自旋锁。 * 并为挂起信号,定时器及时间统计表相关的几个字段赋初值。 */ INIT_LIST_HEAD(&p->children); INIT_LIST_HEAD(&p->sibling); p->vfork_done = NULL; spin_lock_init(&p->alloc_lock); spin_lock_init(&p->proc_lock);
clear_tsk_thread_flag(p, TIF_SIGPENDING); init_sigpending(&p->pending);
p->it_real_value = 0; p->it_real_incr = 0; p->it_virt_value = cputime_zero; p->it_virt_incr = cputime_zero; p->it_prof_value = cputime_zero; p->it_prof_incr = cputime_zero; init_timer(&p->real_timer); p->real_timer.data = (unsigned long) p;
p->utime = cputime_zero; p->stime = cputime_zero; p->rchar = 0; /* I/O counter: bytes read */ p->wchar = 0; /* I/O counter: bytes written */ p->syscr = 0; /* I/O counter: read syscalls */ p->syscw = 0; /* I/O counter: write syscalls */ acct_clear_integrals(p);
/**
* 把大内核锁计数器初始化为-1
*/
p->lock_depth = -1;/* -1 = no lock */
do_posix_clock_monotonic_gettime(&p->start_time);
p->security = NULL;
p->io_context = NULL;
p->io_wait = NULL;
p->audit_context = NULL;
#ifdef CONFIG_NUMA
p->mempolicy = mpol_copy(p->mempolicy);
if (IS_ERR(p->mempolicy)) {
retval = PTR_ERR(p->mempolicy);
p->mempolicy = NULL;
goto bad_fork_cleanup;
}
#endif
p->tgid = p->pid; if (clone_flags & CLONE_THREAD) p->tgid = current->tgid;// 这个是进程的领头进程 如果是clone 那么就是主进程 如果不是是fork的就是自己 也体现linux内核的线程就是进程的概念
if ((retval = security_task_alloc(p))) goto bad_fork_cleanup_policy; if ((retval = audit_alloc(p))) goto bad_fork_cleanup_security; //这个表示内核的检查安全信息
/* copy all the process information */ /** * copy_semundo,copy_files,copy_fs,copy_sighand,copy_signal * copy_mm,copy_keys,copy_namespace创建新的数据结构,并把父进程相应数据结构的值复制到新数据结构中。 * 除非clone_flags参数指出它们有不同的值。 */ if ((retval = copy_semundo(clone_flags, p))) goto bad_fork_cleanup_audit; if ((retval = copy_files(clone_flags, p))) goto bad_fork_cleanup_semundo; if ((retval = copy_fs(clone_flags, p))) goto bad_fork_cleanup_files; if ((retval = copy_sighand(clone_flags, p))) goto bad_fork_cleanup_fs; if ((retval = copy_signal(clone_flags, p))) goto bad_fork_cleanup_sighand; if ((retval = copy_mm(clone_flags, p))) goto bad_fork_cleanup_signal; if ((retval = copy_keys(clone_flags, p))) goto bad_fork_cleanup_mm; if ((retval = copy_namespace(clone_flags, p))) goto bad_fork_cleanup_keys;
分析这个
copy_semundo(clone_flags, p)这个是一个进程间通信的拷贝;一般来说fork调用基本不做太多处理在clon里才做处理
(retval = copy_files(clone_flags, p))
分析这个
从名字上来看这个是拷贝文件 毫无疑问是拷贝文件 也就是文件描述符 在子进程里面之前的指针是指向父进程的文件描述符也就是说如果没有这一步的化
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6
7 int main()
8 {
9 int pid;
10 pid=(int)fork();
11 if(pid==0)
12 {
13 int a=open("my.cpp",O_RDONLY);
14 printf("zi=%d\n",a);
15 }
16 else
17 {
18 int b=open("my.cpp",O_RDONLY);
19 printf("fu=%d\n",b);
20 }
21
22 return 0;
23 }
这样出来的fu 和zi就应该不同 但实际上是相同的
如果是线程就会变了
static int copy_files(unsigned long clone_flags, struct task_struct * tsk)
{ struct files_struct *oldf, *newf; struct file **old_fds, **new_fds; int open_files, size, i, error = 0, expand;
/* * A background process may not have any files ... */ oldf = current->files; if (!oldf) goto out;
if (clone_flags & CLONE_FILES) { atomic_inc(&oldf->count); goto out; } 这就会改变
if ((retval = copy_fs(clone_flags, p))) goto bad_fork_cleanup_files;
分析这个 同上面解释一样 只是这个拷贝的是目录
if ((retval = copy_sighand(clone_flags, p))) goto bad_fork_cleanup_fs; if ((retval = copy_signal(clone_flags, p))) goto bad_fork_cleanup_sighand;//同上 拷贝信号处理程序描述符 拷贝进程的信号描述符
其实也就是这样的图
接下来就是重点了 if ((retval = copy_mm(clone_flags, p))) goto bad_fork_cleanup_signal;
关于内存的也就是真正的写实拷贝的函数
static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
{ struct mm_struct * mm, *oldmm; int retval;
tsk->min_flt = tsk->maj_flt = 0; tsk->nvcsw = tsk->nivcsw = 0;
tsk->mm = NULL; tsk->active_mm = NULL;//首先将子进程的min_flt 也就是缺页的错误数为0 maj_flt也就是主要页错误 在内存中主要是通过mm 同上是内存描述符
oldmm = current->mm; /** * 内核线程?? */ if (!oldmm) return 0;//得到当前得mm也就是oldmm
/** * 指定了CLONE_VM标志,表示创建线程。 */ if (clone_flags & CLONE_VM) { /** * 新线程共享父进程的地址空间,所以需要将mm_users加一。 */ atomic_inc(&oldmm->mm_users); mm = oldmm; /* * There are cases where the PTL is held to ensure no * new threads start up in user mode using an mm, which * allows optimizing out ipis; the tlb_gather_mmu code * is an example. */ /** * 如果其他CPU持有进程页表自旋锁,就通过spin_unlock_wait保证在释放锁前,缺页处理程序不会结果。 * 实际上,这个锁除了保护页表,还必须禁止创建新的轻量级进程。因为它们共享mm描述符 */ spin_unlock_wait(&oldmm->page_table_lock); /** * 在good_mm中,将父进程的地址空间赋给子进程。 * 注意前面对mm的赋值,表示了新线程使用的mm * 完了,就这么简单 */ goto good_mm; }//这些是线程mm
/** * 没有CLONE_VM标志,就必须创建一个新的地址空间。 * 必须要有地址空间,即使此时并没有分配内存。 */ retval = -ENOMEM; /** * 分配一个新的内存描述符。把它的地址存放在新进程的mm中。 */ mm = allocate_mm(); if (!mm) goto fail_nomem;
/* Copy the current MM stuff.. */ /** * 并从当前进程复制mm的内容。 */ memcpy(mm, oldmm, sizeof(*mm)); if (!mm_init(mm)) goto fail_nomem;
/** * 调用依赖于体系结构的init_new_context。 * 对于80X86来说,该函数检查当前进程是否有定制的局部描述符表。 * 如果有,就复制一份局部描述符表并把它插入tsk的地址空间 */ if (init_new_context(tsk,mm)) goto fail_nocontext;
//接下来就是重点也就是写时拷贝得实现
/** * dup_mmap不但复制了线程区和页表,也设置了mm的一些属性. * 它也会改变父进程的私有,可写的页为只读的,以使写时复制机制生效。 */ retval = dup_mmap(mm, oldmm);
这个函数里面最主要是
* copy_page_range创建必要的页表来映射线性区所包含的一组页。并且初始化新页表的表项。 * 对私有、可写的页(无VM_SHARED标志,有VM_MAYWRITE标志),对父子进程都标记为只读的。 * 为写时复制进行处理。 */ retval = copy_page_range(mm, current->mm, tmp);
我不知道有没有人考虑过页表为什么分级 为什么节约空间 ?
这个是就是实现得代码
这个模拟了一个线性区的东西个人理解其实就是记录页表下标的结构体
src_pgd = pgd_offset(src, start); dst_pgd = pgd_offset(dst, start); start其实就是相当于下标了
当然在内核中就是4级
/**
* 在dump_mmap中,插入一个新的线性区描述符后,通过本过程创建必要的页表映射线性区所包含的一组页。
* 并且初始化新页表的表项。
* 与私有的、可写的页(VM_SHARED标志关闭,VM_MAYWRITE标志打开)所对应的任意页框都标记为对父子进程都是只读
* 以便这种页框能用写时复制机制进行处理。
*/
int copy_page_range(struct mm_struct *dst, struct mm_struct *src, struct vm_area_struct *vma)
这个就是内核写实拷贝的主要代码
/* * If it's a COW mapping, write protect it both * in the parent and the child */ if ((vm_flags & (VM_SHARED | VM_MAYWRITE)) == VM_MAYWRITE) { ptep_set_wrprotect(src_pte); pte = *src_pte; }
接下来就是一些检查安全性的代码了
if ((retval = copy_keys(clone_flags, p))) goto bad_fork_cleanup_mm; if ((retval = copy_namespace(clone_flags, p))) goto bad_fork_cleanup_keys;
retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
这个函数就是为什么pid会有2个值的原因
也就是说在本来拷贝的内核栈上的寄存器信息都保存起来的值在子进程调用的时候就改变了eax寄存器所以返回值也改变了
/** * 如果clone_flags参数的值被置为CLONE_CHILD_SETTID或CLONE_CHILD_CLEARTID * 就把child_tidptr参数的值分别复制到set_child_tid或clear_child_tid字段。 * 这些标志说明:必须改变子进程用户态地址空间的dhild_tidptr所指向的变量的值 * 不过实际的写操作要稍后再执行。 */ p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL; /* * Clear TID on mm_release()? */ p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr: NULL;
/* * Syscall tracing should be turned off in the child regardless * of CLONE_PTRACE. */ /** * 清除TIF_SYSCALL_TRACE标志。使ret_from_fork函数不会把系统调用结束的消息通知给调试进程。 * 也不应该通知给调试进程,因为子进程并没有调用fork. */ clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);
/* Our parent execution domain becomes current domain These must match for thread signalling to apply */ p->parent_exec_id = p->self_exec_id;
/* ok, now we should be set up.. */ /** * 用clone_flags参数低位的信号数据编码统建始化tsk_exit_signal字段。 * 如CLONE_THREAD标志被置位,就把exit_signal字段初始化为-1。 * 这样做是因为:当创建线程时,即使被创建的线程死亡,都不应该给领头进程的父进程发送信号。 * 而应该是领头进程死亡后,才向其领头进程的父进程发送信号。 */ p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 : (clone_flags & CSIGNAL); p->pdeath_signal = 0; p->exit_state = 0;
/* Perform scheduler related setup */ /** * 调用sched_fork完成对新进程调度程序数据结构的初始化。 * 该函数把新进程的状态置为TASK_RUNNING,并把thread_info结构的preempt_count字段设置为1, * 从而禁止抢占。 * 此外,为了保证公平调度,父子进程共享父进程的时间片。 */ sched_fork(p);
/* * Ok, make it visible to the rest of the system. * We dont wake it up yet. */ p->group_leader = p; INIT_LIST_HEAD(&p->ptrace_children); INIT_LIST_HEAD(&p->ptrace_list);
/* Need tasklist lock for parent etc handling! */ write_lock_irq(&tasklist_lock);
/* * The task hasn't been attached yet, so cpus_allowed mask cannot * have changed. The cpus_allowed mask of the parent may have * changed after it was copied first time, and it may then move to * another CPU - so we re-copy it here and set the child's CPU to * the parent's CPU. This avoids alot of nasty races. */ p->cpus_allowed = current->cpus_allowed; /** * 初始化子线程的cpu字段。 */ set_task_cpu(p, smp_processor_id());
/* * Check for pending SIGKILL! The new thread should not be allowed * to slip out of an OOM kill. (or normal SIGKILL.) */ if (sigismember(¤t->pending.signal, SIGKILL)) { write_unlock_irq(&tasklist_lock); retval = -EINTR; goto bad_fork_cleanup_namespace; }
/* CLONE_PARENT re-uses the old parent */ /** * 初始化表示亲子关系的字段,如果CLONE_PARENT或者CLONE_THREAD被设置了 * 就用current->real_parent初始化,否则,当前进程就是初创建进程的父进程。 */ if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) p->real_parent = current->real_parent; else p->real_parent = current; p->parent = p->real_parent;
if (clone_flags & CLONE_THREAD) { spin_lock(¤t->sighand->siglock); /* * Important: if an exit-all has been started then * do not create this new thread - the whole thread * group is supposed to exit anyway. */ if (current->signal->flags & SIGNAL_GROUP_EXIT) { spin_unlock(¤t->sighand->siglock); write_unlock_irq(&tasklist_lock); retval = -EAGAIN; goto bad_fork_cleanup_namespace; } p->group_leader = current->group_leader;
if (current->signal->group_stop_count > 0) { /* * There is an all-stop in progress for the group. * We ourselves will stop as soon as we check signals. * Make the new thread part of that group stop too. */ current->signal->group_stop_count++; set_tsk_thread_flag(p, TIF_SIGPENDING); }
spin_unlock(¤t->sighand->siglock); }
/** * 把新进程加入到进程链表 */ SET_LINKS(p);
/** * PT_PTRACED表示子进程必须被跟踪,就把current->parent赋给tsk->parent,并将子进程插入调试程序的跟踪链表中。 */ if (unlikely(p->ptrace & PT_PTRACED)) __ptrace_link(p, current->parent);
/** * 把新进程描述符的PID插入pidhash散列表中。 */ attach_pid(p, PIDTYPE_PID, p->pid); attach_pid(p, PIDTYPE_TGID, p->tgid);
/** * 如果子进程是线程组的领头进程(CLONE_THREAD标志被清0) */ if (thread_group_leader(p)) { /** * 将进程插入相应的散列表。 */ attach_pid(p, PIDTYPE_PGID, process_group(p)); attach_pid(p, PIDTYPE_SID, p->signal->session); if (p->pid) __get_cpu_var(process_counts)++; }
/** * 计数 */ nr_threads++; total_forks++; write_unlock_irq(&tasklist_lock); retval = 0;
fork_out: if (retval) return ERR_PTR(retval); return p;
bad_fork_cleanup_namespace: exit_namespace(p);
bad_fork_cleanup_keys: exit_keys(p);
bad_fork_cleanup_mm: if (p->mm) mmput(p->mm);
bad_fork_cleanup_signal: exit_signal(p);
bad_fork_cleanup_sighand: exit_sighand(p);
bad_fork_cleanup_fs: exit_fs(p); /* blocking */
bad_fork_cleanup_files: exit_files(p); /* blocking */
bad_fork_cleanup_semundo: exit_sem(p);
bad_fork_cleanup_audit: audit_free(p);
bad_fork_cleanup_security: security_task_free(p);
bad_fork_cleanup_policy:
#ifdef CONFIG_NUMA mpol_free(p->mempolicy);
#endif
bad_fork_cleanup: if (p->binfmt) module_put(p->binfmt->module);
bad_fork_cleanup_put_domain: module_put(p->thread_info->exec_domain->module);
bad_fork_cleanup_count: put_group_info(p->group_info); atomic_dec(&p->user->processes); free_uid(p->user);
bad_fork_free: free_task(p); goto fork_out;
}
fork全面剖析
最新推荐文章于 2021-05-13 07:55:17 发布