Linux 进程管理之内核栈和struct pt_regs

news2024/11/18 13:58:40

文章目录

  • 前言
  • 一、内核栈
  • 二、struct pt_regs
    • 2.1 简介
    • 2.2 获取pt_regs
  • 参考资料

前言

Linux内核栈是用于存储内核执行期间的函数调用和临时数据的一块内存区域。每个运行的进程都有自己的内核栈,用于处理中断、异常、系统调用以及内核函数的执行。

每个进程的内核栈:在Linux中,每个进程都有自己的内核栈。当进程从用户空间切换到内核空间时,它将使用自己的内核栈来执行内核代码。

栈空间分配:内核栈在进程创建时被分配和初始化。在进程切换时,内核会自动切换到相应进程的内核栈。

大小限制:Linux内核栈的大小通常是固定的,取决于架构和编译时的配置。它通常较小,以节省内存空间。

栈溢出:由于内核栈的大小有限,如果在内核执行期间使用过多的栈空间,会导致栈溢出。栈溢出可能会导致系统崩溃或不可预测的行为。因此,内核开发人员需要注意避免在内核代码中使用过多的栈空间。

中断和上下文切换:当发生中断或系统调用时,当前进程的上下文会被保存到其内核栈中,然后切换到内核中断处理程序或系统调用处理程序的上下文。完成处理后,内核将恢复进程的上下文,并继续执行原来的任务。

一、内核栈

linux程序通过系统调用、中断、异常等手段从用户态切换到内核态时,内核中会有各种各样的函数调用:

对于x86_64架构函数调用,前六个参数使用rdi、rsi、rdx、rcx、r8、r9 这 6 个寄存器,用于传递存储函数调用时的 6 个参数。如果超过 6 的时候,需要放到栈里面。然而,前 6 个参数有时候需要进行寻址,但是如果在寄存器里面,是没有地址的,因而还是会放到栈里面,只不过放到栈里面的操作是被调用函数做的。

这样就需要栈来保存函数调用过程的局部变量,函数参数等,这些局部变量,函数参数就是保存在进程的内核栈中 struct task_struct -> stack。

struct task_struct {
	......
	void				*stack;
	......
}

Linux 给每个 task 都分配了内核栈,内核栈大小:

union thread_union {
	......
	unsigned long stack[THREAD_SIZE/sizeof(long)];
};

THREAD_SIZE就表示内核栈的大小。

// linux-5.4.18/arch/x86/include/asm/page_types.h

/* PAGE_SHIFT determines the page size */
#define PAGE_SHIFT		12
// linux-5.4.18/arch/x86/include/asm/page_64_types.h

#ifdef CONFIG_KASAN
#define KASAN_STACK_ORDER 1
#else
#define KASAN_STACK_ORDER 0
#endif

#define THREAD_SIZE_ORDER	(2 + KASAN_STACK_ORDER)
#define THREAD_SIZE  (PAGE_SIZE << THREAD_SIZE_ORDER)

当没有配置CONFIG_KASAN选项,KASAN_STACK_ORDER = 0,在 PAGE_SIZE 的基础上左移两位,那么内核栈的大小就是4个页面的大小:4 * 4096,即 16 KB.

内核栈是一个非常特殊的结构,如下图所示:
在这里插入图片描述

在内核栈的最高地址端,存放的是另一个结构 pt_regs,这个结构体保存着进程从应用层进入到内核层时,用户态寄存器的状态。

我们可以看到在结构 pt_regs上面还有一个内核栈预留空间,这在x86_32位架构的一个遗留问题,在x86_64架构和arm64架构都没有该内核栈预留空间,因此:
在这里插入图片描述

获取内核栈:

/*
 * When accessing the stack of a non-current task that might exit, use
 * try_get_task_stack() instead.  task_stack_page will return a pointer
 * that could get freed out from under you.
 */
static inline void *task_stack_page(const struct task_struct *task)
{
	return task->stack;
}

#define setup_thread_stack(new,old)	do { } while(0)

static inline unsigned long *end_of_stack(const struct task_struct *task)
{
	return task->stack;
}

task_stack_page 函数用于获取任务的栈页指针,end_of_stack 函数用于获取栈的末尾地址。

二、struct pt_regs

2.1 简介

对于内核栈上面的struct pt_regs结构体,linux程序通过系统调用、中断、异常等手段从用户态切换到内核态时,内核态需要保存用户态的寄存器上下文,通常内核态会在内核态堆栈的最顶端保留一段空间来存储用户态的寄存器上下文,这段空间的存储格式为pt_regs,当进程用内核态切换到用户态时,就会获取pt_regs结构体中的成员,这样就可以获取当进程用户态运行的寄存器上下文状态了。

当系统调用从用户态到内核态的时候,首先要做的第一件事情,就是将用户态运行过程中的 CPU 上下文保存起来,其实主要就是保存在这个结构的寄存器变量里。这样当从内核系统调用返回的时候,才能让进程在刚才的地方接着运行下去。

对于x86_64:

struct pt_regs {
/*
 * C ABI says these regs are callee-preserved. They aren't saved on kernel entry
 * unless syscall needs a complete, fully filled "struct pt_regs".
 */
	unsigned long r15;
	unsigned long r14;
	unsigned long r13;
	unsigned long r12;
	unsigned long bp;
	unsigned long bx;
/* These regs are callee-clobbered. Always saved on kernel entry. */
	unsigned long r11;
	unsigned long r10;
	unsigned long r9;
	unsigned long r8;
	unsigned long ax;
	unsigned long cx;
	unsigned long dx;
	unsigned long si;
	unsigned long di;
/*
 * On syscall entry, this is syscall#. On CPU exception, this is error code.
 * On hw interrupt, it's IRQ number:
 */
	unsigned long orig_ax;
/* Return frame for iretq */
	unsigned long ip;
	unsigned long cs;
	unsigned long flags;
	unsigned long sp;
	unsigned long ss;
/* top of stack page */
};

对于arm64:

/*
 * This struct defines the way the registers are stored on the stack during an
 * exception. Note that sizeof(struct pt_regs) has to be a multiple of 16 (for
 * stack alignment). struct user_pt_regs must form a prefix of struct pt_regs.
 */
struct pt_regs {
	union {
		struct user_pt_regs user_regs;
		struct {
			u64 regs[31];
			u64 sp;
			u64 pc;
			u64 pstate;
		};
	};
	u64 orig_x0;
#ifdef __AARCH64EB__
	u32 unused2;
	s32 syscallno;
#else
	s32 syscallno;
	u32 unused2;
#endif

	u64 orig_addr_limit;
	/* Only valid when ARM64_HAS_IRQ_PRIO_MASKING is enabled. */
	u64 pmr_save;
	u64 stackframe[2];
};

2.2 获取pt_regs

内核通过task_pt_regs宏根据一个task来获取该task的struct pt_regs:

对于x86_64:

#ifdef CONFIG_X86_32
# ifdef CONFIG_VM86
#  define TOP_OF_KERNEL_STACK_PADDING 16
# else
#  define TOP_OF_KERNEL_STACK_PADDING 8
# endif
#else
# define TOP_OF_KERNEL_STACK_PADDING 0
#endif

TOP_OF_KERNEL_STACK_PADDING为内核栈预留空间,对于x86_64架构该预留空间为0。

#define task_pt_regs(task) \
({									\
	unsigned long __ptr = (unsigned long)task_stack_page(task);	\
	__ptr += THREAD_SIZE - TOP_OF_KERNEL_STACK_PADDING;		\
	((struct pt_regs *)__ptr) - 1;					\
})

宏定义用于获取给定任务(进程)的 pt_regs 结构体指针。

从 task_struct 找到内核栈的开始位置(栈顶)。然后这个位置加上 内核栈的大小THREAD_SIZE ,然后减去这个内核栈的预留空间,对于x86_64内核栈的预留空间为0,就到内核栈的最后的位置(栈顶),此时是struct pt_regs结构体的最后一个成员的ss的地址,而我们要获取的是struct pt_regs结构的首地址,所以要减去一个 struct pt_regs结构体大小,然后转换为 struct pt_regs,再减一,就相当于减少了一个 pt_regs 结构体大小的位置,就到了这个结构的首地址,也就是 struct pt_regs结构体r15成员的位置,r15是struct pt_regs结构的第一个成员。

从这个图我们也可以知道struct pt_regs结构的成员入栈时,是ss最先入栈,r15最后入栈。

在这里插入图片描述

我们可以看一下linux程序通过系统调用从用户态切换到内核态时,内核态保存用户态的寄存器上下文时候的压栈顺序:

// linux-5.4.18/arch/x86/entry/entry_64.S

ENTRY(entry_SYSCALL_64)
	......
	/* Construct struct pt_regs on stack */
	pushq	$__USER_DS				/* pt_regs->ss */
	pushq	PER_CPU_VAR(cpu_tss_rw + TSS_sp2)	/* pt_regs->sp */
	pushq	%r11					/* pt_regs->flags */
	pushq	$__USER_CS				/* pt_regs->cs */
	pushq	%rcx					/* pt_regs->ip */
GLOBAL(entry_SYSCALL_64_after_hwframe)
	pushq	%rax					/* pt_regs->orig_ax */
	.......

可以看到是pt_regs->ss成员先压入栈中。

对于arm64获取pt_regs:

#define task_pt_regs(p) \
	((struct pt_regs *)(THREAD_SIZE + task_stack_page(p)) - 1)

参考资料

Linux 5.4.18

极客时间:趣谈操作系统

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

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

相关文章

网络技术十三:DNS(域名服务器)

DNS 域名 产生背景 通过IP地址访问目标主机&#xff0c;不便于记忆 通过容易记忆的域名来标识主机位置 域名的树形层次化结构 根域 领级域 主机所处的国家/区域&#xff0c;注册人的性质 二级域 注册人自行创建的名称 主机名 区域内部的主机的名称 由注册人自行创建…

2023-python-import耗时是为什么?

场景 场景&#xff1a; 树莓派4B 离线安装【arch64架构】 了 torch,sklearn等机器学习库 运行程序文件时候&#xff0c; import的时间总共花了 10s&#xff0c;无法忍受。 查阅下网站&#xff1a; import官方说辞 看蒙了&#xff0c;太多了&#xff1b; 反正就看看大概&…

Credo(纳斯达克股票代码:CRDO)推出Seagull 452系列高性能光DSP芯片

加州圣何塞和中国深圳&#xff0c;2023年9月5日——Credo Technology&#xff08;纳斯达克股票代码&#xff1a;CRDO&#xff09;是一家提供安全、高速连接解决方案的创新企业。Credo致力于为数据基础设施市场提供其所必须的高能效、高速率解决方案&#xff0c;以满足其不断增长…

怎么让图片动起来?试试这几种方法

怎么让图片动起来&#xff1f;让图片动起来可以为你的内容增添趣味性和互动性&#xff0c;吸引更多的关注和互动。在社交媒体上使用动态图片可以吸引更多的关注和互动&#xff0c;让你的内容更容易被人们发现和分享。在广告宣传方面&#xff0c;动态图片可以帮助你更好地展示产…

matlab和python做zscore结果不一样的问题

解决matlab和python做zscore标准化结果不一样的问题 总结: matlab和python做zscore时使用的求std公式的默认方法有差异&#xff0c;导致了结果差异。 想要结果相同则使用以下代码&#xff1a; td rand(50,15,39)%td是三维矩阵&#xff0c;求zscore结果 #Python代码&#xf…

爬虫逆向实战(29)-某蜂窝详情页(cookie、混淆、MD5、SHA)

一、数据接口分析 主页地址&#xff1a;某蜂窝 1、抓包 通过抓包可以发现数据是静态的&#xff0c;在html中。 2、判断是否有加密参数 请求参数是否加密&#xff1f; 无请求头是否加密&#xff1f; 无响应是否加密&#xff1f; 无cookie是否加密&#xff1f; 通过查看“c…

AIGC是不是有点虎头蛇尾

一、前言 2023年上半年AI与AIGC真是风风火火&#xff0c;不管是技术界还是资本界还是其他任何领域&#xff0c;如果你不知道chatgpt和AIGC&#xff0c;你就是个跟不上时代的人儿。如今大半年过去了&#xff0c;好像这个chatgpt和AIGC比没有太多的人提起&#xff0c;是不是有点…

ONNX OpenVino TensorRT MediaPipe NCNN Diffusers

框架 和Java生成的中间文件可以在JVM上运行一样&#xff0c;AI技术在具体落地应用方面&#xff0c;和其他软件技术一样&#xff0c;也需要具体的部署和实施的。既然要做部署&#xff0c;那就会有不同平台设备上的各种不同的部署方法和相关的部署架构工具 onnx 在训练模型时可以…

工业4.0时代生产系统对接集成优势,MES和ERP专业一体化管理-亿发

在现代制造业中&#xff0c;市场变化都在不断加速。企业面临着不断加强生产效率、生产质量和快速适应市场需求的挑战。在制造行业&#xff0c;日常管理中的ERP系统、MES系统就显得尤为重要。越来越多的企业正在采用MES系统和ERP管理系统的融合&#xff0c;以实现智能化生产管理…

手把手教你在linux中部署kong网关

一 Kong网关介绍 Kong是一个云原生&#xff0c;快速&#xff0c;可扩展的分布式微服务抽象层&#xff08;也被称为API网关或API中间件&#xff09;&#xff0c; 它的核心价值是高性能和可扩展性&#xff0c;于2015年作为一个开源项目提供;Kong可以充当微服务请求的网关&#x…

SpringCloud环境搭建及入门案例

技术选型&#xff1a; Maven 3.8.4SpringBoot 2.7.8SpringCloud 2021.0.4SpringCloudAlibaba 2022.0.1.0Nacos 2.1.1Sentinel 1.8.5 模块设计&#xff1a; 父工程&#xff1a;SpringCloudAlibaba订单微服&#xff1a;order-service库存微服&#xff1a;stock-service 1.创建…

g++安装 yum -y install gcc+ gcc-c++ 报错Unable to find a match: gcc+

文章目录 1. Linux 中 g: command not found 解决方法2. g安装 yum -y install gcc gcc-c 报错Unable to find a match: gcc 1. Linux 中 g: command not found 解决方法 原因&#xff1a;G没有安装或者没有更新 解决方法如下&#xff1a; centos&#xff1a; yum -y update…

Vue echarts 饼图 引导线加小圆点,文字分行展示

需求 重点代码 完整代码 initChart() {// 创建 echarts 实例。var myChartOne this.$echarts.init(this.$refs.Echart);myChartOne.setOption({tooltip: {trigger: "item",},title: {top: center,text: [{name| this.chartTitle.name },{value| this.chartTitle.…

探索Kubernetes的高可用性:单master集群和多master节点集群方案

一、单Master集群 k8s 集群是由一组运行 k8s 的节点组成的&#xff0c;节点可以是物理机、虚拟机或者云服务器。k8s 集群中的节点分为两种角色&#xff1a;master 和 node。 master 节点&#xff1a;master 节点负责控制和管理整个集群&#xff0c;它运行着一些关键的组件&…

OpenCL编程指南-10.2使用C++包装器API的矢量相加示例

选择OpenCL平台并创建一个上下文 建立OpenCL的第一步是选择一个平台。第2章介绍过&#xff0c;OpenCL使用了ICD模型&#xff0c;其中可以有多个OpenCL实现在一个系统上并存。类似于HelloWorld示例&#xff0c;这个矢量相加程序展示了选择OpenCL平台的一种最简单的方法&#xf…

7000+客户经验总结,《数字化转型实践指南》重磅发布

数字化转型 2022年1月&#xff0c;国务院印发《“十四五”数字经济发展规划》&#xff0c;明确要求加快企业数字化转型升级&#xff0c;推进数字化转型&#xff0c;自此数字化转型又开启了新篇章。 调研数据显示&#xff0c;成功的数字化转型能让企业效率提升20-25倍&#xff…

微信小程序技术分享,以及项目实战:商城花园

目前移动端开发技术非常多&#xff0c;原生的有如下&#xff1a; 一、原生开发 Android Studio: 是用于开发 Android 应用的集成开发环境 (IDE)。 Xcode: 是用于开发 iOS 应用的 IDE。 鸿蒙HarmonyOS体系&#xff1a;华为官方提供的&#xff0c;HarmonyOS是一款面向万物互联时…

HTTPS加密协议详解:TLS/SSL握手过程

1、握手与密钥协商过程 基于RSA握手和密钥交换的客户端验证服务器为示例详解TLS/SSL握手过程。 (1).client_hello 客户端发起请求&#xff0c;以明文传输请求信息&#xff0c;包含版本信息&#xff0c;加密套件候选列表&#xff0c;压缩算法候选列表&#xff0c;随机数&#…

RabbitMQ: Publish/Subscribe结构

生产者 package com.qf.mq2302.publishSub;import com.qf.mq2302.utils.MQUtils;import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection;public class EmitLog {private static final String EXCHANGE_NAME "logs";public static void main…

word转PDF文件变小,图片模糊

word论文29M&#xff0c;文件——另存为——只有1.5M左右&#xff0c;图片压缩严重&#xff0c;图片看不清。 word中很多大图&#xff0c;5M一张的图&#xff0c;所以word很大。 找了很多方法&#xff0c;转换后都在2M左右&#xff0c;勉强可以。 直到找到了这个&#xff0c…