리눅스 커널 페이징문제...
x86, 우분투 10.04 리눅스 커널에서(대부분 비슷하지만)
커널영역의 가상주소는 0xC0000000 ~ 0xFFFFFFFF 이고 이것은
물리주소 0번지부터 선형매핑됩니다(ZONE_DMA, ZONE_NORMAL).
그리고 모든 페이지디렉토리, 페이지테이블은 이 영역에 존재합니다.
CR3 레지스터에는 페이지 디렉토리의 물리주소가 들어있습니다.
이 전제하에...
"페이지 디렉토리의 커널가상주소에 대해서 페이징을 하면 페이지 디렉토리의 물리주소가 나타나야 한다"
는 가정을 확인해보기위해 아래와같이 실험을 했지만 뭔가 이상합니다.
아래의 실험에서 제가 잘못 알고있는 부분이나 실수한 부분은 어디일까요?
1. 커널모듈 A 의 insmod 초기화함수(init_module)에서 CR3 레지스터 값을 읽어옴
unsigned int cr3=0;
asm ("movl %%cr3, %0\n" :"=r"(cr3));
printk("cr3 : %p\n", cr3);
이때의 CR3 의 값은 커널모듈 A 를 올릴때의 insmod 의 context 상에서의 페이지 디렉토리의 물리주소가 있겠죠?
그렇다면 여기에 대응되는 커널영역의 가상주소는 0xC0000000 만 더해주면 되겠죠?.
unsigned int* p = 0xc0000000 + cr3;
2. 이제 커널영역의 가상주소 p 는 insmod 의 페이지디렉토리의 시작지점을 가리킴.
그리고 이 가상주소(페이지 디렉토리의 시작) 에 대한 페이징을 따라가기 위해서
PGD index, PTE index 를 다음과같이 계산함.(상위 10비트, 10비트 추출)
unsigned int index = ((unsigned int)p) >> 22;
unsigned int index2 = ((unsigned int)p & 0x3FF000) >> 12;
3. 그리고 다음과같이 p 가 가리키는 1024 개의 4바이트 엔트리들중 PGD 인덱스에 해당하는 엔트리를 가져옴.
unsigned int* p2=0;
int i;
for(i=0; i<1024; i++){
if( i==index ){
p2 = *p;
}
p++;
}
4. 이렇게 가져온 p2 는 가상주소 p 의 주소매핑이 담겨있는 페이지테이블의 물리시작주소를 가지고있음
(상위 20비트가 페이지 테이블의 PFN임) 따라서 위와같이 상위 20비트를 마스킹하고 다시 이것을 커널영역의
가상주소로 바꾸기 위해서 0x3000000 를 더해줌(0xC0000000 를 4로 나눈값임. int pointer 이므로, 1을 더하면 포인터가 4 증가하므로)
5. 이제 p2 는 페이지 테이블의 가상주소(커널영역의) 를 가지고 있음.
이제 다시 이 페이지 테이블의 1024개의 엔트리중에서 위에서 계산했던 index2 에 해당하는 엔트리에서
실제 PFN 을 가져옴.
p = p2;
for(i=0; i<1024; i++){
if( i==index2 ){
p2 = *p;
}
p++;
}
그러나 여기서 페이지테이블의 엔트리가 0(null) 으로 나옴....
저는 여기서 최초의 페이지 디렉토리의 물리주소 PFN 이 발견될거라 예상했는데
왜 null 인건지 모르겠네요, 제가 뭘 잘못알고있는걸까요?
비록 커널영역의 가상주소가 물리주소랑 단순하게 1:1 매핑되긴 하지만
그렇다고 해서 가상주소가 주어질때 MMU 도 단순히 0xC0000000 를 빼는건 아니지않나요?
결과적으론 0xC0000000 를 뺀것과 같더라도 MMU 는 페이지디렉토리를 따라가고, 또 페이지테이블을 따라가고
그렇게해서 PFN 을 얻어오는거 아닌가요?(TLB 는 제끼고)
이부분을 확실히 하고싶네요
자체해결
가상머신 위에서 테스트한게 문제였네요 ㅠㅠ
아무래도 VMM 이 페이징하는부분에 관여를 하기때문에
실제 책에있는 이론과 다른부분이 있는듯...
실제 머신위에서 테스트하니 정확히 예상대로 결과가 나오네요
자기실력이 좋다고 느껴지는건 공부를 안하고 있다는 신호.
댓글 달기