《Linux 内核设计与实现》03. 进程管理
文章目录
- 进程描述符及任务结构
- 分配进程描述符
- 进程描述符的存放
- 进程状态
- 设置当前进程状态
- 进程上下文
- 进程家族树
- 进程创建
- 线程在 Linux 中的实现
- 创建线程
- 内核线程
- 进程终结
- 删除进程描述符
- 孤儿进程
进程描述符及任务结构
内核把进程存放在任务队列(task list)中,该队列由双向循环链表实现。
链表中的每个元素都是 task_struct 类型,也称为进程描述符。
// include/linux/sched.h
struct task_struct {unsigned long state;int prio;unsigned long policy;struct task_struct *parent;struct list_head tasks;pid_t pid;...
}
进程的另一个名字是任务(task)。
分配进程描述符
Linux 通过 slab 分配器分配 task_struct 结构,这样能达到对象复用和缓存着色的目的。
各个进程的 task_struct 存放在它们内核栈的尾端。目的是为了让像 x86 那样寄存器少的硬件体系结构只需要通过栈指针就能够计算出某个进程的位置,从而避免使用额外的寄存器专门记录。
对于栈是向下增长的来说,就将 thread_info 放在栈底,而对于栈是向上增长的来说,就放在栈顶。
关于 thread_info 结构:
// asm/thread.info.h
struct thread_info {struct pcb_struct pcb; /* palcode state */struct task_struct *task; /* main task structure */unsigned int flags; /* low level flags */unsigned int ieee_state; /* see fpu.h */struct exec_domain *exec_domain; /* execution domain */mm_segment_t addr_limit; /* thread address space */unsigned cpu; /* current CPU */int preempt_count; /* 0 => preemptable, <0 => BUG */int bpt_nsaved;unsigned long bpt_addr[2]; /* breakpoint handling */unsigned int bpt_insn[2];struct restart_block restart_block;
};
进程描述符的存放
PID 是内核用来标识一个进程的唯一方式。PID 是一个整数,为了与老版本兼容,它最大值默认被设置为 32768。
PID 最大值可以通过 /proc/sys/kernel/pid_max 来修改上限。
PID 被内核放在了进程描述符(task_struct)中。
在内核中,访问任务需要获得指向其 task_struct 的指针。所以如果内核要执行某个任务,就必须先得到指向其 task_struct 的指针。Linux 中通过 current 宏来实现,硬件体系结构不一样,该宏实现的方式也不同:
- 若硬件体系结构的寄存器足够,那么就可以将 task_struct 指针直接存储到该寄存器。
- 若硬件体系结构的寄存器有限,那么就可以在内核栈的尾端创建一个 thread_info 结构,通过计算偏移间接查找 task_struct 结构。
current 宏在 x86 中实现如下:
arch/alpha/include/asm/current.h:
#ifndef _ALPHA_CURRENT_H
#define _ALPHA_CURRENT_H#include <linux/thread_info.h>// 通过 current_thread_info() 得到当前进程的指针后直接访问其对应的任务进程(task_struct)指针 task
#define get_current() (current_thread_info()->task)
#define current get_current()#endif /* _ALPHA_CURRENT_H */
arch/alpha/include/asm/thread_info.h:
// 通知编译器将指向 thread_info 类型的指针存放到寄存器 $8 中
register struct thread_info *__current_thread_info __asm__("$8");
// 获取当前进程信息(thread_info)指针
#define current_thread_info() __current_thread_info
进程状态
位于进程描述符中的 state 域中,每个进程无论何时都有其中一种状态。
- TASK_RUNNING
- TASK_INTERRUPTIBLE
- TASK_UNINTERRUPTIBLE
- __TASK_TRACED
- __TASK_STOPPED
设置当前进程状态
- set_current_state(state):将当前进程设置为 state 状态。
- set_task_state(tsk, state):将 tsk 进程设置为 state 状态。
#define __set_task_state(tsk, state_value) do { (tsk)->state = (state_value); } while (0)
#define set_task_state(tsk, state_value) set_mb((tsk)->state, (state_value))/** set_current_state() includes a barrier so that the write of current->state* is correctly serialised wrt the caller's subsequent test of whether to* actually sleep:** set_current_state(TASK_UNINTERRUPTIBLE);* if (do_i_need_to_sleep())* schedule();** If the caller does not need such serialisation then use __set_current_state()*/
#define __set_current_state(state_value) do { current->state = (state_value); } while (0)
#define set_current_state(state_value) set_mb(current->state, (state_value))
进程上下文
例如一个在用户空间中运行的程序(可执行程序代码是进程的主要组成部分,这些代码从一个可执行文件载入到进程的地址空间执行),一般程序都是在用户空间中执行。当一个程序在运行过程中调用了系统调用或者触发了某个异常,此时它就会陷入内核空间。此时内核需要代替用户程序执行用户程序所需的程序(其实就是说,用户程序没权限去得到或执行内核的东西,但是我用户程序需要内核的部分东西来辅助用户程序的执行,所以我需要内核来帮我去执行某些程序,最后将结果给我),这便是“代表进程执行”。
用户程序陷入内核后,后面代码不会执行,而是先去内核执行对应的程序,此时用户程序的执行环境便是上下文。
进程家族树
Unix 和 Linux 的进程之间都存在一个明显的继承关系,所有的进程都是 PID 为 1 的 init 进程的后代。待内核一切准备就绪后,便会执行 init 进程来初始化系统所需的资源。
系统中每个进程必有一个父进程(init),而每个进程可以有零个或多个子进程。
struct task_struct {struct task_struct *parent; // 父进程struct task_struct *children; // 子进程
};
获取和遍历进程的方式:include/linux/list.h
/*** list_entry - get the struct for this entry* @ptr: the &struct list_head pointer.* @type: the type of the struct this is embedded in.* @member: the name of the list_struct within the struct.*/
#define list_entry(ptr, type, member) \container_of(ptr, type, member)/*** list_for_each - iterate over a list* @pos: the &struct list_head to use as a loop cursor.* @head: the head for your list.*/
#define list_for_each(pos, head) \for (pos = (head)->next; prefetch(pos->next), pos != (head); \pos = pos->next)
如果在一个拥有大量进程的系统中遍历所有进程,代价是很大的,因此尽量不要这么做。
进程创建
其它操作系统都提供了产生进程的机制,共两步:
- 在新的地址空间中创建进程,读入可执行文件。
- 开始执行可执行文件。
Unix 将上面两个步骤分别封装到了 fork() 和 exec() ,这两个函数组合起来使用便和其它操作系统使用单一函数创建进程一样:
- fork():通过拷贝当前进程生成一个子进程。
- exec():负责读取可执行文件并将其载入地址空间开始运行。
Linux 的 fork() 使用写时拷贝页实现。
Linux 通过 clone() 系统调用实现 fork()。
线程在 Linux 中的实现
Linux 把所有线程都当进程来实现。线程仅仅被视为一个与其它进程共享某些资源的进程。线程也有自己的 task_struct 只不过它们都共享父进程的地址空间,也就是说它们没有自己的地址空间。在其它操作系统中,线程被称为“轻量级进程”,可在 Linux 中进程本就够轻量了。
创建线程
和创建进程一样,需要调用 clone() 系统调用来实现,不过需要传递一些参数标志来指明需要共享的资源:
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
传递给 clone() 的参数标志决定了新创建进程的行为方式和父子进程之间共享的资源种类。

内核线程
内核进程需要在后台执行一些操作。这种任务可以通过内核线程完成 —— 独立运行在内核空间的标准进程。内核线程和普通的进程间的区别在于内核线程没有独立的地址空间(即 task_struct 中指向地址空间的指针 mm 为 NULL)。它们只在内核空间运行,不会跨越到用户态。
内核线程只能通过其它内核线程来创建。
从现有内核线程中创建一个新的内核线程的方法在 linux/kthread.h 中的 kthread_create()。
进程终结
当一个进程终结时,内核必须释放它所占有的所有资源,并通过父进程。
终结通过 exit() 系统调用来实现,具体实现靠 do_exit(),位于 kernel/exit.c 中。
此时只是释放所占为的内存资源,即对于内存资源位图需要重置。
删除进程描述符
调用完 exit() 后,对应的内存位图被重置,但是此时并没有将进程描述符 task_struct 以及 thread_info 给删除掉,由此可见资源的释放和进程描述符的删除是被分开执行的。
这样做是为了当线程僵尸后,可以得到该线程的信息,以便于通知父进程。当父进程得到了子进程以及死亡的消息后,在来删除进程描述符和 thread_info。
孤儿进程
如果父进程在子进程退出之前就先退出了,必须要有机制保证子进程能找到一个新的父进程,否则这些孤儿进程就会在退出时永远处于僵死状态,白白的消耗内存。
解决方案是给子进程在当前进程组内找一个线程作为父进程,实在不行,就让 init 作为它们的父进程。
相关文章:
《Linux 内核设计与实现》03. 进程管理
文章目录 进程描述符及任务结构分配进程描述符进程描述符的存放进程状态设置当前进程状态进程上下文进程家族树 进程创建线程在 Linux 中的实现创建线程内核线程 进程终结删除进程描述符孤儿进程 进程描述符及任务结构 内核把进程存放在任务队列(task list…...
深入探究HDFS:高可靠、高可扩展、高吞吐量的分布式文件系统【上进小菜猪大数据系列】
上进小菜猪,沈工大软件工程专业,爱好敲代码,持续输出干货。 引言 在当今数据时代,数据的存储和处理已经成为了各行各业的一个关键问题。尤其是在大数据领域,海量数据的存储和处理已经成为了一个不可避免的问题。为了应…...
GIMP制作艺术字技巧
GIMP下载官网 https://www.gimp.org/downloads/ 我使用的版本 2.10.32 字体下载 https://ziyouziti.com/index-index-all.html 下载解压之后会有otf、ttf等字体文件,需要拷贝到gimp当前用户目录 C:\Users\用户名\AppData\Roaming\GIMP\2.10\fonts GIMP绘制字…...
Redis 布隆过滤器总结
Redis 布隆过滤器总结 适用场景 大数据判断是否存在来实现去重:这就可以实现出上述的去重功能,如果你的服务器内存足够大的话,那么使用 HashMap 可能是一个不错的解决方案,理论上时间复杂度可以达到 O(1) 的级别,但是…...
云基础设施安全:7个保护敏感数据的最佳实践
导语:云端安全防护进行时! 您的组织可能会利用云计算的实际优势:灵活性、快速部署、成本效益、可扩展性和存储容量。但是,您是否投入了足够的精力来确保云基础设施的网络安全? 您应该这样做,因为数据泄露、…...
centos7安装nginx
1.配置环境 1).gcc yum install -y gcc2).安装第三方库 pcre-devel yum install -y pcre pcre-devel3).安装第三方库 zlib yum install -y zlib zlib-devel2.下载安装包并解压 nginx官网下载:http://nginx.org/en/download.html 或者 使用wget命令进行下载 wg…...
PyQt5 基础篇(一)-- 安装与环境配置
1 PyQt5 图形界面开发工具 Qt 库是跨平台的 C 库的集合,是最强大的 GUI 库之一,可以实现高级 API 来访问桌面和移动系统的各种服务。PyQt5 是一套 Python 绑定 Digia QT5 应用的框架。PyQt5 实现了一个 Python模块集,有 620 个类,…...
Java—JDK8新特性—函数式接口【内含思维导图】
目录 3.函数式接口 思维导图 3.1 什么是函数式接口 3.2 functionalinterface注解 源码分析 3.3 Lambda表达式和函数式接口关系 3.4 使用函数式接口 3.5 内置函数式接口 四大核的函数式接口区别 3.5.1 Supplier 函数式接口源码分析 3.5.2 Supplier 函数式接口使用 3.…...
【MySQL】外键约束和外键策略
一、什么是外键约束? 外键约束(FOREIGN KEY,缩写FK)是用来实现数据库表的参照完整性的。外键约束可以使两张表紧密的结合起来,特别是针对修改或者删除的级联操作时,会保证数据的完整性。 外键是指表…...
3. SQL底层执行原理详解
一条SQL在MySQL中是如何执行的 1. MySQL的内部组件结构1.1 Server层1.2 Store层 2. 连接器3. 分析器4. 优化器5. 执行器6. bin-log归档 本文是按照自己的理解进行笔记总结,如有不正确的地方,还望大佬多多指点纠正,勿喷。 1. MySQL的内部组件结…...
Bus动态刷新
Bus动态刷新全局广播配置实现 启动 EurekaMain7001ConfigcenterMain3344ConfigclientMain3355ConfigclicntMain3366 运维工程师 修改Gitee上配置文件内容,增加版本号发送POST请求curl -X POST "http://localhost:3344/actuator/bus-refresh" —次发送…...
逆波兰式的写法
一、什么是波兰式,逆波兰式和中缀表达式 6 *(37) -2 将运算数放在数值中间的运算式叫做中缀表达式 - * 6 3 7 2 将运算数放在数值前间的运算式叫做前缀表达式 6 3 7 * 2 - 将运算数放在数值后间的运算式叫做后缀表达式 二、生成逆波兰表达式 6 *(37) -2 生成…...
Linux系统日志介绍
Linux系统日志都是放在“/var/log”目录下面,各个日志文件的功能: /var/log/messages — 包括整体系统信息,其中也包含系统启动期间的日志。此外,mail,cron,daemon,kern和auth等内容也记录在va…...
第三十二章 React路由组件的简单使用
1、NavLink的使用 一个特殊版本的 Link,当它与当前 URL 匹配时,为其渲染元素添加样式属性 <NavLink className"list-group-item" to"/home">Home</NavLink> <NavLink className"list-group-item" to&quo…...
“裸奔”时代下,我们该如何保护网络隐私?
当我们在互联网上进行各种活动时,我们的个人信息和数据可能会被攻击者窃取或盗用。为了保护我们的隐私和数据安全,以下是一些实用的技巧和工具,可以帮助您应对网络攻击、数据泄露和隐私侵犯的问题: 使用强密码:使用独特…...
c#笔记-方法
方法 方法定义 方法可以将一组复杂的代码进行打包。 声明方法的语法是返回类型 方法名 括号 方法体。 void Hello1() {for (int i 0; i < 10; i){Console.WriteLine("Hello");} }调用方法 方法的主要特征就是他的括号。 调用方法的语法是方法名括号。 He…...
054、牛客网算法面试必刷TOP101--堆/栈/队列(230509)
文章目录 前言堆/栈/队列1、BM42 用两个栈实现队列2、BM43 包含min函数的栈3、BM44 有效括号序列4、BM45 滑动窗口的最大值5、BM46 最小的K个数6、BM47 寻找第K大7、BM48 数据流中的中位数8、BM49 表达式求值 其它1、se基础 前言 提示:这里可以添加本文要记录的大概…...
怎么让chatGTP写论文-chatGTP写论文工具
chatGTP如何写论文 ChatGPT是一个使用深度学习技术训练的自然语言处理模型,可以用于生成自然语言文本,例如对话、摘要、文章等。作为一个人工智能技术,ChatGPT可以帮助你处理一些文字内容,但并不能代替人类的创造性思考和判断。以…...
springboot 断点上传、续传、秒传实现
文章目录 前言一、实现思路二、数据库表对象二、业务入参对象三、本地上传实现三、minio上传实现总结 前言 springboot 断点上传、续传、秒传实现。 保存方式提供本地上传(单机)和minio上传(可集群) 本文主要是后端实现方案&…...
2023河南省赛vp题解
目录 A题: B题 C题 D题 E题 F题 G题 H题 I题 J题 K题 L题 A题: 1.思路:考虑暴力枚举和双hash,可以在O(n)做完。 2.代码实现: #include<bits/stdc.h> #define sz(x) (int) x.size() #define rep(i,z,…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...
