通常使用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(&current->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进程管理(二、 进程创建)的更多相关文章

  1. Linux进程管理(二)

    目录 Linux进程管理(二) 参考 vmstat命令 top命令 Linux进程管理(二)

  2. Linux进程管理 (1)进程的诞生

    专题:Linux进程管理专题 目录: Linux进程管理 (1)进程的诞生 Linux进程管理 (2)CFS调度器 Linux进程管理 (3)SMP负载均衡 Linux进程管理 (4)HMP调度器 L ...

  3. Linux 内核进程管理之进程ID 。图解

    http://www.cnblogs.com/hazir/tag/kernel/ Linux 内核进程管理之进程ID   Linux 内核使用 task_struct 数据结构来关联所有与进程有关的数 ...

  4. linux进程管理之进程创建

    所谓进程就是程序执行时的一个实例. 它是现代操作系统中一个很重要的抽象,我们从进程的生命周期:创建,执行,消亡来分析一下Linux上的进程管理实现. 一:前言 进程管理结构; 在内核中,每一个进程对应 ...

  5. linux进程管理之进程创建(三)

    在linux系统中,许多进程在诞生之初都与其父进程共同用一个存储空间.但是子进程又可以建立自己的存储空间,并与父进程“分道扬镳”,成为与父进程一样真正意义上的进程. linux系统运行的第一个进程是在 ...

  6. Linux 内核进程管理之进程ID

    Linux 内核使用 task_struct 数据结构来关联所有与进程有关的数据和结构,Linux 内核所有涉及到进程和程序的所有算法都是围绕该数据结构建立的,是内核中最重要的数据结构之一.该数据结构 ...

  7. Linux内存管理 一个进程究竟占用多少空间?-VSS/RSS/PSS/USS

    关键词:VSS.RSS.PSS.USS._mapcount.pte_present.mem_size_stats. 在Linux里面,一个进程占用的内存有不同种说法,可以是VSS/RSS/PSS/US ...

  8. Linux 内核进程管理之进程ID【转】

    转自:http://www.cnblogs.com/hazir/p/linux_kernel_pid.html Linux 内核使用 task_struct 数据结构来关联所有与进程有关的数据和结构, ...

  9. 【Android手机测试】linux内存管理 -- 一个进程占多少内存?四种计算方法:VSS/RSS/PSS/USS

    在Linux里面,一个进程占用的内存有不同种说法,可以是VSS/RSS/PSS/USS四种形式,这四种形式首字母分别是Virtual/Resident/Proportional/Unique的意思. ...

  10. linux进程篇 (二) 进程的基本控制

    2. 进程的基本操作 接口函数 #include <unistd.h> //创建子进程 pid_t fork(void); //结束子进程 void exit(int status); / ...

随机推荐

  1. 如何用SPSS做联合分析

    如何用SPSS做联合分析 如果产品的描述是由几个属性特征决定的,比如说mp3的音质.外形.容量.价格等等,商家为了确定哪个属性对消费者的影响最大,以及预测什么样的属性组合最受消费者的欢迎,选择的办法应 ...

  2. Filter - 过滤敏感词汇(动态代理)

    /** * 敏感词汇过滤器 */ @WebFilter("/*") public class SensitiveWordsFilter implements Filter { pu ...

  3. 公司jar包提交到集群的方法

    yarn -jar xx.jar 此时包会提交到集群上运行 也可以把jar包放到hbase 的lib下面用hbase jar 方式调用

  4. 跟我一起做一个vue的小项目(十)

    接下来我们对城市列表页面进行优化,除了对数据优化,也会进行节流处理 //src\pages\city\components\Alphabet.vue <template> <ul c ...

  5. Ubuntn16.04安装opencv3.1(特别注意环境变量)

    参考:http://lib.csdn.net/article/opencv/25737: http://blog.csdn.net/yiranyhy/article/details/72935499: ...

  6. Windows Sublime text3 搭建Go语言环境

    第一步:Go环境和配置 1.安装 Go 开发环境(省略),假设Go安装目录为 C:\Go 2.配置环境变量,下面两个环境变脸没有就加上. 资料参考:http://studygolang.com/art ...

  7. Linux中管理员用户与普通用户之间的切换

    使用su进行用户切换 管理员用户切换至普通用户:  su [用户名] 使用su命令从高级别用户切换至低级别用户无需输入密码 普通用户切换至管理员用户:  普通用户切换至管理员用户使用 su - 使用l ...

  8. OpenLayers修改要素

    <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...

  9. 一段简单简介的JAVA内存分页代码

    1.原因 工作中有的时候我们要处理的分页是无法全部用数据库去处理的,因为有些业务数据需要计算,所以我们需要把数据拿到程序中去分页 2.代码 //前端传入分页参数 Pageable pageable = ...

  10. Codevs2490 导弹防御塔

    2490 导弹防御塔 2490 导弹防御塔 时间限制: 1 s 空间限制: 64000 KB 题目等级 : 大师 Master         题目描述 Description Freda的城堡—— ...