이전
__turn_mmu_on, __turn_mmu_on_end영역만 page table에 매핑
__create_page_table 계속...
커널 코드 시작부터 .bss 섹션의 끝까지 메모리를 매핑한다.
page table의 시작위치는 0x4000 (r4)
* x >> (20-2)
1)
x >> 18
x / 2^18
x / (2^20 x 2^2)
2)
x >> 20 ; x/1M
x << 2 ; x*4
그 후, BSS의 마지막 부분을 매핑한다.
_end(.bss section의 끝) 0x8092378C -1 상수값을 r6에 넣는다.
(참고. https://github.com/iamroot12D/linux/issues/31)
cf)
arm명령어 체계에서는 상수를 맨 오른쪽 비트 1개만 사용하여 표현하기때문에
0~256까지만 표현할 수 있기때문에, 상수자체가 256이 넘는 것은 계산할 수가 없다.
즉, mov r0, 270 은 안된다.
하지만,
mov r0, 0xlf00000000
=> 상수 0xlf에, shift bit에 24가 들어가서 계산이 된다.
즉, 소스에서 상수를 로드할 때, mov가 아닌 ldr을 사용하는 이유이다.
여기까지 우리가 하는 일을 잠시 다시 생각해보자.
메모리 매핑 현재 과정
다시 소스 !!
r6 : .bss의 끝(.end) 0x8092378b
r7 : mmu_flags 값
r8 : 물리메모리의 시작, 0x0 (커널의 베이스 어드레스)
(cf. bss영역이 9.X M정도여서, 약 10M 라고 가정하에... 그림그림.)
=> 찾아보니, 정확히 한 9.2M정도 이넹~~
=> 즉,
r6 = 0x809 => 0x2024
즉, 0x6000~0x6024까지 커널의 .bss섹션까지의 영역과 매핑한다.
....
.bss위에 DTB가 있었는데, 이것도 옮긴다.
r3 = 6028 ; DTB의 영역을 매핑할 주소를 구함
가상메모리에서 6028와 매핑되어있는 물리주소
2번 수행해서 2M영역을 페이지 테이블에 맵핑하는 이유는,
DTB의 최대 사이즈가 1M 일 수 있다. 우리의 페이지 엔트리는 1M 단위로 하는데, .bss까지의 .end가 9.2정도 이므로, 10M 영역을 넘을 수 있기때문에,
영역이 겹쳐버릴 수 있다. 그래서 2번 수행해서 총 2MB을 추가로 확보하는 것이다.
* addruart : UART 장치의 선두 주소를 얻어옴. 레지스터에 시리얼 포트 주소 값을 설정)
0xc02 => c는 앞에비트니까 생각하지말고, 02는 0010으로
c b
... |
... |
0 |
0 |
1 |
0 |
r3 = 0xf0201000를 20번 right 시프트하면 5개자리가 떨어져나가서 0xf02(물리주소)이고,
r3 = 이걸 두번 다시 left shift하면 0xf02 * 4
r3 = (0xf02 * 4) + 0x4000 = 0x202
r7 = 0x202 = 0xc02
r0 = 0x7c08
r3 = 0x2020000+0x00000c02
r3 = 20200c02
여기에 xn비트 적용하면, r3 = 0x20200c12
* xn비트(execute never)가 서있으면, https://en.wikipedia.org/wiki/NX_bit
이걸 페이지 테이블에 매핑하면 가상주소의 인덱스가 7c08인 값이 20200c12
참고.
http://kth3321.blogspot.kr/2013/04/arm-cortex-1.html
디버깅에 필요한 잡업들을 위해 매핑까지 수정하면,
ret lr
(리턴 lr!! creatfe page끝 )
<결론>
* 커널이 사용할 2GB : PAGE_OFFSET 값으로
* 가상주소를 물리주소로 변환하는 방법
은 right shift 20, left shift 2
*
이전시간에 turn_mmu_on 을 가상 메모리 공간에 0M부분에도 매핑하는데, 오늘은 커널부분을 커널영역부분에 맵핑했다
결국 turn_mmu_on이 매핑되는 부분은 2군데가 되는데
이는 turn_mmu_on을 하기 전까지 우리는 커널영역의 주소공간을 사용한다.
즉, 가상메모리 주소공간이 0x80000000 공간을 사용하지 않고 0x0~~ 주소체계를 사용하고 있기때문에 바로 주소가 변환하는 매커니즘을 거치면 오류가 발생하기 때문에,
이를 방지하기 위해 따로 매핑을 해놓고 사용한다.
* head.S에서 지금까지한게 SMP랑 __create_page_table
[initfn 매크로 부분]
* r10 : proc.info 구조체, (라즈베리파이의 CPU의 proc.info)
(실행주소가 0x8000베이스로 실행되는 중)
--v7proc_info는 파일에 들어있는 값이고 0x80008200이런식으로 되어있었다. (지난번에 했음)
v7proc_info 들어가면, __lookup_processor_type레이블이 있고, __lookup_processor_type_data를 타고 0x8080이 들어갔다고 치고, (실행주소가 0x8000베이스로 돌아가는 중, 런타임시)
__lookup_processor_type돌아와서 adr명령어를 통해서 r3 = 0x8080 로 들어간다.
*adr명령어는 PC의 기준으로 현재 실행되고있는 메모리의 값을 읽어오는 거라서(런타임시), 0x8000베이스고,
(덤프확인 주소들이 https://github.com/iamroot12D/linux/issues/31)
r3 |
r4 |
r5 |
r6 |
0x8080 |
"."은 현재 데이터이므로 0x80008080 (컴파일시점에서 소스에 박힘) |
|
|
2: .long 0x80008080 => r4
.long 0x80000000
initfn --v7-setup, --v7proc_info => 이렇게 빼주는게, 컴파일시에 읽어온 0x80000000 베이스의 주소를
현재 런타임에 사용되는 베이스인 0x8000 형식으로 만들어주기위해 빼주는 과정임.
(initfn가 --v7-setup - --v7proc_info 하는 매크로임)
예를들어
initfn 80008300, 80008400
=> r12에는 -100이 들어감
r10 = 8400
-------------
이 둘을 add해서 r12에 저장, 즉 8400 + (-100) = 8300
결국 --v7_setup(80008300)인 컴파일시점의 주소를 현재 실행중인 베이스인 0x8000으로 바꾸는 역할을 하는 코드임.
결국 0x8000000을 0x8000베이스로 바꿔주기위해서 앞부분을 빼주는 역할을 함
=> 결국 이것도, 메모리에 있는 --v7_setup매크로를 호출하기 위해서 이렇게 한거.
자, 이제 v7_setup로 쩜프~!!
(아 왜 이렇게 짠거야...?에 대한 토론)
CISC와 다르게 RISC에서는 무조건 4바이트 명령어로 16비트 명령어 체계에서 2byte만 상수를 표현할수 있다. (0-256)
그래서 한줄로 표현할수있는 것도, 복잡하게 여러줄로 나눠서 표현해야하기도 함.
위에서 봤던, ldr, mov명령이라던가...
CISC도 파이프라이닝을 위해서 SISC 처럼 명령어를 마이크로 명령어라는 단위로 잘라서 사용하기도 한다.
이런 테크닉들이 많이 있다.
CISC는 명령어 길이가 가변적이므로, 설계도 복잡해지고 CPU성능에도 많은 가변적인 문제가 있다.
'System > Linux Kernel' 카테고리의 다른 글
kernel/head.S (0) | 2016.02.27 |
---|---|
[Linux Kernel Concept, File System] (1) 유닉스 파일시스템과 Inode구조체 (2) | 2016.02.14 |
[Linux Kernel Concept] GNU / Linux Kernel / Linux Distros (0) | 2016.02.13 |
gzip (0) | 2016.01.23 |
barrier와 volatile (0) | 2016.01.23 |