안녕하세요. page table mapping 과정에서 모르는 부분이 있어 질문 드립니다.
질문을 요약하면, 아래 compute_indices가 "일반적인 상황에서" 사용되기에는 부적합해 보이는데
제가 생각한 것이 맞는지 궁금합니다.
linux-5.1.16/arch/arm64/kernel/head.S:218 |
1 .macro compute_indices, vstart, vend, shift, ptrs, istart, iend, count 10 lsr \istart, \vstart, \shift 14 sub \count, \iend, \istart |
compute_indices는 page table이 몇 개의 entry를 가지게되는지 계산하는 매크로입니다.
map_memory 매크로는 각 페이지 테이블 단계별로 compute_indices를 호출합니다. (최대 4번)
VA 48bit, page size 4K 일 때 4단계 매핑한다고 설정하고 값을 계산해보려 합니다.
주소는 편의상 binary로 표현했습니다.
0b000000000_111111111_111111111_111111111_000000000000 (시작 주소)
0b000000001_000000000_000000000_000000000_111111111111 (마지막 주소)
이렇게 하면 사이즈는 8K입니다.
PGD를 계산하기 위해 compute_indices가 처음 호출되면 (호출시 count = 0),
최상위 9bit를 가지고 계산하여 istart = 0, iend = 1이 되고 리턴할 때 count는 1이 됩니다.
즉 PGD의 엔트리 개수는 2개이고, 필수적으로 존재해야하는 1개의 entry 이외에
추가로 1개의 entry가 더 필요하기때문에 count = 1이 됩니다.
문제는 두번째로 PUD를 계산할 때 입니다. 이 때는 앞서 PGD 계산의 결과로 count = 1 입니다.
최상위 9bit를 제외한 두번째 9bit를 가지고 계산을 합니다.
istart = 0b111111111
iend = 0b000000000
질문1. 8번 라인이 수행되지 않는다고 가정하겠습니다. (8번 라인은 질문2와 관련됩니다)
14번 라인에서 iend에서 istart를 빼서 count를 계산합니다.
그런데 0b000000000 - 0b111111111 = 0b1...1000000001 가 됩니다.
이 계산 결과에도 0b111111111 mask를 씌워야 하는 게 아닐까요?
즉, 14번 라인이 문제 없이 수행되려면 모든 레벨에 대해서 시작 주소의 인덱스가 마지막 주소의 인덱스보다
작아야 하는 것 아닐지요.
질문2. 8번 라인에서는 PGD에서 리턴된 count 값에 따라서 iend값을 늘려 줍니다.
즉, PGD에서 추가로 한개의 entry가 더 있기때문에 iend 개수는 512만큼 (하나의 페이지 분량) 늘어나게 됩니다.
그러나 이 예시에서는 두 개의 PUD에 각각 한개씩의 entry만 있어도 충분합니다.
PUD의 entry 개수에 따라서 PMD의 영역이 할당될텐데,
이런식으로 무조건 512개씩 엔트리를 늘려버리면 특정한 케이스에 대해서는 뒤로갈수록 너무 심한 공간 낭비가 될텐데요.
이렇게 계산하는 것이 적절한가요?
결론적으로 compute_indices의 계산 방식은 일반적인 경우에는 적용될 수 없을 것 같은데, 제 생각이 맞을지요.
실제로 id mapping을 할 때는 page table 영역을 12K만큼밖에 안잡아놓고 있고,
section mapping을 하고 PGD, PUD의 entry가 각각 1개씩밖에 없기때문에
각 레벨의 page table이 한개씩만 있어도 돼서 총 table 3개로 커버가 되는 상황이라고 이해했습니다.
즉 메모리 영역이 1G 이하일 뿐만 아니라, 제가 억지로 만든 예시처럼 주소가 상위 레벨에서 여러 페이지에
걸쳐있지 않기 때문에 문제가 없는 것 같아 보입니다.
이런 특수한 상황에서만 돌아가도록 짜여진 매크로가 맞을까요?
(+ idmap이 매핑하는 메모리 공간의 크기는 어떻게 확인 가능할까요?)
답변 부탁드리겠습니다!!
감사합니다.
댓글 3
-
rnsscman
2019.08.01 02:29
-
cien
2019.08.01 14:41
그렇네요~!
14번 라인에서 (작은 수) - (큰 수) 일 경우에 계산 결과에 mask를 씌워야 하지 않느냐는 것이 첫번째 질문이었는데,
말씀하신대로 PUD에서 마지막 주소의 인덱스가 시작 주소의 인덱스보다 더 작다면,
PGD에서는 마지막 주소 인덱스가 시작 주소 인덱스보다 클수밖에 없기때문에 8번 라인이 무조건 수행되겠네요.
즉, 결론적으로 14번 라인에서 (작은 수) - (큰 수) 가 되는 경우는 발생할 수가 없군요.
이 부분을 짚고 넘어가니 두번째 질문은 자동으로 해결됩니다.
답변 감사합니다~!!
-
문c(문영일)
2019.08.02 00:24
안녕하세요? 문영일입니다.
최근 커널의 디폴트 설정으로 빌드 시 4K 페이지, VA_BITS=48로 4 단계 페이지 테이블을 사용합니다. 여기까지는 잘 파악하신 것으로 보입니다.
그런데 ARM64 시스템에서는 2M 단위의 PMD 섹션(블럭) 매핑을 지원하고, 현재 커널도 지원하고 있습니다.
init_pg_dir 및 idmap_pg_dir을 사용하는 경우에는 3단계(SWAPPER_PGTABLE_LEVELS=3)만을 사용하여 매핑합니다.
따라서 위의 코드 조건에서는 다음 1번이 아니라 2번과 같이 동작합니다.
----다음----
1) pgd -> pud -> pmd -> pte -> 4K 페이지
2) pgd -> pmd -> pte -> 2M 페이지
SWAPPER_PGTABLE_LEVELS 값과 관련하여 다시 한 번 살펴보시면 좋을 듯 합니다.
수고하세요.
.
정확한 해답을 드릴 수는 없지만
지난 주 스터디하면서 고민했던 부분이라서 제가 이해한 내용으로 의견드립니다. (계산 및 해석이 틀렸을 수도 있습니다.)
질문 1:
8번 라인이 수행되지 않는 걸로 가정하셨는데
8번 라인이 수행된 결과를 가지고 이후를 해석해야 되는 것으로 보입니다
2번째 compute_indeces의 계산과정을 추적해보면
iend 계산:
2 lsr \iend, \vend, \shift
-> iend = vend >> shift = vend >> 30 = 0b1 000000000 = 0x200 = 512
-> 0b00000000"1 000000000" 000000000 000000000 111111111111 (마지막 주소)
3 mov \istart, \ptrs
-> istart = ptrs = 512 -> ptrs = PTRS_PER_PUD = PTRS_PER_PTE = (1 << (PAGE_SHIFT - 3)) = (1 << (12 - 3)) = 0x200 = 512
4 sub \istart, \istart, #1
-> istart = istart - 1 = 512 - 1 = 511
5 and \iend, \iend, \istart // iend = (vend >> shift) & (ptrs - 1)
-> iend = iend & istart = 512 & 511 = 0
6 mov \istart, \ptrs
-> istart = ptrs = 512
7 mul \istart, \istart, \count
-> istart = istart * count = 512 * 1 = 512
8 add \iend, \iend, \istart // iend += (count - 1) * ptrs
-> iend = iend + istart = 0 + 512 = 512
istart 계산:
10 lsr \istart, \vstart, \shift
-> istart = vstart >> shift = vstart >> 30 = 0b0111111111 = 0x1FF = 511
-> 0b00000000"0 111111111" 111111111 111111111 000000000000 (시작 주소)
11 mov \count, \ptrs
count = ptrs = 512
12 sub \count, \count, #1
count = count - 1 = 512 - 1 = 511
13 and \istart, \istart, \count
istart = istart & count = 511 & 511 = 511
count 계산:
14 sub \count, \iend, \istart
count = iend - istart = 512 - 511 = 1
최종적으로 계산된 count는 1이고 (iend = 512, istart = 511 -> populate_entries에서 사용)
이 결과에 대한 의미를 리뷰해보면,
주어진 주소의 범위에서 PUD가 가질 수 있는 인덱스의 값은
"111111111"과"000000000"로 2개이기 때문에 유효한 것으로 보입니다
결론-> 8번 라인을 포함해서 계산하시면 문제없이 해석 될 것같습니다
질문 2:
질문에서 언급하신 내용대로 최대 4개의 페이지 테이블은 모두 2개의 엔트리를 가지는 것으로 보입니다.
pgd: "000000000", "000000001"
pud: "000000000", "111111111"
pmd: "000000000", "111111111"
pte: "000000000", "111111111"
하지만 무조건 512개씩 엔트리를 늘리는 것이 아니라
compute_indeces를 통해 계산된 istart, iend가 populate_entries로 전달되어 index, eindex로 사용되기 때문에
index가 511, 512인 경우에만 엔트리를 메모리에 저장하는 것으로 보입니다.
.macro populate_entries, tbl, rtbl, index, eindex, flags, inc, tmp1
.Lpe\@: phys_to_pte \tmp1, \rtbl
orr \tmp1, \tmp1, \flags // tmp1 = table entry
str \tmp1, [\tbl, \index, lsl #3]
add \rtbl, \rtbl, \inc // rtbl = pa next level
add \index, \index, #1
cmp \index, \eindex
b.ls .Lpe\@
.endm
결론 -> 무조건 512개씩 늘리는 것이 아니라, compute_indeces를 통해 계산된 istart와 iend의 차이만큼 늘리는 것으로 보입니다