RVOS操作系统内存管理简单实现-02

news2024/11/26 8:47:37

RVOS操作系统内存管理简单实现-02

  • 内存管理分类
  • 内存映射表(Memory Map)
  • Linker Script 链接脚本
    • 语法
    • 基于符号定义获取程序运行时内存分布
  • 基于 Page 实现动态内存分配
    • 代码讲解
    • 调试
  • 扩展


本系列参考: 学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春 整理而来,主要作为xv6操作系统学习的一个前置基础。

RVOS是本课程基于RISC-V搭建的简易操作系统名称。

课程代码和环境搭建教程参考github仓库: https://github.com/plctlab/riscv-operating-system-mooc/blob/main/howto-run-with-ubuntu1804_zh.md

前置知识:

  • RVOS环境搭建-01

内存管理分类

  • 自动管理内存 - 栈 (Stack)
  • 静态内存 - 全局变量/静态变量
  • 动态管理内存 - 堆(heap)

内存映射表(Memory Map)

在这里插入图片描述
可执行文件中各个段在虚拟内存中的地址,在链接阶段确定,然后程序装载阶段,就按照各个段在链接阶段设置好的虚拟地址进行装载。

此部分内容详细可参考<<程序员的自我修养—装载,链接和库>>一书


Linker Script 链接脚本

链接器一般都提供多种控制整个链接过程的方法,以用来产生用户所须要的文件。

一般链接器有如下三种方法:

  • 使用命令行来给链接器指定参数,ld的-o、-e参数就属于这类。
  • 将链接指令存放在目标文件里面,编译器经常会通过这种方法向链接器传递指令。方法也比较常见,只是我们平时很少关注,比如VISUAL C++编译器会把链接参数放在PE目标文件的.drectve段以用来传递参数。
  • 使用链接控制脚本,使用链接控制脚本方法就是本节要介绍的,也是最为灵活、最为强大的链接控制方法。

由于各个链接器平台的链接控制过程各不相同,我们只能侧重一个平台来介绍。ld链接器的链接脚本功能非常强大,我们接下来以ld作为主要介绍对象。

ld 在用户没有指定链接脚本的时候会使用默认链接脚本。我们可以使用下面的命令行来查看ld默认的链接脚本:

ld -verbose

默认的ld链接脚本存放在/usr/lib/ldscripts/下,不同的机器平台、输出文件格式都有相应的链接脚本。

  • 比如Intel IA32下的普通可执行ELF文件链接脚本文件为elf_i386.x;
  • IA32下共享库的链接脚本文件为elf_i386.xs等。

ld会根据命令行要求使用相应的链接脚本文件来控制链接过程,当我们使用ld来链接生成一个可执行文件的时候,它就会使用elf_i386.x作为链接控制脚本;

当我们使用ld来生成一个共享目标文件的时候,它就会使用elf_i386.xs作为链接控制脚本。

当然,为了更加精确地控制链接过程,我们可以自己写一个脚本,然后指定该脚本为链接控制脚本。比如可以使用-T参数:

ld –T link.script

什么情况下需要使用链接脚本?

绝大部分情况下,我们使用链接器提供的默认链接规则对目标文件进行链接。这在一般情况下是没有问题的,但对于一些特殊要求的程序,比如:

  • 操作系统内核、BIOS(Basic Input Output System)或一些在没有操作系统的情况下运行的程序(如引导程序Boot Loader或者嵌入式系统的程序,或者有一些脱离操作系统的硬盘分区软件PQMagic等),以及另外的一些须要特殊的链接过程的程序,如一些内核驱动程序等,它们往往受限于一些特殊的条件,如须要指定输出文件的各个段虚拟地址、段的名称、段存放的顺序等,因为这些特殊的环境,特别是某些硬件条件的限制,往往对程序的各个段的地址有着特殊的要求。

在编译普通的应用程序时,可以使用默认的链接器脚本,但是对于内核程序来说,它本身也是一个.elf文件,这个.elf文件该怎么组织,各个段放到内存中什么地方,这个由于和底层硬件强相关,所以需要我们自己编写相关的链接器脚本:

  • 在之前的环境准备小节中,我们makefile文件中编写的ld链接命令中只通过-Ttext=0x80000000命令指明了代码段的在内存中的起始地址
os.elf: ${OBJS}
	${CC} ${CFLAGS} -Ttext=0x80000000 -o os.elf $^
	${OBJCOPY} -O binary os.elf os.bin
  • 但是在本节中我们将会使用链接器脚本文件os.ld来描述整个链接过程
    在这里插入图片描述

语法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

.代表当前所处的内存地址

在这里插入图片描述

链接器会把定义符号放入符号表中,符号表中的符号是我们可以在程序中访问到的。

链接器语法详细内容可以参考GUN文档,或者程序员自我修养–装载,链接与库的4.5节。


基于符号定义获取程序运行时内存分布

在这里插入图片描述

参考课程02节的os.ld链接器脚本文件

如何在代码中获取在链接器脚本中定义的相关符号值呢?

在这里插入图片描述

参考课程02节mem.s文件

注意:

  • 在C代码中直接获取链接器脚本中定义的符号是有一定的限制的。C语言是一种静态编译语言,在编译时会将源代码转换为机器码,并生成可执行文件。链接器脚本用于指导链接器如何组织可执行文件的各个部分,包括代码段、数据段、符号表等。
  • 在C代码中,无法直接引用链接器脚本中定义的符号的值,因为C编译器并不了解链接器脚本的细节。C编译器只能根据给定的C代码进行编译,将代码转换为机器码,并生成符号表。符号表中包含了在C代码中定义的全局变量、函数等符号及其对应的地址。
  • 要在C代码中获取链接器脚本中定义的符号的值,一种常见的做法是通过在C代码中声明外部变量,并使用链接器脚本中定义的符号来初始化这些外部变量。这样,链接器在链接阶段会将外部变量与链接器脚本中定义的符号关联起来,并将符号的值赋给外部变量。然后,C代码就可以通过访问这些外部变量来获取链接器脚本中定义的符号的值。
  • 总之,C代码无法直接获取链接器脚本中定义的符号的值,但可以通过声明外部变量并与符号关联来间接获取。这种间接的方式使得C代码能够与链接器脚本进行交互,并共享符号的值。

在c程序中获取链接器脚本中定义的符号,有两种方式:

  • 链接器脚本中使用PROVIDER定义符号,并在c语言中通过extern声明外部变量进行绑定
SECTIONS
{
  .text :
  {
    *(.text)
  }
  
  .data :
  {
    *(.data)
  }
  
  .bss :
  {
    *(.bss)
  }
  
  /* 定义一个名为 _custom_symbol 的符号,并将其赋值为 42 */
  PROVIDE(_custom_symbol = 42);
}


#include <stdio.h>
extern int _custom_symbol;
int main() {
    printf("The value of _custom_symbol is: %d\n", _custom_symbol);
    return 0;
}
  • 通过汇编定义一个全局变量绑定到链接器脚本中的符号,c程序中定义extern变量和汇编文件中定义的全局变量相绑定
SECTIONS
{
  /* ...其他部分... */

  /* 定义一个名为 _asm_var 的符号,并将其赋值为 100 */
  PROVIDE(_asm_var = 100);
}

.section .data
.global asm_var
asm_var:
  .word _asm_var


#include <stdio.h>
extern int asm_var;
int main() {
    printf("The value of asm_var is: %d\n", asm_var);
    return 0;
}

将汇编文件作为绑定的中间转换层有以下几个好处:

  1. 灵活性:使用汇编文件可以更加灵活地控制符号的定义和绑定。你可以直接在汇编文件中定义符号,并将其与链接器脚本中的符号绑定,而不依赖于C语言的语法和限制。这使得你可以更精确地控制符号的位置、大小和属性。

  2. 细粒度控制:汇编语言提供了更细粒度的控制能力。你可以直接使用汇编指令来定义变量、设置符号的初始值,以及指定变量的大小和对齐方式。这使得你可以更好地适应特定的需求,如嵌入式系统的内存布局和对齐要求。

  3. 可读性:使用汇编文件作为绑定的中间转换层可以提高代码的可读性和可维护性。通过将符号的定义和绑定从链接器脚本和C代码中分离出来,可以更清晰地表达代码的意图,并使得代码更易于理解和修改。

  4. 跨平台支持:使用汇编文件作为中间转换层可以更好地支持跨平台开发。汇编语言是与硬件平台相关的,通过直接编写汇编代码,可以更好地适应不同的硬件架构和操作系统环境。这使得你的代码更具可移植性和可扩展性。

总之,通过将汇编文件作为绑定的中间转换层,可以提供更大的灵活性、细粒度的控制能力,提高代码的可读性和可维护性,以及更好地支持跨平台开发。这对于一些特定的需求和项目来说是非常有益的。


基于 Page 实现动态内存分配

在这里插入图片描述
数据结构设计:
在这里插入图片描述
此处采用数组方式来管理内存。


代码讲解

此部分代码基于课程02小节的page.c文件展开讲解

  • 获取链接器脚本中定义的符号,这些变量在链接器链接过程中计算得出
/*
 * Following global vars are defined in mem.S
 */
extern uint32_t TEXT_START;
extern uint32_t TEXT_END;
extern uint32_t DATA_START;
extern uint32_t DATA_END;
extern uint32_t RODATA_START;
extern uint32_t RODATA_END;
extern uint32_t BSS_START;
extern uint32_t BSS_END;
extern uint32_t HEAP_START;
extern uint32_t HEAP_SIZE;
  • 堆区的范围和最大能够分配的页数量
/*
 * _alloc_start points to the actual start address of heap pool
 * _alloc_end points to the actual end address of heap pool
 * _num_pages holds the actual max number of pages we can allocate.
 */
static uint32_t _alloc_start = 0;
static uint32_t _alloc_end = 0;
static uint32_t _num_pages = 0;

对于数据结构的选择,我们这里选取数组结构:
在这里插入图片描述
由于物理内存被划分为一块块固定大小的内存,所以我们可以通过附加索引信息记录某个页是否已经分配出去,并且索引记录的下标和对应的物理页下标进行映射,映射公式为:

  • 物理页地址=alloc_start + 索引下标 * PAGE_SIZE

并且我们使用Page结构体来作为索引记录,用于表示某个物理页是否已经分配出去,并且由于用户通常一次性申请好几个连续物理页,释放的时候传入分配内存起始地址,我们需要回收先前分配给该用户的多个连续物理页,因此还需要一个记号标记当前物理页是否为某次连续分配中的最后一个物理页:

/*
 * Page Descriptor 
 * flags:
 * - bit 0: flag if this page is taken(allocated)
 * - bit 1: flag if this page is the last page of the memory block allocated
 */
struct Page {
	uint8_t flags;
};
  • 利用flags标记的第0位表示物理页是否分配
  • 利用flags标记的第1位表示是否为某次分配中的最后一个物理页

内存管理模块初始化:

void page_init()
{
	/* 
	 * We reserved 8 Page (8 x 4096) to hold the Page structures.
	 * It should be enough to manage at most 128 MB (8 x 4096 x 4096) 
	 */
	 //_num_pages是实例用户可用的物理页数量
	_num_pages = (HEAP_SIZE / PAGE_SIZE) - 8;
	printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages);
	
	struct Page *page = (struct Page *)HEAP_START;
	//初始化索引记录---每条索引记录对应一个用户可用物理页面
	for (int i = 0; i < _num_pages; i++) {
		_clear(page);
		page++;	
	}
    //物理页对齐4KB---将给定的地址按页面边界(4KB)对齐,确保地址位于所在页面的起始位置
	_alloc_start = _align_page(HEAP_START + 8 * PAGE_SIZE);
	//堆内存最大范围
	_alloc_end = _alloc_start + (PAGE_SIZE * _num_pages);

	printf("TEXT:   0x%x -> 0x%x\n", TEXT_START, TEXT_END);
	printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END);
	printf("DATA:   0x%x -> 0x%x\n", DATA_START, DATA_END);
	printf("BSS:    0x%x -> 0x%x\n", BSS_START, BSS_END);
	printf("HEAP:   0x%x -> 0x%x\n", _alloc_start, _alloc_end);
}

//初始化过程就是将标志位清空
static inline void _clear(struct Page *page){
	page->flags = 0;
}
  • 保留堆内存前面8个物理页用于存放索引记录信息
  • 初始化相关索引信息
  • 堆内存分配起始地址页面对齐

注意: 此处出现的printf函数是在02小节中编写的printf.c文件中出现的,而非c语言提供的库函数,最终输出底层还是借助的上一节中编写uart.c代码,借助串口输出到连接设备的屏幕上。


连续分配多个物理页面:

/*
 * Allocate a memory block which is composed of contiguous physical pages
 * - npages: the number of PAGE_SIZE pages to allocate
 */
void *page_alloc(int npages)
{
	/* Note we are searching the page descriptor bitmaps. */
	int found = 0;
	//遍历索引数组
	struct Page *page_i = (struct Page *)HEAP_START;
	//_num_pages表示堆内存页面总数(用户可用堆内存--上面page_init函数中初始化过了)
	for (int i = 0; i <= (_num_pages - npages); i++) {
		//判断当前页面是否空闲
		if (_is_free(page_i)) {
			found = 1;
			/* 
			 * meet a free page, continue to check if following
			 * (npages - 1) pages are also unallocated.
			 */
			// 检查接下来的npages-1个物理页面是否同样空闲
			struct Page *page_j = page_i + 1;
			for (int j = i + 1; j < (i + npages); j++) {
			    //只要有一个物理页面不空闲,说明这块连续内存空间大小不满足我们的要求
				if (!_is_free(page_j)) {
				   //重新设置found=0
					found = 0;
					break;
				}
				page_j++;
			}
			/*
			 * get a memory block which is good enough for us,
			 * take housekeeping, then return the actual start
			 * address of the first page of this memory block
			 */
			//找到了满足要求的连续内存空间
			if (found) {
				//设置好相关物理页面对应的索引记录标志位为占用状态
				struct Page *page_k = page_i;
				for (int k = i; k < (i + npages); k++) {
					_set_flag(page_k, PAGE_TAKEN);
					page_k++;
				}
				//设置连续分配的页面中最后一个页面的flags标志位第1位为1,表示为当前分配中的最后一个物理页面
				page_k--;
				_set_flag(page_k, PAGE_LAST);
				//返回分配内存的起始地址
				return (void *)(_alloc_start + i * PAGE_SIZE);
			}
		}
		page_i++;
	}
	return NULL;
}

判断页面是否空闲和设置索引记录标记的函数如下:

static inline int _is_free(struct Page *page)
{
	if (page->flags & PAGE_TAKEN) {
		return 0;
	} else {
		return 1;
	}
}

static inline void _set_flag(struct Page *page, uint8_t flags)
{
	page->flags |= flags;
}

释放内存:

/*
 * Free the memory block
 * - p: start address of the memory block
 */
void page_free(void *p)
{
	/*
	 * Assert (TBD) if p is invalid
	 */
	//内存地址不合法或者超出的堆内存最大限制,直接返回
	if (!p || (uint32_t)p >= _alloc_end) {
		return;
	}
	/* get the first page descriptor of this memory block */
	struct Page *page = (struct Page *)HEAP_START;
	//定位对应的索引记录下标
	page += ((uint32_t)p - _alloc_start)/ PAGE_SIZE;
	/* loop and clear all the page descriptors of the memory block */
	//将对应page被占用的标记清空,同时如果是连续分配的最后一个页面,清空其PAGE_LAST标记
	while (!_is_free(page)) {
		if (_is_last(page)) {
			_clear(page);
			break;
		} else {
			_clear(page);
			page++;;
		}
	}
}

清空PAGE_LAST标志的函数如下:

static inline int _is_last(struct Page *page)
{
	if (page->flags & PAGE_LAST) {
		return 1;
	} else {
		return 0;
	}
}

调试

#include "os.h"

/*
 * Following functions SHOULD be called ONLY ONE time here,
 * so just declared here ONCE and NOT included in file os.h.
 */
extern void uart_init(void);
extern void page_init(void);

void start_kernel(void)
{
	uart_init();
	uart_puts("Hello, RVOS!\n");

	page_init();
    //页面分配测试
	page_test();
	while (1) {}; // stop here!
}


void page_test()
{
	void *p = page_alloc(2);
	printf("p = 0x%x\n", p);
	//page_free(p);

	void *p2 = page_alloc(7);
	printf("p2 = 0x%x\n", p2);
	page_free(p2);

	void *p3 = page_alloc(4);
	printf("p3 = 0x%x\n", p3);
}

输出:
在这里插入图片描述


扩展

可尝试基于课程02节已有的Page.c扩展出类似C语言中提供的malloc和free函数。

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

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

相关文章

6-3 简单贪心(思想!不难)(看看这就这?)

贪心入门 贪心概念 贪心算法(又称贪婪算法)是指&#xff0c;在对问题求解时&#xff0c;总是做出在当前看来是最好的选择。也就是说&#xff0c;不从整体最优上加以考虑&#xff0c;他所做出的是在某种意义上的局部最优解。 贪心算法不是对所有问题都能得到整体最优解&#x…

【自制C++深度学习推理框架】卷积层的设计思路

卷积层的设计思路 使用Im2Col来实现高性能卷积 在深度学习中实现高性能卷积有以下几个方法&#xff1a; 并行计算&#xff1a;在网络或硬件层面上&#xff0c;利用并行计算的优势对卷积过程进行加速&#xff0c;例如使用GPU。 转换卷积算法&#xff1a;卷积操作可由矩阵相乘…

【生成数据】随机漫步

使用python来生成随机漫步数据&#xff0c;再使用matplotlib将这些数据呈现出来。 随机漫步&#xff1a;每次行走都是完全随机的&#xff0c;没有明确的方向&#xff0c;结果是由一系列随机决策决定的。也可以这么认为&#xff0c;随机漫步就是蚂蚁在晕头转向的情况下&#xff…

DNS详解

2.4 DNS&#xff1a;因特网的目录服务 我们首先要了解域名和IP地址的区别。IP地址是互联网上计算机唯一的逻辑地址&#xff0c;通过IP地址实现不同计算机之间的相互通信&#xff0c;每台联网计算机都需要通过IP地址来互相联系和分别。 但由于IP地址是由一串容易混淆的数字串构成…

Java String ,StringBuffer 和 StringBuilder 类

文章目录 一、Java String 类二、Java StringBuffer 和 StringBuilder 类总结 一、Java String 类 字符串广泛应用 在 Java 编程中&#xff0c;在 Java 中字符串属于对象&#xff0c;Java 提供了 String 类来创建和操作字符串。 创建字符串 创建字符串最简单的方式如下: St…

SiLu激活函数解释

SiLu激活函数 在yolo v5中&#xff0c;我们使用了SiLu激活函数 首先&#xff0c;了解一下激活函数的作用&#xff1a; 激活函数在神经网络中起到了非常重要的作用。以下是激活函数的一些主要功能&#xff1a; 引入非线性&#xff1a;激活函数的主要目标是在模型中引入非线性…

STM32F7xx Keil5 RTX RL-TCPnet DP83822移植

使用之前RTX工程模板 RTE中RL-TCPnet配置 暂时全部默认配置&#xff0c;DHCP已打开 修改RTE_Device.h ETH配置 修改DP83822驱动 去掉文件只读属性&#xff0c;之后需要修改&#xff0c;添加到工程 修改DP83822 ID RTE创建tcp server例程&#xff0c;参考该例程&#xff0c;进…

2023.6.4 第五十六次周报

目录 前言 文献阅读&#xff1a;一种预测中国东海岸非平稳和不规则波的VMD-LSTM/GRU混合模型 背景 研究区域和数据 VMD LSTM/GRU预测模型 VMD-LSTM/GRU 方法的数值算法 序列的非平稳分析 神经网络设计 结论 代码&#xff1a;lstm预测污染物浓度 总结 前言 I read …

ChatGPT结合Excel公式办公 —— ChatGPT统计富豪信息

&#x1f4a7; C h a t G P T 统计富豪信息 \color{#FF1493}{ChatGPT统计富豪信息} ChatGPT统计富豪信息&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390; &#x1f433; 《数据结构与算法…

设计原则-里氏替换原则

凡事皆有利弊&#xff0c;面向对象设计语言通过提供继承、多态等机制使得项目代码更具有复用性、可扩展性等优点&#xff0c;但是这些机制也存在一定的使用风险&#xff0c;比如继承的滥用、多态实现的不确定性等问题都有可能会引起重大线上事故。 一、里氏替换原则概念 里氏…

智能计算补充(从第四章p44往后)

智能计算补充&#xff08;从第四章p44往后&#xff09; 本文内容大部分来自于任振兴老师的讲课PPT&#xff0c;主要是对老师PPT内容的总结和提炼&#xff0c;侵权请联系我删除。 文章目录 智能计算补充&#xff08;从第四章p44往后&#xff09;适应度尺度变换1、适应度尺度变换…

DicomObjects.Core 3.0.17 Crack

DicomObjects.NET 核心版简介 DicomObjects.Core Assembly DicomObjects.NET 核心版简介 DicomObjects.Core 由一组相互关联但独立的 .核心兼容的“对象”&#xff0c;使开发人员能够快速轻松地将DICOM功能添加到其产品中&#xff0c;而无需了解或编程DICOM标准的复杂性。此帮助…

Golang 协程/线程/进程 区别以及 GMP 详解

Golang 协程/线程/进程 区别详解 转载请注明来源&#xff1a;https://janrs.com/mffp 概念 进程 每个进程都有自己的独立内存空间&#xff0c;拥有自己独立的地址空间、独立的堆和栈&#xff0c;既不共享堆&#xff0c;亦不共享栈。一个程序至少有一个进程&#xff0c;一个进程…

数据库多表设计

说明&#xff1a;在项目的数据库设计时&#xff0c;表与表之间是有联系的&#xff0c;如学生管理系统中&#xff0c;有部门表&#xff0c;教师表、学生表、课程表等等 一位教师隶属于一个部门&#xff0c;一个部门有多位教师&#xff0c;因此部门表和教师表&#xff0c;是一对…

【c++修行之路】c++11特性--上

文章目录 前言列表初始化用法介绍原理&#xff1a;std::initializer_list 简化声明的方式autodecltype 右值引用移动构造万能引用和完美转发万能引用完美转发 类的新增功能可变参数模板lambda表达式深入探究lambda表达式lambda表达式带来的便利结语 前言 大家好久不见&#xf…

bmp文件格式与保存

BMP文件由三部分组成&#xff0c;分辨是文件头&#xff0c;DIM头和像素数据。具体格式如下&#xff1a; 基本介绍 1. 文件头 14个字节 signature&#xff1a; 为文件标志位&#xff0c;恒为0X42 FileSize&#xff1a;是指整个文件的大小 REservedx&#xff1a;保留位恒为0 …

leetcode700. 二叉搜索树中的搜索(java)

二叉搜索树中的搜索 leetcode700 二叉搜索树中的搜索题目描述 解题思路代码演示二叉树专题 leetcode700 二叉搜索树中的搜索 leetcode 700 二叉搜索树中的搜索。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/search-i…

chatgpt赋能python:Python去掉GIL:提升Python多线程编程性能的突破口

Python去掉GIL&#xff1a;提升Python多线程编程性能的突破口 Python 是世界上最受欢迎的编程语言之一&#xff0c;其中一大原因是其简单易用、优雅简洁的语法。Python 也是一个卓越的多用途编程语言&#xff0c;广泛应用于 Web 开发、科学计算、人工智能等领域。但是&#xf…

javaScript蓝桥杯-----宝贵的一票

目录 一、介绍二、准备三、目标四、代码五、检测踩坑&#xff01;&#xff01;六、完成 一、介绍 公司经常举办各种活动&#xff0c;但一到投票环节就犯了难&#xff0c;于是公司决定安排小蓝开发一个投票系统&#xff0c;更好的收集大家的投票信息。为了赶在下一次活动开始前…

k8s亲和性和反亲和性

1.前言 k8s的亲和性和反亲和性都是通过标签来影响pod的调度&#xff0c;在此基础上亲和性又分为硬亲和性和软亲和性&#xff0c;required为硬亲和性即标签内容必须要符合才能调度&#xff0c;preferred为软亲和性即标签内容不一定要符合也能调度&#xff0c;除此之外还有node亲…