【Linux】进程地址空间揭秘(初步认识)

news2025/6/5 8:47:21

10.进程地址空间(初步认识)

文章目录

  • 10.进程地址空间(初步认识)
      • 一、进程地址空间的实验现象解析
      • 二、进程地址空间
      • 三、虚拟内存管理
        • 补充:数据的写时拷贝(浅谈)
        • 补充:页表(浅谈)
        • 补充:关于地址空间mm_struct初始化问题(浅谈)
      • 四、为什么要有虚拟地址空间
      • 五、总结

一、进程地址空间的实验现象解析

这里有一个之前我们验证进程之间的独立性的代码,现在在此基础上做一些改动,得到以下带代码:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int g_value = 100;  // 全局变量定义 
int main() 
{
    printf("父进程 PID:%d PPID:%d\n", getpid(), getppid());  // 获取进程信息 
    pid_t id = fork();  // 创建子进程 
    if (id < 0) return -1;  // 错误处理 
    if (id == 0) 
	{
        // 子进程逻辑
        while(1) 
        {
            printf("子进程 PID:%d PPID:%d g_value:%d 地址:%p\n",getpid(), getppid(), g_value++, &g_value);  // 修改变量 
            sleep(1);  // 延时控制 
        }
    } 
	else 
	{
        while(1)
        {
            // 父进程逻辑
            printf("父进程 PID:%d PPID:%d g_value:%d 地址:%p\n",getpid(), getppid(), g_value, &g_value);  // 保持原值 
            sleep(1);
        }
    }
    return 0;
}

运行结果:

[lisihan@hcss-ecs-b735 lession14]$ gcc -o code1 code1.c 
[lisihan@hcss-ecs-b735 lession14]$ ./code1 
父进程 PID:31004 PPID:27811
父进程 PID:31004 PPID:27811 g_value:100 地址:0x601054
子进程 PID:31005 PPID:31004 g_value:100 地址:0x601054
父进程 PID:31004 PPID:27811 g_value:100 地址:0x601054
子进程 PID:31005 PPID:31004 g_value:101 地址:0x601054
父进程 PID:31004 PPID:27811 g_value:100 地址:0x601054
子进程 PID:31005 PPID:31004 g_value:102 地址:0x601054
父进程 PID:31004 PPID:27811 g_value:100 地址:0x601054
子进程 PID:31005 PPID:31004 g_value:103 地址:0x601054
子进程 PID:31005 PPID:31004 g_value:104 地址:0x601054
父进程 PID:31004 PPID:27811 g_value:100 地址:0x601054
子进程 PID:31005 PPID:31004 g_value:105 地址:0x601054
父进程 PID:31004 PPID:27811 g_value:100 地址:0x601054
父进程 PID:31004 PPID:27811 g_value:100 地址:0x601054

实验结果特征:

  1. 父进程持续输出原始值:

    父进程 PID:1001 PPID:999 g_value:100 地址:0x601044 
    
  2. 子进程输出递减序列:

    子进程 PID:1002 PPID:1001 g_value:101 地址:0x601044 
    
  3. 地址显示完全相同但值独立变化

  • 变量内容不⼀样,所以⽗⼦进程输出的变量绝对不是同⼀个变量
  • 但地址值是⼀样的,说明,该地址绝对不是物理地址!
  • 在Linux地址下,这种地址叫做虚拟地址
  • 我们在⽤C/C++语⾔所看到的地址,全部都是虚拟地址!物理地址,⽤⼾⼀概看不到,由OS统⼀管理。OS需要把虚拟地址转化为物理地址

二、进程地址空间

首先要了解一个概念,就是内存布局:

在这里插入图片描述

如图所示,如果从低地址到高地址,我们整个程序的内存在布局情况,包括正文部分、初始化数据、未初始化数据、堆区、栈区。我们的程序地址空间布局是依照这张图展开的。它不叫程序地址空间,它全称应该叫做进程地址空间,所以它不属于语言范畴,它属于系统范畴,这是属于系统方面的概念。

我们也可以用一个简单的代码来验证上面的结论:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int unval;
int gval = 100;

int main()
{
    printf("code addr: %p\n", main);
    printf("gval addr: %p\n", &gval);
    printf("unval addr: %p\n", &unval);

    int *mem = (int*)malloc(10*sizeof(int));
    printf("heap add: %p\n", mem);


    int a,b,c;
    printf("stack addr : %p\n", &a);
    printf("stack addr : %p\n", &b);
    printf("stack addr : %p\n", &c);
}

运行结果:

[lisihan@hcss-ecs-b735 lession14]$ gcc -o code2 code2.c 
[lisihan@hcss-ecs-b735 lession14]$ ./code2
code addr: 0x40057d
gval addr: 0x60103c
unval addr: 0x601044
heap add: 0x20bc010
stack addr : 0x7ffeeb9704b4
stack addr : 0x7ffeeb9704b0
stack addr : 0x7ffeeb9704ac

可以看到他们的地址数,整个地址是依次增大的。很明显,堆和栈之间,中间有一大块的镂空。所以,我们的程序地址空间布局是依照这张图展开的。

这幅图,遵循《深入理解计算机系统》等权威教材,自底向上进行绘制,认为低地址在下方,高地址在上方。以前学到的进程地址空间,可能只考虑了部分区域,甚至像共享环境变量这样的部分也可能没见过。

所以之前说‘程序的地址空间’是不准确的,准确的应该说成 进程地址空间 ,那该如何理解呢?看图:

在这里插入图片描述

​ 图二

  • 上⾯的图就⾜矣说明问题,同⼀个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址

  • 对虚拟地址的理解:

    • 当一个程序在用户态运行时,操作系统会为其创建一个进程(process)实例,并为此进程“画一张大饼”——即分配一个虚拟地址空间,使其认为自己独占整个物理内存。多个进程间彼此隔离,互不干扰;即便父子进程共享同一份代码和数据,其后续修改也会触发写时拷贝,确保各自独立。

    • 用户态代码访问虚拟地址时,CPU 先查询当前进程的页表,将虚拟地址转换为物理地址,然后读写实际内存。用户和应用程序感知到的永远是虚拟空间,物理空间由操作系统统一管理。

三、虚拟内存管理

在操作系统中,管理进程的虚拟地址空间是其核心职责。具体的管理方式始于对该空间本身的精确刻画,即操作系统需要为每个运行中的程序定义一个专属的描述结构。这种描述随后被整合进程序的核心控制信息块中。

本质上,这个虚拟地址空间是操作系统内核维护的一种关键数据结构。当创建一个程序时,其核心控制信息块内会包含一个指向其物理内存使用情况的引用。为了避免程序直接操作物理内存地址,在一个进程的task_struct中,操作系统在程序与物理内存之间引入了一个中间数据结构,其类型通常命名为 mm_struct。该结构虽然内部复杂,但宏观上承担此角色。程序的核心控制信息块内部维护着一个特定指针,该指针直接关联到当前程序对应的专属地址空间描述结构。这意味着每个程序都拥有自己独立的、由操作系统定义的页表。

struct task_struct
{
/*...*/
    //对于普通的⽤⼾进程来说该字段指向他的虚拟地址空间的⽤⼾空间部分,对于内核线程来说这部分为NULL。
	struct mm_struct *mm; 
    // 该字段是内核线程使⽤的。当该进程是内核线程时,它的mm字段为NULL,表⽰没有内存地址空间,可也并不是真正的没有,这是因为所有进程关于内核的映射都是⼀样的,内核线程可以使⽤任意进程的地址空间。
	struct mm_struct *active_mm; 
/*...*/
}

操作系统为每个程序构建好这个专属的页表(如图2所示)后,程序在运行过程中始终通过它来访问内存。当程序需要读写内存时,最终都必须经由自身这个页表结构来完成访问操作。

struct mm_struct
{
    /*...*/
    struct vm_area_struct *mmap; /* 指向虚拟区间(VMA)链表 */
    struct rb_root mm_rb; /* red_black树 */
    unsigned long task_size; /*具有该结构体的进程的虚拟地址空间的⼤⼩*/
    /*...*/
    // 代码段、数据段、堆栈段、参数段及环境段的起始和结束地址。
    unsigned long start_code, end_code, start_data, end_data;
    unsigned long start_brk, brk, start_stack;
    unsigned long arg_start, arg_end, env_start, env_end;
    /*...*/
}

可以说,mm_struct结构是对整个⽤⼾空间的描述。每⼀个进程都会有⾃⼰独⽴的mm_struct,这样每⼀个进程都会有⾃⼰独⽴的地址空间才能互不⼲扰。

进程的地址空间分布情况:

在这里插入图片描述

虽然操作系统通过这个结构体让程序感觉自己独占了巨大的连续内存范围,但程序实际能使用的物理内存量通常远小于此视图范围,并且过量申请会被系统限制或终止。操作系统提供这个页表的承诺,但并不保证其全部空间都必然映射到物理资源。

补充:数据的写时拷贝(浅谈)

fork 创建子进程后,父子进程共享同一份物理页,页表条目被标记为只读。只有在任一进程对共享页执行写入操作时,触发页错误中断,内核才会分配新物理页并复制原内容,修改对应进程的页表映射,以实现各自独立。

通俗来讲:之前提到过,进程具有独立性即使是父子关系的进程也是如此,又因为进程 = PCB + 代码和数据,当创建子进程时,OS会拷贝一份内核进程控制块(PCB),代码子进程和父进程共享,只有当子进程或父进程对数据进行写入操作的时候,数据才会从新拷贝一份给两个进程,换言之如果一份数据对于父子进程都是只读的,就不会拷贝,父子进程公用一套数据。(这里只是简单说个大概,后面会详谈)

补充:页表(浅谈)

页表不只有虚拟地址与物理地址的映射关系,还有一些标志位,比如说读写权限的标志位。

  1. 如果有一些物理地址的内容是只读的,当进程以写的权限去访问这块物理地址的时候发现不匹配,操作系统会直接清除掉这个进程。

    在学C语言的时候有一个经典例子:

    char *str = "hello linux";
    *str = 'H'
    

    这段代码在编译的时候不会有任何报错,但是运行的时候就会直接崩溃。原因在与str指向的是一个字符串,这个字符串储存在字符常量区,这个区域是只读的,不可以进行写入操作,但在编译的时候编译器是无法判断进程运行时的错误的,所以编译没有报错。我们写好的程序运行之后都是一个一个的进程,根据上面所学的页表的读写权限的标志位也就不难理解出现这种情况的原因了。

    野指针也会触发这种情况:野指针指向的地址的访问权限与页表中对应物理地址的权限不匹配或者野指针指向的地址空间在页表中根本不存在,都会使得程序无法正常运行。

    查找资料:

    权限位(RWX):标记内存区域的可读、可写、可执行属性。例如代码区设为只读(R-X),尝试写入会触发操作系统干预,直接终止进程。这解释了C语言中修改字符串常量导致崩溃的底层原因——字符常量区被映射为只读,const关键字本质是编译器层面的辅助检查,而真正的保护由页表硬件机制实现。

  2. 当一个进程创建的时候,先加载的是内核中的PCB,然后才开始慢慢加载代码和其他数据,这也符合我们之前对进程的理解“先描述,再组织”。所以就可能会存在内核数据结构已经加载完成了,但是代码和数据还没有加载到内存中的情况。还有一种情况,就是之前在进程状态中提到的阻塞挂起状态,此时若当前进程处于阻塞状态,其代码和数据占用内存但无实际意义,操作系统会将该进程的部分代码和数据换出至磁盘。此时也会出现代码和数据还没有加载到内存中的情况。

    页表中还有一个标志位可以检查目标内容是否在内存中,方便在进程运行之前把内容加载进来

    查找资料:

    存在位(Present Bit):指示目标数据是否在物理内存中。若为0,表示数据尚未加载或已被换出到磁盘(如Swap分区)。此时访问会触发“缺页异常”,操作系统负责将所需数据从磁盘调入内存,更新页表后恢复进程执行。此机制支撑了按需加载(Demand Paging):大型程序(如游戏)启动时并非全部载入内存,仅加载必要部分,后续访问时动态调入,极大提高了物理内存利用率,使小内存运行大程序成为可能。

补充:关于地址空间mm_struct初始化问题(浅谈)

进程数据结构中的mm_struct结构体主要用于管理进程的内存空间。每个进程都有一个对应的 mm_struct 实例,它包含了关于该进程虚拟地址空间的所有必要信息。所以本质上这个结构体仍然需要初始化,以代码区为例,比如初始化一个地址空间,代码区有自己的数据结构。在mm_struct中,有start codeend code表示代码区域的开始和结束,从而划分出区域。进程加载时需要创建PCB和地址空间,地址空间和PCB由操作系统初始化。地址空间内有多个属性,这些属性如何初始化?

//完整定义包含关键字段:
struct mm_struct {
    struct vm_area_struct *mmap;    // 内存区域链表
    pgd_t *pgd;                    // 页全局目录
    atomic_t mm_users;            // 用户计数
    atomic_t mm_count;            // 引用计数
    unsigned long start_code;     // 代码段起始
    unsigned long end_code;       // 代码段结束
    unsigned long start_data;     // 数据段起始
    unsigned long end_data;       // 数据段结束
    unsigned long start_brk;      // 堆区起始
    unsigned long brk;            // 堆区当前边界
    unsigned long start_stack;    // 栈区起始
    unsigned long arg_start;      // 参数区起始
    unsigned long arg_end;        // 参数区结束
    unsigned long env_start;      // 环境变量起始
    unsigned long env_end;        // 环境变量结束
};

首先介绍一个命令(工具)readelf:

readelf 是一个强大的工具,用于显示 ELF(Executable and Linkable Format)文件的信息。ELF 文件是一种常见的二进制文件格式,在 Linux 系统中广泛使用,包括可执行文件、共享库和目标文件。readelf 可以帮助开发者和系统管理员检查这些文件的内容和结构。

简单来说,readelf可以读取一个可执行文件的每一个分段信息,其他的功能这里暂时不说,从这里可以知道:

  • 虚拟地址空间的初始化依赖于可执行程序的ELF格式特性:编译器在生成可执行文件时,已按功能将程序划分为多个逻辑段(Section),包括存储机器指令的代码段(.text)、存放字符串常量的只读数据段(.rodata)、保存已初始化全局变量的数据段(.data)以及未初始化数据段(.bss)。
  • 通过readelf -S命令可解析ELF文件头信息,获取各段关键属性:1) Size字段定义段大小(如代码段16字节);2) Addr字段标识段在虚拟地址空间的预期起始位置;3) Flags权限标记(R/W/X组合)声明段的内存访问规则。

四、为什么要有虚拟地址空间

  • 地址空间和⻚表是OS创建并维护的!也就意味着凡是想使⽤地址空间和⻚表进⾏映射,也⼀定要在OS的监管之下来进⾏访问!也顺便保护了物理内存中的所有的合法数据,包括各个进程以及内核的相关有效数据!
  • 因为有地址空间的存在和⻚表的映射的存在,我们的物理内存中可以对未来的数据进⾏任意位置的加载!物理内存的分配 和 进程的管理就可以做到没有关系,进程管理模块和内存管理模块就完成了解耦合
  • 因为有地址空间的存在,所以我们在C、C++语⾔上new, malloc空间的时候,其实是在地址空间上申请的,物理内存可以甚⾄⼀个字节都不给你。⽽当你真正进⾏对物理地址空间访问的时候,才执⾏内存的相关管理算法,帮你申请内存,构建⻚表映射关系(延迟分配),这是由操作系统⾃动完成,⽤⼾包括进程完全0感知!!
  • 因为⻚表的映射的存在,程序在物理内存中理论上就可以任意位置加载。它可以将地址空间上的虚拟地址和物理地址进⾏映射,在进程视⻆所有的内存分布都可以是有序的。

简单表述:

这种设计实现三项关键优势:

  1. 空间效率:避免一次性加载整个程序;

  2. 安全隔离:页表基于mm_struct配置的权限拦截非法访问(如向代码段写入);

  3. 动态扩展:BSS段等未初始化区域仅预留虚拟地址范围,实际物理页在首次访问时分配。最终,进程通过专属的"内存视图"(mm_struct)访问物理内存,而操作系统通过维护编译器预设的段属性与硬件协作,实现了虚拟地址空间的动态管理与安全控制。

了解上述内容,我们可以回答一下问题:

Q:为什么在程序中的全局变量、字符常量具有全局性,在程序运行期间都会有效?

A:全局变量、字符常量一般存放在已初始化数据区、未初始化数据区,它不像栈和堆一样会在进程运行期间创建和销毁,在之前讲的进程地址空间可知,进程地址空间与物理地址的映射关系在进程存在期间一直存在,所以这部分地址的映射关系不会改变,随着进程一直存在,且全局变量的地址可以被整个程序使用。

Q:为什么父进程的环境变量能够被子进程继承?

A:因为在进程内存空间中命令行参数与环境变量也有一块区域用于存放他们的数据,父进程创建子进程的时候会写时拷贝到子进程,因此子进程可以继承父进程的环境变量。

五、总结

AI生成
本文介绍了进程地址空间的基本概念,通过实验展示了父子进程共享相同虚拟地址但实际物理地址不同的现象,解释了虚拟地址与物理地址的区别。文章详细分析了进程地址空间的内存布局结构(包括代码段、数据段、堆栈等区域),并通过代码示例验证了各段地址的分布规律。重点阐述了操作系统如何通过mm_struct结构体管理进程的虚拟地址空间,以及页表机制在虚拟地址到物理地址转换中的作用。最后指出操作系统的内存管理机制使每个进程都拥有独立的地址空间视图,保证了进程间的隔离性。

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

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

相关文章

设计模式——备忘录设计模式(行为型)

摘要 备忘录设计模式是一种行为型设计模式&#xff0c;用于在不破坏封装性的前提下&#xff0c;捕获对象的内部状态并在需要时恢复。它包含三个关键角色&#xff1a;原发器&#xff08;Originator&#xff09;、备忘录&#xff08;Memento&#xff09;和负责人&#xff08;Car…

UI自动化测试的革新,新一代AI工具MidScene.js实测!

前言 AI已经越来越深入地走入我们的实际工作,在软件测试领域,和AI相关的新测试工具、方法也层出不穷。在之前我们介绍过结合 mcp server 实现 AI 驱动测试的案例,本文我们将介绍一个近期崭露头角的国产AI测试工具 Midscene.js Midscene.js简介 MidScene.js 是由字节跳动 w…

4. Qt对话框(2)

在上节中已经学习了对话框的确认和取消&#xff0c;本节内容继续接上节完成登录对话框实例并得到登录信息。 本文部分ppt、视频截图原链接&#xff1a;[萌马工作室的个人空间-萌马工作室个人主页-哔哩哔哩视频] 1 实现登录对话框 1.1 功能需要 得到登录信息&#xff0c;需要…

Android Studio 2022.2.1.20 汉化教程

查看Android Studio 版本 Android Studio Flamingo | 2022.2.1 Patch 2 下载&#xff1a;https://plugins.jetbrains.com/plugin/13710-chinese-simplified-language-pack----/versions/stable

golang -- slice 底层逻辑

目录 一、前言二、结构三、创建3.1 根据 make创建3.2 通过数组创建 四、内置append追加元素4.1 追加元素4.2 是否扩容4.2.1 不扩容4.2.2 扩容 总结 一、前言 前段时间学了go语言基础&#xff0c;过了一遍之后还是差很多&#xff0c;所以又结合几篇不同资料重新学习了一下相关…

SOC-ESP32S3部分:26-物联网MQTT连云

飞书文档https://x509p6c8to.feishu.cn/wiki/IGCawAgqFibop7kO83KcsDFBnNb ESP-MQTT 是 MQTT 协议客户端的实现&#xff0c;MQTT 是一种基于发布/订阅模式的轻量级消息传输协议。ESP-MQTT 当前支持 MQTT v5.0。 特性 支持基于 TCP 的 MQTT、基于 Mbed TLS 的 SSL、基于 WebSo…

制造业的未来图景:超自动化与劳动力转型的双重革命

市场现状&#xff1a;传统制造业的转型阵痛 当前全球制造业正站在历史性变革的十字路口。埃森哲对552位工厂经理的全球调研显示&#xff0c;60%的受访者将劳动力转型视为首要战略任务​​&#xff0c;而63%的工厂正在加速部署自动化技术[1]。超过​75%的工厂经理​​认为&…

【Unity】相机 Cameras

1 前言 主要介绍官方文档中相机模块的内容。 关于“9动态分辨率”&#xff0c;这部分很多API文档只是提了一下&#xff0c;具体细节还需要自己深入API才行。 2 摄像机介绍 Unity 场景在三维空间中表示游戏对象。由于观察者的屏幕是二维屏幕&#xff0c;Unity 需要捕捉视图并将…

如何在 Solana 上发币,并创建初始流动性让项目真正“动”起来?

在 Solana 上发行代币如今已不再是技术门槛&#xff0c;而是市场策略和执行效率的较量。如果你只是简单发了一个代币&#xff0c;却没为它建立流动性和市场机制&#xff0c;那么它就只是一个“死币”。 本文将带你一步步理解&#xff0c;如何从发币到建立流动性池&#xff0c;…

核心机制:滑动窗口

TCP 协议 1.确认应答 可靠传输的核心机制 2.超时重传 可靠传输的核心机制 3.连接管理 TCP/网络 最高的面试题 三次握手,建立连接(必须是 三次) 四次挥手,断开连接(可能是 三次) 核心机制四:滑动窗口 算法中的"滑动窗口" 出自 TCP 前面的三个…

苹果电脑深度清理,让老旧Mac重焕新生

在日常使用苹果电脑的过程中&#xff0c;随着时间推移&#xff0c;系统内会积累大量冗余数据&#xff0c;导致电脑运行速度变慢、磁盘空间紧张。想要让设备恢复流畅&#xff0c;苹果电脑深度清理必不可少。那么&#xff0c;如何进行苹果电脑深度清理呢&#xff1f;接下来为你详…

微服务面试(分布式事务、注册中心、远程调用、服务保护)

1.分布式事务 分布式事务&#xff0c;就是指不是在单个服务或单个数据库架构下&#xff0c;产生的事务&#xff0c;例如&#xff1a; 跨数据源的分布式事务跨服务的分布式事务综合情况 我们之前解决分布式事务问题是直接使用Seata框架的AT模式&#xff0c;但是解决分布式事务…

高性能MYSQL(三):性能剖析

一、性能剖析概述 &#xff08;一&#xff09;关于性能优化 1.什么是性能&#xff1f; 我们将性能定义为完成某件任务所需要的时间度量&#xff0c;换句话说&#xff0c;性能即响应时间&#xff0c;这是一个非常重要的原则。 我们通过任务和时间而不是资源来测量性能。数据…

mysql(十四)

目录 多表查询 1.准备工作 2--创建表格 3--插入数据 2.笛卡尔积查询 3.内连接查询 1--隐式内连接 格式 查询 2--显示内连接&#xff08;Inner join .. on &#xff09; 格式 查询 4.外连接查询 1--左外连接查询&#xff08;LEFT OUTER JOIN .. ON &#xff09; 格式 查询 2-- 右…

工业物联网中的事件驱动采样架构及优化

论文标题 Event-Based Sampling Architecture and Optimization for Industrial Internet of Things 工业物联网中的事件驱动采样架构及优化 作者信息 Tejas Thosani Process Control Systems, Micron Technology Inc., Manassas, USA tthosanimicron.com Andres Prado Esp…

基于 HT for Web 的轻量化 3D 数字孪生数据中心解决方案

一、技术架构&#xff1a;HT for Web 的核心能力 图扑软件自主研发的 HT for Web 是基于 HTML5 的 2D/3D 可视化引擎&#xff0c;核心技术特性包括&#xff1a; 跨平台渲染&#xff1a;采用 WebGL 技术&#xff0c;支持 PC、移动端浏览器直接访问&#xff0c;兼容主流操作系统…

JavaScript 性能优化:从入门到实战

在当今快节奏的互联网时代&#xff0c;用户对网页和应用的加载速度与响应性能要求越来越高。JavaScript 作为网页交互的核心语言&#xff0c;其性能表现直接影响用户体验。本文将用简单易懂的语言&#xff0c;带你了解 JavaScript 性能优化的实用技巧&#xff0c;帮助你的代码跑…

启动metastore时报错MetaException(message:Version information not found in metastore

把hdfs清空重新安装了一下&#xff0c;hive的mysql元数据库删除掉之后重建之后一直启动报错 metastore.RetryingHMSHandler (RetryingHMSHandler.java:<init>(83)) - HMSHandler Fatal error: MetaException(message:Version information not found in metastore.) 后来…

MyBatisPlus(1):快速入门

我们知道&#xff0c;MyBatis是一个优秀的操作数据库的持久层框架&#xff08;优秀持久层框架——MyBatis&#xff09;&#xff0c;其基于底层的JDBC进行高度封装&#xff0c;极大的简化了开发。但是对于单表操作而言&#xff0c;我们需要重复地编写简单的CRUD语句。这其实是不…

京东热点缓存探测系统JDhotkey架构剖析

热点探测使用场景 MySQL 中被频繁访问的数据 &#xff0c;如热门商品的主键 IdRedis 缓存中被密集访问的 Key&#xff0c;如热门商品的详情需要 get goods$Id恶意攻击或机器人爬虫的请求信息&#xff0c;如特定标识的 userId、机器 IP频繁被访问的接口地址&#xff0c;如获取用…