内存基础小知识(arm)

本文深入探讨了ARM处理器的内存管理机制,包括高速缓存控制、TLB工作原理、页表结构及其相关宏定义,如PAGE_SHIFT、PMD_SHIFT等。还详细介绍了页表项的访问权限设置和清理,以及TLB的刷新操作。此外,提到了高端内存映射(kmap/kunmap)和vmalloc/vfree等内核内存分配方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

 

 

处理器的cr0寄存器Pcd标志用来启用或者禁用高速缓存电路。

pcd标志是访问叶匡中的数据时高速缓存是否开启,pwt表示将数据写到叶匡的时候采用的回写策略是通写还是回写。

TLB用于缓存刚刚被访问的物理地址。每个cpu都有一个TLB。

常用宏功能:

PAGE_SHIFT:指定offset字段的位数,页框大小。

PMD_SHIFT指定线性地址的offset字段与table字段的总位数。32位当PAE被禁用的时候是22位offset(12)+table(10),PMD_MASK为0xffe00000。

PUD_SHIFT:页上级目录项所映射的区域大小的对数。32位的环境上由于是两级页表所以总是等于PMD_SHIF。

PGDIR_SHIFT:页全局目录项所能映射的区域大小的对数。PGDIR_MASK宏用于屏蔽offset、table、middle、upper字段的所有位。

PTRS_PER_PTE、OTRS_PED_PMD、PTRS_PER_PUD、PTRS_PER_PGD用于计算页表、页中间目录、页上级目录和页全局目录表中的表项个数。

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

__pte\__pmd\__pud\__pgd\__pgprot和pte_val\pmd_val\pud_val\pgd_val\pgprot_val是将相应表项和无符号整数相互转换。

pte_none\pmd_none\pud_none\pgd_none表示相应的表项值为0则返回1否则返回0。

pte_clear\pmd_clear\pud_clear\pgd_clear清除相应的表项,set_pte\set_pmd\set_pud\set_pgd向一页表中写入指定的值。

pte_same表示两个页表项指向相同的页且有相同的访问优先级则返回1否则返回0。

pmd_large(e)当e指向大型页则返回1。

pmd_bad用于检查传入的页中间目录参数,如果页中间所指向的页表满足下列之一返回1。

a页不存在(present标志被清除)b页只允许读访问(read\write标志被清除) c access或者 dirty标志被清除。pud_bad和pgd_bad总是返回0,没有定义pte_bad。

pte_user\pte_read读取user/supervisor标志

pte_write\pte_exec读取read/write标志

pte_dirty 读取dirty标志

pte_young 读取accessed标志

pte_file读取dirty标志

设置页标志函数:

mk_pte_huge设置页表中的page size和present标志

pte_wrprotect 清除read/write标志

pte_rdprotect/pte_exprotect 清除user/supervisor标志

pte_mkwrite 设置read/write标志

pte_mkread/pte_mkexec  设置user/supervisor标志

pte_mkclean 清除dirty标志

pte_mkdirty 设置dirty标志

pte_mkold/pte_mkyoung  清除/清除accessd标志

pte_modify(p,v) 把页表p所有访问权限设置为v

ptep_set_wrprotect 与pet_protect类似但是只是针对页表项。

petp_set_access_flags 如果dirty为1则将页的存取权限设置为指定的值,并且调用flush_tlb_page函数刷新tlb。

ptep_test_and_clear_dirty 只是针对页表项

petp_test_and_clear_young 只是针对页表项

页表操作宏:

pgd_index(addr) 找到现行地址addr对应的目录项在页全局目录中的索引。

pgd_offset(mm,addr)根据addr找到页全局目录内的相应页目录项的线性地址。

pgd_offset_k(addr) 产生内核页全局目录包含addr的目录项。

pgd_page(pgd) 产生页上级目录所在页框的页描述符地址。

pud_offset(pgd,addr) 页上级目录中目录项addr对应的线性地址

pud_page(pud) 通过页上级目录pud产生相应的页中间目录的线性地址。

pmd_index(addr) 产生线性地址addr在页中间目录的目录项的索引

pmd_page(pmd) 页中间目录项pmd产生的页表描述符地址。

mk_pte(p,prot) 接收页描述符地址p和存取权限prot作为参数

pte_index(addr) 产生线性地址addr对应的表项在页表中的索引

pte_offdset_kernel(dir,addr),dir是页中间目录,线性地址addr在页中间目录dir中的对应的项即页表的线性地址。

pte_offset_map(dir,addr)接收指向一个页中间目录项的指针dir和线性地址addr作为参数,产生与线性地址addr相对应的页表项的线性地址。

pte_page(x) 返回页表项x所引用的描述符地址

pte_to_pgoff(pte) 从一个页表项的pte字段中提取文件偏移量,这个文件偏移量对应着一个非线性文件内存映射所在的页。

pgoff_to_pte(offset)为非线性文件内存所映射的页创建对应的页表项的内容

pgd_alloc(mm) \pgd_free分配一个新的页全局目录

pud_alloc(mm,pgd,addr)/pud_free分配pud

pmd_alloc(mm,pud,addr)/pmd_free,为addr分配个新的页中间目录

pte_alloc_(mm,pmd,addr) 如果页中间目录为空则分配个新的页表,addr对应的项目被创建且user/supervisor标志被设置为1。如果页表在高端内存则建立一个临时映射

pte_alloc_kernel(mm,pmd,addr)分配一个新的页表然后返回与addr相关的线性地址。

pte_free(pte)释放与页描述符指针相关的页表

pte_free_kernel(pte) 内核主页表使用类似与pte_free

clear_page_range(mmu,start,end)从线性地址start到end通过反复释放页表和清除页中间目录项来清除进程页表内容。

set_fixmap(idx,phyaddr)将idx指定的页表设置为phyaddr指定的物理地址

set_fixmap_nocache(idx,phyaddr)类似set_fixmap 只是多设置了pcd标志表示不使用cache缓存。

L1_CACHE_BYTES 表示高速缓存行的L1的大小以字节为单位

flush_tlb_all   刷新所有的TLB表项。

flush_tlb_kernel_range刷新给定范围内的所有的TLB表

flush_tlb 刷新当前进程拥有的非全局页相关的所有TLB

flush_tlb_mm 刷新指定进程拥有的TLB非页全局相关的所有TLB

flush_tlb_range 刷新指定进程的线性地址间隔对应的TLB表项

flush_tlb_pgtables 刷新指定进程中特定的相邻页表集相关的TLB

flush_tlb_page 刷新指定进程中单个页表项相关的TLB表项

任何进程切换都会更新活动页表集,本地tlb必须刷新

_count 页的引用计数  page_count()函数返回_count+1后的值

flags  页框状态的标志

使用GFP_KERNEL的标志分配内存页面即使没有足够的页面也不会被阻塞,仅仅是分配失败而已,,一般情况下内核会保留一个页框池,在内存不足时供GFP_KERNEL使用,保留内存数量存放在min_free_kbytes(KB);

请求页框标志: 

 

组合标志:

 

 

page_address(mm/highmem.c):返回页框对应的线性地址

1 根据PG_highmem标志判断页框是否在高端地址,不在则直接根据页框下标转换成物理地址再转换成线性地址。

2 是高端地址,则到page_address_htable散列表查找,找到对应的页框则返回线性地址否则返回NULL;

永久映射:

高端内存映射函数(arm arch/arm/mm/highmem.c):

kamp

1 首先判断页框是否是高端地址通过PG_highmem标志。不是则通过page_address直接返回对应的线性地址。

2 是高端地址调用kmap_high

  1. 调用page_address检查是否已经被映射,没有则调用map_new_virtual
  2. 增加引用计数
  3. 如果刷新完所有的kamp_count依旧没有找到空闲的则进入睡眠阻塞,唤醒之后调用page_address查看是否别人已经映射了,如果没有则重新查找。

 map_new_virtual:

  1. 遍历pkmap_count查找一个空项,保存查找到的索引值
  2. 设置特殊页表pkmap_page_table的索引值对应的项,设置                    

pkmap_count表示已经使用。

对应的函数kunmap

  1 如果不是高端地址页框则直接返回,因为永远线性映射区不需要释放

2 调用 kunmap_high去释放

kunmap_high:

  1. 将pkmap_count对应的项减一
  2. 如果减一之后为1则判断是否存在睡眠等待的进程,存在则唤醒

struct vm_struct {

    struct vm_struct    *next;//指向下一个vm,链表第一个元素被vmlist变量指向

    void            *addr;//内存区的第一个内存单元线性地址

    unsigned long       size;//内存区大小+4096

    unsigned long       flags;//非连续内存区映射的内存类型

    struct page     **pages;//指向nr_pages数组指针,该数组由指向页描述符的指针组成

    unsigned int        nr_pages;//内存区填充的页面的个数

    phys_addr_t     phys_addr;//该字段为0,除非内存已经被创建来映射一个硬件设备的io共享内存

    const void      *caller;

};

flags:

VM_ALLOC表示vmalloc得到的页

VM_MAP 使用vmap映射的已经被分配的页

IO_IOREMAP 表示使用ioremap映射的硬件设备的板上内存

get_vm_area如果flags没有指定VM_NO_GUARD则添加vmalloc的间隙。

vmalloc函数的解析:

{

return __vmalloc_node_flags(size, NUMA_NO_NODE,GFP_KERNEL);

}

return __vmalloc_node(size, 1, flags, PAGE_KERNEL,node, __builtin_return_address(0));

  1    获取vm且设置va(首先查找全局free_vmap_cache红黑树找到开始地址最小的va,然后从此vm开始遍历查找两个va之间最小的且合适的间隙如果没有找到则在va末尾添加一个,然后将va信息填充到vm里)

2    调用__vmalloc_area_node去获取物理页面并且设置页表。

临时映射:可以用在中断或者可延迟函数内部不会阻塞上下文。注意是在kmap_atomic关抢占在__kunmap_atomic才会打开抢占。pagefault也一样。

kmap_atomic:建立临时映射

1 判断是否是高端地址,如果不是直接调用page_address返回线性地址

2 对于高端地址则设置pte页表为page构造的pte。

__kunmap_atomic:解除临时映射

    1 刷新dcache 。

2 清除对应的页表。

__zone_watermark_ok:检查水位是否符合指定的值

1 首先判断是否设置了ALLOC_HARDER或者ALLOC_OOM值,如果设置了则认为是alloc_harder申请。如果没有指定则将free_pages减去nr_reserved_highatomic,去掉高端保留内存

2 指定了则继续判断是否指定了ALLOC_OOM,如果指定了则设置min减半否则min减少1/4。

3 判断free_pages是否小于min+z->lowmem_reserve,如果小于则返回false

4 判断传入参数order为0 则立即返回真

5 从order开始遍历存在非空的则返回true,如果是alloc_harder,则MIGRATE_HIGHATOMIC不空则也返回true。

总结就是判断在mark和保留内存水位之上且存在一个大于等于order的连续内存。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值