研究Linux内存管理过程中,走了不少弯路,内存管理在Linux内核中是很重要,且很复杂的一部分,这里记录我对Linux内存管理结构的了解。
首先对涉及内存的几个术语解释:
虚拟地址空间:
这是系统视图的地址空间,也是我们使用内存主要用到的地址空间
物理地址空间:
CPU视图的地址空间,MMU会将虚拟地址转换为物理地址来访问内存。
MMU:
内存管理单元,主要负责对内存管理(地址转换、访问控制等)
Linux内存映射原理:
首先我们只能使用虚拟地址,但访问内存只能通过物理地址,所以虚拟地址首先会通过MMU转换为物理地址,然后经过总线就可以寻址到内存了,核心是 虚拟地址转 物理地址。
Linux使用4级页表来转换物理地址,每级页表存储虚拟内存地址的部分偏移,这里可以理解为索引吧,以下为4级页表解释
PGD 全局页目录
PUD 上层页面目录
PMD 中间页目录
PTE 页表项(页表项即为物理地址了)
下图说明了一个虚拟地址,被拆分了以下部分,每个部分对应于页表的偏移,当我们访问内存时,就是根据这些信息来获取内存的物理地址的。
当我们在程序内部使用变量内容时,如下:
int main(int argc,char *argv[])
{
int *ptr=(int*)malloc(sizeof(unsigned int));
*ptr=1992;
while(1)
{
printf("pid %ld vaddr %ld value %d\n",getpid(),ptr,*ptr);
sleep(2);
}
return 0;
}
这里指针变量存储了该内存对应的虚拟地址,当我们访问时该内存是,首先根据当前进程的实例(task_struct->mm),获取到进程的pgd,然后依次获取 pud、pmd、pte。
虚拟地址和物理地址之间的关联即通过页表的方式进行映射。
在内核中我们可以通过编写内核驱动的方式来访问任意进程的内存(内核中没有任何限制),内核读写任意进程内存核心实现代码如下:
static int vaddrtopaddr(struct task_struct *p,unsigned long vaddr)
{
struct mm_struct *mm=p->mm;
pgd_t *pgd;
pud_t *pud;
pmd_t *pmd;
pte_t *pte;
unsigned long kks;
unsigned long paddr = 0;
unsigned long page_addr = 0;
unsigned long page_offset = 0;
pgd =pgd_offset(mm,vaddr);
if (pgd_none(*pgd)) {
printk("not mapped in pgd\n");
return -1;
}
pud = pud_offset((p4d_t*)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);
if (pmd_none(*pmd)) {
printk("not mapped in pmd\n");
return -1;
}
pte = pte_offset_kernel(pmd, 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; //内存的物理地址
unsigned long _pageaddr=pte_page(*pte); //拿到物理地址对应的内存结构实例 page,这是Linux的物理内存管理结构,每个page结构实例表示一块物理内存
//kmap要的是page结构的虚拟地址
unsigned long vptr=kmap(_pageaddr); //将该物理地址映射到内核的虚拟地址空间中,这样才能访问该物理内存。
if(vptr<=0)
{
printk("vptr is null");
}
else
{
printk("vptr 0x%lx vaddr value 0x%lx\n",vptr,vptr+page_offset);
unsigned int *vint=(unsigned int*)(vptr+page_offset);
printk("int val %d\n",*vint);
*vint=2021;
}
kunmap(page_addr);
return 1;
}
完整代码 Git