Linux内核子系统--进程管理剖析

news2024/10/7 2:20:47

Linux 是一个计算需求不断变化的非常动态的系统。 Linux 计算需求的表示以进程的公共抽象为中心,进程可以是短期的(从命令行执行的命令)或长期的(网络服务)。因此,进程的总体管理及其调度非常重要。

在用户空间中,进程由进程标识符 (PID) 表示。从用户的角度来看,PID 是唯一标识进程的数值。 PID 在进程的生命周期中不会改变,但 PID 可以在进程终止后重用,因此缓存它们并不总是理想的。

在用户空间中,您可以通过多种方式创建进程。您可以执行一个程序(这会导致创建一个新进程),或者在程序内,您可以调用 fork 或 exec 系统调用。 fork 调用导致创建子进程,而 exec 调用则用新程序替换当前进程上下文。我将讨论每种方法以了解它们的工作原理。

在这篇文章中,我首先展示进程的内核表示以及它们在内核中的管理方式,然后回顾在一个或多个处理器上创建和调度进程的各种方法,最后讨论如果它们死掉会发生什么,从而构建进程的描述。

进程表示

在 Linux 内核中,进程由一个称为 task_struct 的相当大的结构表示。该结构包含表示流程的所有必要数据,以及用于记账和维护与其他流程(父进程和子进程)关系的大量其他数据。对task_struct 的完整描述超出了本文的范围,但task_struct 的一部分如清单1 所示。此代码包含本文探讨的特定元素。请注意,task_struct 位于 ./linux/include/linux/sched.h 中。

/* task_struct部分代码 */
struct task_struct {
    volatile long state;
    void ∗stack;
    unsigned int flags;
    
    int prio, static_prio;
    struct list_head tasks;
    struct mm_struct ∗mm, ∗active_mm;

    pid_t pid;
    pid_t tgid;

    struct task_struct ∗real_parent;
    char comm[TASK_COMM_LEN];
    struct thread_struct thread;
    struct files_struct ∗files;
    ...
};

在上面代码片段中,你可以看到你期望的几个项目,例如执行状态、堆栈、一组标志、父进程、执行线程(可以有多个)和打开的文件。本文后面将探讨这些内容,这里简单介绍一些。状态变量是一组指示任务状态的位。最常见的状态表示进程正在运行或在运行队列中即将运行(TASK_RUNNING)、睡眠(TASK_INTERRUPTIBLE)、睡眠但无法唤醒(TASK_UNINTERRUPTIBLE)、停止(TASK_STOPPED)或其他一些状态。这些标志的完整列表可在 ./linux/include/linux/sched.h 中找到。

flags 字定义了大量的指示符,指示一切,从进程是否正在创建(PF_STARTING)或正在退出(PF_EXITING),甚至进程当前是否正在分配内存(PF_MEMALLOC)。可执行文件的名称(不包括路径)占据comm(命令)字段。

每个进程还被赋予了一个优先级(称为 static_prio),但进程的实际优先级是根据负载和其他因素动态确定的。优先级值越低,其实际优先级越高。

任务字段提供链表功能。它包含一个 prev 指针(指向上一个任务)和一个 next 指针(指向下一个任务)。

进程的地址空间由 mm 和 active_mm 字段表示。 mm 表示进程的内存描述符,而 active_mm 是前一个进程的内存描述符(改进上下文切换时间的优化)。

最后,thread_struct 标识了进程的存储状态。该元素取决于运行 Linux 的特定体系结构,你可以在 ./linux/include/asm-i386/processor.h 中查看相关示例。在此结构中,你将找到进程从执行上下文切换时的存储(硬件寄存器、程序计数器等)。

进程管理

现在,让我们探讨如何在 Linux 中管理进程。在大多数情况下,进程是动态创建的,并由动态分配的task_struct 表示。一个例外是 init 进程本身,它始终存在并由静态分配的 task_struct 表示。你可以在 ./linux/arch/i386/kernel/init_task.c 中看到这样的示例。

Linux 中的所有进程都以两种不同的方式收集。第一个是哈希表,通过PID值进行哈希;第二个是循环双向链表。循环链表非常适合迭代任务列表。由于链表是循环的,因此没有头或尾;但由于 init_task 始终存在,你可以将其用作锚点以进一步迭代。让我们看一个示例来演练当前的任务集。

任务列表无法从用户空间访问,但是你可以通过以模块的形式将代码插入内核来轻松解决该问题。下面的代码片段显示了一个非常简单的程序,它迭代任务列表并提供有关每个任务的少量信息(名称、pid 和父级名称)。请注意,该模块使用 printk 来输出内容。要查看输出,你需要使用 cat 实用程序查看 /var/log/messages 文件(或实时 tail -f /var/log/messages)。 next_task函数是sched.h中的一个宏,它简化了任务列表的迭代(返回下一个任务的task_struct引用)。

/* 用于发出任务信息的简单内核模块(procsview.c) */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>

int init_module( void )
{
  /∗ Set up the anchor point ∗/
  struct task_struct ∗task = &init_task;

  /∗ Walk through the task list, until we hit the init_task again ∗/
  do {
        printk( KERN_INFO "∗∗∗ %s [%d] parent %s\n",
        task‑>comm, task‑>pid, task‑>parent‑>comm );
  } while ( (task = next_task(task)) != &init_task );

  return 0;
}

void cleanup_module( void )
{
  return;
}

可以使用下来代码所示的 Makefile 来编译该模块。编译后,可以使用 insmod procsview.ko 命令插入内核对象,删除时可使用 rmmod procsview 命令。


obj‑m += procsview.o

KDIR := /lib/modules/$(shell uname ‑r)/build
PWD := $(shell pwd)

default:
    $(MAKE) ‑C $(KDIR) SUBDIRS=$(PWD) modules

进程创建

现在让我们来看看从用户空间创建一个进程的过程。用户空间任务和内核任务的底层机制是相同的,因为两者最终都依赖于一个名为 do_fork 的函数来创建新进程。在创建内核线程的情况下,内核调用一个名为 kernel_thread 的函数(参见 ./linux/arch/i386/kernel/process.c),该函数执行一些初始化,然后调用 do_fork。

用户空间进程的创建也会发生类似的操作。在用户空间中,程序调用 fork,这会导致对名为 sys_fork 的内核函数的系统调用(请参阅 ./linux/arch/i386/kernel/process.c)。函数关系如图 1 所示。

                                          图1 用于进程创建的函数层次结构

从图1中,可以看到do_fork提供了进程创建的基础。你可以在 ./linux/kernel/fork.c 中找到 do_fork 函数(以及伙伴函数 copy_process)。
do_fork 函数首先调用 alloc_pidmap,分配一个新的 PID。接下来,do_fork 检查调试器是否正在跟踪父进程。如果是,则在 clone_flags 中设置 CLONE_PTRACE 标志以准备分叉。然后 do_fork 函数继续调用 copy_process,传递标志、堆栈、寄存器、父进程和新分配的 PID。

copy_process 函数是新进程创建为父进程的副本的地方。该函数执行除启动进程之外的所有操作,该进程稍后处理。 copy_process 的第一步是验证 CLONE 标志以确保它们一致。如果不是,则返回 EINVAL 错误。接下来,咨询Linux安全模块(LSM)以查看当前任务是否可以创建新任务。要了解有关安全增强型 Linux (SELinux) 环境中 LSM 的更多信息,请查看资源部分。

接下来,调用 dup_task_struct 函数(位于 ./linux/kernel/fork.c 中),该函数分配一个新的 task_struct 并将当前进程的描述符复制到其中。在建立新的线程堆栈后,一些状态信息被初始化并且控制权返回到copy_process。回到 copy_process,除了其他一些限制和安全检查之外,还执行一些内务处理,包括对新 task_struct 的各种初始化。然后调用一系列复制函数来复制进程的各个方面,从复制打开的文件描述符 (copy_files)、复制信号信息(copy_sighand 和 copy_signal)、复制进程内存 (copy_mm) 到最后复制线程 (copy_thread)。

然后,新任务被分配给处理器,并根据允许执行进程的处理器 (cpus_allowed) 进行一些额外的检查。新进程的优先级继承父进程的优先级后,将执行少量额外的内务处理,并将控制权返回到 do_fork。此时,您的新进程已存在但尚未运行。 do_fork 函数通过调用wake_up_new_task 修复了这个问题。您可以在 ./linux/kernel/sched.c 中找到该函数,它初始化一些调度程序内务信息,将新进程放入运行队列中,然后唤醒它执行。最后,返回do_fork时,PID值返回给调用者,过程完成。

进程调度

虽然 Linux 中存在进程,但它可以通过 Linux 调度程序进行调度。虽然超出了本文的讨论范围,但 Linux 调度程序为每个优先级维护一组列表,其中包含 task_struct 引用。任务通过调度函数(在 ./linux/kernel/sched.c 中提供)调用,该函数根据加载和先前进程执行历史记录确定要运行的最佳进程。您可以在右侧的资源部分了解有关 Linux 版本 2.6 调度程序的更多信息。

进程销毁

进程销毁可以由多个事件驱动–正常进程终止、通过信号或通过调用退出函数。然而进程退出是被驱动的,进程通过调用内核函数 do_exit(在 ./linux/kernel/exit.c 中可用)结束。此过程如图 2 所示。
![图2
进程销毁的函数层次结构]Alt
do_exit 背后的目的是从操作系统中删除对当前进程的所有引用(对于所有未共享的资源)。销毁进程首先通过设置PF_EXITING标志来表明进程正在退出。内核的其他方面使用此指示来避免在删除该进程时对其进行操作。将进程与其在其生命周期中获得的各种资源分离的循环是通过一系列调用执行的,包括 exit_mm(用于删除内存页)到 exit_keys(用于处理每个线程会话和进程安全密钥)。 do_exit 函数执行进程处置的各种统计,然后通过调用 exit_notify 执行一系列通知(例如,向父进程发出子进程正在退出的信号)。最后进程状态变为PF_DEAD,并调用schedule函数选择新进程执行。请注意,如果需要向父级发送信号(或正在跟踪进程),则任务不会完全消失。如果不需要发出信号,则调用release_task实际上会回收进程使用的内存。

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

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

相关文章

EMC学习笔记(十八)滤波器设计

滤波器设计 1.标准要求2.设计理论2.1 滤波器电路设计过程2.2 插入损耗定义2.3 原始噪声测量2.4 插入损耗计算2.5 滤波失配原则2.6 滤波拓扑选择2.7 滤波参数计算2.8 滤波参数确定 Tips&#xff1a;学习资料来自网络&#xff0c;仅供学习使用。 EMI滤波器设计&#xff08;汽车电…

JVM系列(9)——调优初体验

学习这篇文章之前&#xff0c;要了解&#xff1a; JVM系列&#xff08;2&#xff09;——垃圾回收 JVM系列&#xff08;3&#xff09;——内存分配与回收策略 先了解概念&#xff1a; 吞吐量&#xff1a;用户执行时间/(用户执行时间垃圾回收时间)&#xff1b;就是干正经事的时间…

C++语法(25)--- 异常与智能指针

C语法&#xff08;24&#xff09; C11_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/131054426?spm1001.2014.3001.5501 1.异常 try { // 保护的标识代码 }catch( ExceptionName e1 ) { // catch 块 }catch( ExceptionName e2 ) { // catch 块…

RT-Thread qemu mps2-an385 bsp 移植制作 :BSP 制作篇

下载 V2M-MPS2_CMx_BSP mps2 的资料很少&#xff0c;所以唯一能下载的是 ARM 官方的 V2M-MPS2_CMx_BSP&#xff0c;下载地址为&#xff1a; https://keilpack.azureedge.net/pack/Keil.V2M-MPS2_CMx_BSP.1.8.0.pack 其实这是个 Keil MDK5 的 Pack 包&#xff0c;安装后&#x…

JAVA-ReentrantLock(五)

概念 在Java中&#xff0c;“lock”&#xff08;锁&#xff09;是一种用于并发控制的机制。它用于确保在多线程环境中&#xff0c;同一时刻只有一个线程可以访问共享资源或临界区。当一个线程获得了锁&#xff0c;其他线程将被阻塞&#xff0c;直到持有锁的线程释放它。这样可…

Cocos Creator 3.8 后期效果 Shader 编写(1/2) 基础篇

原文链接&#xff1a;Cocos Creator 3.8 后期效果 Shader 编写&#xff08;1/2&#xff09; 基础篇 在 Cocos Creator 3.8 版本中&#xff0c;新增了不少实用的特性&#xff0c;其中我最喜欢的&#xff0c;就是它自带后期效果管线&#xff0c;并且还内置了许多高级效果。 有用…

XUbuntu22.04之Linux剪切板和selection primary区域(一百八十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

二、Java框架基础02 XML

二、XML 2.1 XML 简介 XML 即可扩展标记语言&#xff0c;一种简单的数据存储语言&#xff0c;使用一系列简单的标记来描述结构化数据 XML 的特点 XML 与操作系统&#xff0c;编程语言的开发平台无关规范统一&#xff0c;实现不同系统之间的数据交互 2.1.1 XML 的文档结构 以下…

vulnhub靶场之CengBox3

1.信息收集 输入命令&#xff1a;netdiscover -i eth0 -r 192.168.239.0 &#xff0c;发现181机器存活 输入命令nmap -p- -sV -O -Pn -A 192.168.239.181 &#xff0c;进行端口探测&#xff0c;发现存在22、80、443端口&#xff0c;还发现存在域名ceng-company.vm。 将域名c…

【linux基础】05-linux文件系统

概述 在Linux中,文件系统是一种分层结构,它将文件和目录组织成树状结构。文件系统从“根”目录开始,该目录由单个正斜杠(“/”)表示。 如下图所示: Linux 支持多种类型的文件系统,包括: Ext4:这是大多数 Linux 发行版的默认文件系统。它是一个日志文件系统,提供良…

拉格朗日乘数法(Lagrange)的推导

同济版高数上&#xff0c;关于拉格朗日乘数法&#xff0c;以及好多知识点说的语焉不详、模棱两可&#xff0c;在阅读了知乎等博主的几篇文章后&#xff0c;才算勉强弄懂了该知识的原理。 首先说一下高数上隐函数求导。所谓的隐函数求导&#xff0c;就是在方程中多个变量之间的…

如何使用Java 实现excel模板导出---多sheet导出?

实现多个sheet的excel导出功能 效果展示&#xff1a; maven依赖 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.17</version></dependency><dependency><groupId>or…

泛微E-Cology XXE漏洞复现(QVD-2023-16177)

0x01 产品简介 泛微协同管理应用平台E-Cology是一套兼具企业信息门户、知识文档管理、工作流程管理、人力资源管理、客户关系管理、项目管理、财务管理、资产管理、供应链管理、数据中心功能的企业大型协同管理平台。 0x02 漏洞概述 泛微e-cology某处功能点最初针对用户输入的…

STM32 点灯实现 7.18

嵌入式&#xff1a; 以应用为中心&#xff0c;以专用计算机为基础&#xff0c;软硬件可裁剪ARM A系列芯片&#xff1a;高端芯片&#xff0c;实现人机互动 R系列&#xff1a;实现时效性 M系列&#xff1a;低端芯片&#xff0c;控制硬件设备&#xff0c;灯&#xff0c;风扇....…

Springboot初识(一)

一.什么是Spring Boot Spring Boot是一个开源的、用于简化Spring应用程序开发的框架。它是Spring项目的一个子项目&#xff0c;旨在为Spring应用程序提供更快速、更便捷的开发体验。Spring Boot基于Spring框架&#xff0c;同时也整合了其他Spring项目和第三方库&#xff0c;使…

Unity-AssetBundle

一、AB 包介绍 ​ AB 包是特定于平台的资源压缩包&#xff0c;类似于压缩文件。其中资源可包括&#xff1a;模型、贴图、预设体、音效、材质球等等。 ​ 相较于 Resources 文件夹下的资源文件&#xff0c;AB 包能够更好管理资源&#xff1a; Resources 文件夹&#xff1a;打包…

【设计模式】23种设计模式——建造者模式Builder(原理讲解+应用场景介绍+案例介绍+Java代码实现)

介绍 建造者模式又叫生成器模式&#xff0c;是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别)&#xff0c;使这个抽象过程的不同实现方法可以构造出不同属性的对象建造者模式是一步一步创建一个复杂的对象&#xff0c;它允许用户只通过指定复杂对象的类型和…

【PHP面试题79】在Linux中如何设置MySQL和PHP服务开机启动

文章目录 &#x1f680;一、前言&#x1f680;二、设置MySQL服务开机启动&#x1f50e;2.1 打开终端&#x1f50e;2.2 编辑MySQL配置文件&#x1f50e;2.3 修改配置文件&#x1f50e;2.4 检查MySQL服务是否已启动&#x1f50e;2.5 设置MySQL服务开机启动 &#x1f680;三、设置…

C# Modbus通信从入门到精通(12)——Modbus ASCII协议原理

Modbus ASCII是串行链路上的协议,也就是说ModbusASCII是通过串口通信来实现的,它可以通过RS232、RS485物理层的接口来实现,同时它也是一个主从协议,在同一时间总线上只能有一个主站和一个或多个(最多247)个从站。Modbus通信总是由主站发起,从站没有接收到主站的请求时不…

NOAA国家强风暴实验室的天气雷达研究历史(1962年~2016年)

一、1962年-NSSP开始研究WSR-57 美国气象局国家严重风暴项目(NSSP)的一小群研究人员从堪萨斯城搬到俄克拉荷马州诺曼的天气雷达实验室,并开始研究最近安装的研究天气监视雷达-1957(WSR-57)。 二、1964年-NSSL开发的脉冲多普勒雷达技术 1956年,康奈尔航空实验室建造了一…