Linux进程的创建

news2024/11/21 0:20:48

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;
        else
            p->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结构复制一份并且初始化,另一个是试图唤醒新创建的子进程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/71751.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

web大作业:基于html+css+javascript+jquery实现智能分控网站

🎉精彩专栏推荐 💭文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业: 【📚毕设项目精品实战案例 (10…

c语言数据结构---链表

我喜欢我的懦弱,痛苦和难堪也喜欢。喜欢夏天的光照,风的气息,蝉的鸣叫,喜欢这些,喜欢得不得了。 ——村上春树《寻羊冒险记》 1.对链表的理解 2.链表每个节点的创建 3.输出链表 链表:顾名思义&#xff0c…

Redis-全面详解(学习总结---从入门到深化)

Redis概述_为什么要用NoSQL 单机Mysql的美好年代 在90年代,一个网站的访问量一般都不大,用单个数据库完全可以轻松应付。在那个时候,更多的都是 静态网页,动态交互类型的网站不多。 遇到问题: 随着用户数的增长&#…

centos7搭建DHCP服务器,实现上网

环境如下: 三台主机,一台centos7,当DNS服务器,一台centos7测试,一台window10测试。 版本centos7.6 三者都是在vmnet8环境下 centos7(dns服务器):192.168.139.200 centos&#x…

2小时开发《点球射门游戏》,动画演示思路(下),代码已开源

前沿 首选感谢各位对我这边文章(2小时开发《点球射门游戏》,动画演示思路(上),代码已开源)的点赞、收藏与支持,今天在这里主要是接上一篇文章,讲一讲游戏界面中的一些动画与逻辑的实现&#xf…

SpringBoot + Elasticsearch 实现模糊查询,批量CRUD,排序,分页,高亮!

一、引入依赖 当前Elasticsearch服务端的版本为8.5.1,此处Spring Data Elasticsearch的版本为2.6.1 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId><version&…

Java中不能在foreach中进行元素的remove和add操作

参考文献&#xff1a;https://juejin.im/post/6844903794795347981 在阿里巴巴Java开发手册中&#xff0c;有这样一条规定&#xff1a; 但是手册中并没有给出具体原因&#xff0c;本文就来深入分析一下该规定背后的思考。 foreach循环 以下实例演示了 普通for循环 和 foreach…

安卓APP源码和设计报告——健身系统

一、设计背景 1.需求分析 对于很多人来说拥有一副好身材能让自己增添不少魅力;对于爱吃而又担心自己发胖的人来说适当的运动健身是最好的选择。移动互联网时代&#xff0c;市场上“约跑”“约健身”健身APP软件成为新时代闺蜜朋友的互动模式&#xff0c;健身热潮的来临&#…

客快物流大数据项目(九十三):ClickHouse的ReplacingMergeTree深入了解

文章目录 ClickHouse的ReplacingMergeTree深入了解 一、创建ReplacingMergeTree表的说明 二、创建ReplacingMergeTree引擎的表

主成分分析 (PCA) 和独立成分分析 (ICA)附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步进步&#xff0c;matlab项目目标合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信息&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算…

某乎x-zse-96

看到有读者咨询知乎x-zse-96,简单做一下分析和记录。 版本:“x-api-version”:“3.0.91”,“x-zse-93”:“101_3_3.0” 随便找了一个搜视频接口 /api/v4/search_v3 经测试发现,目前请求必带的参数有headers 中的x-zse-96、x-zse-93、x-api-version 和 cookie中的d_c0。 …

聊一聊责任链模式

将一堆“事情”串联在一起&#xff0c;有序执行&#xff0c;就叫责任链 一、概述 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是将链中每一个节点看作是一个对象&#xff0c;每个节点处理的请求均不同&#xff0c;且内部自动维护一个下一节点对象。当…

【数据结构】图算法和LRUCache

文章目录最小生成树1.最小生成树概念2.Kruskal算法3.Prim算法最短路径算法单源最短路径--Dijkstra算法单源最短路径--Bellman-Ford算法多源最短路径--Floyd-Warshall算法LRUCacheLRUCache实现源码地址最小生成树 1.最小生成树概念 连通图中的每一棵生成树&#xff0c;都是原图…

软硬协同:基于倚天的视频云编码性能升级

算力时代&#xff0c;靠吃「硬件红利」便能搞定新应用场景的「甜蜜期」已经过去。人类社会的每一次科技跃迁&#xff0c;其本质都是计算力的突破与演进。 算盘拨出农耕文明的繁荣&#xff0c;机械计算机催生出第一次工业革命的袅袅蒸汽&#xff0c;而云计算的发展让万物互联成…

Spark零基础入门实战(二)Scala基础之数据类型

在Scala中,所有的值都有一个类型,包括数值和函数。如图1-4所示,说明了Scala的类型层次结构。 Any是Scala类层次结构的根,也被称为超类或顶级类。Scala执行环境中的每个类都直接或间接地从该类继承。该类中定义了一些通用的方法,例如equals()、hashCode()和toString()。…

【畅购商城】微信支付之支付模块

目录 支付页面 接口 后端实现 ​​​​​​​前端实现​​​​​​​ ​​​​​​​支付页面 步骤一&#xff1a;创建 flow3.vue组件 步骤二&#xff1a;引入第三方资源&#xff08;js、css&#xff09; <script> import TopNav from ../components/TopNav impor…

[附源码]Python计算机毕业设计Django疫情物资管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

电动汽车 (BEV) 优化调度(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f389;作者研究&#xff1a;&#x1f3c5;&#x1f3c5;&#x1f3c5;本科计算机专业&#xff0c;研究生电气学硕…

Vue3中的pinia使用(收藏版)

1.pinia介绍 pinia 是 Vue 的存储库&#xff0c;它允许您跨组件/页面共享状态。就是和vuex一样的实现数据共享。 依据Pinia官方文档&#xff0c;Pinia是2019年由vue.js官方成员重新设计的新一代状态管理器&#xff0c;更替Vuex4成为Vuex5。 Pinia 目前也已经是 vue 官方正式的状…

原创 | 传统医药零售如何实现数字化转型

一、数字化转型是零售药店未来实现增长的必由之路“十四五”规划将清洁能源、军事、消费、医药以及高端制造等行业定为未来需要重点关注的领域。围绕“医保、医疗、医药”三条主线&#xff0c;医药行业推出了一系列重要的改革措施&#xff0c;即将进入有史以来最大变革的黄金时…