继续学内核之旅。
作为具有十多年开发经验的猿,自认为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
#include
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
可以看到,跟内核里的日志输出是一致的。