30分钟看懂linux内核 - page介绍

news2024/9/28 1:25:15

【推荐阅读】

浅析linux内核网络协议栈--linux bridge

深入理解SR-IOV和IO虚拟化

了解Docker 依赖的linux内核技术

浅谈linux 内核网络 sk_buff 之克隆与复制

深入linux内核架构--进程&线程

内核中最初勾引我好奇心的还是内存管理方面,我们平时编写应用程序时,一个进程所能拥有的内存大小几乎可以趋近于物理内存最大值或是超越这个值,虽然知道内核做内存方面的映射然后向我们的用户空间呈现出所谓的虚拟内存,但还是对其中实现疑惑甚多,而且一些关于内存的名词也是有许多,什么虚拟地址,内核线性地址,内核逻辑地址,balablabla...

屁话不讲了,我们直接来看内核最底层是如何来管理物理内存的。

struct page {
    atomic_t _count;        /* Usage count, see below. */
    atomic_t _mapcount; /* Count of ptes mapped in mms,
                                    * to show when page is mapped
                                    * & limit reverse map searches.
                                    */
    union {
        struct {
        unsigned long private;      /* Mapping-private opaque data:
                         * usually used for buffer_heads
                         * if PagePrivate set; used for
                         * swp_entry_t if PageSwapCache;
                         * indicates order in the buddy
                         * system if PG_buddy is set.
                         */
        struct address_space *mapping;  /* If low bit clear, points to
                         * inode address_space, or NULL.
                         * If page mapped as anonymous
                         * memory, low bit is set, and
                         * it points to anon_vma object:
                         * see PAGE_MAPPING_ANON below.
                         */
        };
        struct kmem_cache *slab;    /* SLUB: Pointer to slab */
        struct page *first_page;    /* Compound tail pages */
    };
    struct list_head lru;       /* Pageout list, eg. active_list
                     * protected by zone->lru_lock !
                     */
};
【文章福利】小编推荐自己的Linux内核技术交流群: 【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份 价值699的内核资料包(含视频教程、电子书、实战项目及代码)

内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂

内核将物理内存划分为一个个 4K or 8K 大小的小块(物理页),而这一个个小块就对应着这个page结构,它是内核管理内存的最小单元

上面的结构体只贴出了部分数据域,其注释内核也写得很清楚了

需要说得是,这个page结构描述的是某片物理页,而不是它包含的数据

不管是内核还是我们用户空间,分配内存时,底层都逃不掉这一个个的page,所以这个page可以作为:

1. 页缓存使用(mapping域指向address_space对象)

这个东西主要是用来对磁盘数据进行缓存,我们平时监控服务器时,经常会用top/free看到cached参数,这个参数其实就是页缓存(page cache),一般如果这个值很大,就说明内核缓冲了许多文件,读IO就会较小

2. 作为私有数据(由private域指向)

可以是作为块冲区中所用,也可以用作swap,当是空闲的page时,那么会被伙伴系统使用。

3. 作为进程页表中的映射

映射到进程页表后,我们用户空间的malloc才能获得这块内存

先来看一下内核中和page相关的一些常量:

include/asm-x86/page.h

#define PAGE_SHIFT  12
#define PAGE_SIZE   (_AC(1,UL) << PAGE_SHIFT)
#define PAGE_MASK   (~(PAGE_SIZE-1))

可以看出一个page所对应的物理块的大小(PAGE_SIZE)是4096

arch/x86/kernel/e820.c

#ifdef CONFIG_X86_32
# ifdef CONFIG_X86_PAE
#  define MAX_ARCH_PFN      (1ULL<<(36-PAGE_SHIFT))
# else
#  define MAX_ARCH_PFN      (1ULL<<(32-PAGE_SHIFT))
# endif
#else /* CONFIG_X86_32 */
# define MAX_ARCH_PFN MAXMEM>>PAGE_SHIFT
#endif

内核会将所有struct page* 放到一个全局数组(mem_map)中,而内核中我们常会看到pfn,说得就是页帧号,也就是数组的index,这里的MAX_ARCH_PFN就是系统的最大页帧号,但这个只是理论上的最大值,在start_kernel()时,setup_arch()函数会通过e820_end_of_ram_pfn()函数来获得实际物理内存并返回最终的max_pfn,可以看下e820_end_of_ram_pfn的实现(其内部直接调用e820_end_pfn函数)

/*
* Find the highest page frame number we have available
*/
static unsigned long __init e820_end_pfn(unsigned long limit_pfn, unsigned type)
{
    int i;
    unsigned long last_pfn = 0;
    unsigned long max_arch_pfn = MAX_ARCH_PFN;

    for (i = 0; i < e820.nr_map; i++) {
        struct e820entry *ei = &e820.map[i];
        unsigned long start_pfn;
        unsigned long end_pfn;

        if (ei->type != type)
            continue;

        start_pfn = ei->addr >> PAGE_SHIFT;
        end_pfn = (ei->addr + ei->size) >> PAGE_SHIFT;

        if (start_pfn >= limit_pfn)
            continue;
        if (end_pfn > limit_pfn) {
            last_pfn = limit_pfn;
            break;
        }
        if (end_pfn > last_pfn)
            last_pfn = end_pfn;
    }

    if (last_pfn > max_arch_pfn)
        last_pfn = max_arch_pfn;

    printk(KERN_INFO "last_pfn = %#lx max_arch_pfn = %#lx\n",
             last_pfn, max_arch_pfn);
    return last_pfn;
}

从上面的宏定义还可以看到

在x86_32时,内核会看是否启用PAE,PAE会比没有PAE所拥有的page更多(也即是说能访问更多的物理内存),PAE是一种物理地址扩展技术,让你在32位的系统中能访问超越4G的空间,其技术实现还是通过局部地址的映射,这里不展开说

接着来看下page结构的相关宏/函数:

pfn_to_page/page_to_pfn - 这两个底层使用 __pfn_to_page/__page_to_pfn宏,它们的作用是struct page* 和 前面提到的pfn页帧号之间的转换,看下实现

__pfn_to_page:(mem_map + ((pfn) - ARCH_PFN_OFFSET))
__page_to_pfn:((unsigned long)((page) - mem_map) + ARCH_PFN_OFFSET)

就是简单地和mem_map进行加减操作(最后那个OFFSET可以无视,默认0),由于mem_map也是struct page*类型,所以相加减就能得到对应的pfn(数组index)和对应的struct page*,如图

#define phys_to_page(phys) (pfn_to_page(phys >> PAGE_SHIFT))
#define page_to_phys(page) (page_to_pfn(page) << PAGE_SHIFT)

这两个宏的功能分别是将struct page*和物理地址之间进行转换

例如page_to_phys, 通过page_to_pfn宏取得相应的pfn后,还记得PAGE_SHIFT吗,假设pfn是1,左移12位,就是4096,也就是第二个对应的物理页的位置,这样就取得了物理地址(虽然内核在虚拟地址中是在高地址的,但是在物理地址中是从0开始的,所以这里也是从0开始)

#define virt_to_page(kaddr) pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)
#define page_to_virt(page)  __va(page_to_pfn(page) << PAGE_SHIFT)

这两个宏的作用是在struct page*和内核逻辑/线性地址 之间做转换

这里要补几个概念性的问题 -

内核逻辑/线性地址:其实对于linux内核来说,这个地址等同于物理地址,只是它们之间有一个固定的偏移量,linux内核中常提到的逻辑地址和线性地址其实是同一个东西

内核虚拟地址:与上面的内核逻辑地址的区别在于,内核虚拟地址不一定是在硬件物理上是连续的,有可能是通过分页映射的不连续的物理地址

这里的virt指得就是逻辑/线性地址,而不是真正的virtual地址

继续看__pa和__va宏

#define __pa(x)         ((unsigned long) (x) - PAGE_OFFSET)
#define __va(x)         ((void *)((unsigned long) (x) + PAGE_OFFSET))

可以看到它们只是做了一个偏移量(PAGE_OFFSET),在x86_32中,这个PAGE_OFFSET是0xC0000000,为什么是这个值呢,因为32位系统中,内核的虚拟地址只有1G,这个之后具体讲内存布局的时候再讨论

还有一个常用的宏/函数是page_address,它特殊的地方在于,以上的那些宏针对的或是返回的都是内核逻辑地址,也就是说是做简单的偏移加减,但是在32位系统中有个high_mem的概念 - 高端内存,它的作用让内核如何访问超出32位范围的内存,方法就是利用某一小块固定的内存做映射(这里的HighMem我个人认为就是前面提到的PAE技术的一种实现,以后讨论)

所以一个page对应的虚拟地址,有可能是直接做物理偏移的地址(也就是以上几个宏可以直接应用的),还有就是被高端内存映射的

针对后者,以上的几个宏是无法得到page的虚拟地址的,只有应用到page_address函数

我们看下page_address的实现:

void *page_address(struct page *page)
{
    unsigned long flags;
    void *ret;
    struct page_address_slot *pas;

    if (!PageHighMem(page))
        return lowmem_page_address(page);

    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;
}

标红的地方会判断page是否是HighMem,如果不是,直接调用lowmem_page_address,这个函数内部实现就是page_to_virt,所以就是简单地做偏移了,关于HighMem的映射之后再讨论了

以上就是内核常用的几个page转换的宏/函数,最后咱们简单看下page的分配接口(释放的我懒得一一匹配写了)

返回page结构的:

struct page * alloc_pages(gfp_mask, order) // 分配 1<<order 个连续的物理页

struct page * alloc_page(gfp_mask) // 分配一个物理页

返回page对应的逻辑地址的:

__get_free_pages(gfp_mask, order) // 和alloc_pages一样,只不过返回的是第一个页的内核逻辑地址

__get_free_page(gfp_mask) // 返回一个页的逻辑地址

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

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

相关文章

代码随想录训练营第53天|LeetCode 1143.最长公共子序列、1035.不相交的线、53. 最大子序和

参考 代码随想录 题目一&#xff1a;LeetCode 1143.最长公共子序列 确定dp数组下标及其含义 dp[i][j]&#xff1a;字符串text1中的0&#xff5e;i字符构成的字符串和字符串text2中的0&#xff5e;j字符构成的字符串的最长 公共子序列 的长度为dp[i][j]。注意和之前做过的Leet…

将postgresql配置为Linux服务管理 systemctl service

发行版安装的数据库在装完后会自动添加数据库服务&#xff0c;但编译安装的不会&#xff0c;需要手动添加。 cd postgresql-14.0/contrib/start-scripts/ cp linux /etc/init.d/postgresql cd /etc/init.d/ vi postgresql 修改以下部分 ## EDIT FROM HERE# Installation pre…

Vuex4.0.0 源码解析

本文章基于以下版本撰写 VUE 版本&#xff1a; 3.0VUEX 版本&#xff1a;4.0.0Vuex仓库&#xff1a;https://github.com/vuejs/vuex/tree/v4.0.0Vux文档&#xff1a;https://vuex.vuejs.org/zh/ 在 vue 中使用 vuex import { createApp } from vue import { createStore } f…

C语言中函数的先后关系,java和C语言的语法区别

以上代码中&#xff0c;sum函数在上面&#xff0c;main函数在下面&#xff0c;在main函数中就可以调用sum函数。 如果sum函数定义在main函数后面&#xff0c;则需要在main函数前面声明sum函数。即在main函数前面写&#xff1a;void sum(int begin, int end); C语言中&#xf…

EMC测试中的电流探头

专栏 专注介绍电磁兼容&#xff08;EMC&#xff09;的相关知识&#xff0c;算是对本人浸染 EMC 专业十余年的一个阶段小结 。 本文目录1.EMC测试中的电流探头是什么&#xff1f;2.电流探头的主要指标要求3.什么是转移阻抗&#xff1f;1.EMC测试中的电流探头是什么&#xff1f; …

校园兼职网站

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 网站前台&#xff1a;关于我们、联系我们、资讯信息、企业信息、职位信息 管理员模块&#xff1a; 1、管理关于我们、联…

vTESTstudio入门到精通 - vTESTstudio工具栏介绍_File

vTESTstudio作为跟CANoe工具并套使用的一个强大的工具&#xff0c;那么想要用好它就得对他有个全面的认识&#xff0c;第一步起码要知道tade工具栏都包含哪些功能要清楚&#xff0c;今天我们就着重分享下vTESTstudio工具栏包含哪些内容&#xff0c;以便在我们编程的时候更好的去…

java 瑞吉外卖day2 笔记 员工增加 员工信息分页查询

PostMapping public R save(HttpServletRequest request,RequestBody Employee employee){log.info("新增员工&#xff1a;{}",employee);//设置初始密码123456 需要进行md5加密employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));em…

homeassistant 接入小米温湿度计2

方法可能有很多种&#xff0c;但是我只用这种方法介入成功了 环境&#xff1a; - 香橙派&#xff08;自带蓝牙&#xff0c;树莓派应该也可以&#xff09; - 小米温湿度计2 - 网站&#xff1a;Telink Flasher v4.7 、 Telink Flasher (atc1441.github.io) - 固件&#xff1…

基于java+springmvc+mybatis+vue+mysql的高校普法系统

项目介绍 随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化,高校普法系统也不例外&#xff0c;但目前国内的市场仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;同时信息量也越来越庞大&#xff0c;人工管理显然已无法应对时代的变化&#xff0c;而…

堆排序快速排序插入排序

堆排序 数据结构使用的是1 dimension的数组来作为二叉树的堆结构&#xff0c;所以并没有使用结构体&#xff0c;而是直接使用了数组 而且堆是完全二叉树&#xff0c;也就是除了最后一层以外&#xff0c;其他层都是满二叉树&#xff0c;最后一层可能不满&#xff0c;所以1dime…

基于jeecgboot的flowable流程支持服务任务的功能

1、描述 Java服务任务用于调用外部Java类。 2、图形表示法 服务任务可视化为圆角矩形&#xff0c;左上角有一个小齿轮图标&#xff0c;如下图&#xff1a; 3、XML表示 有三种方法声明如何调用Java逻辑&#xff0c;下面分别介绍&#xff1a; 调用固定的类 使用flowable:cla…

Flask打开调试模式

调试模式 Flask支持本地开发&#xff0c;但是每次修改代码后需要手动重新启动程序&#xff0c;这不是很友好&#xff0c;事实上Flask支持调试模式来做到更好&#xff0c;Flask服务会在代码更改时自动重新加载&#xff0c;如果出现问题&#xff0c;还能提供调试器以供调试 如何…

docker登陆MySQL提示密码错误,Navicat也连接不上

问题原因 今天在操作自己云服务器MySQL数据库的时候发现使用root进行远程登录的时候被拒绝了&#xff0c;一直提示Access denied for user ‘root‘‘localhost‘ (using password: YES)。 可以确认自己账号密码是没有错的。后来以为是远程访问出问题了&#xff0c;进入到容器…

JavaSE基础篇:泛型说明

本文整理自B站&#xff1a;JavaSE强化教程泛型&#xff0c;由点到面的讲解了整个泛型体系第一章&#xff1a;泛型概述一&#xff1a;泛型概念二&#xff1a;泛型类1&#xff1a;泛型类使用2&#xff1a;泛型类派生子类1)&#xff1a;子类也是泛型类2)&#xff1a;子类不是泛型类…

SQL进阶笔记

SQL进阶笔记 CASE表达式 简单case表达式 case sexwhen 1 then 男when 2 then 女else 其他 end搜索case表达式 case when sex 1 then 男when sex 2 then 女else 其他 end编写SQL语句的时候需要注意&#xff0c;在发现为真的WHEN子句时&#xff0c;CASE表达式的真假值判断就会中…

Java并发编程(一)—— FutureTask超详细教程

一、前言 创建线程有几种方式?这个问题的答案应该是可以脱口而出的吧: 继承 Thread 类实现 Runnable 接口但这两种方式创建的线程是属于三无产品: 没有参数没有返回值没办法抛出异常用着三无产品总是有一些弊端,其中没办法拿到返回值是最让人不能忍的,于是 Callable 就诞…

TeamsApp LukcyDraw升级之路 之 DB Infra 篇

今天继续 LuckyDraw 的升级之路&#xff0c;之前在1500以上用户同时使用的时候&#xff0c;特别是在短时间内大家一起点击参与抽奖参的时候&#xff0c;服务会出现大量的错误&#xff0c;分析后发现&#xff0c;出现错误的原因基本都是 Azure Storage Table 返回的。当时使用 A…

【GRU回归预测】基于matlab卷积神经网络结合门控循环单元CNN-GRU数据预测(多输入单输出)【含Matlab期源码 2274期】

⛄一、CNN-GRU数据预测 1 理论基础 1.1 CNN算法 负荷序列数据为一维数据&#xff0c;用一维卷积核对数据进行卷积处理&#xff0c;以获取数据的特征。 现设定卷积核的维度为3&#xff0c;移动步长为1&#xff0c;对输入数据进行卷积&#xff0c;以获得特征图图谱&#xff0c;即…

记录那不用百度的美好代码

努力工作&#xff0c;日益消瘦&#xff0c;总有些代码不想记住。我称之为&#xff1a;拿来即用系列 一&#xff1a;格式化时间 1.去除时间的T // 去除日期中T function timeFormatSeconds(time) {if(!time) return time;var date time.substr(0, 10); //年月日var hours ti…