当前位置: 首页 > 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,…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢

随着互联网技术的飞速发展&#xff0c;消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁&#xff0c;不仅优化了客户体验&#xff0c;还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用&#xff0c;并…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》

这段 Python 代码是一个完整的 知识库数据库操作模块&#xff0c;用于对本地知识库系统中的知识库进行增删改查&#xff08;CRUD&#xff09;操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 &#x1f4d8; 一、整体功能概述 该模块…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)

macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 &#x1f37a; 最新版brew安装慢到怀疑人生&#xff1f;别怕&#xff0c;教你轻松起飞&#xff01; 最近Homebrew更新至最新版&#xff0c;每次执行 brew 命令时都会自动从官方地址 https://formulae.…...

comfyui 工作流中 图生视频 如何增加视频的长度到5秒

comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗&#xff1f; 在ComfyUI中实现图生视频并延长到5秒&#xff0c;需要结合多个扩展和技巧。以下是完整解决方案&#xff1a; 核心工作流配置&#xff08;24fps下5秒120帧&#xff09; #mermaid-svg-yP…...

Linux-进程间的通信

1、IPC&#xff1a; Inter Process Communication&#xff08;进程间通信&#xff09;&#xff1a; 由于每个进程在操作系统中有独立的地址空间&#xff0c;它们不能像线程那样直接访问彼此的内存&#xff0c;所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...

【java面试】微服务篇

【java面试】微服务篇 一、总体框架二、Springcloud&#xff08;一&#xff09;Springcloud五大组件&#xff08;二&#xff09;服务注册和发现1、Eureka2、Nacos &#xff08;三&#xff09;负载均衡1、Ribbon负载均衡流程2、Ribbon负载均衡策略3、自定义负载均衡策略4、总结 …...

Django RBAC项目后端实战 - 03 DRF权限控制实现

项目背景 在上一篇文章中&#xff0c;我们完成了JWT认证系统的集成。本篇文章将实现基于Redis的RBAC权限控制系统&#xff0c;为系统提供细粒度的权限控制。 开发目标 实现基于Redis的权限缓存机制开发DRF权限控制类实现权限管理API配置权限白名单 前置配置 在开始开发权限…...

高效的后台管理系统——可进行二次开发

随着互联网技术的迅猛发展&#xff0c;企业的数字化管理变得愈加重要。后台管理系统作为数据存储与业务管理的核心&#xff0c;成为了现代企业不可或缺的一部分。今天我们要介绍的是一款名为 若依后台管理框架 的系统&#xff0c;它不仅支持跨平台应用&#xff0c;还能提供丰富…...