linux存储管理

本文详细介绍了Linux存储管理的硬件层次,包括Intel X86的虚拟存储器、分页和分段机制。在软件实现方面,阐述了Linux内核的三层映射机制以及地址映射过程。同时,探讨了Linux内核内存管理的基本数据结构,如pgd_t、pmd_t、pte_t、page和zone。最后,讨论了Linux存储管理的实现,包括页目录、页表和页表项的设置。

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

Linux存储管理

(一)Intel X86的存储管理(硬件层次)

(二)Linux存储管理基本框架(软件实现)

(三)Linux存储管理实现(实现中使用到的的数据结构)

(四)Linux存储管理实现(实现中使用到的函数)

 

(一)Intel X86的存储管理(硬件层次)

1、虚拟存储器

1)基本的思想:把地址空间与主存容量区分开,程序员在地址空间写程序,程序在真正的内存中运行。由一个专门的机制实现地址空间(虚拟地址)和内存(物理地址)之间的映射。

2)许多概念如进程、进程的上下文切换、存储器分配、虚拟地址空间、缺页处理都与虚拟存储器机制有关。

2、分页机制

1)进程和内存都被划分为固定的块。

2)无需用连续的叶框来存放一个进程,操作系统为每个进程生成一个页表。

4)通过页表实现逻辑地址到物理地址的转换。

5)无需将一个进程的全部装入主存,程序访问的局部性。

3、分段机制

1) 将程序模块和数据模块分配给不同的主存段,一个程序有多个代码段和多个数据段构成。

2段通常有段名和基地址.

3)分段系统将主存空间按实际程序中的段来划分,每个段在主存中的位置记录在段表中,并附以段长项。

4IA-32 x86体系采用段页式虚拟存储管理方式

1)在保护模式IA32采用段页式虚拟存储管理方式。

2)存储地址用逻辑地址(48位)、线性地址(32位)、物理地址(32位)进行描述.

逻辑地址----  》线性地址(分段来完成)------》物理地址(分页来完成)

3分段过程(48位转换成32  逻辑地址到线性地址)

Intel在原有的四个段寄存器的基础上,又添加了两个段寄存器,来存放GDT(全局描述符表)和LDT(局部描述符表),实质上就是一个索引,根据这个索引找到对应的段表中的某一个段表项(指向的是段表项中的段基址)获得段基址,段基址+有效地址即为线性地址。

4)分页过程

采用了多级页表方式,32位的线性地址的划分即为:

22-31    页目录索引

12-21      页表索引

0-11     页内偏移量

 

(二)Linux存储管理基本框架(软件实现)

1Linux为了考虑到不同的CPU上的实现,linux的内核映射机制设计成三层,在页目录和页表间设了一层中间目录。

页面目录PGD     中间目录 PMD    页表PT     页表项PTE

2地址映射的全过程(linux基于硬件的实现地址映射)

A、段式映射

Linux系统会简化硬件提供的分段过程,设置基地址均为0,操作系统会把CSDS这些段寄存器进行填充,包括这些寄存器对应的描述符cache,所以通过指令得到的虚拟地址就是偏移量即 线性地址

虚拟地址是链接的时候链接器给的,即线性地址,这个线性地址也是段的线性地址,因为段的基地址为0,所以链接器给出的指令的地址就是这个指令的有效地址,链接器给出的操作数的地址经过就算就是操作数的有效地址(线性地址)。

B、页式映射

概括的说就是将虚拟地址分段,使每段虚拟地址都作为一个索引指向页表,而页表则指向下一级别的页表或指向最终的物理页面。

1虚拟地址是链接的时候链接器给的,即线性地址,对照线性地址的格式,最高的10位对应的值去页目录中找到目录项,这个目录项的高20位指向一个页面表,CPU20位后边填上120就得到该页面表的指针,找到页面表之后通过线性地址的中间10位对应的值找到对应的表项,页面表项的p标志位为1表示所映射的页面在内存中了。32位页面表项的高20位指向一个物理内存页面,在后边填上120就得到物理内存页面的起始地址,起始地址加上线性地址的低12位,就得到了物理内存地址。这里有两次在得到的高20位后面填120,因为页面表和页占一个页面,都是4K字节对齐的,其起始地址的低12位一定全是0.

页面映射过程中要访存3次(页面目录  页面表  真正的目标),所以虚存的高效实现有赖于高速缓存(快表)的实现。

(三)Linux内核实现内存管理的基本数据结构

A、有关物理空间的数据结构

page是管理每一个物理页面的,zone是把一堆的物理页面进行分区的,pgd_t,pmd_t,pte_t分别代表页面目录、中间目录和页面表)

1pgd_t   pmd_t   pte_t(页目录、页表)

既然页式映射用到了页目录(PGD)和页表(PT)linux内核准备了pgd_tpmd_tpte_t构成的数组.

#if CONFIG_X86_PAE

typedef struct { unsigned long pte_low, pte_high; } pte_t;

typedef struct { unsigned long long pmd; } pmd_t;

typedef struct { unsigned long long pgd; } pgd_t;

#define pte_val(x) ((x).pte_low | ((unsigned long long)(x).pte_high << 32))

#else

typedef struct { unsigned long pte_low; } pte_t;

typedef struct { unsigned long pmd; } pmd_t;

 typedef struct { unsigned long pgd; } pgd_t;

#define pte_val(x) ((x).pte_low)

#endif

#define PTE_MASK  PAGE_MASK

 

PGD包含了一个pgd_t类型的数组,多数体系结构中pgd_t类型等同于无符号的长整型类型,PGD中的表项指向二级页目录表项PMD,他是一个 pmd_t类型的数组,其中的表项指向PTE的表项 是一个pte_t类型的数组,该表项指向物理页面。如图所示:

其中pte_t的高20位用来看做物理页面的序号,低12位用于页面的状态信息和访问权限,而这低12 位另外定义了一个页面保护结构pgprot_t来说明,这与MMU的页面表项的低12位相对应,其中9位是标志位。

 

2page(每个物理页)

内核用struct page结构表示系统中的每个物理页(和虚拟页无关),内核中有个全局变量mem_map,指向page数据结构数组,内核用page结构来管理系统中的所有的页,内核需要知道一个也是否空闲,是谁拥有这个页。

typedef struct page

{

struct list_head list;

struct address_space *mapping;

unsigned long index;

struct page *next_hash;

atomic_t count;

unsigned long flags;   

struct list_head lru;

unsigned long age;

wait_queue_head_t wait;

struct page **pprev_hash;

struct buffer_head * buffers;

void *virtual;

struct zone_struct *zone;

} mem_map_t;

 

flags :页的状态

count :页的引用计数,当为0 时内核并没有引用它,在新的分配中就可以使用它。一个页可以由页缓存使用(mapping域指向和这个页相关的address_sapce对象或者作为私有数据private指向)或者作为进程页表的映射。

virtual:页的虚拟地址

3zone

有些页位于内存的特定的物理地址,不能将其用于特定的任务,所以内核把页划分为不同的区(zone),内核使用区对相似特性的页进行分组。

1一些硬件只能用某些特定的内存地址来执行DMA(直接内存访问)       分配了ZONE_DMA

2一些体系结构其内存的物理寻址范围比虚拟寻址的范围大,这样有一些内存不能永久的映射到内核空间上     ZONE_HIGHMEM

3ZONE_NOMAL

4每个管理区有一个数据结构zone_struct,zone_struct结构中有一组空闲队列。因为常常成块的分配物理空间内的连续的多个页面。即维持一个连续长度为(24816)的页面块队列。

B、有关虚存空间的数据结构

虚拟空间是以进程为基础的,虚拟空间管理不像物理空间那样有一个总的物理仓库。如下图为虚存空空间的数据结构描述图

1、mm_struct(虚拟内存描述符  进程整个地址空间的抽象)

 

struct mm_struct {

//虚拟内存区域的构建的链表

struct vm_area_struct * mmap;        /* list of VMAs */

//虚拟内存区域的构建的树(可能是红黑树也可能是AVL树)

struct vm_area_struct * mmap_avl;     /* tree of VMAs */

//指向最近一次用到那个虚存结构

struct vm_area_struct * mmap_cache;    /* last find_vma result */

//pgd指向进程的页目录额,当内核调度一个进程的时候,就将这个指针转换成物理地址,并写入CR3寄存器中。

 pgd_t * pgd;

//计数器,一个mm_struct可能有多个进程共享,列如fork之后父子进程共享,

atomic_t mm_users; /* How many users with user space? */

//主引用计数只要mm_users不为0, mm_atomic_t mm_count; /* How many references to "struct mm_struct" (users count as 1) */

int map_count; /* number of VMAs */

//互斥,PV操作的信号量

struct  semaphore  mmap_sem;//自旋锁   检索和操作页表时必须使用锁,以防止竞争条件

spinlock_t page_table_lock;

//所有的mm_sturuct结构体通过自身的mmlist域连接在一个双向链表中,该链表的首元素是init_mm描述符,即init进程代表的地址空间

struct list_head mmlist; /* List of all active mm's */

 

unsigned long start_code, end_code, start_data, end_data;

unsigned long start_brk, brk, start_stack;

unsigned long arg_start, arg_end, env_start, env_end;

unsigned long rss, total_vm, locked_vm;

unsigned long def_flags;

unsigned long cpu_vm_mask;

unsigned long swap_cnt; /* number of pages to swap on next pass */

unsigned long swap_address;

 

/* Architecture-specific MM context */

mm_context_t context;

};

 

2、vm_area_struct(虚拟内存区域 VMA, 地址空间内连续区间上的一个独立虚拟内存范围)

struct vm_area_struct {

struct mm_struct * vm_mm;     

unsigned long vm_start;

 unsigned long vm_end;

 

//end和strat决定了一个虚存空间,划分取决于页面访问权限,如果一个地址范围内的前一半页面和后一半页面有不同的访问权限,则应分为两个区间。

 

//虚存区间的线性队列

struct vm_area_struct   *vm_next;

 

pgprot_t vm_page_prot;

unsigned long vm_flags;

 

/* AVL tree of VM areas per task, sorted by address */

//虚存区间建立的AVL树,提高搜索效率

 

short vm_avl_height;

struct vm_area_struct * vm_avl_left;

struct vm_area_struct * vm_avl_right;

 

///用于管理磁盘和虚存之间发生关系的结构(mmap函数文件和虚存进行映射和盘区交换)

struct vm_area_struct *vm_next_shae;

struct vm_area_struct **vm_pprev_share;

 

//指向vm_operations_struct数据结构的指针(里面全是函数指针),用于虚存的打开关闭和建立映射,页面出错时就要用到这里面函数

struct vm_operations_struct * vm_ops;

unsigned long vm_pgoff; /* offset in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */

struct file * vm_file;

unsigned long vm_raend;

void * vm_private_data; /* was vm_pte (shared mem) */

};

2VMA标志   unsigned long vm_flags;   

比较重要的VM_READ   VM__WRITE   VM_EXEC   

3VMA操作     struct  vm_operations_struct  * vm_ops; (页缺失调用的函数就在这里)

4)内存区域的树形结构(short vm_avl_height;   用于快速搜索)和链表表结构  struct vm_area_struct   *vm_next;    用于遍历)用了两套数据结构

 

(四)Linux存储管理实现(实现中使用到的函数)

所有进程使用一个页目录表,每个进程都拥有一个页表

linux调用这些函数,把上述提到的有关内存管理的数据结构联系起来,完成映射。

 

我们先从图上看一下这几类数据结构之间的联系

 

1、设置CR3(CR3:页目录基址寄存器   保存页目录的起始地址。)

1)一个系统只能有一个页面目录,每个进程都有其自身的页面目录项就是PGD中的其中一项pgd_t,存放在mm_struct中,每当调度一个进程的时候,内核都要为即将运行的进程设置好CR3,MMU的硬件会从CR3中取得指向当前页目录的指针。在该进程运行之前,CR3已经设置好了。MMU在进行映射是所用的是物理地址,既有下面的转换:

内核中切换进程时将CR3设置为指向新的进程的页目录PGD,而该目录的起始地址在内核代码中是虚地址,但CR3需要的是物理地址。

static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, struct task_struct *tsk,
unsigned cpu)
 { 

asm volatile("movl %0,%%cr3": :"r" (__pa(next->pgd))); 

} 

注:

#define __PAGE_OFFSET (0xC0000000) 

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

系统空间占据了每个虚存空间的最高的1G字节,但是在物理内存中却是从0地址开始。所以,对于内核来说其地址映射是很简单的线性映射,(0xC0000000) 就是两者之间的位移量。对于系统空间而言,给定一个虚地址x,其物理地址就是__pa(x) 内核代码中当需要知道与一个虚地址对应的物理地址时提供方便)

 

???通过PGD和虚拟地址的高10位找到某一个pgd_t是通过MMU来完成的吗

2、设置页面表项

每个页表项中的内容是随机的,是由物理页地址内容确定的,即由内存管理程序通过设置页表项来确定,

 

叶框地址指定了一页物理内存的起始地址,因为内存是4K字节对齐的,所以其12位用作标志位等

在上一节中讲到作为指针只需要它的高20位,低12位用于页面状态信息和页面的访问权限,但是这12位的信息作为页面保护定义到pgport_t结构中,将页面序号左移12位,再与页面控制/状况位段相或,就得到了相对应的表项的值。

linux中宏操作mk_pte()来完成。

#define __mk_pte(page_nr,pgprot) __pte(((page_nr) << PAGE_SHIFT) | pgprot_val(pgprot)) 

3、pte和mem_map结合找到对应的某个物理页面

pte_t高20位用来看做物理页面的序号。内核中有个全局变量mem_map,指向page数据结构数组,页面表项(pte_t)的高20位对于软件是一个物理页面的序号,将这个序号用作数组下标就可以从mem_map找到这个代表物理页面的page数据结构。(对于硬件,则在地位补上12个0后就是物理页面的起始地址)。对应的宏操作

#define pte_page(x) (mem_map+((unsigned long)(((x).pte_low >> PAGE_SHIFT))))&mem_map[x] 一样的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值