Linux进程的数据结构

news2024/11/20 20:44:19

在Linux里面,无论是进程还是线程,到了内核里面统一叫任务(task),有一个统一的结构task_struct进行管理。

在程序执行过程中,一旦调用到系统调用,就需要进入内核继续执行,那么讲用户态执行和内核态执行串起来就要用到一下两个重要的成员变量:

struct thread_info thread_info;
void *stack;

用户态函数栈

在用户态中,程序的执行往往是一个函数调用另一个函数。函数调用都是通过栈来进行的。函数调用其实也很简单,其实就是指令跳转,从代码的一个地方跳到另外一个地方。这里比较棘手的问题是,参数和返回地址应该怎么传递过去呢?因为函数调用的特点就是先进后出,先调用的最后返回,跟栈的特点很类似,所以这里用栈保存函数的调用过程很合适。

在进程的内存空间里面,栈是一个从高地址到低地址,往下增长的结构,也就是上面是栈底,下 面是栈顶,入栈和出栈的操作都是从下面的栈顶开始的。

 先来看32位操作系统的情况。

在CPU里,ESP(Extended Stack Pointer)是栈顶指针寄存器,入栈操作Push和出栈操作Pop指令,会自动调整ESP的值。另外有一个寄存器EBP(Extended Base Pointer),是栈基地址指针寄存器,指向当前栈帧的最底部。

 再来看64位操作系统的情况

对于64位操作系统,模式多少有些不一样。因为64位操作系统的寄存器数目比较多。rax用于保存函数调用的返回结果。栈顶指针寄存器变成了rsp,指向栈顶位置。堆栈的Pop和Push操作会自动调整rsp,栈基指针寄存器变成了rbp,指向当前栈帧的起始位置。

以上栈的餐桌都是在进程的内存空间进行的。

内核态函数栈

通过系统调用,从进程的内存空间到内核中了。内核中也有各种各样的函数调用来调用去的,也需要这样一个机制。这时候就用到了一开始提到的成员变量stack,也就是内核栈。

Linux给每个task都分配了内核栈。在32位系统上arch/x86/include/asm/page_32_types.h,是这样定义的:一个PAGE_SIZE是4K,左移一位就是乘以2,也就是8K。

#define THREAD_SIZE_ORDER 1
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)

内核栈在64位系统上arch/x86/include/asm/page_64_types.h,是这样定义的:在PAGE_SIZE的基础上左移两位,也即16K,并且要求起始地址必须是8192的整数倍。

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

内核栈的结构如下:

 这段空间的最低位置,是一个thread_info结构。这个结构是对task_struct结构的补充。因为 task_struct结构庞大但是通用,不同的体系结构就需要保存不同的东西,所以往往与体系结构有关的,都放在thread_info里面。

在内核代码里面有这样一个union,将thread_info和stack放在一起,在include/linux/sched.h文件中就有:

union thread_union {
#ifndef CONFIG_THREAD_INFO_IN_TASK
    struct thread_info thread_info;
#endif
    unsigned long stack[THREAD_SIZE/sizeof(long)];
};

在内核栈的最高地址端,存放的是另一个结构pt_regs,定义如下。其中,32位和64位的定义不一样:

#ifdef __i386__
struct pt_regs {
unsigned long bx;
unsigned long cx;
unsigned long dx;
unsigned long si;
unsigned long di;
unsigned long bp;
unsigned long ax;
unsigned long ds;
unsigned long es;
unsigned long fs;
unsigned long gs;
unsigned long orig_ax;
unsigned long ip;
unsigned long cs;
unsigned long flags;
unsigned long sp;
unsigned long ss;
};
#else 
struct pt_regs {
unsigned long r15;
unsigned long r14;
unsigned long r13;
unsigned long r12;
unsigned long bp;
unsigned long bx;
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;
unsigned long orig_ax;
unsigned long ip;
unsigned long cs;
unsigned long flags;
unsigned long sp;
unsigned long ss;
/* top of stack page */
};
#endif 

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

通过task_struct找内核栈

如果有一个task_struct的stack指针在手,可以通过下面的函数找到这个线程内核栈:

static inline void *task_stack_page(const struct task_struct *task)
{
    return task->stack;
}

从task_struct如何得到相应的pt_regs呢?我们可以通过下面的函数:

/*
 * TOP_OF_KERNEL_STACK_PADDING reserves 8 bytes on top of the ring0 stack.
 * This is necessary to guarantee that the entire "struct pt_regs"
 * is accessible even if the CPU haven't stored the SS/ESP registers
 * on the stack (interrupt gate does not save these registers
 * when switching to the same priv ring).
 * Therefore beware: accessing the ss/esp fields of the
 * "struct pt_regs" is possible, but they may contain the
 * completely wrong values.
 */
#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;                              \
})

这是先从task_struct找到内核栈的开始位置。然后这个位置加上THREAD_SIZE就到了最后的位置,然后转换为struct pt_regs,再减一,就相当于减少了一个pt_regs的位置,就到了这个结构的首地址。

这里面有一个TOP_OF_KERNEL_STACK_PADDING,这个的定义如下:

#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

也就是说,32位机器上是8,其他是0。这是为什么呢?因为压栈pt_regs有两种情况。我们知道,CPU用ring来区分权限,从而Linux可以区分内核态和用户态。

因此,第一种情况,我们拿涉及从用户态到内核态的变化的系统调用来说。因为涉及权限的改 变,会压栈保存SS、ESP寄存器的,这两个寄存器共占用8个byte。

另一种情况是,不涉及权限的变化,就不会压栈这8个byte。这样就会使得两种情况不兼容。如果没有压栈还访问,就会报错,所以还不如预留在这里,保证安全。在64位上,修改了这个问题,变成了定长的。

支持,如果能拿到task_struct,那么就能轻松得到内核栈和内核寄存器。

通过内核栈找到task_struct

那如果一个当前在某个CPU上执行的进程,想知道自己的task_struct在哪里,又该怎么办呢?

这就要通过我们的thread_info这个结构了:

struct thread_info {
    struct task_struct *task; /* main task structure */
    __u32 flags; /* low level flags */
    __u32 status; /* thread synchronous flags */
    __u32 cpu; /* current CPU */
    mm_segment_t addr_limit;
    unsigned int sig_on_uaccess_error:1;
    unsigned int uaccess_err:1; /* uaccess failed */
};

这里面有个成员变量task指向task_struct,所以我们常用current_thread_info()->task来获取 task_struct:

static inline struct thread_info *current_thread_info(void)
{
    return (struct thread_info *)(current_top_of_stack() - THREAD_SIZE);
}

而thread_info的位置就是内核栈的最高位置,减去THREAD_SIZE,就到了thread_info的起始地址。

但是现在变成这样了,只剩下一个flags:

struct thread_info {
     unsigned long flags; /* low level flags */
};

这时候怎么获取当前运行中的task_struct呢?current_thread_info有了新的实现方式。在include/linux/thread_info.h中定义了current_thread_info:

#include <asm/current.h>
#define current_thread_info() ((struct thread_info *)current)
#endif

那current又是什么呢?在arch/x86/include/asm/current.h中定义了:

struct task_struct;
DECLARE_PER_CPU(struct task_struct *, current_task);
static __always_inline struct task_struct *get_current(void)
{
    return this_cpu_read_stable(current_task);
}
#define current get_current

新的机制里面,每个CPU运行的task_struct不通过thread_info获取了,而是直接放在Per CPU 变量里面了。

多核情况下,CPU是同时运行的,但是它们共同使用其他的硬件资源的时候,我们需要解决多个 CPU之间的同步问题。

Per CPU变量是内核中一种重要的同步机制。顾名思义,Per CPU变量就是为每个CPU构造一个 变量的副本,这样多个CPU各自操作自己的副本,互不干涉。比如,当前进程的变量 current_task就被声明为Per CPU变量。

要使用Per CPU变量,首先要声明这个变量,在arch/x86/include/asm/current.h中有:

DECLARE_PER_CPU(struct task_struct *, current_task);

然后是定义这个变量,在arch/x86/kernel/cpu/common.c中有:

DEFINE_PER_CPU(struct task_struct *, current_task) = &init_task;

也就是说,系统刚刚初始化的时候,current_task都指向init_task。

当某个CPU上的进程进行切换的时候,current_task被修改为将要切换到的目标进程。例如,进程切换函数__switch_to就会改变current_task:

__visible __notrace_funcgraph struct task_struct *
__switch_to(struct task_struct *prev_p, struct task_struct *next_p)
{
......
    this_cpu_write(current_task, next_p);
......
    return prev_p;
}

当要获取当前的运行中的task_struct的时候,就需要调用this_cpu_read_stable进行读取。

现在如果你是一个进程,正在某个CPU上运行,就能够轻松得到task_struct了。

总结:

在用户态,应用程序进行了至少一次函数调用。32位和64的传递参数的方式稍有不同,32位的就是用函数栈,64位的前6个参数用寄存器,其他的用函数栈。

在内核态,32位和64位都使用内核栈,格式也稍有不同,主要集中在pt_regs结构上。

在内核态,32位和64位的内核栈和task_struct的关联关系不同。32位主要靠thread_info, 64位主要靠Per-CPU变量。

 

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

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

相关文章

5.DI之注解配置

1.编写Spring框架核心配置文件applicationContext.xml 在项目目录“/src/main/resources”下新建applicationContext.xml文件&#xff0c;具体代码如下。 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework…

HashMap1.8也会发生死循环—记录

目录 代码 jstack 分析 什么是哈希表 在讨论哈希表之前&#xff0c;我们先大概了解下其他数据结构在新增&#xff0c;查找等基础操作执行性能 数组&#xff1a;采用一段连续的存储单元来存储数据。对于指定下标的查找&#xff0c;时间复杂度为O(1)&#xff1b;通过给定值进…

web课程设计网页规划与设计:HTML+CSS美妆设计题材——雅诗兰黛(5页)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

制作移动端整页滚动动画

制作移动端整页滚动动画 需要用到 rem7.5.js(rem适配) pageSlider.js(控制动画的js文件) 基于zepto&#xff0c;引入zepto.js文件 animate.css(动画样式) base.css(公共样式) 下面看一下页面结构 <div class"section sec1"style"background-image:url(./ima…

Java设计模式 - 管道模式

管道模式是责任链模式的常用变种之一&#xff0c;但是管道模式和责任链模式有一个关键的区别&#xff0c;在看一些博客的时候并没有体现出来出来&#xff0c;很多人都把责任链模式当做管道模式来说。 定义 管道模式使用有序的Stage(或者Handler)来顺序的处理一个输入值&#…

以智能视觉驱动智慧出行,看中科创达如何持续深耕汽车市场

当前&#xff0c;360全景环视&#xff08;AVM&#xff09;正在往丰富的ADAS功能、透明底盘功能、不同等级的自动泊车功能等领域拓展&#xff0c;全景环视市场的多元化增长路径已经全面开启。 高工智能汽车研究院监测数据显示&#xff0c;2022年1-9月中国市场&#xff08;不含进…

工厂应该如何批量打印送货单和产品标签

01基本需求 图1是公司的出货明细表&#xff0c;要求按图2的样式批量打印标签&#xff0c;然后将标签张贴在产品包装箱上&#xff0c;否则客户不收货。 其中A7单元格用于存放条形码&#xff0c;条形码用于存放产品的订单号。 图1 订单表 图2 标签模板样式 02操作步骤 利用E…

【2023春招Java岗面试】 90% 会问到的 200+Java 面试题汇总(含答案解析),一定要抓住重点

前言 很多朋友问&#xff0c;有没有整理今年的一些面试题&#xff0c;最近抽时间整理了一份 Java 面试题。或许这份面试题还不足以囊括所有 Java 问题&#xff0c;但有了它&#xff0c;我相信足以应对目前市面上绝大部分的 Java 面试了&#xff0c;因为这篇文章不论是从深度还…

简单讲解Linux PSCI框架

说明&#xff1a; Kernel版本&#xff1a;4.14ARM64处理器使用工具&#xff1a;Source Insight 3.5&#xff0c; Visio 1. 介绍 PSCI, Power State Coordination Interface&#xff0c;由ARM定义的电源管理接口规范&#xff0c;通常由Firmware来实现&#xff0c;而Linux系统可…

014 | 探讨京族民间传说中的海洋文化特质 | 大学生创新训练项目申请书 | 极致技术工厂

&#xff08;一&#xff09;研究目的 京族是我国56个民族中唯一一个生活在海边的少数民族&#xff0c;这一特殊的少数民族位于我国西南沿海边陲&#xff0c;有着独具特色的海洋文化。渔民的海神信仰、特殊的舞蹈和乐器积淀了京族丰富的历史文化。以海洋地域为背景、世世代代以海…

神经网络的应用(分类和预测)——python

神经网络的应用一.数据预处理 ​ 由于神经网络输入数据的范围可能特别大&#xff0c;导致神经网络收敛慢、训练时间长。因此在训练神经网络前一般对数据进行预处理(不妨假设这里的指标都是效益型的(即都是正项指标))&#xff0c;一种重要的预处理的处理手段是归一化处理&#…

CMake中set_property/get_property的使用

CMake中的set_property命令用于在给定作用域(scope)内设置命名属性&#xff0c;其格式如下&#xff1a; set_property(<GLOBAL |DIRECTORY [<dir>] |TARGET [<target1> ...] |SOURCE [<src1> ...][DIRECTORY <…

小程序AP配网和AK配网教程(开源)

小程序AP配网和AK配网教程&#xff08;开源&#xff09; 一、 Airkiss配网的实现方式 ​ Airkiss配网我们采用插件的形式&#xff0c;非常简单方便。感谢半颗心脏大佬的开源插件。 1. Airkiss 简介 AirKiss是微信硬件平台为Wi-Fi设备提供的微信配网、局域网发现和局域网通讯…

将两个数组进行竖直(水平)方向的拼接 vstack()和hstack()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 将两个数组进行竖直(水平)方向的拼接 vstack()和hstack() [太阳]选择题 以下关于Python代码的选项说法错误的一项是? import numpy as np myArray1np.array([1,2,3]) print("【…

主线程与分支线程的访问、修改

任务1&#xff1a;定义一个全局变量 int a10&#xff0c;主线程能否访问到&#xff0c;分支线程能否访问到&#xff1b; 任务2&#xff1a;分支线程中修改上述的a 20, 问主线程中访问该a&#xff0c;是10还是20; 任务3&#xff1a;在主线程定义一个局部变量int b1&#x…

代码随想录刷题day59 503.下一个更大元素II;42. 接雨水

代码随想录刷题day59 503.下一个更大元素II&#xff1b;42. 接雨水 单调栈的一些应用。 503.下一个更大元素II 503. 下一个更大元素 II - 力扣&#xff08;Leetcode&#xff09; 还是下一个最大元素&#xff0c;主要是掌握循环数组中利用%取余的这个操作&#xff0c;在循环…

微服务框架 SpringCloud微服务架构 28 数据同步 28.1 同步方案分析

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构28 数据同步28.1 同步方案分析28.1.1 数据同步问题分析28.1.2 总结28 数据…

Yarn增加新队列-----hive向Yarn提交任务后,Hadoop再次向Yarn提交任务阻塞

博学之&#xff0c;审问之&#xff0c;慎思之&#xff0c;明辨之&#xff0c;笃行之&#x1f3c2; hive on spark搭建好后&#xff0c;任务提交会有问题&#xff0c;因为通过hive会话提交的任务一直存在且不会结束&#xff08;除非关掉这个hive会话&#xff09;&#xff0c;根本…

微服务框架 SpringCloud微服务架构 27 自动补全 27.4 修改酒店索引库数据结构

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构27 自动补全27.4 修改酒店索引库数据结构27.4.1 案例27 自动补全 27.4 修…

Java中的Unsafe的介绍与使用

Java中的Unsafe的介绍与使用 相关文章 美团-Unsafe JavaGuide-Unsafe 什么是Unsafe&#xff1f;&#xff1f;&#xff1f; 如何创建Unsafe对象&#xff1f; 通过反射获取Unsafe对象&#xff08;案例&#xff09; Unsafe功能简介 1. 内存操作 2. 内存屏障 3. 对象操…