arch/arm/kernel/head.S (5) – domain 과 PTE를 이용한 권한관리

__enable_mmu 함수의 코드에 다음과 같이 domain 을 설정하는 부분이 있었습니다.
mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
              domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
              domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
              domain_val(DOMAIN_IO, DOMAIN_CLIENT))
mcr p15, 0, r5, c3, c0, 0       @ load domain access register

#define domain_val(dom,type)    
((type) << (2*(dom)))
위의 코드는 cp15 의 c3:c0 레지스터에 r5 에 셋팅한 값을 넣어주고 있습니다.
해당 레지스터는 Domain Access Register 입니다.

즉, mmu 를 enable 하는 과정에서 Domain Access Register 의 값을 설정해주고 있는 것이네요. 그러면, 이 Domain Access Register 는 언제 어떻게 사용이 되는 것인지 알아보겠습니다.

우리가 MMU 를 공부할 때 MMU 의 역할은 "가상주소의 물리주소로의 변환"과 "접근 권한 관리"라고 배웠습니다.
Domain 을 정확히 이해하려면 MMU 가 이러한 "접근 권한 관리"를 어떤 방법으로 수행하는지를 이해해야 합니다.

전체적인 이해를 위해서 아래 그림을 그려보았습니다.

Domain 과 PTE 를 이용한 권한관리.png

접근 권한의 검사는 가상주소의 물리주소로의 변환과정에서 발생합니다.

Level 1 Page Table 의 Entry 의 마지막 2 bit 가 0b10 일 경우에는 해당 Entry (Section Entry)는 1 MB Section 을 가리킨다는 것은 알고 계실 것이라고 가정하고 설명하겠습니다.

(1) MMU 로 가상주소가 인입되면 가상 주소의 상위 12 bit 를 이용하여 Page Table Entry 를 찾는다.
(2) Page Table Entry 의 Domain Field 값을 이용하여 CP15:c3:c0 (Domain Access Register)의 해당 Domain 의 설정값을 검사한다.
(3) 해당 Domain 의 값에 따라 후속 접근 권한 검사를 할 것인지를 판단한다.

- Domain 의 값이 Manager 로 설정되어 있는 경우   : 해당 Section 은 그냥 마음대로 Access 하면 된다.
- Domain 의 값이 Client 로 설정되어 있는 경우       : PTE 의 AP Field 를 검사하여 Read/Write/No Access 권한을 확인한다.
- Domain 의 값이 No Access 로 설정되어 있는 경우 : 해당 Section 은 접근 불가

Domain 이라는게 무엇인지 좀 알아볼께요.
Domain 이라는 것은 사용자가 임의대로 어떤 의미를 부여한 영역들을 의미합니다.

예를 들어, 사용자가 XXX 라는 이름으로 도메인 이름을 지어 놓고, "이 도메인은 누구나 마음대로 접근할 수 있어"라고 어딘가에 설정을 해두고 싶습니다.

그리고, 특정 메모리 영역을 해당 도메인과 연결시켜둠으로써 누군가가 해당 메모리 영역에 접근하려고 하면, 연결된 도메인이 "이 도메인은 누구나 마음대로 접근할 수 있어"라고 설정된 것을 확인한 후 이제 마음놓고 접근하게 하고 싶습니다.

메모리 0x33333333 ~ (0x33333333 + 1MB) 사이의 공간은 XXX 도메인에 속하고 그 도메인의 권한 속성을 따른다고 정의해두면, 누군가가 해당 메모리 영역에 접근할 때 그 영역과 연결된 도메인을 찾아서 그 도메인에 설정된 접근 권한을 검사하게 되면, 도대체 접근하려는 메모리 영역이 접근이 가능한 것인지 가능하면 어느 수준까지 가능한 것인지를 파악할 수 있게 됩니다.

물론 여러개의 메모리 영역이 같은 도메인과 연결될 수도 있습니다. 같은 권한을 주고 싶은 것들을 묶어서 하나의 도메인으로 정의하고 메모리 영역들은 그 도메인을 가리키게 하면 되니까요.

이럴 때 사용하는 것이 도메인입니다.

Domain 과 PTE 를 이용한 권한관리2.png

ARM 에서는 이러한 도메인을 미리 정의해둘 수 있는 공간으로 CP15:c3:c0 레지스터를 사용합니다.

레지스터는 32 bit 인데 Manager, Client, No Access 등의 접근 권한을 표현하려면 2 bit 가 필요할 것입니다.

따라서, CP15:c3:c0 레지스터에는 총 16 개의 도메인을 정의하여 설정해둘 수 있습니다.

16 개의 공간 각각에 대해 모두 도메인을 정의하여 등록해둘 것인지 아닌지는 사용자의 몫입니다.
물론 16 개 중에서 일부 몇개의 공간만을 정의하여 사용할 수도 있습니다.

첫번째 도메인(0~1 bit)은 DOMAIN_IO 라는 이름으로 부를 것이고, 권한은 Manager(11) 로 하겠다.
두번째 도메인(2~3 bit)은 DOMAIN_USER 이라고 명명하고, 권한은 마찬가지로 Manager(11) 로 하겠다.
세번째 도메인(4~5 bit)는 DOMAIN_KERNEL 라고 부르고, 권한은 Client(01)로 설정하겠다.

위와 같이 각각의 도메인에 대해 의미를 부여하고 권한 설정을 할 수 있을 것입니다.
그렇다면, 도메인 이름은 아래와 같은 코드로 정의해볼 수 있겠네요. 이름 자체가 해당 도메인 공간을 가리킬 수 있도록 말이죠.

#define DOMAIN_KERNEL   2
#define DOMAIN_TABLE    2
#define DOMAIN_USER      1
#define DOMAIN_IO          0

즉, 위와 같이 정의하고 싶은 도메인 이름을 정의해두고, 해당 도메인의 권한을 설정할 자리를 찾아갈 때는 << (2*DOMAIN_KERNEL) 와 같이 shift 연산을 사용하면 되겠네요.

2 * 2 이니까 결국 CP15:c3:c0 레지스터에서 3 번째 도메인을 나타내는 자리(4~5 bit)쪽을 가리키게 될 것입니다.
그 자리에 Client(01) 값을 설정하게 되면 결국 해당 도메인의 권한은 Client 가 되는 것입니다.

이러한 배경 지식을 바탕으로 domain_val 매크로 연산을 이해하면 됩니다.
결국 위쪽의 r5 에 넣는 domain_val 매크로 연산의 결과는 다음과 같을 것입니다.

1 << (2*1) | 1 << (2*2) | 1 << (2*2) | 1 << (2*0) => 01 01 01

이는 결국 하위 3 개의 도메인을 모두 01 (Client)로 설정해두겠다는 의미입니다.

도메인의 권한이 Client 로 설정되어 있으면 한 단계의 권한 검사를 더 하게 되는데, 위에서 살펴본 바 대로 Page Table Entry 의 AP Field 값을 보게 됩니다.

AP Field 에는 AP[2] 와 AP[1:0] 두가지가 있는데 해당 3 bit 를 통해 특권모드(IRQ, FIQ, SVC, ABORT, SYSTEM)와 비특권모드에 대해 ReadOnly, ReadWrite, No Access 등 접근권한의 경우의 수를 모두 표현할 수 있습니다.
이는 다양한 문서에 표로 나와있으니 참고하시기 바랍니다.

또한, CP15:c1:c0 (System Control Register)에는 시스템(S) 비트와 롬(R) 비트가 있어서 해당 비트의 조작만으로 시스템 전반적인 접근 권한을 한번에 제어할 수도 있습니다. (ARM Developers' Guide 교재 참조)

참고로 접근 권한이 맞지 않을 때는 Fault 를 발생시키게 됩니다.

가상주소를 이용해서 메모리에 접근하려고 하는데, 도메인이 no access 로 설정되어 있다면 "Domain Fault" 를 발생시킵니다.

그리고, Write 를 하려고 하는데 도메인이 Client 로 설정되어 있어서 AP Field 값을 검사하였더니, Read Only 로 되어 있었다면 "Permission Fault" 가 발생하게 됩니다.

저는 도메인과 접근 권한 관리를 공부하면서 한가지 더 궁금한 점이 생겼었습니다.

"그러면 도대체 PTE 의 AP Field 와 Domain 값은 누가 언제 어떻게 설정해주는거지 ?"

위의 궁금증을 풀기 위해 찾다가 보니 우리가 proc-v7.S 에서 간과하고 넘어간 부분이 있었습니다.

아래 코드를 보시죠.

.macro __v7_proc initfunc, mm_mmuflags = 0, io_mmuflags = 0, hwcaps = 0, proc_fns = v7_processor_functions
    ALT_SMP(.long   PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
            PMD_SECT_AF | PMD_FLAGS_SMP | \mm_mmuflags)
    ALT_UP(.long    PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
            PMD_SECT_AF | PMD_FLAGS_UP | \mm_mmuflags)
    .long   PMD_TYPE_SECT | PMD_SECT_AP_WRITE | \
        PMD_SECT_AP_READ | PMD_SECT_AF | \io_mmuflags
    W(b)    \initfunc
    .long   cpu_arch_name
    .long   cpu_elf_name
    .long   HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB | HWCAP_FAST_MULT | \
        HWCAP_EDSP | HWCAP_TLS | \hwcaps
    .long   cpu_v7_name
    .long   \proc_fns
    .long   v7wbi_tlb_fns
    .long   v6_user_fns
    .long   v7_cache_fns
.endm

위 매크로는 processor 에 맞는 proc info 구조체의 값들을 설정해주는 부분입니다.
커널 빌드 시에 proc_info table 에 processor 별로 모두 설정됩니다.

__lookup_processor_type 함수에서 우리에 맞는 proc info 를 찾았던 것을 기억하실겁니다.
그렇게 찾은 proc info 구조체의 정보들을 kernel/head.S 를 분석하는 과정 여기저기에서 사용했었습니다.

__create_page_tables 의 과정에서는 page table entry 값을 설정할 때 mmuflags 값들을 이용했었구요.

v7_setup 분석 과정에서 위 매크로를 분석할 때 우리는 ALT_SMP, ALT_UP 매크로 자체만 분석을 했었고, 정작 해당 매크로의 인자값들의 의미는 살펴보지 못했습니다.

ALT_SMP 매크로의 인자값을 좀 살펴보죠.

PMD_TYPE_SECT 는 값이 10 으로 Section Entry 라는 것을 나타냅니다.  PTE 의 마지막 2 bit 값이 됩니다.
PMD_SECT_AP_WRITE | PMD_SECT_AP_READ 는 AP Field 에 READ/WRITE  (11) 를 설정하는 용도이구요.
PMD_FLAGS_SMP 는 Cacheable, Bufferable, TEX 등의 값을 설정하는 것입니다.

즉, __v7_proc 매크로에서 빌드 시에 PTE 에서 사용하게 될 각종 Flag 값들을 설정해두게 되는군요.
현재 start_kernel 을 공부하고 있는 이 시점에서 커널 영역을 가리키는 PTE 의 Flag 값들은 위와 같이 설정되어 있는겁니다.

지금까지의 코드 중에서 PTE 의 domain field 를 별도로 설정하는 부분은 찾지 못했습니다.

어차피 위에서 보았듯이 하위 3개의 도메인을 Client 로 설정했으니 그냥 0 번 도메인으로 사용하려는 의도일 것 같습니다만 확신은 없습니다.

아마도 뒷쪽 코드들을 분석하다보면 도메인도 다양한 형태로 사용할 것 같습니다.

You may also like...

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x