Linux内核设计与实现 第三章 进程管理

news/2024/5/19 4:23:02/文章来源:https://blog.csdn.net/weixin_55255438/article/details/126656749

3.1进程

实际上,进程就是正在执行的程序代码的实时结果。
进程是出于执行期的程序以及相关的资源的总称。
进程的另一个名字是任务。
进程不仅仅局限于一段可执行程序代码通常进程还要包含其他资源,像打开的文件,挂起的信号,内核内部数据,处理器状态,若干具有内存映射的内存地址空间,若干执行线程,存放全局变量的数据段等

内核调度对象是线程,而不是进程(看看李志军老师的核心级线程与用户级线程)
对Linux而言,不特别区分线程与进程,线程只不过是一种特殊的进程

Linux系统中,这调用函数fork(),创建进程,该系统调用通过复制一个现有进程来创建一个全新的进程。调用fork()的进程称为父进程,新产生的进程称为子进程。
exec()
clone()
exit()
wait4()
wait()
waitpid()

3.2进程描述符及任务结构

1)内核把进程的列表存放在叫做任务列表的双向循环链表中。进程描述符中包含一个具体进程的所有信息
在这里插入图片描述
2)分配进程描述符
1)铺垫知识
在进程创建时,内核会为进程创建一系列数据结构,其中最重要的就是上章学习的task_struct结构,它就是进程描述符,表明进程在生命周期内的所有特征。同时,内核为进程创建两个栈,一个是用户栈,一个是内核栈,分别处于用户态和内核态使用的栈

实际上在linux kernel中,task_struct、thread_info都用来保存进程相关信息,即进程PCB信息。然而不同的体系结构里,进程需要存储的信息不尽相同,linux使用task_struct存储通用的信息,将体系结构相关的部分存储在thread_info中。
在内核态,32 位和 64 位都使用内核栈,格式也稍有不同,主要集中在 pt_regs 结构上

在内核态,32 位和 64 位的内核栈和 task_struct 的关联关系不同。
x86中32 位主要靠 thread_info,64 位主要靠 Per-CPU 变量,而ARM平台不论是32位还是64位,都是使用thread_info,其原理基本类似。

Linux 给每个 task 都分配了内核栈。在 32 位系统上 arch/x86/include/asm/page_32_types.h,是这样定义的:一个 PAGE_SIZE 是 4K,左移一位就是乘以 2,也就是 8K。但是内核栈在 64 位系统上arch/x86/include/asm/page_64_types.h,是这样定义的:在 PAGE_SIZE 的基础上左移两位,也即 16K,并且要求起始地址必须是 8192 的整数倍。
Linux
2)总结《Linux内核设计与实现》的本节
Linux通过slab分配器分配task_struck结构,这样达到对象复用和缓存着色。(详细见12章)用slab分配器动态生成task_struck时,只需要在向下增长的栈的栈底,在向上增长的栈的栈顶创建结构struct thread_info,thread_info中有一个指向进程描述符的指针(该指针根据slab分配器赋值)
在这里插入图片描述
3)进程描述符的存放
内核通过一个唯一的进程标识值或PID来标识每个进程。PID的最大值默认设置是32768,即short int 的最大值。
内核大部分的处理进程的代码都是直接通过task_struct进行的

通过current宏找到当前正在运行进程的进程描述符的速度是非常重要的。硬件体系结构复杂,硬件资源丰富的专门拿个寄存器来存放指向当前进程的teask_struct的指针。x86这样资源拮据的就只能在内核栈的尾巴创建thread_info结构,通过计算偏移间接地查找task_struct结构。

通过current宏找到当前正在运行进程的进程描述符的时间
=从专门的寄存器读取进程描述符
=计算hread_info的偏移(即用current找到hread_info的位置)+读取hread_info中的进程描述符指针+根据进程描述符指针读取进程描述符

4)进程状态
在这里插入图片描述
在这里插入图片描述

5)设置当前进程状态

set_task_state(task,state)

6)进程上下文
进程上下文:当一个进程在执行时,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文。当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的上下文,以便在再次执行该进程时,能够必得到切换时的状态执行下去。在LINUX中,当前进程上下文均保存在进程的任务数据结构中。在发生中断时,内核就在被中断进程的上下文中,在内核态下执行中断服务例程。但同时会保留所有需要用到的资源,以便中断服务结束时能恢复被中断进程的执行。

7)进程家族树
Unix和Linux系统都存在明显的继承关系,所有进程都是PID为1后代。
内核在系统启动的最后阶段启动init进程,该进程读取系统的初始化脚本并执行其他的相关程序,最终完成系统启动的整个过程。
拥有同一个父进程的所有进程被称为兄弟。进程描述符中有指向父进程和所有子进程的指针
由于任务列表是双向循环链表,因此遍历系统中的所有进程很容易

3.3进程创建

fork()拷贝当前进程创建子进程。
exec()读取可执行文件并将其载入地址空间开始运行。
1)写时拷贝
fork()系统调用直接把所有的资源复制给新建的进程,效率低下。因此fork()使用写时拷贝页实现,不写时父子进程共享数据,需要写时才另存共享的数据
写时拷贝是一种可以推迟甚至免除拷贝数据的技术。

2)fork()
在这里插入图片描述
在这里插入图片描述

3)vfork()

3.4线程在Linux中的实现

在这里插入图片描述

假如我们有一个包含四个线程的进程,
window系统通常会有一个包含指向四个不同线程的指针的进程描述符,该描述符负责描述像地址空间、打开的文件这样的共享资源。线程本身再去描述它独占的资源。
在这里插入图片描述

Linux系统仅仅创建四个进程并分配四个普通的task_struct结构,建立这四个进程时指定它们共享某些资源
1)创建线程
在这里插入图片描述
在这里插入图片描述

2)内核线程
linux在初始化的时候,除了静态的idle线程,还会创建kernel_init线程和kthreadd线程。kthreadd线程为2号线程,该线程专门用来负责为kernel创建其他线程。下面看一下如何利用kthreadd创建一个内核线程。

//kernel thread create information内核线程创建信息
struct kthread_create_info
{/* Information passed to kthread() from kthreadd. */int (*threadfn)(void *data); //要创建的线程的执行函数void *data;int node;/* Result passed back to kthread_create() from kthreadd. */struct task_struct *result; //用来向线程申请者返回task_structstruct completion done;//向申请者通知创建完成struct list_head list;//挂载进kthreadd的处理队列
};

为了容易区分,我们把需要创建新线程的叫做申请者,具体负责创建新进程的叫做执行者,这边执行者就是kthreadd线程。kthread_create_info数据结构用来在申请者和执行者之间传递对象。

a)新线程创建的申请

struct kthread_create_info create;
struct task_struct *task;
create.threadfn = threadfn;   //新建线程的执行函数
create.data = data;
create.node = node;
init_completion(&create.done);//初始化完成量spin_lock(&kthread_create_lock);
list_add_tail(&create.list, &kthread_create_list);//添加到kthreadd执行队列
spin_unlock(&kthread_create_lock);wake_up_process(kthreadd_task);   //唤醒kthreadd线程
wait_for_completion(&create.done);//等待kthreadd线程完成线程创建
task=create.result;               //返回新建线程的描述符
wake_up_process(task);            //唤醒新建线程,这样新线程处于就绪态

b) 新线程创建
kthreadd_task是kthreadd线程的进程描述符,在系统初始化的时候创建:

static noinline void __init_refok rest_init(void)
{
........................................pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
........................................}

下面看一下kthreadd线程如何管理调度其它的内核线程:
可以看到,在kthread_create_list链表中获取到申请者传过来的kthread_create_info结构,利用该信息调用create_kthread来创建线程。

int kthreadd(void *unused)
{struct task_struct *tsk = current;/* Setup a clean context for our children to inherit. */set_task_comm(tsk, "kthreadd");ignore_signals(tsk);set_cpus_allowed_ptr(tsk, cpu_all_mask);set_mems_allowed(node_states[N_MEMORY]);current->flags |= PF_NOFREEZE;for (;;) {set_current_state(TASK_INTERRUPTIBLE);if (list_empty(&kthread_create_list))//如果队列空,睡眠schedule();__set_current_state(TASK_RUNNING);spin_lock(&kthread_create_lock);while (!list_empty(&kthread_create_list)) {//队列不为空,则对该队列进行循环,创建线程struct kthread_create_info *create;create = list_entry(kthread_create_list.next,struct kthread_create_info, list);//这个就是申请者传过来的结构list_del_init(&create->list);//先从队列上删除该create spin_unlock(&kthread_create_lock);create_kthread(create);//为申请者创建线程spin_lock(&kthread_create_lock);}spin_unlock(&kthread_create_lock);}return 0;
}

create_kthread()调用kernel_thread()创建kthread线程,参数为create,看一下kernel_thread是如何执行的:

static void create_kthread(struct kthread_create_info *create)
{int pid;#ifdef CONFIG_NUMAcurrent->pref_node_fork = create->node;
#endif/* 我们需要自己的信号处理程序(默认情况下不接受信号)。 */pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);//create_kthread()调用kernel_thread()创建kthread线程if (pid < 0) {create->result = ERR_PTR(pid);complete(&create->done);}
}

可以看到kthread()是kthreadd()函数创建的线程的入口地址,该函数最终执行到申请者提供的的threadfn函数,至此创建者完成了自己的使命,申请者开始有了自己的新线程,并执行threadfn任务

static int kthread(void *_create)
{/* Copy data: it's on kthread's stack复制数据:它在kthread的堆栈上 */struct kthread_create_info *create = _create;//内核线程创建信息结构体kthread_create_infoint (*threadfn)(void *data) = create->threadfn;void *data = create->data;struct kthread self;int ret;self.flags = 0;self.data = data;init_completion(&self.exited);init_completion(&self.parked);current->vfork_done = &self.exited;/* OK, tell user we're spawned, wait for stop or wakeup 好,告诉用户我们已经生成,等待停止或唤醒*/__set_current_state(TASK_UNINTERRUPTIBLE);create->result = current;//向申请者返回当前线程的描述符complete(&create->done);//告诉申请者,线程创建完成schedule(); // 进入休眠状态后,调度去执行申请者的wake_up_process(task); //唤醒新建线程,这样新线程处于就绪态,马上就会执行threadfn新建线程的执行函数//kthread会将其所在进程的状态设为TASK_UNINTERRUPTIBLE,然后调用schedule函数。所以,kthread将会使其所在的进程进入休眠状态,直到被别的进程唤醒。如果被唤醒,将会调用create->threadfn(create->data);ret = -EINTR;if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {__kthread_parkme(&self);ret = threadfn(data);//申请者提供的线程执行函数}/* we can't just return, we must preserve "self" on stack我们不能只是返回,我们必须在堆栈上保留“self” */do_exit(ret);//do_exit(是进程的退出码,是子进程返回给父进程的值)
}

3.5进程终结

1)删除进程的描述符
释放与进程相关联的所有可以释放的资源,进程进入终止状态,它仅占用的内核栈、thread_info结构、tast_struct结构会在其父进程检索后,由其父进程通知内核可以释放它仅占用的内核栈、thread_info结构、tast_struct结构

2)孤儿进程造成的进退维谷
如果夫进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父亲,否则这些成为孤儿的进程就会在退出时永远处于僵死状态,白白浪费内存。
解决方法是给子进程在当前线程组内找一个线程作为父亲,如果当前线程组内没有其他线程,就让init做它们的父亲。init会例行调用wait()来检查其子进程,清除所有与其相关的僵死进程

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot项目整理(持续更新)

SpringSecurity 1.导入依赖&#xff1a; 在pom.xml中导入依赖&#xff0c;再访问页面就会出现login&#xff0c;这是SpringSecurity自己写的页面&#xff0c;用于登录认证 <dependency><groupId>org.springframework.boot</groupId><artifactId>spr…

整合流量与资源的分享购商业模式,实现整个生态布局

大多数企业都很容易忽视一个市场&#xff0c;就是我们的日常生活服务板块&#xff0c;所谓民以食为天&#xff0c;我们应该顺应人们的生活习惯而做出来的电商商业模式&#xff0c;才是最贴合民心的&#xff0c;也能够从用户的最基础的需求出发来为其打造商业模式。 将目标放在生…

Room (三) RecyclerView 呈现列表数据

1. 用到的组件 Room&#xff0c;ViewModel&#xff0c;LiveData&#xff0c;Repository&#xff0c;AsyncTack 2. Module 中 build.gradle 文件中添加 dependencies {def room_version "2.4.3"implementation "androidx.room:room-runtime:$room_version&quo…

【Linux操作系统】-- 多线程(三)-- 线程池+单例模式

目录 线程池 场景 代码实现 线程安全的单例模式 懒汉实现方式和懒汉实现方式 饿汉方式实现单例模式 懒汉方式实现单例模式 实战代码演练单例模式 线程池 在C中用户使用new/malloc都是向操作系统OS申请的&#xff0c;在系统的角度&#xff0c;就相当于new/malloc在底层封…

MySQL之临时表

写在前面 本文一起看下MySQL的临时表。 1&#xff1a;什么是临时表 通过create temporary table t语句创建的表&#xff0c;就是临时表&#xff0c;临时表的临时体现在其是其生命周期是和会话一样的&#xff0c;当会话结束&#xff0c;即连接关闭时MySQL会自动将创建的临时表…

氨丙基咪唑离子液体(AMIBr)改性纤维素气凝胶吸附剂(CAgAMIBr)的实验要求

氨丙基咪唑离子液体(AMIBr)改性纤维素气凝胶吸附剂(CAgAMIBr)的实验要求 离子液体(ILs)&#xff0c;是完全由离子组成的液体&#xff0c;可以进一步定义为熔点低于100C的熔盐。 离子液体是在室温或接近室温下可呈现液体的液态有机盐。离子液体因具有一些优良的特性使其在分离…

树的直径 树形dp+2次dfs

题目描述 给定一棵树 T &#xff0c;树 T 上每个点都有一个权值。 定义一颗树的子链的大小为&#xff1a;这个子链上所有结点的权值和 。 请在树 T 中找出一条最大的子链并输出。 输入描述: 第一行输入一个 n,1≤n≤105。 接下来一行包含n个数&#xff0c;对于每个数 ai,−10^5…

我赢助手小技巧:学会这三招,爆款内容视频完播率提高50%(下)

上一篇我们说了爆款内容的四大共性和底层逻辑&#xff0c;今天我们来看一看如何去设置标题、封面和剧情&#xff0c;实现视频的完播率。 第三个技巧叫内容高潮。 要在3秒钟之内让用户兴趣高涨&#xff0c;把这样的脚本写出来&#xff0c;怎么样去做&#xff1f;你要把特效、悬…

PCL 生成空间圆点云

目录 一、算法原理二、代码实现三、结果展示一、算法原理 三维空间圆形式如下: 三维空间圆的参数方程: { x ( θ ) = c

蚂蚁核心架构师内部Java并发编程进阶笔记,白嫖简直太香了!

并发编程作为Java开发者很重要以及非常核心的知识&#xff0c;我希望读者朋友具备以下的预备知识&#xff1a; 希望你不是一个初学者线程安全问题,需要你接触过Java Web开发、Jdbc 开发、Web服务器、分布式框架时才会遇到基于JDK8 ,最好对函数式编程、lambda 有一定了解采用了…

thinkphp使用dompdf导出pdf(html转pdf)

目录一 、安装二、安装字体&#xff08;解决无法输出中文&#xff09;三、使用3.1 示例3.2 入参声明3.3 调用声明四、总结一 、安装 命令行安装&#xff1a; composer require dompdf/dompdf下载 GitHub Dompdf库 二、安装字体&#xff08;解决无法输出中文&#xff09; 因…

关于内存条的知识要点⑴

这些天在安装神州网信政府版的过程中&#xff0c;遇到很多计算机配置比较低&#xff0c;比如2009、2010、2012年的计算机&#xff0c;为了让用户使用顺畅一些&#xff0c;需要做一些硬件上的更改&#xff0c;比如加装内存条或者更换固态硬盘等。很多人即使是写代码的IT技术人员…

599. 两个列表的最小索引总和

599. 两个列表的最小索引总和https://leetcode.cn/problems/minimum-index-sum-of-two-lists/ 难度简单224 假设 Andy 和 Doris 想在晚餐时选择一家餐厅&#xff0c;并且他们都有一个表示最喜爱餐厅的列表&#xff0c;每个餐厅的名字用字符串表示。 你需要帮助他们用最少的索…

计算机毕业论文选题java毕业设计软件基于SSM实现的固定资产管理系统

&#x1f345;文末获取联系&#x1f345; 目录 一、项目介绍 二、开题报告 三、截图 四、源码获取 一、项目介绍 计算机毕业设计java毕设之固定资产管理系统_哔哩哔哩_bilibili计算机毕业设计java毕设之固定资产管理系统共计2条视频&#xff0c;包括&#xff1a;IT实战营…

【文献研究】国际班轮航运的合作博弈:The coopetition game in international liner shipping

背景&#xff1a;本人在整理资料时翻找出来的以前做的研究自己写的总结&#xff0c;2017年发布在《Maritime Policy & Management》期刊的一篇关于国际班轮航运合作博弈的英文文献&#xff0c;本人本着学习的目的就文献的重点内容进行了浅层次的解读&#xff0c;就自己的理…

技术状态管理计划-模板

1 引言 1.1 目的和范围 本计划规定了XXX项目技术状态管理的原则、主要内容和要求&#xff0c;是指导XXX项目以及技术状态项研制全过程的技术状态管理的基本文件&#xff0c;也是各配套研制单位在研制过程中实施技术状态管理必须遵循的基本规定。   本计划适用于XXX项目以及技…

JdbcTemplate操作数据库

文章目录一、JdbcTemplate&#xff08;概念和准备&#xff09;1、什么是JdbcTemplate2、准备工作二、JdbcTemplate操作数据库(增删改)1、对应数据库创建实体类2、编写service和dao3、测试类三、JdbcTemplate操作数据库&#xff08;查询&#xff09;1、对应数据库创建实体类2、编…

物联网开发笔记(7)- 使用Wokwi仿真ESP32开发板实现LED灯点亮、按钮使用

上面几节我们使用Micrpython在Wokwi网站上实现了树莓派Pico开发板的仿真。学习了树莓派Pico的LED闪灯、按键操作等。以及Wokwi的使用&#xff0c;比如选中元器件后&#xff0c;按键盘“R”键切换方向&#xff0c;按键盘“Backspace”或者“Delete”删除原件&#xff0c;鼠标滚轮…

22-09-02 西安 JVM 类加载器、栈、堆体系、堆参数调优、GC垃圾判定、垃圾回收算法

JVM入门 1、JVM结构图 JVM是运行在操作系统之上的&#xff0c;它与硬件没有直接的交互 方法区&#xff1a;存储已被虚拟机加载的类元数据信息(元空间) 堆&#xff1a;存放对象实例&#xff0c;几乎所有的对象实例都在这里分配内存 虚拟机栈(java栈)&#xff1a;虚拟机栈描述…

深挖全媒体多模态数据价值,蜜度亮相2022世界人工智能大会

蜜度深度挖掘全媒体多模态数据核心价值&#xff0c;提供重要垂直领域解决方案。 编辑 | 宋慧 出品 | CSDN云计算 2022 年 9 月1至3日&#xff0c;由国家七部委和上海市人民政府共同主办的2022世界人工智能大会&#xff08;WAIC )隆重举行&#xff0c;大会围绕“人类、科技、产…