fork是一个系统调用,系统调用的流程,流程的最后会在sys_call_table中找到相应的系统调用sys_fork。,sys_fork的定义如下:

SYSCALL_DEFINE0(fork)
{
......return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
}

sys_fork会调用_do_fork,_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;
......p = copy_process(clone_flags, stack_start, stack_size,child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
......if (!IS_ERR(p)) {struct pid *pid;pid = get_task_pid(p, PIDTYPE_PID);nr = pid_vnr(pid);if (clone_flags & CLONE_PARENT_SETTID)put_user(nr, parent_tidptr);
......wake_up_new_task(p);
......put_pid(pid);
}
......

复制结构

_do_fork里面做的第一件大事就是copy_process,如果所有数据结构都从头创建一份太麻烦了,还不如使用惯用“伎俩”,Ctrl C + Ctrl V。task_struct的结构图如下:

copy_process代码具体实现如下:

static __latent_entropy 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;
......p = dup_task_struct(current, node);

在dup_task_struct主要做了下面几件事情:

调用alloc_task_struct_node分配一个task_struct结构;

调用alloc_thread_stack_node来创建内核栈,这里面调用__vmalloc_node_range分配一个连续的THREAD_SIZE的内存空间,赋值给task_struct的void *stack成员变量;

调用arch_dup_task_struct(struct task_struct *dst, struct task_struct *src),将task_struct 进行复制,其实就是调用memcpy;

调用setup_thread_stack设置thread_info。

这里之后task_strcut复制了一份,而且内核栈也创建好了。

接着继续看copy_process:

retval = copy_creds(p, clone_flags);

这里就是跟权限相关了,这里的copy_creds主要做了下面几件事情:

调用prepare_creds,准备一个新的struct cred *new。如何准备呢?其实还是从内存中分配一个新的struct cred结构,然后调用memcpy复制一份父进程的cred;

接着p->cred = p->real_cred = get_cred(new),将新进程的“我能操作谁”和“谁能操作我”两个权限都指向新的cred。

接下来,copy_process重新设置进程运行的统计量

p->utime = p->stime = p->gtime = 0;
p->start_time = ktime_get_ns();
p->real_start_time = ktime_get_boot_ns();

接下来,copy_process开始设置调度相关的变量

retval = sched_fork(clone_flags, p);

sched_fork主要做了下面几件事情:

调用__sched_fork,在这里面将on_rq设为0,初始化sched_entity,将里面的exec_start、 sum_exec_runtime、prev_sum_exec_runtime、vruntime都设为0。你还记得吗,这几个变 量涉及进程的实际运行时间和虚拟运行时间。是否到时间应该被调度了,就靠它们几个;

设置进程的状态p->state = TASK_NEW; 初始化优先级prio、normal_prio、static_prio;

设置调度类,如果是普通进程,就设置为p->sched_class = &fair_sched_class;

调用调度类的task_fork函数,对于CFS来讲,就是调用task_fork_fair。在这个函数里,先调 用update_curr,对于当前的进程进行统计量更新,然后把子进程和父进程的vruntime设成一 样,最后调用place_entity,初始化sched_entity。这里有一个变量 sysctl_sched_child_runs_first,可以设置父进程和子进程谁先运行。如果设置了子进程先运 行,即便两个进程的vruntime一样,也要把子进程的sched_entity放在前面,然后调用 resched_curr,标记当前运行的进程TIF_NEED_RESCHED,也就是说,把父进程设置为应该 被调度,这样下次调度的时候,父进程会被子进程抢占。

接下来,copy_process开始初始化与文件和文件系统相关的变量

retval = copy_files(clone_flags, p);
retval = copy_fs(clone_flags, p);

copy_files主要用于复制一个进程打开的文件信息。这些信息用一个结构files_struct来维护,每个打开的文件都有一个文件描述符。在copy_files函数里面调用dup_fd,在这里面会创建一个新的files_struct,然后将所有的文件描述符数组fdtable拷贝一份。

copy_fs主要用于复制一个进程的目录信息。这些信息用一个结构fs_struct来维护。一个进程有自己的根目录和根文件系统root,也有当前目录pwd和当前目录的文件系统,都在fs_struct里面 维护。copy_fs函数里面调用copy_fs_struct,创建一个新的fs_struct,并复制原来进程的 fs_struct。

接下来,copy_process开始初始化与信号相关的变量

init_sigpending(&p->pending);
retval = copy_sighand(clone_flags, p);
retval = copy_signal(clone_flags, p);

copy_sighand会分配一个新的sighand_struct。这里最主要的是维护信号处理函数,在 copy_sighand里面会调用memcpy,将信号处理函数sighand->action从父进程复制到子进程。

init_sigpending和copy_signal用于初始化,并且复制用于维护发给这个进程的信号的数据结 构。copy_signal函数会分配一个新的signal_struct,并进行初始化。

接下来,copy_process开始复制进程内存空间

retval = copy_mm(clone_flags, p);

进程都自己的内存空间,用mm_struct结构来表示。copy_mm函数中调用dup_mm,分配一个新的mm_struct结构,调用memcpy复制这个结构。dup_mmap用于复制内存空间中内存映射的部分。前面讲系统调用的时候,我们说过,mmap可以分配大块的内存,其实mmap也可以将 一个文件映射到内存中,方便可以像读写内存一样读写文件

接下来,copy_process开始分配pid,设置tid,group_leader,并且建立进程之间的亲缘关系

    INIT_LIST_HEAD(&p->children);INIT_LIST_HEAD(&p->sibling);
......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;elsep->exit_signal = (clone_flags & CSIGNAL);p->group_leader = p;p->tgid = p->pid;
}
......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;
}

唤醒新进程

_do_fork做的第二件大事是wake_up_new_task。新任务刚刚建立,有没有机会抢占别人,获得 CPU呢?首先,我们需要将进程的状态设置为TASK_RUNNING

void wake_up_new_task(struct task_struct *p)
{struct rq_flags rf;struct rq *rq;
......p->state = TASK_RUNNING;
......activate_task(rq, p, ENQUEUE_NOCLOCK);p->on_rq = TASK_ON_RQ_QUEUED;trace_sched_wakeup_new(p);check_preempt_curr(rq, p, WF_FORK);
......
}

activate_task函数中会调用enqueue_task:

static inline void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
{
.....p->sched_class->enqueue_task(rq, p, flags);
}

如果是CFS的调度类,则执行相应的enqueue_task_fair:

static void
enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{struct cfs_rq *cfs_rq;struct sched_entity *se = &p->se;
......cfs_rq = cfs_rq_of(se);enqueue_entity(cfs_rq, se, flags);
......cfs_rq->h_nr_running++;
......
}

在enqueue_task_fair中取出的队列就是cfs_rq,然后调用enqueue_entity。

在enqueue_entity函数里面,会调用update_curr,更新运行的统计量,然后调用 __enqueue_entity,将sched_entity加入到红黑树里面,然后将se->on_rq = 1设置在队列上。

回到enqueue_task_fair后,将这个队列上运行的进程数目加一。然后,wake_up_new_task会调用check_preempt_curr,看是否能够抢占当前进程。

在check_preempt_curr中,会调用相应的调度类的rq->curr->sched_class- >check_preempt_curr(rq, p, flags)。对于CFS调度类来讲,调用的是 check_preempt_wakeup。

static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int wake_flags)
{struct task_struct *curr = rq->curr;struct sched_entity *se = &curr->se, *pse = &p->se;struct cfs_rq *cfs_rq = task_cfs_rq(curr);
......if (test_tsk_need_resched(curr))return;
......find_matching_se(&se, &pse);update_curr(cfs_rq_of(se));if (wakeup_preempt_entity(se, pse) == 1) {goto preempt;}return;
preempt:resched_curr(rq);
......
}

在check_preempt_wakeup函数中,前面调用task_fork_fair的时候,设置 sysctl_sched_child_runs_first了,已经将当前父进程的TIF_NEED_RESCHED设置了,则直接返 回。否则,check_preempt_wakeup还是会调用update_curr更新一次统计量,然后 wakeup_preempt_entity将父进程和子进程PK一次,看是不是要抢占,如果要则调用 resched_curr标记父进程为TIF_NEED_RESCHED。

如果新创建的进程应该抢占父进程,在什么时间抢占呢?别忘了fork是一个系统调用,从系统调 用返回的时候,是抢占的一个好时机,如果父进程判断自己已经被设置为 TIF_NEED_RESCHED,就让子进程先跑,抢占自己。

总结:

fork系统调用的过程咱们就解析完了。它包含两个重要的事件,一个是将task_struct结构复制一份并且初始化,另一个是试图唤醒新创建的子进程。

Linux进程的创建相关推荐

  1. Linux进程的创建函数fork()及其fork内核实现解析

    进程的创建之fork() Linux系统下,进程可以调用fork函数来创建新的进程.调用进程为父进程,被创建的进程为子进程. fork函数的接口定义如下: #include <unistd.h& ...

  2. Linux进程的创建和父子进程同步,操作系统实验报告_Linux进程创建与通信.doc

    操作系统实验报告_Linux进程创建与通信 2011-2012学年第一学期 专 业: 班 级: 学 号: 姓 名:提交日期:2011年11月实验二 Linux进程创建与进程通信 [实验目的 1. 熟悉 ...

  3. 嵌入式Linux系统编程学习之十一Linux进程的创建与控制

    文章目录 一.fork函数 二.进程的终止 三.wait 和 waitpid 函数 四.exec 函数族 五.system 函数 六.popen 函数 总结 一.fork函数 fork 函数原型: # ...

  4. linux进程的创建、执行和消亡

    在linux系统中,第一个进程是系统固有的,与生俱来的或者说是由内核的设计者安排好了的,内核在引导并完成了基本的初始化以后,就有了系统第一进程(实际上是内核线程).除此之外,所有其他的进程和内核线程都 ...

  5. 【Linux】Linux进程的创建与管理

    在Linux系统中,除了系统启动之后的第一个进程由系统来创建,其余的进程都必须由已存在的进程来创建,新创建的进程叫做子进程,而创建子进程的进程叫做父进程.那个在系统启动及完成初始化之后,Linux自动 ...

  6. Linux 进程控制(创建/退出/等待/替换)

    目录 进程创建 fork()函数 fork返回值 fork写时拷贝 fork失败原因 fork用法 进程退出 退出场景 常见的退出方法 正常退出 异常退出 _exit()系统调用 exit()函数 _ ...

  7. Linux进程的创建图文教程,进程的创建和终止(超详细)

    大多数系统的进程能够并发执行,它们可以动态创建和删除.因此,操作系统必须提供机制,用于创建进程和终止进程. 进程创建 进程在执行过程中可能创建多个新的进程.创建进程称为父进程,而新的进程称为子进程.每 ...

  8. linux——进程(创建、终止、等待、替换)

    进程的基本操作 概念 程序运行的一个实例,其占有一定的空间. 查询某一进程当前情况 ps aux | grep 进程名 终止进程 kill -9 pid: //pid指需要终止的进程pid 创建 pi ...

  9. Linux进程-进程的创建

    今天学习了Linux的进程创建的基本原理,是基于0.11版本核心的.下面对其作一下简单的总结. 一.Linux进程在内存中的相关资源    很容易理解,Linux进程的创建过程就是内存中进程相关资源产 ...

最新文章

  1. 第3周 区_SQL Server中管理空间的基本单位
  2. nginx反向代理取得IP地址
  3. 做一个基于python的树莓派MCU性能-温度监控仪表盘
  4. Pandas matplotlib 无法显示中文
  5. 配置Docker代理已实现外网访问
  6. 提升Web用户体验的71个设计要点
  7. vectorvn1610报价_【8.5873.5444.G323】价格_厂家 - 中国供应商
  8. 每日两道前端面试题20190221
  9. (三十九)数据的持久化存储-plist实现(XML属性表)
  10. oracle的文件管理ofm,oracle 文件管理功能
  11. 【树形dp】VK Cup 2012 Round 1 D. Distance in Tree
  12. git上传过滤忽略文件
  13. 论文查重算法 python_论文查重降重绝密方法
  14. php度分秒,度分秒计算方法-度分秒的计算方法!急
  15. 单片机段式LCD驱动教程
  16. Windows 中剪贴板的操作
  17. vant ,vue 图片上传压缩
  18. 主动学习,半监督学习,直推学习
  19. linux的crontab 命令,每三个月的月末执行一次
  20. 打开桌面上计算机特别慢,如何解决Win7电脑启动慢的问题?

热门文章

  1. 面向高稳定,高性能之-Hbase数据实时同步到ElasticSearch(之二)
  2. 华为c8815手机在开发Android调试时logcat不显示输出信息的解决办法
  3. FFmpeg进行音频的解码和播放
  4. Redhat 7 安装 iftop软件
  5. 解鞍卸甲——手写简易版Spring框架(终):使用三级缓存解决循环依赖问题
  6. BAMBOOROSY编舞,灵感来自THE SEA【大型圣诞狂欢派对系列宣传】
  7. css过长文字自动换行
  8. 潘多拉固件设置ipv6_openwrt-LEDE系统IPV6设置教程
  9. 基础35 空心三角形
  10. IDEA配置远程debug