linux内核高端内存映射-kmap/kunmap

news2026/3/14 16:20:11
动态映射高端内存页面,在32位系统中,物理内存分为低端内存(Low Memory,直接映射区)和高端内存(High Memory,动态映射区)。低端内存可通过固定偏移(PAGE_OFFSET)直接映射到内核虚拟地址空间,而高端内存(超出直接映射范围的物理内存)需通过 kmap动态分配虚拟地址并建立映射,供内核临时访问.kmap是Linux内核中用于将物理页面(尤其是高端内存页面)映射到内核虚拟地址空间的函数,使得内核能够通过虚拟地址直接访问物理内存。它是内核处理高端内存(High Memory)动态映射的核心机制之一,主要用于32位系统(虚拟地址空间有限),64位系统因地址空间充足,通常无需高端内存映射,kmap行为会更简化。1、关键数据结构mm/highmem.c /* * Describes one page-virtual association */ struct page_address_map { struct page *page; void *virtual; struct list_head list; /*挂接到page_address_slot-lh上*/ }; 每个page占用一个,512*4k2MPKMAP区刚好2M大小pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB) static struct page_address_map page_address_maps[LAST_PKMAP]/*512*/; /* * Hash table bucket */ static struct page_address_slot { struct list_head lh; /* List of page_address_maps */ spinlock_t lock; /* Protect this buckets list */ } ____cacheline_aligned_in_smp page_address_htable[1PA_HASH_ORDER/*17,128*/]; arch/arm/include/asm/highmem.h 若页面属于高端内存(物理地址high_memory),kmap通过PKMAP区域(永久内核映射区)动态分配虚拟地址并建立映射 /* pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB)*/ #define PKMAP_BASE (PAGE_OFFSET - PMD_SIZE) /*0xc0000000-0x2000000xbfe00000*/ /*2M/4K 512*/ #define LAST_PKMAP PTRS_PER_PTE /*512*/ #define LAST_PKMAP_MASK (LAST_PKMAP - 1) #define PKMAP_NR(virt) (((virt) - PKMAP_BASE) PAGE_SHIFT) #define PKMAP_ADDR(nr) (PKMAP_BASE ((nr) PAGE_SHIFT)) /*标记pkmap区中内个虚拟地址页面是否已经被映射*/ static int pkmap_count[LAST_PKMAP/*512*/]; static __cacheline_aligned_in_smp DEFINE_SPINLOCK(kmap_lock); 页表项 pte_t * pkmap_page_table; 高端内存提供的核心操作接口如下: #ifdef CONFIG_HIGHMEM void *kmap(struct page *page); void kunmap(struct page *page); void *kmap_atomic(struct page *page); void __kunmap_atomic(void *kvaddr); void *kmap_atomic_pfn(unsigned long pfn); struct page *kmap_atomic_to_page(const void *ptr); #endif2、kmap实现arch/arm/mm/highmem.c void *kmap(struct page *page) { might_sleep(); if (!PageHighMem(page)) /*非高端内存,线性映射区*/ return page_address(page); /*返回page对应虚拟地址*/ return kmap_high(page); } include/linux/page-flags.h #define PageHighMem(__p) is_highmem(page_zone(__p)) static inline int is_highmem(struct zone *zone) { #ifdef CONFIG_HIGHMEM int zone_off (char *)zone - (char *)zone-zone_pgdat-node_zones; return zone_off ZONE_HIGHMEM * sizeof(*zone) || (zone_off ZONE_MOVABLE * sizeof(*zone) zone_movable_is_highmem()); #else return 0; #endif } static inline struct zone *page_zone(const struct page *page) { return NODE_DATA(page_to_nid(page))-node_zones[page_zonenum(page)]; } page-flags的位分配由内核在编译时通过宏定义(如ZONES_SHIFT、NODES_SHIFT、SECTIONS_SHIFT等)确定, 不同架构/内核版本可能略有差异,但核心结构一致各个部分在page-flags内部偏移量计算 static inline int page_to_nid(const struct page *page) { return (page-flags NODES_PGSHIFT) NODES_MASK; } static inline enum zone_type page_zonenum(const struct page *page) { return (page-flags ZONES_PGSHIFT) ZONES_MASK; } 通过计算page所在的zone来判断page是在低端还是高端内存。 /** * page_address - get the mapped virtual address of a page * page: struct page to get the virtual address of * * Returns the pages virtual address. */ void *page_address(const struct page *page) { unsigned long flags; void *ret; struct page_address_slot *pas; if (!PageHighMem(page)) /*低端内存地址*/ return lowmem_page_address(page); /*page在page_address_htable[]中对应的slot槽位*/ pas page_slot(page); ret NULL; spin_lock_irqsave(pas-lock, flags); if (!list_empty(pas-lh)) { /*非空*/ struct page_address_map *pam; /*遍历返回地址*/ list_for_each_entry(pam, pas-lh, list) { if (pam-page page) { ret pam-virtual; goto done; } } } done: spin_unlock_irqrestore(pas-lock, flags); return ret; } hash计算,将冲突的地址挂接到槽位对应链表lh上 static struct page_address_slot *page_slot(const struct page *page) { return page_address_htable[hash_ptr(page, PA_HASH_ORDER/*7*/)/*hash值*/]; } static inline unsigned long hash_ptr(const void *ptr, unsigned int bits) { return hash_long((unsigned long)ptr, bits); } #define hash_long(val, bits) hash_32(val, bits) static inline u32 hash_32(u32 val, unsigned int bits) { /* On some cpus multiply is faster, on others gcc will do shifts */ u32 hash val * GOLDEN_RATIO_PRIME_32; /*对val取黄金比列值*/ /* High bits are more random, so use them. */ return hash (32 - bits); /*进行移位操作,保留高位bits*/ } /** * kmap_high - map a highmem page into memory * page: struct page to map * * Returns the pages virtual memory address. * * We cannot call this from interrupts, as it may block. */ void *kmap_high(struct page *page) { unsigned long vaddr; /* * For highmem pages, we cant trust virtual until * after we have the lock. */ lock_kmap(); vaddr (unsigned long)page_address(page); if (!vaddr) /*未被映射*/ vaddr map_new_virtual(page); /*从FKMAP中找到一个可用虚拟地址,并设置pte*/ pkmap_count[PKMAP_NR(vaddr)]; /*pkmap_count[] 为2*/ BUG_ON(pkmap_count[PKMAP_NR(vaddr)] 2); unlock_kmap(); return (void*) vaddr; } static inline unsigned long map_new_virtual(struct page *page) { unsigned long vaddr; int count; unsigned int last_pkmap_nr; unsigned int color get_pkmap_color(page); start: count get_pkmap_entries_count(color); printk(-----%s,color:%d \n,__func__,color); /* Find an empty entry */ for (;;) { last_pkmap_nr get_next_pkmap_nr(color); if (no_more_pkmaps(last_pkmap_nr, color)) { /*清除无效地址*/ flush_all_zero_pkmaps(); count get_pkmap_entries_count(color); } if (!pkmap_count[last_pkmap_nr]) /*可以使用*/ break; /* Found a usable entry */ if (--count) continue; /* count0, fkmap中无可用地址空间等待其他人释放空间出来 将任务进行休眠,存在其他任务释放高端内存时会将该任务唤醒 */ /* * Sleep for somebody else to unmap their entries */ { DECLARE_WAITQUEUE(wait, current); wait_queue_head_t *pkmap_map_wait get_pkmap_wait_queue_head(color); __set_current_state(TASK_UNINTERRUPTIBLE); /*不可中断休眠*/ add_wait_queue(pkmap_map_wait, wait); unlock_kmap(); schedule(); remove_wait_queue(pkmap_map_wait, wait); lock_kmap(); /* Somebody else might have mapped it while we slept */ if (page_address(page)) return (unsigned long)page_address(page); /* Re-start */ goto start; } } /*找到可以使用的虚拟地址*/ vaddr PKMAP_ADDR(last_pkmap_nr); /*虚拟地址*/ set_pte_at(init_mm, vaddr, (pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot)); pkmap_count[last_pkmap_nr] 1; /*1*/ set_page_address(page, (void *)vaddr); return vaddr; } static inline int get_pkmap_entries_count(unsigned int color) { return LAST_PKMAP; } /* * Get next index for mapping inside PKMAP region for page with given color. */ static inline unsigned int get_next_pkmap_nr(unsigned int color) { static unsigned int last_pkmap_nr; /*静态变量*/ last_pkmap_nr (last_pkmap_nr 1) LAST_PKMAP_MASK; return last_pkmap_nr; } static inline int no_more_pkmaps(unsigned int pkmap_nr, unsigned int color) { return pkmap_nr 0; /*溢出*/ } static void flush_all_zero_pkmaps(void) { int i; int need_flush 0; flush_cache_kmaps(); /*清cache*/ for (i 0; i LAST_PKMAP/*512*/; i) { struct page *page; /* * zero means we dont have anything to do, * 1 means that it is still in use. Only * a count of 1 means that it is free but * needs to be unmapped */ /* 计数为0意味着我们无需执行任何操作 大于1意味着它仍在使用中 只有计数为1时,才意味着它是空闲的,但需要被解除映射 */ if (pkmap_count[i] ! 1) continue; pkmap_count[i] 0; /* sanity check */ BUG_ON(pte_none(pkmap_page_table[i])); /* * Dont need an atomic fetch-and-clear op here; * no-one has the page mapped, and cannot get at * its virtual address (and hence PTE) without first * getting the kmap_lock (which is held here). * So no dangers, even with speculative execution. */ /*解除映射*/ page pte_page(pkmap_page_table[i]); pte_clear(init_mm, PKMAP_ADDR(i), pkmap_page_table[i]); set_page_address(page, NULL); need_flush 1; } if (need_flush) flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP)); } #define PKMAP_NR(virt) (((virt) - PKMAP_BASE) PAGE_SHIFT) #define PKMAP_ADDR(nr) (PKMAP_BASE ((nr) PAGE_SHIFT)) /** * set_page_address - set a pages virtual address * page: struct page to set * virtual: virtual address to use */ void set_page_address(struct page *page, void *virtual) { unsigned long flags; struct page_address_slot *pas; struct page_address_map *pam; BUG_ON(!PageHighMem(page)); pas page_slot(page); /*槽位号*/ if (virtual) { /* Add */ pam page_address_maps[PKMAP_NR((unsigned long)virtual)]; pam-page page; pam-virtual virtual; spin_lock_irqsave(pas-lock, flags); list_add_tail(pam-list, pas-lh); /*将pam加入到pas-list中*/ spin_unlock_irqrestore(pas-lock, flags); } else { /* Remove */ spin_lock_irqsave(pas-lock, flags); list_for_each_entry(pam, pas-lh, list) { if (pam-page page) { list_del(pam-list); /*将pam从pas-list中删除*/ spin_unlock_irqrestore(pas-lock, flags); goto done; } } spin_unlock_irqrestore(pas-lock, flags); } done: return; }若页面属于高端内存(物理地址high_memory),kmap通过PKMAP区域(永久内核映射区)动态分配虚拟地址并建立映射:PKMAP区域:内核虚拟地址空间中预留的固定区域。映射过程检查页面是否已通过PKMAP映射:内核维护pkmap_count数组(跟踪每个PKMAP槽位的引用计数)和pkmap_page_table(页表项)若未映射,从PKMAP区域分配一个空闲虚拟地址槽位,更新页表建立物理页框与虚拟地址的映射若PKMAP区域已满(所有槽位被占用),kmap会阻塞当前进程,等待其他映射释放槽位(通过 schedule()主动让出 CPU)3、kunmap实现void kunmap(struct page *page) { BUG_ON(in_interrupt()); if (!PageHighMem(page)) /*非高端内存直接返回*/ return; kunmap_high(page); } /** * kunmap_high - unmap a highmem page into memory * page: struct page to unmap * * If ARCH_NEEDS_KMAP_HIGH_GET is not defined then this may be called * only from user context. */ void kunmap_high(struct page *page) { unsigned long vaddr; unsigned long nr; unsigned long flags; int need_wakeup; unsigned int color get_pkmap_color(page); wait_queue_head_t *pkmap_map_wait; lock_kmap_any(flags); /*虚拟地址*/ vaddr (unsigned long)page_address(page); BUG_ON(!vaddr); nr PKMAP_NR(vaddr); /* * A count must never go down to zero * without a TLB flush! */ need_wakeup 0; switch (--pkmap_count[nr]) { /*设置为1,由flush_all_zero_pkmaps来进行回收处理*/ case 0: BUG(); case 1: /* * Avoid an unnecessary wake_up() function call. * The common case is pkmap_count[] 1, but * no waiters. * The tasks queued in the wait-queue are guarded * by both the lock in the wait-queue-head and by * the kmap_lock. As the kmap_lock is held here, * no need for the wait-queue-heads lock. Simply * test if the queue is empty. */ /*存在释放时就将因为fkmap无可用虚拟地址而休眠的任务唤醒*/ pkmap_map_wait get_pkmap_wait_queue_head(color); need_wakeup waitqueue_active(pkmap_map_wait); } unlock_kmap_any(flags); /* do wake-up, if needed, race-free outside of the spin lock */ if (need_wakeup) wake_up(pkmap_map_wait); /*唤醒任务*/ }注意事项:必须配对使用kunmap:映射后需显式解除,否则会导致虚拟地址槽位泄漏(PKMAP区域耗尽后后续映射失败)写后标记脏页:若修改了文件映射的页面(如vma-vm_file非空),需调用set_page_dirty(page)或set_page_dirty_lock(page)标记脏页,确保数据回写磁盘避免长期映射:kmap映射的地址应尽快释放,尤其在高并发场景下,减少PKMAP区域竞争

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…