当前位置: 首页 > article >正文

从0开始的操作系统手搓教程19:构建我们的内存管理——第二步:内存子系统进化,获取页!

目录

讨论页表的分析和索引的完成

完成一个宽泛的页获取

从指定的内存池中分配若干页

获取准备用来提供给客户端方向的虚拟地址起始位置

根据内存池的选择,完成对物理内存的获取

关联我们的物理内存和虚拟内存

编写尝试

运行的截图


现在,我们将会完成简单的从虚拟内存和物理内存完成分配且在页表中构成映射的办法

我们来讨论的是如何在内存子系统获取页这种粗粒度。换而言之,我们准备处理一个接口:get_kernel_page(const int pg_cnt)。处理我们的请求需要面对的是两个内存池。

// Pool flags tells to allocate from which pool flags :)
typedef enum { PF_KERNEL = 1,  // allocate from kernelPF_USER = 2     // allocate from user
} PoolFlag;

整个PoolFlag维护了我们准备面向请求的内存池。然后,我们把页表里面属性给抽象出来:

/*Memory Page Property Settings
*/ 
// Define the existence flag for page table or page directory entry
// The PG_P bit is set to 1 if the entry is valid (exists), and 0 if it's invalid (does not exist)
#define PG_P_1   (1)     // Page table or page directory entry exists (valid)
#define PG_P_0   (0)     // Page table or page directory entry does not exist (invalid)
​
// Define the Read/Write (R/W) attribute flag values for page table or directory entries
// The R/W bit specifies the access rights: read/write or read/execute
#define PG_RW_R  (0)     // Read/execute only (no write permissions)
#define PG_RW_W  (2)     // Read/write/execute (write permissions enabled)
​
// Define the User/Supervisor (U/S) attribute flag values for page table or directory entries
// The U/S bit specifies whether the page is accessible in user mode or only in supervisor (kernel) mode
#define PG_US_S  (0)     // System-level access (supervisor/privileged mode only)
#define PG_US_U  (4)     // User-level access (accessible by user-mode programs)
  • PG_P_1 表示 P 位的值为 1,表示此页内存已存在。

  • PG_P_0 表示 P 位的值为 0,表示此页内存不存在。

  • PG_RW_W 表示 RW 位的值为 W,即 RW=1,表示此页内存允许读、写、执行。

  • PG_RW_R 表示 RW 位的值为 R,即 RW=0,表示此页内存允许读、执行。

  • PG_US_S 表示US 位的值为 S,即US=0,表示只允许特权级别为 0、1、2 的程序访问此页内存,3 特权级程序不被允许。

  • PG_US_U 表示 US 位的值为 U,即 US=1,表示允许所有特权级别程序访问此页内存。

讨论页表的分析和索引的完成

让我们回忆一下页表的部分,请参考笔者之前在搓Loader时候的:

  • 高10 位是页目录项pde 的索引,用于在页目录表中定位 pde,细节是处理器获取高 10 位后自动将其乘以 4,再加上页目录表的物理地址,这样便得到了 pde 索引对应的pde 所在的物理地址,然后自动在该物理地址中,即该 pde 中,获取保存的页表物理地址(也就是索引我们到PTE的物理地址)

  • 中间 10 位是页表项 pte 的索引,用于在页表中定位 pte。细节是处理器获取中间 10 位后自动将其乘以 4,再加上第一步中得到的页表的物理地址,这样便得到了 pte 索引对应的 pte 所在的物理地址, 然后自动在该物理地址(该 pte)中获取保存的普通物理页的物理地址。

  • 低 12 位是物理页内的偏移量,页大小是4KB,12 位可寻址的范围正好是4KB,因此处理器便直接把低12 位作为第二步中获取的物理页的偏移量,无需乘以4。用物理页的物理地址加上这低 12 位的和 便是这 32 位虚拟地址最终落向的物理地址。

基于上面我们的讨论,我们的编程思路就很清晰了。现在,让我们从顶向下,一步一步完成我们的系统编程。

完成一个宽泛的页获取

所以,很容易想到,我们的需求是:获取给定页数的内核页,其参数(输入)显然是一个描述个数的,获取一个返回这一串连续页的虚拟地址。注意的是——我们的程序从头到尾都在操作虚拟内存,想要得到物理内存就必须操作页表自己完成转换。得到的地址就是一个线性偏移关系的物理地址,从而在实际上操作我们的物理地址。

// Function to allocate a specified number of kernel pages and return their virtual addresses
void* get_kernel_pages(const uint32_t kpage_count) { void* vaddr =  malloc_page(PF_KERNEL, kpage_count);  // Request kernel pagesif (vaddr) {  // If successful, clear the allocated pages and return the addressk_memset(vaddr, 0, kpage_count * PG_SIZE); } return vaddr;  // Return the virtual address of the allocated kernel pages
}

这个函数所完成的事情简直不需要我再多说什么。但是这里,我们有一个有意思的问题,这个可以暂时跳过,等您完成了这个博客的任务,再回来看这里

可以看到,我们返回一个虚拟地址之后,会习惯性的对我们返回的连续的虚拟地址进行清零操作。你可能会疑问:为什么呢?

答案是,任何一个操作系统作为底层的构件,就必须保证做最小满足客户需求的事情的基础上,尽可能的提升用户的使用体验性能。往往,我们再回收一个页面(不是再这个章节中完成),回直接解除映射,而不是先清零,再解除映射。毕竟——一些页我们可能永远不会在之后用到,一个合理的办法是——直到我们确认对query内存的使用所属权。这个时候,我们才会去做清空。

所以答案呼之欲出:我们之前实际上再解除映射的时候不会完成清空。这之前,可就会存在上一次使用这些内存的数据。这些数据如果被误用,可能导致程序行为异常或者错误的内存映射,这个时候程序的排查就会异常困难。这个事情,笔者建议你去完成一下MIT S6081的操作系统实验:

Lab: System calls中的Attack xv6实验,理解一下如果我们不去使用页表清理的手段,我们的操作系统会如何被攻击。

从指定的内存池中分配若干页

static bool accept_allocate_policy(const uint32_t pg_cnt)
{return pg_cnt > 0 && pg_cnt < (MAX_ACCEPT_SINGLE_ALLOCATE_MB * MB / PG_SIZE);
}
​
// Function to allocate a specified number of pages and map them between virtual and physical memory
void* malloc_page(const PoolFlag pf, const uint32_t pg_cnt) { KERNEL_ASSERT(accept_allocate_policy(pg_cnt));  // Ensure the requested page count is valid
​/*********** The malloc_page function performs three actions: ***********1. Allocates virtual addresses through vaddr_get.2. Allocates physical pages through palloc.3. Maps the virtual and physical addresses in the page table via page_table_add.********************************************************************/
​// Get the starting virtual address for the requested number of pagesvoid* vaddr_start = vaddr_get(pf, pg_cnt); if (vaddr_start == NULL) { return NULL;  // Return NULL if the virtual address allocation fails} 
​uint32_t vaddr = (uint32_t)vaddr_start, cnt = pg_cnt; MemoryPool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;  // Select the appropriate memory pool
​// The virtual address is contiguous, but physical addresses may not be. Therefore, handle the mapping one page at a time.while (cnt-- > 0) { // Allocate a physical page from the chosen memory poolvoid* page_phyaddr = palloc(mem_pool); if (!page_phyaddr) {  // If allocation fails, revert previously allocated pagesreturn NULL; } 
​// Add the mapping between the virtual address and physical page in the page tablepage_table_add((void*)vaddr, page_phyaddr); vaddr += PG_SIZE;  // Move to the next virtual page} 
​return vaddr_start;  // Return the starting virtual address of the allocated space
}

函数的开头中,我们有了一个非常有趣的限制。我们谈到,我们的分配Policy是静态按照对半开的,显然,我们需要保证我们申请的page_count是不可以超过3840的(本身当前我们使用的是32MB的内存空间,对半开16MB,其中,内核少1MB,我们实际上是15MB所用的内核的数量),如果超过了,我们需要直接拉起警报。说明内核在检查我们的分配大小的时候存在大小检查的缺失。这里,笔者采用更加灵活的静态方案。这里的定义中:

#define MB              (1024 * 1024)           // memory_tools.h
#define MAX_ACCEPT_SINGLE_ALLOCATE_MB   (15)    // memory_settings.h
获取准备用来提供给客户端方向的虚拟地址起始位置

这个函数是由vaddr_get发起请求的,其定义如下:

/* Allocate virtual memory pages from the specified pool (kernel or user).* If successful, return the starting virtual address; otherwise, return NULL. */
static void *vaddr_get(const PoolFlag pf, const uint32_t pg_cnt)
{int vaddr_start = 0, bit_idx_start = -1;uint32_t cnt = 0;
​switch (pf) // Determine which pool to allocate from based on the PoolFlag{case PF_KERNEL: // If the pool is the kernel memory pool{bit_idx_start = bitmap_scan(&kernel_vaddr.virtual_mem_bitmap, pg_cnt); // Find the first free block of 'pg_cnt' pagesif (bit_idx_start == -1){return NULL; // If no free block is found, return NULL}while (cnt < pg_cnt){bitmap_set(&kernel_vaddr.virtual_mem_bitmap, bit_idx_start + cnt, 1); // Mark the pages as allocated in the bitmapcnt++; // Increment the page count}vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE; // Calculate the starting virtual address of the allocated pagesbreak; // Exit the case block}case PF_USER: // If the pool is the user memory pool{// waiting for further implementations}}return (void *)vaddr_start; // Return the starting virtual address of the allocated pages
}

当然,我们需要做分类讨论,现在我们没有实现用户层抽象,那就暂时空在此处,之后铩羽归来实现。这里,实际上就是对我们内存空间位图申请占用即可。正如我们所说的那样,实际上内核就是在裸奔,想用就用,不想用就不用。我们这里实际上就是申请薄记了此处连续的pages_count个PG_SIZE大小的虚拟地址此时被薄记使用了。

我们返回这个有效的结合位图指示的起始地址之后,就准备进入下一步

根据内存池的选择,完成对物理内存的获取

这个事情由palloc函数完成

/* Allocate 1 physical page from the memory pool m_pool.* If successful, returns the physical address of the page; otherwise, returns* NULL */
static void *palloc(MemoryPool *m_pool) {/* Bitmap scan or setting should be atomic */int bit_idx =bitmap_scan(&m_pool->pool_bitmap, 1); // Find an available physical pageif (bit_idx == -1) {return NULL;}bitmap_set(&m_pool->pool_bitmap, bit_idx,1); // Set the bit at index bit_idx to 1return (void *)((bit_idx * PG_SIZE) + m_pool->phy_addr_start);
}

palloc函数薄记了在我们物理内存的维护位图当中的占用来表达我们进程对pagetable的所有权。现在,我们把对应允许的物理地址返回回来。返回的逻辑跟上面虚拟地址的返回是完全一致的,我们不会再多说。

关联我们的物理内存和虚拟内存
// Function to add a mapping between a virtual address and a physical address in the page table
static void page_table_add(const void *_vaddr, const void *_page_phyaddr)
{uint32_t vaddr = (uint32_t)_vaddr;uint32_t page_phyaddr = (uint32_t)_page_phyaddr;uint32_t *pde = pde_ptr(vaddr); // Get the pointer to the page directory entryuint32_t *pte = pte_ptr(vaddr); // Get the pointer to the page table entry
​/************************   Attention   ************************** When accessing *pte, it may refer to an empty pde.* Therefore, ensure that the pde is created before accessing *pte.* Otherwise, a page fault will occur.* If *pde is 0, the *pte can only be accessed after *pde is properly initialized.************************************************************/
​// First, check the existence of the page directory entry (PDE)// If the PDE exists (P bit is 1), we can proceed to work with the PTEif (*pde & PG_P_1){// Check if the PTE exists. If it does, no error; otherwise, create itif (*pte & PG_P_1){KERNEL_PANIC_SPIN("pte repeat");}else{*pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1); // Create the PTE with necessary flags}}else{ // If the PDE does not exist, we need to create it first// Allocate physical memory for the page directory entry (PDE) from the kernel spaceuint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);
​// Initialize the PDE with the allocated physical address, setting appropriate flags*pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
​// Clear the physical memory allocated for the PDE to avoid any old data being interpreted as page table entriesk_memset((void *)((int)pte & PG_FETCH_OFFSET), 0, PG_SIZE);
​// Ensure that the PTE is not set already before proceeding to writeKERNEL_ASSERT(!(*pte & PG_P_1));
​// Now create the PTE with the appropriate flags (US=1, RW=1, P=1)*pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);}
}

函数page_table_add接受两个参数,虚拟地址_vaddr 和物理地址page_phyaddr,功能是 添加虚拟地址_vaddr与物理地址page_phyaddr的映射。 虚拟地址和物理地址的映射关系是在页表中完成的,本质上是在页表中添加此虚拟地址对应的页表项 pte, 并把物理页的物理地址写入此页表项pte 中。

pte_ptrpde_ptr函数封装了我们的从虚拟地址获取ptr和pde部分的

// Define the macro to extract the Page Directory Entry (PDE) index from a virtual address
// The virtual address is divided into multiple parts for paging: 
// The top 10 bits (bits 22–31) correspond to the Page Directory index.
// The macro uses bitwise operations to mask and shift the address to isolate these bits.
#define PDE_IDX(addr)   ((addr & 0xffc00000) >> 22)  // Extract the top 10 bits for the PDE index// Define the macro to extract the Page Table Entry (PTE) index from a virtual address
// The next 10 bits (bits 12–21) correspond to the Page Table index.
// The macro uses bitwise operations to mask and shift the address to isolate these bits.
#define PTE_IDX(addr)   ((addr & 0x003ff000) >> 12)  // Extract bits 12–21 for the PTE index

回顾我们分析页表的流程。这两个工具函数就变得非常容易理解了。

#include "include/memory/memory_tools.h"
// Function to get the pointer to the Page Table Entry (PTE) corresponding to a virtual address
uint32_t* pte_ptr(uint32_t vaddr) { /* First, access the page table itself + Then, use the Page Directory Entry (PDE) (which is an index in the page directory) to access the Page Table + Finally, use the Page Table Entry (PTE) index to calculate the offset within the page. */// Calculate the PTE address: // 1. Start from the base address for the page tables (0xffc00000).// 2. Shift the virtual address to extract the Page Directory index and use it to locate the page table.// 3. Use the PTE index to find the specific entry within the page table and multiply by 4 to account for 32-bit (4-byte) entries.return (uint32_t*)(0xffc00000 + ((vaddr & 0xffc00000) >> 10) +   // Extract PDE index and shift it for the page table locationPTE_IDX(vaddr) * 4);             // Use PTE index to find the exact entry in the page table
}// Function to get the pointer to the Page Directory Entry (PDE) corresponding to a virtual address
uint32_t* pde_ptr(uint32_t vaddr) { /* Use 0xfffff000 to access the base address of the page directory. */// Calculate the PDE address: // 1. Start from the base address of the page directory (0xfffff000).// 2. Use the Page Directory Entry index to calculate the offset and multiply by 4 for the 32-bit entries.return (uint32_t*)((PG_FETCH_OFFSET) + PDE_IDX(vaddr) * 4); // Return the calculated PDE pointer
}

继续回到我们的代码上。pte 隶属于某个页表,而页表地址保存在 pde 中。一个 pte 代表一个物理页,物理页是 4KB 大小,一 个页表中可支持 1024 个 pte,故一个页表最大支持 4MB 内存。由于我们目前已经有了一个页表,故在 4MB(0x0~0x3ff000)的范围内新增pte 时,只要申请个物理页并将此物理页的物理地址写入新的pte 即 可,无需再做额外操作。可是,当我们访问的虚拟地址超过了此范围时,比如 0x400000,这不仅是添加 pte 的问题,同时还要申请个物理页来新建页表,同时将用作页表的物理页地址写入页目录表中的第 1 个 页目录项 pde 中。也就是说,只要新增的虚拟地址是 4MB 的整数倍时,就一定要申请两个物理页,一个 物理页作为新的页表,同时在页目录表中创建个新的 pde,并把此物理页的物理地址写入此 pde。另一个 物理页作为普通的物理页,同时在新建的页表中添加个新的 pte,并把此物理页的物理地址写入此 pte。 因此,在添加一个页表项pte 时,我们务必要判断该pte 所在的页表是否存在。在第 96 行就是在判断页表项中的 P 位,如果此位为 1,则表示页目录项已存在,不需要再建立。 后面,我们会清空PTE,来保证我们的映射是干净的,随后再赋值我们新的物理地址写入。

回到起头的malloc page函数,我们就是封装了这三步骤:

  • 通过 vaddr_get 在虚拟内存池中申请虚拟地址。

  • 通过 palloc 在物理内存池中申请物理页。

  • 通过 page_table_add 将以上两步得到的虚拟地址和物理地址在页表中完成映射。

笔者在源代码中附上了非常详细的注释。可以到:

CCOperateSystem/Documentations/7_Memory_Management/7.2_implement_page_fetch_code at main · Charliechen114514/CCOperateSystemhttps://github.com/Charliechen114514/CCOperateSystem/tree/main/Documentations/7_Memory_Management/7.2_implement_page_fetch_code

好好阅读

编写尝试

#include "include/library/ccos_print.h"
#include "include/kernel/init.h"
#include "include/library/kernel_assert.h"
#include "include/memory/memory.h"
int main(void)
{init_all();void* addr = get_kernel_pages(3);ccos_puts("\nget_kernel_page start vaddr is "); __ccos_display_int((uint32_t)addr); ccos_puts("\n");ccos_puts("\nlet's do another query: start vaddr is "); addr = get_kernel_pages(3);__ccos_display_int((uint32_t)addr); ccos_puts("\n");// interrupt_enabled(); // comment the intr for the displaywhile(1);return 0;
}

运行的截图

相关文章:

从0开始的操作系统手搓教程19:构建我们的内存管理——第二步:内存子系统进化,获取页!

目录 讨论页表的分析和索引的完成 完成一个宽泛的页获取 从指定的内存池中分配若干页 获取准备用来提供给客户端方向的虚拟地址起始位置 根据内存池的选择&#xff0c;完成对物理内存的获取 关联我们的物理内存和虚拟内存 编写尝试 运行的截图 现在&#xff0c;我们将会…...

数学软件Matlab下载|支持Win+Mac网盘资源分享

如大家所了解的&#xff0c;Matlab与Maple、Mathematica并称为三大数学软件。Matlab应用广泛&#xff0c;常被用于数据分析、无线通信、深度学习、图像处理与计算机视觉、信号处理、量化金融与风险管理、机器人&#xff0c;控制系统等领域。 Matlab将数值分析、矩阵计算、科学…...

Basler acA1920-40gc

软件 下载Basler软件 | Basler AG 说明书 ace acA1920-40gc | GigE相机 | Basler | Basler AG 支持PTP同步 在使用 Basler acA1920-40gc 相机和 Polyn View 软件时&#xff0c;确认 PTP&#xff08;Precision Time Protocol&#xff09;同步是否成功&#xff0c;可以通过…...

FASIONAD:自适应反馈的类人自动驾驶中快速和慢速思维融合系统

24年11月来自清华、早稻田大学、明尼苏达大学、多伦多大学、厦门大学马来西亚分校、电子科大&#xff08;成都&#xff09;、智平方科技和河南润泰数字科技的论文“FASIONAD : FAst and Slow FusION Thinking Systems for Human-Like Autonomous Driving with Adaptive Feedbac…...

sql server 复制从备份初始化数据

参考 &#xff1a; 从备份初始化订阅&#xff08;事务&#xff09; - SQL Server | Microsoft Learn sql server 复制默认是用快照初始化数据的&#xff0c;也支持从备份初始化数据&#xff0c;参考如上...

java八股文之框架

1.Spring框架中的Bean是否线程安全的 Spring框架中的Bean默认是单例的&#xff0c;不是线程安全的。因为一般在Spring的bean的中都是注入无状态的对象&#xff0c;没有线程安全问题&#xff0c;如果在bean中定义了可修改的成员变量&#xff0c;是要考虑线程安全问题的&#xf…...

R语言+AI提示词:贝叶斯广义线性混合效应模型GLMM生物学Meta分析

全文链接&#xff1a;https://tecdat.cn/?p40797 本文旨在帮助0基础或只有简单编程基础的研究学者&#xff0c;通过 AI 的提示词工程&#xff0c;使用 R 语言完成元分析&#xff0c;包括数据处理、模型构建、评估以及结果解读等步骤&#xff08;点击文末“阅读原文”获取完整代…...

2020年蓝桥杯Java B组第二场题目+部分个人解析

#A&#xff1a;门牌制作 624 解一&#xff1a; public static void main(String[] args) {int count0;for(int i1;i<2020;i) {int ni;while(n>0) {if(n%102) {count;}n/10;}}System.out.println(count);} 解二&#xff1a; public static void main(String[] args) {…...

【Azure 架构师学习笔记】- Azure Databricks (13) -- 搭建Medallion Architecture part 1

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Databricks】系列。 接上文 【Azure 架构师学习笔记】- Azure Databricks (12) – Medallion Architecture简介 前言 上文已经介绍了关于Medallion的知识&#xff0c;本文开始用ADB 来实现&#xff0c; 但是基于内容较…...

2025年2月21日优雅草内测分发站全新升级-测试运营-优雅草内测分发站新用户提供免费100下载点-2月28日正式运营并且提供私有化部署版本

2025年2月21日优雅草内测分发站全新升级-测试运营-优雅草内测分发站新用户提供免费100下载点-2月28日正式运营并且提供私有化部署版本 说明 优雅草内测分发站新用户提供免费100下载点&#xff0c;优雅草分运营站和demo测试站 运营站&#xff1a;www.youyacao.cn 提供免费100…...

通过 PromptTemplate 生成干净的 SQL 查询语句并执行SQL查询语句

问题描述 在使用 LangChain 和 Llama 模型生成 SQL 查询时&#xff0c;遇到了 sqlite3.OperationalError 错误。错误信息如下&#xff1a; OperationalError: (sqlite3.OperationalError) near "sql SELECT Name FROM MediaType LIMIT 5; ": syntax error [SQL: …...

本地部署Embedding模型API服务的实战教程

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于大模型算法的研究与应用。曾担任百度千帆大模型比赛、BPAA算法大赛评委,编写微软OpenAI考试认证指导手册。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。授权多项发明专利。对机器学…...

IP段转CIDR:原理Java实现

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…...

[STM32]从零开始的STM32 DEBUG问题讲解及解决办法

一、前言 最近也是重装了一次keil&#xff0c;想着也是重装了&#xff0c;也是去官网下载了一个5.41的最新版&#xff0c;在安装和配置编译器和别的版本keil都没太大的区别&#xff0c;但是在调试时&#xff0c;遇到问题了&#xff0c;在我Debug的System Viewer窗口中没有GPIO&…...

MySQL当中的Lock

1. 总览锁的类型 锁的类型&#xff1a; 锁类型 符号/缩写 描述 全局锁 FTWRL 锁定整个数据库&#xff08;FLUSH TABLES WITH READ LOCK&#xff09;&#xff0c;用于全库备份。 表级锁 - 表锁 S/X LOCK TABLES ... READ&#xff08;共享锁&#xff09;或 WRITE&#…...

electron-builder打包时github包下载失败【解决办法】

各位朋友们&#xff0c;在使用electron开发时&#xff0c;选择了electron-builder作为编译打包工具时&#xff0c;是否经常遇到无法从github上下载依赖包问题&#xff0c;如下报错&#xff1a; Get "https://github.com/electron/electron/releases/download/v6.1.12/ele…...

【免费】YOLO[笑容]目标检测全过程(yolo环境配置+labelimg数据集标注+目标检测训练测试)

一、yolo环境配置 这篇帖子是我试过的&#xff0c;非常全&#xff0c;很详细【cudaanacondapytorchyolo(ultralytics)】 yolo环境配置 二、labelimg数据集标注 可以参考下面的帖子&#xff0c;不过可能会出现闪退的问题&#xff0c;安装我的流程来吧 2.1 labelimg安装 label…...

服务器禁止操作汇总(Server Prohibits 0peration Summary)

服务器禁止操作汇总 一、禁忌操作TOP10 1. 直接断电关机 &#x1f4a5; 血泪案例&#xff1a;某物流公司运维拔电源强制关机&#xff0c;导致数据库事务中断&#xff0c;20万订单状态丢失。 &#x1f4cc; 技术解析&#xff1a; • 直接断电可能引发&#xff1a; ✅ 文件系统…...

UE5 Slate类的基础创建

创建一个slate类的基础代码 #pragma onceclass SCustomDetailPlane : public SCompoundWidget {SLATE_BEGIN_ARGS(SCustomDetailPlane){}SLATE_END_ARGS()public:SCustomDetailPlane();~SCustomDetailPlane();void Construct(const FArguments& InArgs);};***************…...

springboot2.7.18升级springboot3.3.0遇到的坑

druid的警告&#xff0c;警告如下&#xff1a; 运行警告2025-02-28T09:20:31.28508:00 WARN 18800 --- [ restartedMain] trationDelegate$BeanPostProcessorChecker : Bean com.alibaba.druid.spring.boot3.autoconfigure.stat.DruidSpringAopConfiguration of type [com.a…...

服务器IPMI用户名、密码批量检查

背景 大规模服务器部署的时候&#xff0c;少不了较多的网管和监测平台&#xff0c;这些平台会去监控服务器的性能、硬件等指标参数&#xff0c;为了便于管理和控制&#xff0c;则需要给服务器IPMI带外管理添加较多的用户&#xff0c;这就需要对较多的服务器检查所对应的IPMI用…...

JAVA面试_进阶部分_netty面试题

1.BIO、NIO 和 AIO 的区别&#xff1f; BIO&#xff1a;一个连接一个线程&#xff0c;客户端有连接请求时服务器端就需要启动一个线程进行处理。线程开销大。 伪异步 IO&#xff1a;将请求连接放入线程池&#xff0c;一对多&#xff0c;但线程还是很宝贵的资源。 NIO&#x…...

小红书湖仓架构的跃迁之路

作者&#xff1a;李鹏霖(丁典)&#xff0c;小红书-研发工程师&#xff0c;StarRocks Contributor & Apache Impala Committer 本文整理自小红书工程师在 StarRocks 年度峰会上的分享&#xff0c;介绍了小红书自助分析平台中&#xff0c;StarRocks 与 Iceberg 结合后&#x…...

C++-第十七章:包装器

目录 第一节&#xff1a;std::function 第二节&#xff1a;std::bind 2-1.基本介绍 2-2.调整顺序(不常用) 2-3.调整个数 2-4.std::bind与std::function 下期预告&#xff1a; C中有3种可调用对象&#xff1a;函数指针、仿函数对象、lambda函数&#xff0c;经过包装器包装后屏…...

如何判断邮件列表中邮箱地址的有效性?

判断邮件列表中邮箱地址的有效性&#xff0c;对于提高邮件送达率、避免资源浪费和维护发件人信誉至关重要。以下是一些实用的判断方法&#xff1a; 一、使用专业的邮箱验证工具 市面上有许多专业的邮箱验证工具&#xff0c;如 Geeksend邮箱验证工具 等。这些工具通过与邮件服…...

翻译: 深入分析LLMs like ChatGPT 二

监督微调&#xff08;SFT&#xff09; 使用人工标注的对话数据集&#xff08;如1M条"用户-助手"对话&#xff09;继续训练模型。 标注员遵循指导原则编写理想回答&#xff0c;使模型学习助手的回应风格。 示例对话格式&#xff1a; [系统] 你是一个有帮助的AI助手……...

conda怎么迁移之前下载的环境包,把python从3.9升级到3.10

克隆旧环境&#xff08;保留旧环境作为备份&#xff09; conda create -n cloned_env --clone old_env 在克隆环境中直接升级 Python conda activate cloned_env conda install python3.10 升级 Python 后出现 所有包导入失败 的问题&#xff0c;通常是因为依赖包与新 Pyth…...

k8s之pod的调度之污点与容忍污点,什么是污点? 如何容忍污点

在 Kubernetes 中&#xff0c;污点&#xff08;Taint&#xff09; 和 容忍&#xff08;Toleration&#xff09; 是用于控制 Pod 调度到特定节点的重要机制。污点允许节点拒绝某些 Pod 的调度&#xff0c;而容忍则允许 Pod 忽略节点的污点&#xff0c;从而调度到特定节点上。 1.…...

Linux切换Python版本

1、更新apt sudo apt update2、查询python安装路径 which python 或者which python33、查询安装版本 # 查看所有以 "python" 开头的命令&#xff08;包括版本号&#xff09; ls -l 安装路径* 例如 ls -l /usr/bin/python*4、修改软连接 udo unlink /usr/bin/pyt…...

TCP的三次握手与四次挥手:建立与终止连接的关键步骤

引言 ‌TCP&#xff08;传输控制协议&#xff09;工作在OSI模型的传输层‌。OSI模型将计算机网络功能划分为七个层级&#xff0c;从底层到顶层依次是&#xff1a;物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。传输层负责在网络节点之间提供可靠的端到端通信&a…...