Linux下进程的创建过程分析(_do_fork/do_fork详解)--Linux进程的管理与调度
来源:互联网 发布:lol mac国服怎么取消了 编辑:程序博客网 时间:2024/05/22 12:20
Linux下进程的创建过程分析(_do_fork/do_fork详解)–Linux进程的管理与调度(八)
参照
分析Linux内核创建一个新进程的过程
前言
Unix标准的复制进程的系统调用时fork(即分叉),但是Linux,BSD等操作系统并不止实现这一个,确切的说linux实现了三个,fork,vfork,clone(确切说vfork创造出来的是轻量级进程,也叫线程,是共享资源的进程)
关于用户空间使用fork, vfork和clone, 请参见
Linux中fork,vfork和clone详解(区别与联系)
fork, vfork和clone的系统调用的入口地址分别是sys_fork, sys_vfork和sys_clone, 而他们的定义是依赖于体系结构的, 因为在用户空间和内核空间之间传递参数的方法因体系结构而异
系统调用的参数传递
系统调用的实现与C库不同, 普通C函数通过将参数的值压入到进程的栈中进行参数的传递。由于系统调用是通过中断进程从用户态到内核态的一种特殊的函数调用,没有用户态或者内核态的堆栈可以被用来在调用函数和被调函数之间进行参数传递。系统调用通过CPU的寄存器来进行参数传递。在进行系统调用之前,系统调用的参数被写入CPU的寄存器,而在实际调用系统服务例程之前,内核将CPU寄存器的内容拷贝到内核堆栈中,实现参数的传递。
因此不同的体系结构可能采用不同的方式或者不同的寄存器来传递参数,而上面函数的任务就是从处理器的寄存器中提取用户空间提供的信息, 并调用体系结构无关的_do_fork(或者早期的do_fork)函数, 负责进程的复制
即不同的体系结构可能需要采用不同的方式或者寄存器来存储函数调用的参数, 因此linux在设计系统调用的时候, 将其划分成体系结构相关的层次和体系结构无关的层次, 前者复杂提取出依赖与体系结构的特定的参数, 后者则依据参数的设置执行特定的真正操作
fork, vfork, clone系统调用的实现
关于do_fork和_do_frok
The commit 3033f14ab78c32687 (“clone: support passing tls argument via C
rather than pt_regs magic”) introduced _do_fork() that allowed to pass
@tls parameter.参见 http://lists.openwall.net/linux-kernel/2015/03/13/30
linux2.5.32以后, 添加了TLS(Thread Local Storage)机制, clone的标识CLONE_SETTLS接受一个参数来设置线程的本地存储区。sys_clone也因此增加了一个int参数来传入相应的点tls_val。sys_clone通过do_fork来调用copy_process完成进程的复制,它调用特定的copy_thread和copy_thread把相应的系统调用参数从pt_regs寄存器列表中提取出来,但是会导致意外的情况。
only one code path into copy_thread can pass the CLONE_SETTLS flag, and
that code path comes from sys_clone with its architecture-specific
argument-passing order.
前面我们说了, 在实现函数调用的时候,我iosys_clone等将特定体系结构的参数从寄存器中提取出来, 然后到达do_fork这步的时候已经应该是体系结构无关了, 但是我们sys_clone需要设置的CLONE_SETTLS的tls仍然是个依赖与体系结构的参数, 这里就会出现问题。
因此linux-4.2之后选择引入一个新的CONFIG_HAVE_COPY_THREAD_TLS,和一个新的COPY_THREAD_TLS接受TLS参数为
额外的长整型(系统调用参数大小)的争论。改变sys_clone的TLS参数unsigned long,并传递到copy_thread_tls。
/* http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.5#L2646 */extern long _do_fork(unsigned long, unsigned long, unsigned long, int __user *, int __user *, unsigned long);extern long do_fork(unsigned long, unsigned long, unsigned long, int __user *, int __user *);/* linux2.5.32以后, 添加了TLS(Thread Local Storage)机制, 在最新的linux-4.2中添加了对CLONE_SETTLS 的支持 底层的_do_fork实现了对其的支持, dansh*/#ifndef CONFIG_HAVE_COPY_THREAD_TLS/* For compatibility with architectures that call do_fork directly rather than * using the syscall entry points below. */long do_fork(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr){ return _do_fork(clone_flags, stack_start, stack_size, parent_tidptr, child_tidptr, 0);}#endif
我们会发现,新版本的系统中clone的TLS设置标识会通过TLS参数传递, 因此_do_fork替代了老版本的do_fork。
老版本的do_fork只有在如下情况才会定义
只有当系统不支持通过TLS参数通过参数传递而是使用pt_regs寄存器列表传递时
未定义CONFIG_HAVE_COPY_THREAD_TLS宏
其中clone_flags如下表所示
sys_fork的实现
不同体系结构下的fork实现sys_fork主要是通过标志集合区分, 在大多数体系结构上, 典型的fork实现方式与如下
早期实现
asmlinkage long sys_fork(struct pt_regs regs){ return do_fork(SIGCHLD, regs.rsp, ®s, 0);}
新版本
http://lxr.free-electrons.com/source/kernel/fork.c?v=4.5#L1785
#ifdef __ARCH_WANT_SYS_FORKSYSCALL_DEFINE0(fork){#ifdef CONFIG_MMU return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);#else /* can not support in nommu mode */ return -EINVAL;#endif}#endif
我们可以看到唯一使用的标志是SIGCHLD。这意味着在子进程终止后将发送信号SIGCHLD信号通知父进程,
由于写时复制(COW)技术, 最初父子进程的栈地址相同, 但是如果操作栈地址闭并写入数据, 则COW机制会为每个进程分别创建一个新的栈副本
如果do_fork成功, 则新建进程的pid作为系统调用的结果返回, 否则返回错误码
sys_vfork的实现
早期实现
asmlinkage long sys_vfork(struct pt_regs regs){ return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.rsp, ®s, 0);}
新版本
http://lxr.free-electrons.com/source/kernel/fork.c?v=4.5#L1797
#ifdef __ARCH_WANT_SYS_VFORKSYSCALL_DEFINE0(vfork){ return _do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0, 0, NULL, NULL, 0);}#endif
可以看到sys_vfork的实现与sys_fork只是略微不同, 前者使用了额外的标志CLONE_VFORK | CLONE_VM
sys_clone的实现
早期实现
sys_clone的实现方式与上述系统调用类似, 但实际差别在于do_fork如下调用
casmlinkage int sys_clone(struct pt_regs regs){ /* 注释中是i385下增加的代码, 其他体系结构无此定义 unsigned long clone_flags; unsigned long newsp; clone_flags = regs.ebx; newsp = regs.ecx;*/ if (!newsp) newsp = regs.esp; return do_fork(clone_flags, newsp, ®s, 0);}
新版本
http://lxr.free-electrons.com/source/kernel/fork.c?v=4.5#L1805
#ifdef __ARCH_WANT_SYS_CLONE#ifdef CONFIG_CLONE_BACKWARDSSYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, int __user *, parent_tidptr, unsigned long, tls, int __user *, child_tidptr)#elif defined(CONFIG_CLONE_BACKWARDS2)SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags, int __user *, parent_tidptr, int __user *, child_tidptr, unsigned long, tls)#elif defined(CONFIG_CLONE_BACKWARDS3)SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp, int, stack_size, int __user *, parent_tidptr, int __user *, child_tidptr, unsigned long, tls)#elseSYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, int __user *, parent_tidptr, int __user *, child_tidptr, unsigned long, tls)#endif{ return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls);}#endif
我们可以看到sys_clone的标识不再是硬编码的, 而是通过各个寄存器参数传递到系统调用, 因而我们需要提取这些参数。
另外,clone也不再复制进程的栈, 而是可以指定新的栈地址, 在生成线程时, 可能需要这样做, 线程可能与父进程共享地址空间, 但是线程自身的栈可能在另外一个地址空间
另外还指令了用户空间的两个指针(parent_tidptr和child_tidptr), 用于与线程库通信
创建子进程的流程
_do_fork和早起do_fork的流程
_do_fork和do_fork在进程的复制的时候并没有太大的区别, 他们就只是在进程tls复制的过程中实现有细微差别
所有进程复制(创建)的fork机制最终都调用了kernel/fork.c中的_do_fork(一个体系结构无关的函数),
其定义在 http://lxr.free-electrons.com/source/kernel/fork.c?v=4.2#L1679
_do_fork以调用copy_process开始, 后者执行生成新的进程的实际工作, 并根据指定的标志复制父进程的数据。在子进程生成后, 内核必须执行下列收尾操作:
调用 copy_process 为子进程复制出一份进程信息
如果是 vfork(设置了CLONE_VFORK和ptrace标志)初始化完成处理信息
调用 wake_up_new_task 将子进程加入调度器,为之分配 CPU
如果是 vfork,父进程等待子进程完成 exec 替换自己的地址空间
对比,我们从《深入linux内核架构》中找到了早期的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, unsigned long tls){ struct task_struct *p; int trace = 0; long nr; /* * Determine whether and which event to report to ptracer. When * called from kernel_thread or CLONE_UNTRACED is explicitly * requested, no event is reported; otherwise, report if the event * for the type of forking is enabled. */ if (!(clone_flags & CLONE_UNTRACED)) { if (clone_flags & CLONE_VFORK) trace = PTRACE_EVENT_VFORK; else if ((clone_flags & CSIGNAL) != SIGCHLD) trace = PTRACE_EVENT_CLONE; else trace = PTRACE_EVENT_FORK; if (likely(!ptrace_event_enabled(current, trace))) trace = 0; } /* 复制进程描述符,copy_process()的返回值是一个 task_struct 指针 */ p = copy_process(clone_flags, stack_start, stack_size, child_tidptr, NULL, trace, tls); /* * Do this prior waking up the new thread - the thread pointer * might get invalid after that point, if the thread exits quickly. */ if (!IS_ERR(p)) { struct completion vfork; struct pid *pid; trace_sched_process_fork(current, p); /* 得到新创建的进程的pid信息 */ pid = get_task_pid(p, PIDTYPE_PID); nr = pid_vnr(pid); if (clone_flags & CLONE_PARENT_SETTID) put_user(nr, parent_tidptr); /* 如果调用的 vfork()方法,初始化 vfork 完成处理信息 */ if (clone_flags & CLONE_VFORK) { p->vfork_done = &vfork; init_completion(&vfork); get_task_struct(p); } /* 将子进程加入到调度器中,为其分配 CPU,准备执行 */ wake_up_new_task(p); /* forking complete and child started to run, tell ptracer */ if (unlikely(trace)) ptrace_event_pid(trace, pid); /* 如果是 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流程
http://lxr.free-electrons.com/source/kernel/fork.c?v=4.5#L1237
调用 dup_task_struct 复制当前的 task_struct
检查进程数是否超过限制
初始化自旋锁、挂起信号、CPU 定时器等
调用 sched_fork 初始化进程数据结构,并把进程状态设置为 TASK_RUNNING
复制所有进程信息,包括文件系统、信号处理函数、信号、内存管理等
调用 copy_thread_tls 初始化子进程内核栈
为新进程分配并设置新的 pid
对比,我们从《深入linux内核架构》中找到了早期的do_fork流程图,基本一致,可以用来参考学习和对比
主要的区别其实就是最后的copy_thread更改成为copy_thread_tls
/* * This creates a new process as a copy of the old one, * but does not actually start it yet. * * It copies the registers, and all the appropriate * parts of the process environment (as per the clone * flags). The actual kick-off is left to the caller. */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, unsigned long tls){ int retval; struct task_struct *p; retval = security_task_create(clone_flags); if (retval) goto fork_out; // 复制当前的 task_struct retval = -ENOMEM; p = dup_task_struct(current); if (!p) goto fork_out; ftrace_graph_init_task(p); //初始化互斥变量 rt_mutex_init_task(p);#ifdef CONFIG_PROVE_LOCKING DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled); DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);#endif //检查进程数是否超过限制,由操作系统定义 retval = -EAGAIN; if (atomic_read(&p->real_cred->user->processes) >= task_rlimit(p, RLIMIT_NPROC)) { if (p->real_cred->user != INIT_USER && !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN)) goto bad_fork_free; } current->flags &= ~PF_NPROC_EXCEEDED; retval = copy_creds(p, clone_flags); if (retval < 0) goto bad_fork_free; /* * If multiple threads are within copy_process(), then this check * triggers too late. This doesn't hurt, the check is only there * to stop root fork bombs. */ //检查进程数是否超过 max_threads 由内存大小决定 retval = -EAGAIN; if (nr_threads >= max_threads) goto bad_fork_cleanup_count; delayacct_tsk_init(p); /* Must remain after dup_task_struct() */ p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER); p->flags |= PF_FORKNOEXEC; INIT_LIST_HEAD(&p->children); INIT_LIST_HEAD(&p->sibling); rcu_copy_process(p); p->vfork_done = NULL; // 初始化自旋锁 spin_lock_init(&p->alloc_lock); // 初始化挂起信号 init_sigpending(&p->pending); // 初始化 CPU 定时器 posix_cpu_timers_init(p); // ...... /* Perform scheduler related setup. Assign this task to a CPU. 初始化进程数据结构,并把进程状态设置为 TASK_RUNNING */ retval = sched_fork(clone_flags, p); if (retval) goto bad_fork_cleanup_policy; retval = perf_event_init_task(p); /* 复制所有进程信息,包括文件系统、信号处理函数、信号、内存管理等 形式类似于copy_xxx的形式 */ if (retval) goto bad_fork_cleanup_policy; retval = audit_alloc(p); if (retval) goto bad_fork_cleanup_perf; /* copy all the process information */ shm_init_task(p); retval = copy_semundo(clone_flags, p); if (retval) goto bad_fork_cleanup_audit; retval = copy_files(clone_flags, p); if (retval) goto bad_fork_cleanup_semundo; retval = copy_fs(clone_flags, p); if (retval) goto bad_fork_cleanup_files; retval = copy_sighand(clone_flags, p); if (retval) goto bad_fork_cleanup_fs; retval = copy_signal(clone_flags, p); if (retval) goto bad_fork_cleanup_sighand; retval = copy_mm(clone_flags, p); if (retval) goto bad_fork_cleanup_signal; retval = copy_namespaces(clone_flags, p); if (retval) goto bad_fork_cleanup_mm; retval = copy_io(clone_flags, p); if (retval) goto bad_fork_cleanup_namespaces; /* 初始化子进程内核栈 linux-4.2新增处理TLS 之前版本是 retval = copy_thread(clone_flags, stack_start, stack_size, p); */ retval = copy_thread_tls(clone_flags, stack_start, stack_size, p, tls); if (retval) goto bad_fork_cleanup_io; /* 为新进程分配新的pid */ if (pid != &init_struct_pid) { pid = alloc_pid(p->nsproxy->pid_ns_for_children); if (IS_ERR(pid)) { retval = PTR_ERR(pid); goto bad_fork_cleanup_io; } } /* 设置子进程的pid */ /* ok, now we should be set up.. */ p->pid = pid_nr(pid); if (clone_flags & CLONE_THREAD) { p->exit_signal = -1; p->group_leader = current->group_leader; p->tgid = current->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; p->tgid = p->pid; } p->nr_dirtied = 0; p->nr_dirtied_pause = 128 >> (PAGE_SHIFT - 10); p->dirty_paused_when = 0; p->pdeath_signal = 0; INIT_LIST_HEAD(&p->thread_group); p->task_works = NULL; /* * Make it visible to the rest of the system, but dont wake it up yet. * Need tasklist lock for parent etc handling! */ write_lock_irq(&tasklist_lock); /* 调用fork的进程为其父进程 */ /* CLONE_PARENT re-uses the old parent */ if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) { p->real_parent = current->real_parent; p->parent_exec_id = current->parent_exec_id; } else { p->real_parent = current; p->parent_exec_id = current->self_exec_id; } spin_lock(¤t->sighand->siglock); // ...... return p;}
dup_task_struct 流程
http://lxr.free-electrons.com/source/kernel/fork.c?v=4.5#L334
static struct task_struct *dup_task_struct(struct task_struct *orig){ struct task_struct *tsk; struct thread_info *ti; int node = tsk_fork_get_node(orig); int err; //分配一个 task_struct 节点 tsk = alloc_task_struct_node(node); if (!tsk) return NULL; //分配一个 thread_info 节点,包含进程的内核栈,ti 为栈底 ti = alloc_thread_info_node(tsk, node); if (!ti) goto free_tsk; //将栈底的值赋给新节点的栈 tsk->stack = ti; //…… return tsk;}
调用alloc_task_struct_node分配一个 task_struct 节点
调用alloc_thread_info_node分配一个 thread_info 节点,其实是分配了一个thread_union联合体,将栈底返回给 ti
union thread_union { struct thread_info thread_info; unsigned long stack[THREAD_SIZE/sizeof(long)];};
最后将栈底的值 ti 赋值给新节点的栈
最终执行完dup_task_struct之后,子进程除了tsk->stack指针不同之外,全部都一样!
sched_fork 流程
int sched_fork(unsigned long clone_flags, struct task_struct *p){ unsigned long flags; int cpu = get_cpu(); __sched_fork(clone_flags, p); // 将子进程状态设置为 TASK_RUNNING p->state = TASK_RUNNING; // …… // 为子进程分配 CPU set_task_cpu(p, cpu); put_cpu(); return 0;}
我们可以看到sched_fork大致完成了两项重要工作,
一是将子进程状态设置为 TASK_RUNNING,
二是为其分配 CPU
copy_thread和copy_thread_tls流程
我们可以看到linux-4.2之后增加了copy_thread_tls函数和CONFIG_HAVE_COPY_THREAD_TLS宏
但是如果未定义CONFIG_HAVE_COPY_THREAD_TLS宏默认则使用copy_thread同时将定义copy_thread_tls为copy_thread
单独将这个函数是因为这个复制操作与其他操作都不相同, 这是一个特定于体系结构的函数,用于复制进程中特定于线程(thread-special)的数据, 重要的就是填充task_struct->thread的各个成员,这是一个thread_struct类型的结构, 其定义是依赖于体系结构的。它包含了所有寄存器(和其他信息),内核在进程之间切换时需要保存和恢复的进程的信息。
该函数用于设置子进程的执行环境,如子进程运行时各CPU寄存器的值、子进程的内核栈的起始地址(指向内核栈的指针通常也是保存在一个特别保留的寄存器中)
#ifdef CONFIG_HAVE_COPY_THREAD_TLSextern int copy_thread_tls(unsigned long, unsigned long, unsigned long, struct task_struct *, unsigned long);#elseextern int copy_thread(unsigned long, unsigned long, unsigned long, struct task_struct *);/* Architectures that haven't opted into copy_thread_tls get the tls argument * via pt_regs, so ignore the tls argument passed via C. */static inline int copy_thread_tls( unsigned long clone_flags, unsigned long sp, unsigned long arg, struct task_struct *p, unsigned long tls){ return copy_thread(clone_flags, sp, arg, p);}#endif
下面我们来看32位架构的copy_thread_tls函数,他与原来的copy_thread变动并不大, 只是多了后面TLS的设置信息
int copy_thread_tls(unsigned long clone_flags, unsigned long sp, unsigned long arg, struct task_struct *p, unsigned long tls){ struct pt_regs *childregs = task_pt_regs(p); struct task_struct *tsk; int err; /* 获取寄存器的信息 */ p->thread.sp = (unsigned long) childregs; p->thread.sp0 = (unsigned long) (childregs+1); memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps)); if (unlikely(p->flags & PF_KTHREAD)) { /* kernel thread 内核线程的设置 */ memset(childregs, 0, sizeof(struct pt_regs)); p->thread.ip = (unsigned long) ret_from_kernel_thread; task_user_gs(p) = __KERNEL_STACK_CANARY; childregs->ds = __USER_DS; childregs->es = __USER_DS; childregs->fs = __KERNEL_PERCPU; childregs->bx = sp; /* function */ childregs->bp = arg; childregs->orig_ax = -1; childregs->cs = __KERNEL_CS | get_kernel_rpl(); childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED; p->thread.io_bitmap_ptr = NULL; return 0; } /* 将当前寄存器信息复制给子进程 */ *childregs = *current_pt_regs(); /* 子进程 eax 置 0,因此fork 在子进程返回0 */ childregs->ax = 0; if (sp) childregs->sp = sp; /* 子进程ip 设置为ret_from_fork,因此子进程从ret_from_fork开始执行 */ p->thread.ip = (unsigned long) ret_from_fork; task_user_gs(p) = get_user_gs(current_pt_regs()); p->thread.io_bitmap_ptr = NULL; tsk = current; err = -ENOMEM; if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP))) { p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr, IO_BITMAP_BYTES, GFP_KERNEL); if (!p->thread.io_bitmap_ptr) { p->thread.io_bitmap_max = 0; return -ENOMEM; } set_tsk_thread_flag(p, TIF_IO_BITMAP); } err = 0; /* * Set a new TLS for the child thread? * 为进程设置一个新的TLS */ if (clone_flags & CLONE_SETTLS) err = do_set_thread_area(p, -1, (struct user_desc __user *)tls, 0); if (err && p->thread.io_bitmap_ptr) { kfree(p->thread.io_bitmap_ptr); p->thread.io_bitmap_max = 0; } return err;}
copy_thread_tls 这段代码为我们解释了两个相当重要的问题!
一是,为什么 fork 在子进程中返回0,原因是childregs->ax = 0;这段代码将子进程的 eax 赋值为0
二是,p->thread.ip = (unsigned long) ret_from_fork;将子进程的 ip 设置为 ret_form_fork 的首地址,因此子进程是从 ret_from_fork 开始执行的
总结
fork, vfork和clone的系统调用的入口地址分别是sys_fork, sys_vfork和sys_clone, 而他们的定义是依赖于体系结构的, 而他们最终都调用了_do_fork(linux-4.2之前的内核中是do_fork),在_do_fork中通过copy_process复制进程的信息,调用wake_up_new_task将子进程加入调度器中
fork系统调用对应的kernel函数是sys_fork,此函数简单的调用kernel函数_do_fork。一个简化版的_do_fork执行如下:
copy_process()此函数会做fork的大部分事情,它主要完成讲父进程的运行环境复制到新的子进程,比如信号处理、文件描述符和进程的代码数据等。
wake_up_new_task()。计算此进程的优先级和其他调度参数,将新的进程加入到进程调度队列并设此进程为可被调度的,以后这个进程可以被进程调度模块调度执行。
简化的copy_process()流程
dup_task_struct()。分配一个新的进程控制块,包括新进程在kernel中的堆栈。新的进程控制块会复制父进程的进程控制块,但是因为每个进程都有一个kernel堆栈,新进程的堆栈将被设置成新分配的堆栈。
初始化一些新进程的统计信息,如此进程的运行时间
copy_semundo()复制父进程的semaphore undo_list到子进程。
copy_files()、copy_fs()。复制父进程文件系统相关的环境到子进程
copy_sighand()、copy_signal()。复制父进程信号处理相关的环境到子进程。
copy_mm()。复制父进程内存管理相关的环境到子进程,包括页表、地址空间和代码数据。
copy_thread()/copy_thread_tls。设置子进程的执行环境,如子进程运行时各CPU寄存器的值、子进程的kernel栈的起始地址。
sched_fork()。设置子进程调度相关的参数,即子进程的运行CPU、初始时间片长度和静态优先级等。
将子进程加入到全局的进程队列中
设置子进程的进程组ID和对话期ID等。
简单的说,copy_process()就是将父进程的运行环境复制到子进程并对某些子进程特定的环境做相应的调整。
此外应用程序使用系统调用exit()来结束一个进程,此系统调用接受一个退出原因代码,父进程可以使用wait()系统调用来获取此代码,从而知道子进程退出的原因。对应到kernel,此系统调用sys_exit_group(),它的基本流程如下:
将信号SIGKILL加入到其他线程的信号队列中,并唤醒这些线程。
此线程执行do_exit()来退出。
do_exit()完成线程退出的任务,其主要功能是将线程占用的系统资源释放,do_exit()的基本流程如下:
1. 将进程内存管理相关的资源释放
将进程ICP semaphore相关资源释放
__exit_files()、__exit_fs()。将进程文件管理相关的资源释放。
exit_thread()。只要目的是释放平台相关的一些资源。
exit_notify()。在Linux中进程退出时要将其退出的原因告诉父进程,父进程调用wait()系统调用后会在一个等待队列上睡眠。
schedule()。调用进程调度器,因为此进程已经退出,切换到其他进程。
进程的创建到执行过程如下图所示
- 本文已收录于以下专栏:
相关文章推荐
-
linux date指令
功能说明:显示或设置系统时间与日期。 语 法:date [-d ][-u][+%H%I%K%l%M%P%r%s%S%T%X%Z%a%A%b%B%c%d%D%j%m%U%w%x%y%Y%n%t]…- lengyuhong
- 2011-05-17 17:23
- 2285
-
fork进程的过程
要搞清楚fork的执行过程,就必须先讲清楚操作系统中的”进程(process)”概念。一个进程,主要包含三个元素:o. 一个可以执行的程序; o. 和该进程相关联的全部数据(包括变量,内存空间,缓冲区…- mounter625
- 2011-05-31 20:44
- 6059
-
伤心!年度热门编程语言排行榜…
近期,IEEE Spectrum 发布了第四届顶级编程语言交互排行榜。榜首又是Python!此外,七牛云许式伟曾说过Go语言会取代Java…
-
fork之后子进程到底复制了父进程什么
假定父进程malloc的指针指向0x12345678, fork 后,子进程中的指针也是指向0x12345678,但是这两个地址都是虚拟内存地址 (virtual memory),经过内存地址转换后所…- xy010902100449
- 2015-04-03 09:54
- 7408
-
linux top指令
linux top指令- lengyuhong
- 2010-09-01 11:35
- 3278
-
Linux中的内存管理
一.在Linux操作系统的内存中共有五块空间:代码段,数据段,BBS段,堆和栈 代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存种的镜像。代码段需要防止在运行时被非法修改,…- lengyuhong
- 2009-11-25 15:43
- 1227
-
Linux脚本(shell)编程(三) 文件操作
1. 判断文件是否存在 -e filename例如:#!/bin/bash
filename=/home/jifeng/shell/file
if [ -efilename ]the…- lengyuhong
- 2011-02-18 17:44
- 2998
-
Linux脚本(shell)编程(二) 基本语法
shell的基本语法赋值一般采用以下形式:变量名=字符串1. “=”号两边是不能有空格的,不然会出错的。(这点初学者特别容易出错)2. 若赋值语句中,“=”后面没有任何内容,则该变量为一个空字符串,若…- lengyuhong
- 2011-01-09 20:57
- 5440
-
每天一道面试题(二)
(合合信息科技.2013/10/19)给定一长一短的两个字符串A,B,假设A长B短,现在要你判断B是否包含在字符串A中(不区分大小写)。并给出算法计算复杂度和存储复杂度。比如,如果是下面的两个字符串s…- THEONE10211024
- 2013-10-31 11:59
- 1655
-
转载:fork出的子进程和父进程
转载:fork出的子进程和父进程 fork是UNIX关于进程管理的一个术语,本质是新开一个进程,但是不从磁盘加载代码,而是从内存现有进程复制一份。 转自:http://blog.csdn…- u010429424
- 2017-09-25 19:02
- 47
-
fork出的子进程和父进程
一、fork后的父子进程由fork创建的新进程被称为子进程(child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新进程(子进…- THEONE10211024
- 2013-10-31 16:43
- 27481
-
Linux进程创建二——do_fork
前言kernel在启动初期并没有“进程”这个概念,如果不涉及支持多任务并发、调度,kernel可以一直以一个控制流运行。本篇从内核初始化时的0进程开始分析,延伸到多进程的创建。0 进程内核…- yin262
- 2017-01-17 21:12
- 182
-
Linux进程启动过程分析do_execve(可执行程序的加载和运行)—Linux进程的管理与调度(十一)
日期 内核版本 架构 作者 GitHub CSDN 2016-06-06 Linux-4.5 X86 & arm gatieme LinuxDeviceDrive…- gatieme
- 2016-06-06 11:46
- 5985
-
进程的创建 —— do_fork()函数详解
原文链接http://blog.csdn.net/yunsongice/article/details/5508242在讲进程管理专题最核心的课题——进程创建之前,我们先简单地回顾一下上一篇博…- hsly_support
- 2012-03-26 22:44
- 871
-
Linux进程退出详解(do_exit)–Linux进程的管理与调度(十四))
日期 内核版本 架构 作者 GitHub CSDN 2016-05-12 Linux-4.6 X86 & arm gatieme LinuxDeviceDrive…- gatieme
- 2016-06-11 23:31
- 8880
-
Linux0.11 创建进程的过程分析–fork函数的使用
/** linux/kernel/fork.c** (C) 1991 Linus Torvalds*//*注意:signal.c和fork.c文件的编译选项内不能有vc变量优化选项/Og,…- DLUTBruceZhang
- 2013-07-11 14:52
- 1373
-
Linux的do_fork函数的执行过程
linux的do_fork函数的执行过程。- chongdajerry
- 2017-03-29 17:59
- 471
-
Linux下进程管理(函数fork,wait,exec的用法)
在我们编程中用的最多是函数,也就是如何函数调用。那我们如何调用函数呢?一:我们必须要知道函数的功能是什么?二:再看这个函数需要哪些参数?三:最后看返回值是什么?当我们面对一个函数时,既…- jack_zyk
- 2012-04-13 21:12
- 1337
-
Linux下C进程管理(fork,wait,exec)
在我们编程中用的最多是函数,也就是如何函数调用。那我们如何调用函数呢?一:我们必须要知道函数的功能是什么?二:再看这个函数需要哪些参数?三:最后看返回值是什么?当我们面对一个函数时,既…- liangzhao_jay
- 2015-06-02 17:00
- 691
-
linux do_fork()源代码分析
//网上有很多分析do_fork(),但是将的都不详细。这个会比较详细。do_fork()分析从上文可得知, fork、vfork和clone三个系统调用所对应的系统调用服务例程均调用…- a418382926
- 2014-04-02 15:39
- 883
-
linux下fork函数创建进程
有3个进程,其中一个为父进程,其余两个是该父进程创建的子进程,其中一个子进程运行“ls -l”指令,另一个子进程在暂停5s后异常退出。父进程先用阻塞方式等待第一个进程的结束,然后用非阻塞方式等待另一…- ChuJiangKeDeJiuShu
- 2014-01-01 11:51
- 666
- 原创
- 423
- 粉丝
- 889
- 喜欢
- 0
- 码云
- 未开通
他的最新文章
更多文章- Ubuntu安装新版的 Thunderbird 邮件客户端
- 告别S! S! H! 秒杀终端工具——FastLogin快捷登录
- Ubuntu更新卡在 flashplugin-installer
- Git 远程库操作详解
编辑推荐
最新专栏
- Linux高性能服务器编程
- Linux内核剖析
- C++设计模式
- linux date指令
- fork进程的过程
- fork之后子进程到底复制了父进程什么
- linux top指令
- Linux中的内存管理
在线课程
-
Presto的服务治理与架构在京东的实践与应用
讲师:王哲涵
-
深入掌握Kubernetes应用实践
讲师:王渊命
- Linux下进程的创建过程分析(_do_fork/do_fork详解)--Linux进程的管理与调度
- Linux下进程的创建过程分析(_do_fork/do_fork详解)--Linux进程的管理与调度(八)
- Linux进程的管理与调度(八) -- Linux下进程的创建过程分析(_do_fork/do_fork详解)
- Linux内核进程管理-do_fork()执行过程分析
- Linux进程上下文切换过程context_switch详解--Linux进程的管理与调度(二十一)
- Linux内核分析:理解进程调度时机跟踪分析进程调度与进程切换的过程
- 理解进程调度时机跟踪分析进程调度与进程切换的过程(Linux)
- Linux进程启动过程分析do_execve(可执行程序的加载和运行)---Linux进程的管理与调度(十一)
- ELF文件的加载过程(load_elf_binary函数详解)--Linux进程的管理与调度(十三)
- Linux进程的管理与调度(四) -- Linux下的进程类别以及其创建方式
- Linux下的进程类别(内核线程、轻量级进程和用户进程)以及其创建方式--Linux进程的管理与调度(四)
- Linux进程的管理与调度
- Linux进程内核栈与thread_info结构详解--Linux进程的管理与调度(九)
- Linux下进程调度与优先级的深入分析
- 网易公开课《Linux内核分析》学习心得-理解进程调度时机跟踪分析进程调度与进程切换的过程
- 《Linux操作系统分析》之理解进程调度时机跟踪分析进程调度与进程切换的过程
- Linux内核分析之理解进程调度时机跟踪分析进程调度与进程切换的过程
- Linux内核分析课程-- 理解进程调度时机跟踪分析进程调度与进程切换的过程
- 深入浅出React之第三章:使用redux管理应用状态
- Codeforces Round #438:F. Yet Another Minimization Problem(DP决策单调性+二分+莫队)
- 欢迎使用CSDN-markdown编辑器
- 深入浅出React之第四章:推荐的Redux目录结构
- linux命令好多
- Linux下进程的创建过程分析(_do_fork/do_fork详解)--Linux进程的管理与调度
- java int型转char型
- 深入浅出React之第五章:React组件的性能优化
- md5解密技术
- 验证码绘制
- 深入浅出React之第六章:Redux和服务器通信
- 深入浅出React之第七章:使用React-Router实现多页面应用
- 简单回答单点登录的三种方式
- 五. PyQuery