Linux内存地址管理

文章目录

  • 系统内存布局
    • 内核地址的低端和高端内存概念
      • 低端内存
      • 高端内存
  • 地址转换和MMU
    • Linux中的四级分页模型
      • 虚拟地址字段
      • 页表处理
  • 将虚拟地址转换物理地址

Linux系统中的每个内存地址都是虚拟的,它们不直接指向任何物理内存地址。每当访问内存位置时,可以执行转换机制以匹配相应的物理内存,所以我们在程序中必须用虚拟地址来访问数据。

注:下面所说的内核空间和用户空间这样的术语指的都是虚拟地址空间

系统内存布局

在Linux系统中,每个进程都有自己独立的虚拟地址空间。它是一种内存沙箱,存在于进程的生命周期中。在32位系统上,该地址空间大小是4GB。针对每一个进程,4GB的地址空间被分割成两个部分:

  • 内核空间虚拟地址
  • 用户空间虚拟地址

分割方式依赖于特殊的内核配置选项CONFIG_PAGE_OFFSET,这个选项定义内核虚拟地址部分在进程地址空间的起始位置。典型的进程虚拟地址空间布局如下图:
在这里插入图片描述
内核空间和用户空间所使用的地址都是虚拟地址,不同的是,访问内核空间地址需要特权模式,当CPU运行用户空间代码时,活动进程被认为运行在用户模式下,当CPU运行内核空间代码时,活动进程被认为运行在内核模式下。

内核与每个进程共享其地址空间,原因如下:因为每个进程在给定的时刻都使用系统调用,这将涉及内核。将内核的虚拟内存地址映射到每个进程的虚拟地址空间能够避免每次进入(或者退出)内核时内存地址切换产生的开销。这就是内核地址空间被永久映射到每个进程顶部的原因—加快系统调用对内核的访问。

每个进程地址空间顶部都是内核的虚拟地址空间,这一部分每个进程都是相同的。

内存管理单元把内存组织为大小固定的单元—页面,内存页(虚拟页)指的是连续虚拟内存块,内核数据结构也使用相同的名称页面来表示内存页。帧(页面帧)指一段固定长度的连续物理内存块,操作系统在其上映射内存页。每个页面帧都有一个号码,叫做页面帧号(PFN)。

内核地址的低端和高端内存概念

Linux内核具有自己的虚拟地址空间。比如32位的x86,内核的虚拟地址空间是1GB大小,分成两个部分:

  • 低端内存或LOWMEM:第一个896MB
  • 高端内存或HIGHMEM:顶部的128MB

在这里插入图片描述

低端内存

内核地址空间的第一个896MB空间构成低端内存区域。在启动早期,内核永久映射这896MB的空间。该映射产生的地址为逻辑地址,这些都是虚拟地址,但是减去固定的偏移量后就可以将其转换为物理地址。因为映射是永久的,并且事先知道。大多数内核内存函数返回低端内存。事实上,为了满足不同的用途,内核内存被划分为区域,LOWMEM的第一个16MB内存保留为DMA使用。内核空间可以确定3种不同的内存区域:

  • ZONE_DMA:包含的内存页面帧在0~16MB,用于直接内存访问(DMA)
  • ZONE_NORMAL:包含的内存页面帧为16MB~896MB,常规使用
  • ZONE_HIGHMEM:包含的内存页面帧位于896MB及其以上

这就是说,512MB的系统上,不存在以上的划分。

逻辑地址的另一个定义:线性映射到物理地址上的内核空间中的地址,可以用偏移量或者应用位掩码将其转为物理地址,使用__pa(地址)宏可以将逻辑地址(内核中的虚拟地址)转换为物理地址,使用__va(地址)可以做相反的操作。

高端内存

内核地址空间顶部顶部128MB称为高端内存区域,内核用它临时映射1GB以上的物理内存,当需要访问896MB以上的物理内存时,内核会使用这128MB创建到其虚拟地址空间的临时映射,也就是将需要访问数据的物理页映射到这128MB内核虚拟地址空间来,从而实现访问所有物理页面的目标。可以把高端内存定义为逻辑地址存在的内存,但不会将其永久映射到内核地址空间。896MB以上的物理内存按需映射到HIGHMEM区域的128MB。

访问高端内存的映射由内核动态创建,访问后销毁,这使高内存访问速度变慢,64位系统上不存在高端内存这一概念。

地址转换和MMU

每次访问内存位置时,由CPU完成从虚拟地址到物理地址的转换。该机制称为地址转换,这由CPU中的内存管理单元(MMU)来执行。MMU转换的都是虚拟地址,所以访问数据,必须是虚拟地址,不能是物理地址,否则访问不了数据。

对于虚拟内存,内存组织为固定大小的页,而物理内存则按帧组织,页面表(PTE)概念的引入是为了管理页面和帧之间的映射。页面分部在表间,因此每个PTE的表项对于一个页面和帧之间的映射,然后给每个进程一组页面表来描述其整个内存空间。

Linux中的四级分页模型

Linux采用了一种同时适用于32位和64位系统的普通分页模型。从2.6.11版本开始,采用了四级分页模型:请添加图片描述
上图展示的4种页表分别被称为:

  • 页全局目录(Page Global Directory,PGD)
  • 页上级目录(Page Upper Directory,PUD)
  • 页中间目录(Page Middle Directory,PMD)
  • 页表(Page Table,PTE)

虚拟地址被分为5个部分,每个页表项指向一个页框,每一部分的大小与具体的计算机体系结构有关。

MMU如何知道进程页面表?很简单,MMU不存储任何地址。但CPU有一个特殊的寄存器,称为页面表基址寄存器(PTBR)或转换基址寄存器0(TTBR0),它指向进程1级页面表(PGD)的基址。这正是struct mm_struct的字段pgd指向的地址:current->mm.pgd == TTBR0.上面图中的cr3保存的就是该值

虚拟地址字段

下面宏简化了页表处理:

  • PAGE_SHIFT:指定Offset字段的位数,这个宏由PAGE_SIZE使用返回页的大小。最后,PAGE_MASK宏用以屏蔽Offset字段的所有位。
  • PMD_SHIFT:指定虚拟地址的offset字段和table字段的总位数,PMD_MASK宏用于屏蔽Offset字段与Table字段的所有位
  • PUD_SHIFT:确定页上级目录项能映射的区域大小的对数,PUD_MASK宏用于屏蔽Offset字段,Table字段、Middle Air字段和Upper Air字段的所有位
  • PGDIR_SHIFT:确定页全局目录项能映射的区域大小的对数。PGDIR_MASK宏用于屏蔽Offset、Table、Middle Air及Upper Air字段的所有位
  • PTRS_PER_PTE,PTRS_PER_PMD,PTRS_PER_PUS以及PTRS_PER_PGD:用于计算页表、页中间目录、页上级目录和页全局目录表中表现的个数。

页表处理

pte_t、pmd_t、pud_t和pgd_t分别描述页表项、页中间目录项、页上级目录项和页全局目录项。

五个类型转换宏(__pte,__pmd,__pgd和__pgprot)把一个无符号整数转换成所需的类型。另外的五个类型转换宏(pte_val,pmd_val,pud_val,pgd_val和pgport_val)执行相反的转换。

如果相应的表项值位0,那么,宏pte_none、pmd_none、pud_none、和pgd_none产生的值为1,否则产生的值为0

将虚拟地址转换物理地址

进程访问的都是用户空间内虚拟地址,内核访问的都是内核虚拟地址,进程用不了内核虚拟地址,同样内核用不了进程内虚拟地址。如果我们将用户空间地址传给内核,内核必须先将找到该用户空间虚拟地址对应的物理地址,再将物理地址转换成内核虚拟地址,内核才能访问数据

代码地址:http://edsionte.com/techblog/archives/1966

static void get_pgtable_macro(void)
{printk("PAGE_OFFSET = 0x%lx\n", PAGE_OFFSET);printk("PGDIR_SHIFT = %d\n", PGDIR_SHIFT);printk("PUD_SHIFT = %d\n", PUD_SHIFT);printk("PMD_SHIFT = %d\n", PMD_SHIFT);printk("PAGE_SHIFT = %d\n", PAGE_SHIFT);printk("PTRS_PER_PGD = %d\n", PTRS_PER_PGD);printk("PTRS_PER_PUD = %d\n", PTRS_PER_PUD);printk("PTRS_PER_PMD = %d\n", PTRS_PER_PMD);printk("PTRS_PER_PTE = %d\n", PTRS_PER_PTE);printk("PAGE_MASK = 0x%lx\n", PAGE_MASK);
}
static unsigned long vaddr2paddr(unsigned long vaddr)
{pgd_t *pgd;pud_t *pud;pmd_t *pmd;pte_t *pte;unsigned long paddr = 0;unsigned long page_addr = 0;unsigned long page_offset = 0;pgd = pgd_offset(current->mm, vaddr);printk("pgd_val = 0x%lx\n", pgd_val(*pgd));printk("pgd_index = %lu\n", pgd_index(vaddr));if (pgd_none(*pgd)) {printk("not mapped in pgd\n");return -1;}pud = pud_offset(pgd, vaddr);printk("pud_val = 0x%lx\n", pud_val(*pud));if (pud_none(*pud)) {printk("not mapped in pud\n");return -1;}pmd = pmd_offset(pud, vaddr);printk("pmd_val = 0x%lx\n", pmd_val(*pmd));printk("pmd_index = %lu\n", pmd_index(vaddr));if (pmd_none(*pmd)) {printk("not mapped in pmd\n");return -1;}pte = pte_offset_kernel(pmd, vaddr);printk("pte_val = 0x%lx\n", pte_val(*pte));printk("pte_index = %lu\n", pte_index(vaddr));if (pte_none(*pte)) {printk("not mapped in pte\n");return -1;}//页框物理地址机制 | 偏移量page_addr = pte_val(*pte) & PAGE_MASK;page_offset = vaddr & ~PAGE_MASK;paddr = page_addr | page_offset;printk("page_addr = %lx, page_offset = %lx\n", page_addr, page_offset);printk("vaddr = %lx, paddr = %lx\n", vaddr, paddr);return paddr;
}
static int __init v2p_init(void)
{unsigned long vaddr = 0;printk("vaddr to paddr module is running..\n");get_pgtable_macro();printk("\n");vaddr = (unsigned long)vmalloc(1000 * sizeof(char));if (vaddr == 0) {printk("vmalloc failed..\n");return 0;}printk("vmalloc_vaddr=0x%lx\n", vaddr);vaddr2paddr(vaddr);printk("\n\n");vaddr = __get_free_page(GFP_KERNEL);if (vaddr == 0) {printk("__get_free_page failed..\n");return 0;}printk("get_page_vaddr=0x%lx\n", vaddr);vaddr2paddr(vaddr);return 0;
}
static void __exit v2p_exit(void)
{printk("vaddr to paddr module is leaving..\n");vfree((void *)vaddr);free_page(vaddr);
}

整个程序的结构如下:

  1. get_pgtable_macro()打印当前系统分页机制中的一些宏。

  2. 通过vmalloc()在内核空间中分配内存,调用vaddr2paddr()将虚拟地址转化成物理地址。

  3. 通过__get_free_pages()在内核空间中分配页框,调用vaddr2paddr()将虚拟地址转化成物理地址。

  4. 分别通过vfree()和free_page()释放申请的内存空间。

vaddr2paddr()的执行过程如下:

  1. 通过pgd_offset计算页全局目录项的线性地址pgd,传入的参数为内存描述符mm和线性地址vaddr。接着打印pgd所指的页全局目录项。

  2. 通过pud_offset计算页上级目录项的线性地址pud,传入的参数为页全局目录项的线性地址pgd和线性地址vaddr。接着打印pud所指的页上级目录项。

  3. 通过pmd_offset计算页中间目录项的线性地址pmd,传入的参数为页上级目录项的线性地址pud和线性地址vaddr。接着打印pmd所指的页中间目录项。

  4. 通过pte_offset_kernel计算页表项的线性地址pte,传入的参数为页中间目录项的线性地址pmd和线性地址vaddr。接着打印pte所指的页表项。

  5. pte_val(*pte)先取出页表项,与PAGE_MASK相与的结果是得到要访问页的物理地址;vaddr&~PAGE_MASK用来得到线性地址offset字段;两者或运算得到最终的物理地址。

  6. 打印物理地址。

我们可以获得物理地址了,就可以使用__pa(地址)宏可以将物理地址转换为逻辑地址(内核中的虚拟地址),使用__va(地址)可以做相反的操作

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

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

相关文章

录制caf 转 mp3

编译需要使用的 lame库http://www.cocoachina.com/bbs/read.php?tid108237参考的文章http://blog.csdn.net/ysy441088327/article/details/7392842说起来,我一直在找一个音频转换成mp3的方法。一年前,我成功编译出了一个lame for armv7的库。苦于不会使…

杭电2012-素数判定(C)

Problem Description 对于表达式n^2n41&#xff0c;当n在&#xff08;x,y&#xff09;范围内取整数值时&#xff08;包括x,y&#xff09;(-39<x<y<50)&#xff0c;判定该表达式的值是否都为素数。 Input 输入数据有多组&#xff0c;每组占一行&#xff0c;由两个整数…

math.ceil带小数点_JavaScript中带有示例的Math.ceil()方法

math.ceil带小数点JavaScript | Math.ceil()方法 (JavaScript | Math.ceil() Method) Math.ceil() is a function in math library of JavaScript that is used to round up the number passed to the function. The method will return the nearest integer value indeed is g…

開發記要 詭異的變量

告別繁體文盲,從寫blog開始 Variable命名很重要,有多重要,看看.net和java的加密就知道, 都是把variable改到一塌糊塗,你想看看都沒門. 但是這幾天看遺留系統的代碼,真是大開眼界。 我一直以為別人寫a,b,c,d這些單字節variable已經很過分。直到我看到以下這幾個&#xff0…

排序算法---快速排序、堆排序、冒泡排序

排序算法1 快速排序代码实现stdlib库快排2 堆排序堆排序的基本思想如何构造一个大顶堆排序3 冒泡排序1 快速排序 文章原地址&#xff1a;https://blog.csdn.net/morewindows/article/details/6684558 快速排序的平均时间复杂度是0(NlogN)&#xff0c;它采用了一种分治的策略&a…

CSS Hack 汇总快查

*:lang(zh) select {font:12px !important;} /*FF的专用*/ select:empty {font:12px !important;} /*safari可见*/ 这里select是选择符&#xff0c;根据情况更换。第二句是MAC上safari浏览器独有的。 仅IE7识别 *html {…} 当面临需要只针对IE7做样式的时候就可以采用这个HACK…

杭电2013-蟠桃记(C++)

Problem Description 喜欢西游记的同学肯定都知道悟空偷吃蟠桃的故事&#xff0c;你们一定都觉得这猴子太闹腾了&#xff0c;其实你们是有所不知&#xff1a;悟空是在研究一个数学问题&#xff01; 什么问题&#xff1f;他研究的问题是蟠桃一共有多少个&#xff01; 不过&#…

c#中重载单目运算符-_C#程序重载二进制运算符(-,*,/)

c#中重载单目运算符-Here, we will design overloaded methods for binary operators: minus, multiply and divide. In the below program, we will create a Calculator class with data member val. 在这里&#xff0c;我们将为二进制运算符设计重载方法&#xff1a;减&…

项目总结:华南师范大学校园开发教育android客户端总结

忽略之前小打小闹&#xff0c;这个项目算是我的第一个项目--SCNU的网络公选课的android版本的客户端。项目是从5月中旬开始的&#xff0c;中间经历了几个星期的复习考试时间&#xff0c;到现在可以说是完工了吧&#xff08;或许还有写细节要修改&#xff09;。这个项目带给我蛮…

火鸟字幕合并器

火鸟字幕合并器-区块独立勾选-保存。汉王 PDF OCR转载于:https://www.cnblogs.com/hnytwn/archive/2009/10/31/1593395.html

Linux系统编程---守护进程

1 守护进程的概述 Daemon&#xff08;守护进程&#xff09;是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务&#xff0c;不是对整个系统就是对某个用户程序提供服务。Linux系统的大…

c ++明明的随机数_从列表C ++程序中随机建议电影

c 明明的随机数Problem statement: 问题陈述&#xff1a; Write an application code that will suggest movies from a list randomly and there wont be any repeat while suggesting the movies. That means the same movie wont be suggested twice though it will be don…

邮箱服务器

一&#xff0e;邮箱服务器的基本概念 邮件的客户端&#xff1a;可以只安装在电脑上&#xff08;C/S&#xff09;的也可以是网页形式&#xff08;B/S&#xff09;的 邮件服务器&#xff1a;起到邮件的接受与推送的作用 邮件发送的协议&#xff1a; 协议&#xff1a;就是数据传输…

C#提高保存jpg图像的质量

在程序中直接生成的jpg图像&#xff0c;汉字有毛边&#xff0c;经过一番搜索&#xff0c;在msdn上发现了下面控制jpg质量系数的文章&#xff0c;修改后试了一下&#xff0c;效果确实比前面强多了。原理我也不大懂&#xff0c;把代码贴出来&#xff0c;与大家共享。 联合图…

延迟和定时器管理

文章目录1 内核中时间概念2 标准定时器jiffies和HZ定时器API标准定时器案例3 高精度定时器(HRT)高精度定时器案例4 内核中延迟和睡眠原子上下文非原子上下文1 内核中时间概念 时间概念对计算机来说有些模糊&#xff0c;事实上内核必须在硬件的帮助下才能计算和管理时间。硬件为…

Web开发工具(插件)收集

1.IE Developer Toolbar 浏览和修改&#xff0c;选定Web页上的特定元素&#xff0c;查看HTML对象的类名、ID&#xff0c;以及类似链接路径、tab顺序、快捷键等。 2.HttpWatch Professional 一款强大的网页数据分析工具,可以查看当前网页的http数据 FireFox插件 FireFox下插件实…

cin、cin.get()、cin.getline()、getline()、gets()等函数的用法

转载&#xff0c;并经过本人补充cin、cin.get()、cin.getline()、getline()、gets()等函数的用法2007/10/27 22:51学C的时候&#xff0c;这几个输入函数弄的有点迷糊&#xff1b;这里做个小结&#xff0c;为了自己复习&#xff0c;也希望对后来者能有所帮助&#xff0c;如果有差…

Java StringBuilder subSequence()方法与示例

StringBuilder类subSequence()方法 (StringBuilder Class subSequence() method) subSequence() method is available in java.lang package. subSequence()方法在java.lang包中可用。 subSequence() method is used to return the new set of a character sequence that is a …

Linux设备驱动开发---设备树的概念

文章目录1 设备树机制命名约定别名、标签和phandleDT编译器2 表示和寻址设备SPI和I2C寻址平台设备寻址3 处理资源提取特定应用数据文本字符串单元格和无符号的32位整数布尔提取并分析子节点4 平台驱动程序与DTOF匹配风格处理非设备树平台平台数据与DT设备树&#xff08;DT&…

【转】C#中数组复制的4种方法

C#中数组复制的4种方法 from&#xff1a;http://blog.csdn.net/burningcpu/article/details/1434167今天旁边的同事MM叫我调了一段程序&#xff0c;她想复制一个数组&#xff0c;int[] pins {9,3,4,9};int [] alias pins;这里出了错误&#xff0c;也是错误的根源&#xff0c…