学内核之十七:基础的重要性怎么强调都不过分

继续学内核之旅。

作为具有十多年开发经验的猿,自认为C的基础还是可以的。什么指针和数组,自认为分的还是比较清楚的。就这样,最近因为一个基础问题,差点把自己给搞懵了。

事情是这样的。当我们拿到一个内核里的虚拟地址时,在没有其他辅助信息的情况下,如果要想修改该虚拟地址所在页面的读写属性,就需要遍历页表相关结构,获取PTE的内容。首先获取mm_struct结构的指针,获取其中的pgd地址,然后基于该pgd地址,调用内核接口,一级一级获取pud、 pmd、 pte的地址,完成页表的遍历。

这里需要注意一点,软件遍历页表,拿到的pgd地址是虚拟地址。pgd虚拟地址里,根据索引计算的下一级页表的地址则是物理地址(因为这个地址要跟MMU共享),而软件无法直接使用物理地址进行操作,如何拿到下一级页表的虚拟地址呢?这里就需要用到一个前置条件:内核映射是线性映射。可以通过pa 和 va相关宏,完成内核空间物理地址和虚拟地址的转换。

博主遇到的问题不在遍历本身,而是遍历后使用地址时,出现了段错误。为确定问题原因,就添加日志跟踪了下遍历各级页表时的内容。就是这个日志,让我奇怪了一阵。

定义了四个表指针:

    pgd_t *pgd;

    pte_t *pte;

    pmd_t *pmd;

    pud_t *pud;

pgd_offset、pud_offset、pmd_offset后都将指针本身、指针指向的内容和指针本身的地址都打印了出来。

[   32.829866] Walk page table, mm->pdg is c0204000, pgd_index is 601 
[   32.830245] Walk page table, pgd is c0207008 pgd content1 is c0207008 pgd content 2 is c0207010 pgd addr is da41fdb4 
[   32.831476] Walk page table, pud is c0207008  pud content1 is c0207008 
[   32.836826] Walk page table, pmd is c0207008 pmd content1 is 4021141e pmd content 2 is 4031940e pmd addr is da41fdb8 

从日志看到,因为只有二级页表,所以pgd和pmd的内容是一致的,都是c0207008。但是pgd的指针内容(*pgd)也是 c0207008,而pmd的指针内容(*pmd)则是物理地址 4021141e。

pmd的打印看着比较符合习惯思维。pgd给人一种错觉,指针指向地址的内容本身也指向该指针变量。就是自己指向自己,地址c0207008的内存单元的内容还是c0207008。但是,从pmd来看,这又是不对的,地址c0207008的内存单元内容应该是4021141e。这里到底哪儿出了问题?是打印的原因吗?还是到pmd处理时,内存改变了?

继续跟踪,即使在pmd获取后,再查看pgd的相关内容,也并没有变化。排除了上述内存改变的可能。是打印原因吗?换一个变量指向pgd,结果跟pmd一致,怀疑是pgd本身有鬼。于是就查了下pgd的定义,恍然大悟。

typedef struct { pmdval_t pgd[2]; } pgd_t;

typedef u32 pmdval_t;

可以看到,pgd_t是一个数组类型定义。代码中的pgd_t *pgd; 导致pgd是一个数组指针定义,这样,打印的pgd和 *pgd都是数组的内容,而非指针变量及指针变量指向内存的内容。

单独写个程序验证一下:

#include <stdio.h>
#include <stdlib.h>

typedef int pgd_t[2];

int main(int argc, char**argv) {
   int p[4];
   p[0] = 0x87654321;
   p[1] = 0x12345678;
   p[2] = 0x11112222;
   p[3] = 0x33334444;

   pgd_t *pgd;

   int testMem[4];
   testMem[0] = (unsigned long)&p[0];
   testMem[1] = (unsigned long)&p[1];
   testMem[2] = (unsigned long)&p[2];
   testMem[3] = (unsigned long)&p[3];
   
   pgd = (pgd_t *)&testMem;

   int (*arrayPointer)[2];

   arrayPointer = pgd;

   printf("Test pgd  : pgd is       %lx, pgd content is   %lx \n", 
         (unsigned long)pgd, (unsigned long)(*pgd));
   printf("Test pgd  : pgd is       %lx, pgd content is   %lx \n", 
         (unsigned long)pgd[0], (unsigned long)pgd[1]);
   printf("Test mem  : array is     %lx, addr is          %lx, 0 elem addr is   %lx \n", 
         (unsigned long)testMem, (unsigned long)(&testMem), (unsigned long)&testMem[0]);
   printf("Test local: local p0 is  %lx, local p1 is      %lx \n", 
         (unsigned long)&p[0], (unsigned long)&p[1]);

   return 0;
}
Test pgd  : pgd is       7fffeff1a800, pgd content is   7fffeff1a800 
Test pgd  : pgd is       7fffeff1a800, pgd content is   7fffeff1a808 
Test mem  : array is     7fffeff1a800, addr is          7fffeff1a800, 0 elem addr is   7fffeff1a800 
Test local: local p0 is  7fffeff1a7f0, local p1 is      7fffeff1a7f4 

可以看到,跟内核里的日志输出是一致的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙赤子

你的小小鼓励助我翻山越岭

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值