Linux进程管理(二、 进程创建)
通常使用fork创建进程, 也可以用vfork()和clone()。
fork、vfork和clone三个用户态函数均由libc库提供,它们分别会调用Linux内核提供的同名系统调用fork,vfork和clone。
vfork与fork的区别在于创建进程时, vfork完全共享父进程的地址空间,包括页表项。vfork在早期用来替代fork以避免复制地址空间的耗时操作,
但是现在fork已经使用了写时复制技术,在进程创建时,只复制父进程的页表项,用只读的方式共享父进程地址空间,在写入时再复制数据。
fork有了这个优化之后, vfork已较少使用。
fork调用链:
| libc::fork() | --->int$0×80软中断--->| kernel: ENTRY(system_call)--->ENTRY(sys_call_table)--->sys_fork()--->do_fork() |
//----------------------------------------- file: process.c -------------------------------------------- asmlinkage long sys_fork(void)
{
struct pt_regs *regs = task_pt_regs(current);
return do_fork(SIGCHLD, regs->gprs[15], regs, 0, NULL, NULL);
}
//----------------------------------------- file: fork.c ------------------------------------------------- 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)
{
struct task_struct *p;
long nr;
...
p = copy_process(clone_flags, stack_start, regs, stack_size,
child_tidptr, NULL); //最核心的工作:通过copy_process()创建子进程的描述符,分配pid等
if (!IS_ERR(p)) {
...
if (!(clone_flags & CLONE_STOPPED))
wake_up_new_task(p, clone_flags); //将进程放到运行队列上,从而让调度器进行调度运行
else
p->state = TASK_STOPPED;
...
} else {
nr = PTR_ERR(p);
}
return nr;
}
//----------------------------------------- file: fork.c ------------------------------------------------- static struct task_struct *copy_process(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *child_tidptr,
struct pid *pid)
{
int retval;
struct task_struct *p;
int cgroup_callbacks_done = 0; //...
//检查clone_flags
//...
p = dup_task_struct(current); //创建内核栈, thread_info, task_struct, 里面的值是完全复制的父进程的值
if (!p)
goto fork_out; ... /*
* 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.
*/
//检查当前进程总数是否超出最大进程数
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count; //...
//复制、更新子进程描述符的flags成员,
copy_flags(clone_flags, p); //...
//初始化子进程描述符的各个字段,很多被清零,使得子进程和父进程逐渐区别出来。 不过大部分数据仍然未被修改
//... //调度器设置。调用sched_fork函数执行调度器相关的设置
/* Perform scheduler related setup. Assign this task to a CPU. */
sched_fork(p, clone_flags); //-------------------------start 根据clone_flags的具体取值来为子进程拷贝或共享父进程的某些数据结构--------//
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 */
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_namespaces(clone_flags, p))) //命名空间
goto bad_fork_cleanup_keys;
//复制线程。通过copy_threads()函数更新子进程的内核栈和寄存器中的值
retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
if (retval)
goto bad_fork_cleanup_namespaces; //-----------------------------------------end -------------------------------------------------// //分配pid
if (pid != &init_struct_pid) {
retval = -ENOMEM;
pid = alloc_pid(task_active_pid_ns(p));
if (!pid)
goto bad_fork_cleanup_namespaces; if (clone_flags & CLONE_NEWPID) {
retval = pid_ns_prepare_proc(task_active_pid_ns(p));
if (retval < 0)
goto bad_fork_free_pid;
}
} p->pid = pid_nr(pid);
p->tgid = p->pid;
if (clone_flags & CLONE_THREAD)
p->tgid = current->tgid; p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL; ...
//更改p的其他一些字段
... if (likely(p->pid)) {
add_parent(p);
if (unlikely(p->ptrace & PT_PTRACED))
__ptrace_link(p, current->parent); if (thread_group_leader(p)) {
if (clone_flags & CLONE_NEWPID)
p->nsproxy->pid_ns->child_reaper = p; p->signal->tty = current->signal->tty;
set_task_pgrp(p, task_pgrp_nr(current));
set_task_session(p, task_session_nr(current));
attach_pid(p, PIDTYPE_PGID, task_pgrp(current));
attach_pid(p, PIDTYPE_SID, task_session(current));
list_add_tail_rcu(&p->tasks, &init_task.tasks);
__get_cpu_var(process_counts)++;
}
attach_pid(p, PIDTYPE_PID, pid);
nr_threads++;
} total_forks++;
spin_unlock(¤t->sighand->siglock);
write_unlock_irq(&tasklist_lock);
proc_fork_connector(p);
cgroup_post_fork(p);
return p; //...
//错误处理
//...
}
看到这里应该可以知道刚开始提出的第一个问题, 子进程是否能使用父进程的文件描述符?
答案是肯定的,因为task_struct里面成员struct files_struct *files 保存了所有进程打开的文件描述符。
创建子进程的时候,会将files复制一份到子进程files结构体里, 里面的值跟父进程的值是一样的,可以用来操作父进程打开的文件。
如下面代码里的:newf = dup_fd(oldf, &error);
如果调用do_fork的时候传入clone_flags: CLONE_FILES, 那么子进程直接共享父进程files成员,不会复制, 这时候当然更可以用来操作父进程的文件。
线程就是这么做的。
static int copy_files(unsigned long clone_flags, struct task_struct * tsk)
{
struct files_struct *oldf, *newf;
int error = 0; /*
* 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;
} /*
* Note: we may be using current for both targets (See exec.c)
* This works because we cache current->files (old) as oldf. Don't
* break this.
*/
tsk->files = NULL;
newf = dup_fd(oldf, &error);
if (!newf)
goto out; tsk->files = newf;
error = 0;
out:
return error;
}
子进程复制父进程的所有文件描述符:
Linux进程管理(二、 进程创建)的更多相关文章
- Linux进程管理(二)
目录 Linux进程管理(二) 参考 vmstat命令 top命令 Linux进程管理(二)
- Linux进程管理 (1)进程的诞生
专题:Linux进程管理专题 目录: Linux进程管理 (1)进程的诞生 Linux进程管理 (2)CFS调度器 Linux进程管理 (3)SMP负载均衡 Linux进程管理 (4)HMP调度器 L ...
- Linux 内核进程管理之进程ID 。图解
http://www.cnblogs.com/hazir/tag/kernel/ Linux 内核进程管理之进程ID Linux 内核使用 task_struct 数据结构来关联所有与进程有关的数 ...
- linux进程管理之进程创建
所谓进程就是程序执行时的一个实例. 它是现代操作系统中一个很重要的抽象,我们从进程的生命周期:创建,执行,消亡来分析一下Linux上的进程管理实现. 一:前言 进程管理结构; 在内核中,每一个进程对应 ...
- linux进程管理之进程创建(三)
在linux系统中,许多进程在诞生之初都与其父进程共同用一个存储空间.但是子进程又可以建立自己的存储空间,并与父进程“分道扬镳”,成为与父进程一样真正意义上的进程. linux系统运行的第一个进程是在 ...
- Linux 内核进程管理之进程ID
Linux 内核使用 task_struct 数据结构来关联所有与进程有关的数据和结构,Linux 内核所有涉及到进程和程序的所有算法都是围绕该数据结构建立的,是内核中最重要的数据结构之一.该数据结构 ...
- Linux内存管理 一个进程究竟占用多少空间?-VSS/RSS/PSS/USS
关键词:VSS.RSS.PSS.USS._mapcount.pte_present.mem_size_stats. 在Linux里面,一个进程占用的内存有不同种说法,可以是VSS/RSS/PSS/US ...
- Linux 内核进程管理之进程ID【转】
转自:http://www.cnblogs.com/hazir/p/linux_kernel_pid.html Linux 内核使用 task_struct 数据结构来关联所有与进程有关的数据和结构, ...
- 【Android手机测试】linux内存管理 -- 一个进程占多少内存?四种计算方法:VSS/RSS/PSS/USS
在Linux里面,一个进程占用的内存有不同种说法,可以是VSS/RSS/PSS/USS四种形式,这四种形式首字母分别是Virtual/Resident/Proportional/Unique的意思. ...
- linux进程篇 (二) 进程的基本控制
2. 进程的基本操作 接口函数 #include <unistd.h> //创建子进程 pid_t fork(void); //结束子进程 void exit(int status); / ...
随机推荐
- Boost test vs2013 fatal error C1001
Boost test vs2013 fatal error C1001 Boost test库提供了一个用于单元测试的基于命令行界面的测试套件UTF:Unit Test Framework,具有单元测 ...
- 013- unittest单元测试框架
unittest单元测试框架 重要的概念 1. TestCase TestCase 是最小的测试单元,用于检查特定输入集合的特定返回值.unittest提供了TestCase基类,我们创建的测试类需要 ...
- js 之观察者模式
观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自 ...
- Laravel使用EasyWechat 进行微信支付
微信支付和EasyWeChat这个包都是巨坑, 文档写的稀烂, 记录下防止以后又重复踩坑: 安装教程在这: https://www.jianshu.com/p/82d688e1fd2a
- spring源码学习之bean的加载(二)
这是接着上篇继续写bean的加载过程,好像是有点太多了,因为bean的加载过程是很复杂的,要处理的情况有很多,继续... 7.创建bean 常规的bean的创建时通过doCreateBean方法来实现 ...
- 读取复杂结构的yml配置项
1.yml配置项示例:(List的集合在第一项前面加 “-”) rabbitmqsetting: exchangeList: - name: e1 type: topic bindingList: - ...
- 洛谷P3306 随机数生成器
题意:给你一个数列,a1 = x,ai = (A * ai-1 + B) % P,求第一个是t的是哪一项,或者永远不会有t. 解:循环节不会超过P.我们使用BSGS的思想,预处理从t开始跳√P步的,插 ...
- CodeChef--Cards, bags and coins
题目链接 Yet another game from chef. Chef gives you N cards and M bags. Each of the N cards has an integ ...
- 从默认的index.jsp页面跳转或转发到其他页面
使用forward还是redirect都可以完成跳转 forward:浏览器地址不变,所以存在重复提交的问题 <% pageContext.forward("student/list ...
- php的FTP操作类
class_ftp.php <?php /** * 作用:FTP操作类( 拷贝.移动.删除文件/创建目录 ) */ class class_ftp { public $off; // 返回操作状 ...