当前位置: 首页 > news >正文

《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)

如果在一个拥有大量进程的系统中遍历所有进程,代价是很大的,因此尽量不要这么做。

进程创建

其它操作系统都提供了产生进程的机制,共两步:

  1. 在新的地址空间中创建进程,读入可执行文件。
  2. 开始执行可执行文件。

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() 的参数标志决定了新创建进程的行为方式和父子进程之间共享的资源种类。

image-20230414214902669

内核线程

内核进程需要在后台执行一些操作。这种任务可以通过内核线程完成 —— 独立运行在内核空间的标准进程。内核线程和普通的进程间的区别在于内核线程没有独立的地址空间(即 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 中的实现创建线程内核线程 进程终结删除进程描述符孤儿进程 进程描述符及任务结构 内核把进程存放在任务队列&#xff08;task list&#xf…...

深入探究HDFS:高可靠、高可扩展、高吞吐量的分布式文件系统【上进小菜猪大数据系列】

上进小菜猪&#xff0c;沈工大软件工程专业&#xff0c;爱好敲代码&#xff0c;持续输出干货。 引言 在当今数据时代&#xff0c;数据的存储和处理已经成为了各行各业的一个关键问题。尤其是在大数据领域&#xff0c;海量数据的存储和处理已经成为了一个不可避免的问题。为了应…...

GIMP制作艺术字技巧

GIMP下载官网 https://www.gimp.org/downloads/ 我使用的版本 2.10.32 字体下载 https://ziyouziti.com/index-index-all.html 下载解压之后会有otf、ttf等字体文件&#xff0c;需要拷贝到gimp当前用户目录 C:\Users\用户名\AppData\Roaming\GIMP\2.10\fonts GIMP绘制字…...

Redis 布隆过滤器总结

Redis 布隆过滤器总结 适用场景 大数据判断是否存在来实现去重&#xff1a;这就可以实现出上述的去重功能&#xff0c;如果你的服务器内存足够大的话&#xff0c;那么使用 HashMap 可能是一个不错的解决方案&#xff0c;理论上时间复杂度可以达到 O(1) 的级别&#xff0c;但是…...

云基础设施安全:7个保护敏感数据的最佳实践

导语&#xff1a;云端安全防护进行时&#xff01; 您的组织可能会利用云计算的实际优势&#xff1a;灵活性、快速部署、成本效益、可扩展性和存储容量。但是&#xff0c;您是否投入了足够的精力来确保云基础设施的网络安全&#xff1f; 您应该这样做&#xff0c;因为数据泄露、…...

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官网下载&#xff1a;http://nginx.org/en/download.html 或者 使用wget命令进行下载 wg…...

PyQt5 基础篇(一)-- 安装与环境配置

1 PyQt5 图形界面开发工具 Qt 库是跨平台的 C 库的集合&#xff0c;是最强大的 GUI 库之一&#xff0c;可以实现高级 API 来访问桌面和移动系统的各种服务。PyQt5 是一套 Python 绑定 Digia QT5 应用的框架。PyQt5 实现了一个 Python模块集&#xff0c;有 620 个类&#xff0c;…...

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】外键约束和外键策略

一、什么是外键约束&#xff1f; 外键约束&#xff08;FOREIGN KEY&#xff0c;缩写FK&#xff09;是用来实现数据库表的参照完整性的。外键约束可以使两张表紧密的结合起来&#xff0c;特别是针对修改或者删除的级联操作时&#xff0c;会保证数据的完整性。 外键是指表…...

3. SQL底层执行原理详解

一条SQL在MySQL中是如何执行的 1. MySQL的内部组件结构1.1 Server层1.2 Store层 2. 连接器3. 分析器4. 优化器5. 执行器6. bin-log归档 本文是按照自己的理解进行笔记总结&#xff0c;如有不正确的地方&#xff0c;还望大佬多多指点纠正&#xff0c;勿喷。 1. MySQL的内部组件结…...

Bus动态刷新

Bus动态刷新全局广播配置实现 启动 EurekaMain7001ConfigcenterMain3344ConfigclientMain3355ConfigclicntMain3366 运维工程师 修改Gitee上配置文件内容&#xff0c;增加版本号发送POST请求curl -X POST "http://localhost:3344/actuator/bus-refresh" —次发送…...

逆波兰式的写法

一、什么是波兰式&#xff0c;逆波兰式和中缀表达式 6 *(37) -2 将运算数放在数值中间的运算式叫做中缀表达式 - * 6 3 7 2 将运算数放在数值前间的运算式叫做前缀表达式 6 3 7 * 2 - 将运算数放在数值后间的运算式叫做后缀表达式 二、生成逆波兰表达式 6 *(37) -2 生成…...

Linux系统日志介绍

Linux系统日志都是放在“/var/log”目录下面&#xff0c;各个日志文件的功能&#xff1a; /var/log/messages — 包括整体系统信息&#xff0c;其中也包含系统启动期间的日志。此外&#xff0c;mail&#xff0c;cron&#xff0c;daemon&#xff0c;kern和auth等内容也记录在va…...

第三十二章 React路由组件的简单使用

1、NavLink的使用 一个特殊版本的 Link&#xff0c;当它与当前 URL 匹配时&#xff0c;为其渲染元素添加样式属性 <NavLink className"list-group-item" to"/home">Home</NavLink> <NavLink className"list-group-item" to&quo…...

“裸奔”时代下,我们该如何保护网络隐私?

当我们在互联网上进行各种活动时&#xff0c;我们的个人信息和数据可能会被攻击者窃取或盗用。为了保护我们的隐私和数据安全&#xff0c;以下是一些实用的技巧和工具&#xff0c;可以帮助您应对网络攻击、数据泄露和隐私侵犯的问题&#xff1a; 使用强密码&#xff1a;使用独特…...

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基础 前言 提示&#xff1a;这里可以添加本文要记录的大概…...

怎么让chatGTP写论文-chatGTP写论文工具

chatGTP如何写论文 ChatGPT是一个使用深度学习技术训练的自然语言处理模型&#xff0c;可以用于生成自然语言文本&#xff0c;例如对话、摘要、文章等。作为一个人工智能技术&#xff0c;ChatGPT可以帮助你处理一些文字内容&#xff0c;但并不能代替人类的创造性思考和判断。以…...

springboot 断点上传、续传、秒传实现

文章目录 前言一、实现思路二、数据库表对象二、业务入参对象三、本地上传实现三、minio上传实现总结 前言 springboot 断点上传、续传、秒传实现。 保存方式提供本地上传&#xff08;单机&#xff09;和minio上传&#xff08;可集群&#xff09; 本文主要是后端实现方案&…...

2023河南省赛vp题解

目录 A题&#xff1a; B题 C题 D题 E题 F题 G题 H题 I题 J题 K题 L题 A题&#xff1a; 1.思路&#xff1a;考虑暴力枚举和双hash&#xff0c;可以在O(n)做完。 2.代码实现&#xff1a; #include<bits/stdc.h> #define sz(x) (int) x.size() #define rep(i,z,…...

港科夜闻|香港科大与香港资管通有限公司签署校企合作备忘录,成立校企合作基金促科研成果落地...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大与香港资管通有限公司签署校企合作备忘录&#xff0c;成立校企合作基金促科研成果落地。“港科资管通领航基金”28日在香港成立&#xff0c;将致力于推动高校科研成果转化&#xff0c;助力香港国际创科中心建设。…...

Neo4j 笔记

启动命令 neo4j console Cypher句法由四个不同的部分组成&#xff0c; 每一部分都有一个特殊的规则&#xff1a; start——查找图形中的起始节点。 match——匹配图形模式&#xff0c; 可以定位感兴趣数据的子图形。 where——基于某些标准过滤数据。 return——返回感兴趣的…...

数据库基础应用——概念模型

1、实体(Entity) 客观存在并可相互区别的事物称为实体。实体可以是人、物、对象、概念、事物本身、事物之间的联系。&#xff08;例如一名员工、一个部门、一辆汽车等等。&#xff09; 2、属性(Attributre) 实体所具有的每个特性称为属性。&#xff08;例如&#xff1a;员工由员…...

【学姐面试宝典】前端基础篇Ⅴ——JS深浅拷贝、箭头函数、事件监听等

前言 博主主页&#x1f449;&#x1f3fb;蜡笔雏田学代码 专栏链接&#x1f449;&#x1f3fb;【前端面试专栏】 今天继续学习前端面试题相关的知识&#xff01; 感兴趣的小伙伴一起来看看吧~&#x1f91e; 文章目录 什么是事件监听事件委托以及冒泡原理介绍一下 promise&#…...

最新研究,GPT-4暴露了缺点!无法完全理解语言歧义!

夕小瑶科技说 原创作者 |智商掉了一地、Python自然语言推理&#xff08;Natural Language Inference&#xff0c;NLI&#xff09;是自然语言处理中一项重要任务&#xff0c;其目标是根据给定的前提和假设&#xff0c;来判断假设是否可以从前提中推断出来。然而&#xff0c;由于…...

商业数据挖掘-第一章-数据探索式分析-1

数据探索最基本的步骤之一是获取对数据的基本描述,通过获取对数据的基本描述从而获得对数据的基本感觉。下面的一些方法用于帮助我们认识数据。 我们使用波士顿房价预测的数据集进行实验 DataFrame.describe():查看数据的基本分布,具体是对每列数据进行统计,统计值包含频…...

MybatisPlus是否防止SQL注入?

问 如果我希望使用mybatisplus同时也进行防SQL注入操作&#xff0c;应该怎么处理&#xff1f; 答 如果你想在使用 MyBatis-Plus 进行数据库操作的同时也进行防 SQL 注入处理&#xff0c;可以采用以下两种方式&#xff1a; 使用 #{} 占位符&#xff1a;在 QueryWrapper 或 Up…...

5月第1周榜单丨飞瓜数据B站UP主排行榜(哔哩哔哩平台)发布!

飞瓜轻数发布2023年5月1日-5月7日飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数三个维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营能力强的B站…...

数据的插入删除和更新

在之前我们就已经学过了数据的插入&#xff0c;在这里再进行一点内容的补充&#xff1a; 在insert语句中&#xff0c;value子句中参数的顺序与表中各个列的顺序是一一对应的。 mysql> insert into first_table(second_column, first_column) values(aaa, 1); Query OK, 1 r…...

C# byte[] 与 int 类型互转

本文讲述在C#中,怎样使用 BitConverter 类将字节数组转换为 int 然后又转换回字节数组的过程。 为什么需要这样呢&#xff1f;这是因为&#xff0c;比如说,在从网络读取字节之后&#xff0c;可能需要将字节转换为内置数据类型。 除了示例中的 ToInt32(Byte[], Int32) 方法之外…...