2013년 6월 26일 수요일

Xen 소스 트리 구조

소스 코드를 분석 하기 위해서는 여러가지 접근법이 있겠지만, 소스 코드의 전반적인 구조를 파악하는 것은 소스 트리 구조를 알고 있는 것이 많은 도움이 됩니다. 이 내용은 일전에 Xen 소스 분석을 하면서 Xen 소스 트리 구조를 정리한 것 입니다. Xen 소스 코드 분석을 하시는 분들께 도움이 되었으면 좋겠습니다. 

당시 Xen을 분석했던 4.1.2 버전을 기준입니다. Xen 최신 버전(4.2.2)도 소스 파일이나 디렉토리 구조는 크게 차이가 없습니다. 4.1.2 버전과 최신 버전의 가장 큰 차이점이라면 ia64 아키텍처 코드가 삭제되고 ARM 아키텍처 코드가 추가된 것을 들수가 있습니다.

Xen 4.1.2를 다운로드해서 압축을 풀어보면 많은 서브 디렉토리와 소스 파일이 생성 됩니다. Xen은 실직적으로 가상화를 수행하는 하이퍼바이저와 가상화 시스템을 관리하기 위한 각종 툴로 구성되어 있습니다. (운영체제의 구성 요소가 커널과 각종 시스템 프로그램으로 되어 있는 것과 비슷한 맥락입니다.) 가상화 프로그램은 하이퍼바이저가 핵심이지만 하이퍼바이저를 제어하기 위한 인터페이스를 제공하는 어떤 라이브러리와 툴이 있는지 알아야 가상화 시스템 전체 구조를 파악할 수 있습니다. 각 디렉토리에 어떤 소스 파일들이 있고, Xen에 어떤 툴과 라이브러리가 있는지 가볍게 한번 보시기 바랍니다.

아래는 Xen의 최상위 디렉토리와 툴 디렉토리만 나타낸 것 입니다. xen 디렉토리는 핵심이라고 할 수 있는 하이퍼바이저 소스 코드가 들어 있는 곳이므로 나중에 다시 다루도록 하고 지금은 나머지 최상위 디렉토리들과 툴들에 대해서만 한번 살펴 보겠습니다.

Xen 전체 디렉토리 구조

(XEN)
├── buildconfigs
├── config
├── docs
├── extras
│   └── mini-os
├── stubdom
├── tools
│   ├── blktap
│   ├── blktap2
│   ├── check
│   ├── console
│   ├── debugger
│   ├── examples
│   ├── firmware
│   ├── flask
│   ├── hotplug
│   ├── ioemu-qemu-xen
│   ├── libaio
│   ├── libfsimage
│   ├── libxc
│   ├── libxen
│   ├── libxl
│   ├── memshr
│   ├── misc
│   ├── ocaml
│   ├── pygrub
│   ├── python
│   ├── remus
│   ├── security
│   ├── sv
│   ├── tests
│   ├── vnet
│   ├── vtpm
│   ├── vtpm_manager
│   ├── xcutils
│   ├── xenbackendd
│   ├── xenballoon
│   ├── xenmon
│   ├── xenpaging
│   ├── xenpmd
│   ├── xenstat
│   ├── xenstore
│   ├── xentrace
│   └── xm-test
├── unmodified_drivers
└── xen
     └─+

buildconfigs: makefile 빌드시스템 관련 파일들

config: makefile 빌드시스템 관련 파일들
docs: 문서들
extras/mini-os: 간단한 소형 운영체제, Dom0 분할(Dissaggregation)을 위한 stub 도메인에서 사용한다.
stubdom: I/O 성능 향상을 위한 Stub 도메인
tools: Xen 각종 툴들
tools/blktap: 블록탭(Block Tap),사용자 레벨에서 디스크 I/O 인터페이스를 제공하는 라이브러리와 툴, 주요 목적은 좀 더 좋은 성능을 위해 파일기반 이미지의 루프백 드라이버(loopback driver)를 대체하는 것이다. 
tools/blktap2: 블록탭 개선 버전
toos/check: Xen 빌드와 설치를 위한 적합성 검사
tools/console: Xen 콘솔
tools/debugger: 디버거, IA64 아키텍처 전용 저수준 Xenitp, gdb, kdd 디버거를 지원한다.
tools/examples: Xen 가상머신 스크립트와 설정파일 예제들
tools/firmware: BIOS(vgabios, rombios)와 Xen의 전 가상화 도메인에서 가장 먼저 실행되는 것은 hvmloader, 네트워크 부트로더인 gPXE등의 펌웨어들(하위 디렉토리 표시)
tools/flask: Flux Advanced Security Kernel, Xen은 XSM라고 불리는 보안 프레임웍을 제공한다. flask는 XSM을 사용하는 보안 모델의 구현이다.
tools/hotplug: 핫플러그인 관련 툴
tools/ioemu-qemu-xen: QEMU 디바이스 모델, 주로 전가상화에서 장치 에뮬레이션하는 용도로 사용한다.
tools/libaio: 비동기 I/O 라이브러리.
tools/libfsimage: 파일 시스템 이미지 라이브러리. ext, fat, iso9600, reiserfs, ufs, zfs등의 파일 시스템 이미지를 생성하는데 사용한다.
tools/libxc: XC(Xen Control) 라이브러리. Xen 하이퍼바이저에게 하이퍼 콜을 직접 호출하여 제어하는 가장 저수준 라이브러리.
tools/libxen: Xen 관리 API. XML-RPC 프로토콜로 원격지나 로컬에서 Xen 가상머신을 관리한다.
tools/libxl: XL(Xen Light) 라이브러리. libxc 라이브러리를 래핑하여 좀 더 용이한 인터페이스를 제공하는 C 라이브러리. libxl로 구현한 커맨드라인 가상머신 제어 툴인 xl 툴이 포함되어 있다.
tools/memshr: 공유 메모리
tools/misc: 여러가지 툴 모음. xm xen-bugtool xen-python-path xend xenperf xsview xenpm xen-tmem-list-parse gtraceview gtracestat xenlockprof xenwatchdogd/xen-hvmctx xen-hvmcrash(x86 전용)/xen-hptool(마이그레이션용)
tools/ocaml: OCaml 언어 관련 파일들
tools/pygrub: 파이썬으로 구현한 가상 grub, 게스트 부트로더로 사용된다.
tools/python: 파이썬 언어 관련 파일들
tools/remus: 가상머신 백업툴, 장애가 발생하면 백업본으로 자동실행한다.
tools/security: 보안 관련 툴
tools/sv: 가상머신 제어 웹 인터페이스
tools/tests: 각종 테스트 툴. MCE(Machine Check Exception) 주입 테스트, 페이지 단위 접근 테스트, x86 에뮬레이터 테스트, 파이썬 유닛 테스트등의 툴이 있다.
toosl/vnet: 가상 네트워크
tools/vtpm: 가상 TPM(Trusted Platform Module)
tools/vtpm_manager: 가상 TPM 관리툴
tools/xcutils: libxc로 구현한 툴들. lsevtchn(이벤트 채널 목록), readnotes(ELF 파일의 note 섹션 읽음), xc_restore(가상머신 상태 복구), xc_save(가상머신 상태 저장)
tools/xenbackendd: Xen backend 데몬
tools/xenballoon: 게스트의 셀프-발루닝을 위하여 게스트에서 동작하는 데몬. 메모리 발루닝은 메모리 공간 효율성을 위하여 게스트가 사용하는 메모리를 유동적으로 조절하는 것이다.
tools/xenmon: Xen 성능 모니터
tools/xenpaging: 메모리 페이지 내용을 파일에 쓰고 되돌리는 툴
tools/xenpmd: 전원 관리 데몬
tools/xenstat: Xen 도메인과 시스템 상태 모니터링 툴(xentop)과 라이브러리(libxenstat)
tools/xenstore: XenStore 제어 툴
tools/xentrace: Xen에서 추적 버퍼(Xen 추적 버퍼는 Xen 하이퍼바이저가 실행되는 과정의 로그 정보가 들어있다. Xen 하이퍼바이저 코드를 보면 TRACE_0D, TRACE_1D등의 매크로를 종종 볼 수 있다. 이 매크로가 삽입된 부분의 데이타를 추적한다. ) 데이타를 캡처하는 툴.
tools/xm-test: 도메인 제어 테스트 툴.(Xen 가상화 시스템의 관리 기능(도메인 생성/제거, 저장/복귀, 이주등)의 테스트 목적으로 만들어졌다. 이것은 파이썬으로 구현한 xm 툴 기반으로 작성된 스크립트이다. xm 툴은 C로 구현한 xl로 대체되었다.)
unmodified_drivers: 리눅스 드라이버
xen: Xen 하이퍼바이저


Xen 하이퍼바이저 소스 구조

Xen 소스 디렉토리에서 xen 디렉토리에는 Xen의 핵심인 하이퍼바이저 소스 코드가 들어 있습니다. 아래는 ia64 아키텍처 소스 파일과 include 디렉토리의 헤더 파일을 제외한 Xen 하이퍼바이저의 모든 소스 파일에 대한 간략한 설명입니다.    
(XEN)/xen - Xen 하이퍼바이저 최상위 디렉토리
• COPYING – Xen 하이퍼바이저 소스 코드 라이센스
• Makefile – Makefile
• Rules.mk – Makefile 설정

(XEN)/xen/arch - 아키텍처 종속적인 루틴

(XEN)/xen/arch/ia64 - IA64 아키텍처 종속적인 루틴, Xen 4.2 버전에는 IA64 아키텍처 관련 코드가 모두 삭제되었다. 대신 ARM 아키텍처가 추가되었다.

(XEN)/xen/arch/x86 - x86 아키텍처 종속적인 루틴
• apic.c - 로컬 APIC 처리, 로컬 APIC 타이머
• bitops.c – 개별 비트 루틴 검색 
• bzimage.c - 리눅스 커널 이미지(bzImage) 처리
• clear_page.S – 페이지 메모리 클리어
• compat.c - 레거시 하이퍼콜은 인자를 조절하여 새로운 하이퍼콜을 호출한다.
• copy_page.S – 페이지 복사 어셈블리 루틴
• crash.c – CPU 크래쉬 루틴
• debug.c - 디버깅 루틴
• delay.c - x86 아키텍처의 정밀한 딜레이 루프
• dmi_scan.c – DMI(Desktop Management Interface) 스캔 루틴. DMI는 SMBIOS(System Management BIOS)를 포함하므로 SMBIOS로 불리기도 한다. 
• domain.c - x86 전용 도메인 처리(즉, x86 레지스터 설정과 컨텍스트 스위칭)
• domain_build.c – Dom0 생성 루틴
• domctl.c - 아키텍처 종속적인 도메인 제어 루틴
• e820.c - e820 관련 루틴. (AX 레지스터에 'E820'를 쓰고. BIOS 호출(int 15h)를 하면 메모리맵을 얻어온다.)
• extable.c – 예외 테이블
• fluhtlb.c - TLB 플러시
• gdbstub.c - x86 종속적인 gdb stub 루틴
• hpet.c - HPET(High Precision Event Timer) 관리
• i387.c - Pentium III FXSR, SSE 지원
• i8259.c - 인텔 8259 제어
• io_apic.c - 인텔 IO-APIC 지원
• ioport_emulate.c - I/O 포트 에뮬레이트
• irq.c – IRQ(Interrupt Request) 루틴
• machine_kexec.c – 머신 kexec 루틴
• microcode.c - CPU 마이크로코드 업데이트 리눅스 드라이버(마이크로코드(즉, 기계어) 업데이트로 기계어를 추가 할 수 있다.)
• microcode_amd.c - AMD CPU 마이크로코드 업데이트 리눅스 드라이버
• microcode_intel.c - 인텔 CPU 마이크로코드 업데이트 리눅스 드라이버
• mm.c - x86 페이지 테이블 API
• mpparse.c - 인텔 멀티프로세서 스팩 1.1과 1.4를 준수하는 MP-테이블 파싱 루틴
• msi.c - PCI MSI(Message Signaled Interrupt)
• nmi.c - APIC 시스템에서 NMI(Non-Maskerble Interrupt) 와치독 지원
• numa.c - x86-64 NUMA 설정을 위한 일반적인 VM 초기화
• pci.c - x86 아키텍처 종속적인 PCI 접근 함수들
• percpu.c - PERCPU 락 회피 자료구조
• physdev.c - 물리적인 장치 오퍼레이션
• platform_hypercall.c - 하드웨어 플랫폼 오퍼레이션. 도메인 0에서 사용한다.
• setup.c - Xen 하이퍼바이저 초기화 및 설정 루틴(__start_xen)
• shutdown.c - x86 종속적인 셧다운 처리
• smp.c - 인텔 SMP 지원 루틴
• smpboot.c - x86 SMP 부팅 함수
• srat.c - ACPI 3.0 기반 NUMA 설정
• string.c - x86 아키텍처에 최적화된 문자열 함수들
• sysctl.c - x86 종속적인 시스템 제어 오퍼레이션.
• tboot.c - 인텔 TXT(Trusted Execution Technology) 초기화 루틴
• time.c - PCPU 시간 보정 및 관리
• trace.c - 실행과정 추적 루틴
• traps.c - x86 트랩 관련 루틴
• usercopy.c - 사용자 주소 공간 접근 함수
• x86_emulate.c - x86 아키텍처 에뮬레이터
• xen.lds.S - Xen 하이퍼바이저 링크 스크립트 
• Makefile – Makefile
• Rules.mk – Makefile 설정

(XEN)/xen/arch/x86/acpi - ACPI(Advanced Configuration & Power Interface)
• boot.c - 아키텍처 종속적인 저수준 ACPI 부트
• cpu_idle.c - Xen 유휴 상태 모듈
• cpuidle_menu.c - CPU 유휴를 위한 메뉴 거버너
• power.c - Xen의 전원 관리 핵심 기능
• suspend.c – 프로세서 상태 저장과 복구
• wakeup_prot.S - 저수준 프로세서 상태 저장
• Makefile – Makefile

(XEN)/xen/arch/x86/acpi/cpufreq - ACPI P-상태 드라이버
• cpufreq.c- ACPI 프로세서 P-상태 드라이버
• powernow.c- AMD 아키텍처 P-상태 드라이버
• Makefile - Makefile

(XEN)/xen/arch/x86/boot - x86 초기화
• build32.mk – Makefile 정보
• cmdline.S - 초창기 커맨드라인 파싱
• edd.S - BIOS EDD(Enhanced Disk Drive) 지원
• head.S - x86 아키텍처에서 Xen 하이퍼바이저 시작 지점
• mem.S – 메모리맵 정보를 얻어옴
• mkelf32.c - ELF 실행파일 포맷 바이너리 변환툴
• reloc.c - 멀티부트 자료구조와 모듈의 재배치를 위한 32비트 플랫 메모리맵 루틴
• trampoline.S - 모드 변환(리얼 모드 -> 보호 모드) 및 페이징 활성화
• video.h – 비디오 모드 #define
• video.S - 디스플레이 어댑터와 비디오 모드 설정
• wakeup.S - 저수준 프로세서 상태 복구
• x86_32.S - 32비트 보호 모드 초기화 루틴
• x86_64.S - 64비트 롱 모드 초기화 루틴
• Makefile – Makefile

(XEN)/xen/arch/x86/cpu - x86 아키텍처의 각 CPU 벤더별 구현
• amd.c – AMD CPU 관련 루틴
• centaur.c - 센토(Centaur) CPU 관련 루틴
• common.c – CPU 정보 관련 공통 루틴
• cpu.h - CPU 정보 관련 자료 구조 정의
• cyrix.c - NSC/Cyrix CPU 관련 루틴
• intel.c – 인텔 CPU 관련 루틴
• intel_cacheinfo.c - 인텔 CPU 캐시 정보 루틴
• transmeta.c – Transmeta CPU 관련 루틴
• Makefile – Makefile

(XEN)/xen/arch/x86/cpu/mcheck - x86 MCE(Machine Check Exception)
◦ amd_f10.c - AMD Family10 CPU의 MCA(Machine check architecture) 구현
◦ amd_k8.c - AMD K8 CPU의 MCA 구현
◦ amd_nonfatal.c - AMD CPU의 MCA 구현
◦ k7.c - Athlon/Hammer 종속적인 머신 체크 예외 보고
◦ mce.c - x86 머신 체크 예외
◦ mce.h – mce 자료 구조
◦ mce-apei.c - MCE와 APEI(ACPI Platform Error Interface)간의 브릿지
◦ mce_quirks.h – AMD mce 쿼크 헤더 파일
◦ mce_intel.c- 인텔 MCE 처리
◦ mce_amd_quirks.c- AMD MCE 쿼크 처리
◦ mctelem.c - x86 머신 체크 원격 전송
◦ mctelem.h – mctelem.h #define 들
◦ non-fatal.c - 비치명적인 머신 체크 예외 보고
◦ x86_mca.h - AMD K7/K8 CPU의 MCA 구현
◦ vmce - 가상 MCE 지원
◦ Makefile – Makefile

(XEN)/xen/arch/x86/cpu/mtrr - x86 CPU의 MTRR(Memory Type Range Register)
◦ amd.c – AMD용 MTRR 루틴
◦ cyrix.c – cyrix용 MTRR 루틴
◦ generic.c - 일반적인 MTRR 루틴
◦ main.c- 일반적인 MTRR 드라이버
◦ mtrr.h – MTRR 헤더 파일
◦ state.c - MTRR 상태 제어
◦ Makefile - Makefile

(XEN)/xen/arch/x86/genapic - x86 APIC
• bigsmp.c - bigsmp 드라이버
• default.c - 기본 APIC 드라이버
• delivery.c - APIC 드라이버 구현체
• probe.c - 일반적인 x86 APIC 드라이버 조사 레이어
• summit.c -  IBM Summit 칩셋 APIC 드라이버
• x2apic.c - x2APIC 드라이버
• Makefile – Makefile

(XEN)/xen/arch/x86/hvm - x86 하드웨어 지원 가상화(VT-x or SVM)
◦ asid.c - TLB 태깅을 위한 ASID 처리
• emulate.c - HVM 명령어 에뮬레이션. MMIO와 VMX 리얼 모드에서 사용됨.
• hpet.c - HVM 게스트를 위한 HPET 에뮬레이션
• hvm.c - 공통 HVM 루틴
• i8254.c - QEMU 8253/8254 인터벌 타이머 에뮬레이션
• intercept.c - 하이퍼바이저 공간에서 성능이 중요시되는 I/O 패킷 처리
• io.c - I/O와 인터럽트 처리
• irq.c - 인터럽트 분배와 전달
• mtrr.c - MTRR/PAT 가상화
• pmtimer.c - ACPI PM 타이머 에뮬레이션
• quirks.c - HVM 쿼크
• rtc.c - QEMU MC146818 RTC 에뮬레이션
• save.c - HVM 게스트의 에뮬레이션된 하드웨어 상태 저장과 복구
• stdvga.c - 표준 VGA 모드 관련 루틴
• vioapic.c - 가상 I/O APIC
• viridian.c- Viridian 하이퍼콜 인터페이스
• vlapic.c - 가상 Local APIC
• vmsi.c - 가상 MSI 로직 지원
• vpic.c - i8259 인터럽트 컨트롤러 에뮬레이션
• vpmu.c - 가상 PMU(Performance Monitoring Unit)
• vpt.c - 가상 플랫폼 타이머(VPT, Virtual Platform Timer)
• Makefile – Makefile

(XEN)/xen/arch/x86/hvm/svm - AMD의 하드웨어 지원 가상화인 SVM
◦ entry.S - SVM 저수준 VMENTRY/VMEXIT 처리
◦ asid.c - SVM의 ASID 처리
◦ emulate.c - SVM 명령어 에뮬레이션
◦ intr.c- SVM의 인터럽트 처리
◦ svm.c - SVM과 연관된 VMEXIT 처리
◦ vmcb.c - VMCB 관리
• vpmu.c - 가상 PMU(Performance Monitoring Unit)
◦ Makefile - Makefile

(XEN)/xen/arch/x86/hvm/vmx - 인털의 하드웨어 지원 가상화인 VMX
◦ entry.S - VMX 저수준 VMENTRY/VMEXIT 처리
◦ intr.c - VMX와 관련된 I/O와 인터럽트 처리
◦ realmode.c - VMX를 위한 리얼 모드 에뮬레이션
◦ vmcs.c - VMCS 관리
◦ vmx.c - VMX와 관련된 VMEXIT 처리
◦ vpmu_core2.c - HVM 도메인을 위한 CORE 2 종속적인 PMU 가상화
◦ Makefile - Makefile

(XEN)/xen/arch/x86/mm - x86 메모리 관리자
• guest_walk.c - 게스트 메모리 접근을 위한 페이징 에뮬레이션
• p2m.c - 물리주소 -> 머신주소 매핑
• paging.c -x86 종속적인 페이징 지원
• mem_access.c - 메모리 접근 지원
• mem_event.c - 메모리 이벤트 지원                                                                        
• mem_paging.c - 메모리 페이징 지원
• mem_sharing.c - 메모리 공유 지원
• Makefile – Makefile

(XEN)/xen/arch/x86/mm/hap - 하드웨어 지원 페이징(HAP, Hardware Assisted Paging)
• guest_walk.c - HAP를 사용하는 게스트의 게스트 페이징 에뮬레이션
• hap.c - HAP(Hardware Assisted Paging) 루틴
• p2m-ept.c - EPT 상에서 물리주소 -> 머신주소 변환
• private.h – HAP 헤더파일
• Makefile – Makefile

(XEN)/xen/arch/x86/mm/shadow - 섀도 페이지 테이블
• common.c - 공통으로 하나만 들어가는 섀도 페이지 테이블 루틴
• multi.c - 페이징 단계별로 생성되는 다수의 오브젝트 파일에 포함되는 섀도 페이지 테이블 루틴
• multi.h - 페이징 단계별로 여러번 정의되는 섀도 페이지 테이블 정의
• private.h - 섀도 페이지 테이블 헤더 파일
• types.h – 섀도 페이지 테이블 타입 정의
• Makefile – Makefile

(XEN)/xen/arch/x86/oprofile - oprofile, x86 프로파일링 툴
• backtrace.c – 하이퍼바이저 스택 추적
• nmi_int.c - oprofile 공통 루틴
• op_counter.h – Opcounter 자료구조 정의
• op_model_athlon.c - AMD athlon/K7 종속적인 MSR 동작
• op_model_p4.c - 인텔 펜티엄4 종속적인 MSR 동작
• op_model_ppro.c - 인텔 페일리 6 종속적인 MSR 동작
• op_x86_model.h - x86 모델 종속적인 MSR 동작 인터페이스
• xenoprof.c - Xen의 프로파일링 루틴
• Makefile – Makefile

(XEN)/xen/arch/x86/x86_32 - x86 32비트 보호 모드
• entry.S - 보호 모드 저수준 하이퍼콜과 예외 처리 루틴
• asm-offsets.c - 어셈블리 언어 모듈에서 필요한 정의를 생성. 이 코드는 필요한 데이타를 추출하고 가공하여 어셈파일을 생성
• domain_page.c - 도메인 페이지의 임시 매핑을 허용
• gdbstub.c - x86_32 종속적인 gdb stub 루틴
• gpr_switch.S - 게스트와 호스트간의 범용 레지스터 컨텍스트 스위치
• machine_kexec.c – machine_kexec_get_xen() 함수
• mm.c – 32비트 보호 모드 메모리 관리자
• seg_fixup.c - 일반 보호 폴트(General-Protection Fault)에서 호출되는 세그먼트 수리 루틴
• supervisor_mode_kernel.S - 관리자 모드(링 0)에서 실행되는 게스트를 위한 스택 개조 처리
• traps.c - 32비트 보호 모드 Xen 예외 처리
• pci.c - 아키텍처 종속적인 PCI 접근 함수
• Makefile – Makefile


(XEN)/xen/arch/x86/x86_64 - x86 롱 모드(64비트 모드/호환 모드)
• entry.S - 롱 모드 저수준 하이퍼컬과 예외 처리 루틴
• acpi_mmcfg.c - 저수준 ACPI 초기화 지원
• asm-offsets.c - 어셈블리 언어 모듈에서 필요한 정의를 생성. 이 코드는 필요한 데이타를 추출하고 가공하여 어셈파일을 생성
• compat.c - 호환 모드 하이퍼콜 정의
• compat_kexec.S - 호환 모드 kexec 핸들러
• cpu_idel.c - 호환 모드 게스트를 위한 cpu 유휴 루틴
• cpufreq.c - 32비트 호환 게스트를 64비트 하이퍼바이저에 맞춤
• domain.c – arch_compat_vcpu_op() 함수
• gdbstub.c - x86_64 종속적인 gdb stub 루틴
• gpr_switch.S - 게스트와 호스트간의 범용 레지스터 컨텍스트 스위치
• machine_kexec.c – machine_kexec_get_xen() 함수 코드
• mm.c – 64비트 모드 메모리 관리자
• mmconfig.h – PCI MMIO(Memory-Mapped I/O) 설정 함수
• mmconf_fam10h.c – AMD Family 10h MMIO 설정
• mmconfig-shared.c – 저수준 PCI MMIO(32비트, 64비트 공용)
• mmconfig_64.c – 저수준 PCI MMIO(64비트)
• physdev.c - 64비트 모드 관련 phydev 정의
• platform_hypercall.c - 64비트 모드 관련 하이퍼콜 정의
• pci.c - 아키텍처 종속적인 PCI 접근 함수
• traps.c – 64비트 롱 모드 Xen 예외 처리
• Makefile – Makefile

(XEN)/xen/arch/x86/x86_64/compat - x86 호환 모드
• entry.S - 호환 모드 저수준 하이퍼콜과 예외 처리
• mm.c – 64비트 호환 모드 메모리 관리자
• traps.c – 64비트 호환 모드 Xen 예외 처리
• Makefile - Makefile

(XEN)/xen/arch/x86/x86_emulate - x86 아키텍처 에뮬레이터
• x86_emulate.c - x86 (32비트 모드와 64 비트 모드) 명령어 디코더와 에뮬레이터
• x86_emulate.h - x86 (32비트 모드와 64 비트 모드) 명령어 디코더와 에뮬레이터 헤더 파일

(XEN)/xen/common - Xen 하이퍼바이저 공통 루틴
• bitmap.c - 비트맵 관련 함수들
• bunzip2.c - bzip2 압축 해제 루틴
• cpu.c - CPU 관리
• cpupool.c - CPUPOOL 루틴
• decompress.h - 압축 헤제 헤더 파일
• decompress.c - 압축 헤제
• domain.c - 도메인 처리(시작, 종료, 중단, 복구, etc) 관련 함수
• domctl.c - 도메인 제어 오퍼레이션
• event_channel.c - 이벤트 채널. 이벤트는 VIRQ, PIRQ등과 같이 도메인에 비동기 알림 용도로 사용한다.
• gdbstub.c - 아키텍처 독립적인 gdb stub
• grant_table.c - 그랜트 테이블. 페이지 단위 메모리 공유 매커니즘
• inflate.c - gunzip 압축해제 루틴
• kernel.c - 커맨드 라인 파서와 아키텍처 독립적인 초기화 루틴 및 간단한 하이퍼콜 구현
• kexec.c - 아키텍처 독립적인 kexec 코드
• keyhandler.c - 디버깅 용도로 사용하는 key 핸들러
• lib.c - 64비트 연산 라이브러리
• lzo.c - LZO1X 압축 루틴
• memory.c - 메모리 관련 요청을 처리하는 코드
• multicall.c - 다중 하이퍼콜 처리
• notifier.c - 알림 체인 관리 루틴
• page_alloc.c - Xen 하이퍼바이저에서 사용하는 단순한 버디 메모리 할당자
• perfc.c – 성능 카운터 함수
• preempt.c – 선점 카운터 변수
• radix-tree.c – Radix 트리 구현
• rangeset.c - 각 도메인별 숫자 범위 집합
• rbtree.c - Red-Black 트리 구현
• rcupdate.c - 상호 배제를 위한 RCU(Read-Copy Update) 매커니즘
• sched_arinc653.c - ARINC 653 스케쥴러
• sched_credit.c - Credit 스케쥴러
• sched_credit2.c - Credit2 스케쥴러
• sched_sedf.c - Simple EDF 스케쥴러
• schedule.c - 스케쥴러의 추상 레이어 및 공통 루틴
• shutdown.c – 도메인 0 셧다운 관련 코드
• softirq.c - Xen의 SOFTIRQ(인터럽트 후반부 처리)
• sort.c - 정렬 루틴
• spinlock.c – 스핀락 함수
• stop_machine.c - 시스템 중단
• string.c – 문자열 라이브러리 함수
• symbols.c - 커널에서 출력되는 심볼릭 oops와 스택 추적
• symbols-dummy.c - 하이퍼바이저 이미지 링크에 사용되는 더미 심볼 테이블
• sysctl.c - 시스템 제어 동작
• tasklet.c – Xen의 태스크릿(인터럽트 후반부 처리)
• time.c – 날짜와 시간 함수
• timer.c – 타이머
• tmem.c – TMEM(Transcendent Memory) 메모리 할당자
• tmem_xen.c – Xen 종속적인 TMEM 루틴
• trace.c - 디버깅 용도로 사용되는 추적 버퍼 코드
• unlzma.c - Lzma 압축 해제 루틴
• unlzo.c - LZO 압축 해제 루틴
• version.c – 컴파일 날짜에 따른 Xen 버전 넘버
• vsprintf.c – 문자열 변환 루틴
• wait.c – 하이퍼바이저 컨텍스트에서 이벤트 대기(sleep)
• xencomm.c – 게스트간 페이지 메모리 통신
• xenoprof.c - Xen 프로파일링
• xmalloc_tlsf.c - TLSF(Two Levels Segregate Fit) 메모리 할당자
• Makefile - Makefile

(XEN)/xen/common/compat - 호환성 지원
• domain.c – compat_vcpu_op() 함수
• grant_table.c – 그랜트 테이블 호환 버전
• kernel.c – 몇가지 #define을 호환 버전으로 재정의
• memory.c – 메모리 관리자
• multicall.c - 호환 버전 다중 하이퍼콜 처리
• schedule.c - 호환 버전 스케쥴러
• tmem_xen.c - Xen 종속적인 TMEM 루틴
• xenoprof.c - 호환 버전 xenoprof 관련 코드
• xlat.c – 32비트 <-> 64비트 변환 정보
• Makefile – Makefile

(XEN)/xen/common/hvm - HVM 관련 루틴
• save.c - HVM 게스트의 에뮬레이트된 하드웨어 상태 저장 및 복구
• Makefile - Makefile

(XEN)/xen/common/libelf - ELF 실행 파일 포맷 라이브러리
• libelf-dominfo.c - ELF 커널 바이너리의 Xen 특화된 정보 분석
• libelf-loader.c - ELF 바이너리 분석 및 로드
• libelf-private.h – ELF 자료구조와 정의
• libelf-relocate.c - ELF 재배치 코드
• libelf-tools.c - ELF 자료구조에 접근하기 위한 다양한 헬퍼 함수
• Makefile – Makefile
• README – “이 코드는 xen과 tools 두군데서 사용된다.”
• COPYING – libelf 소스 코드 라이센스

(XEN)/xen/crypto - 암호화
• rijndael.c - AES 암호 알고리즘(Rijndael 알고리즘) 루틴
• vmac.c - VMAC와 VHASH 구현
• Makefile – Makefile

(XEN)/xen/drivers - 디바이스 드라이버
• Makefile - Makefile

(XEN)/xen/drivers/acpi - ACPI 드라이버
• hwregs.c - ACPI 레지스터 읽기/쓰기 함수
• numa.c - ACPI NUMA 지원
• osl.c - OS 종속적인 함수
• pmstat.c - 전원 관리 통계 정보
• reboot.c – 리부트 함수
• tables.c - ACPI 부트-타임 테이블 파싱
• Makefile - Makefile

(XEN)/xen/drivers/acpi/apei - APEI(ACPI Platform Error Interface)
• apei-base.c - APEI 코어 루틴
• apei-internal.h - APEI 헤더 파일
• apei-io.c - APEI I/O
• erst.c - APEI 에러 기록 직렬화 테이블(ERST, Err Record Serialization Table)
• Makefile - Makefile

(XEN)/xen/drivers/acpi/tables - ACPI 테이블
• tbfadt.c - FADT 테이블 유틸
• tbinstal.c - ACPI 테이블 설치와 제거
• tbutils.c - 테이블 유틸
• tbxface.c - ACPI 서브시스템을 위한 공용 인터페이스
• tbxfroot.c - 루트 ACPI 테이블(RSDT) 검색
• Makefile - Makefile

(XEN)/xen/drivers/acpi/utilities - ACPI 유틸
• utglobal.c - ACPI 서브시스템을 위한 글로벌 변수
• utmisc.c - 공통 유틸 프로시저
• Makefile - Makefile

(XEN)/xen/drivers/char - 문자 드라이버
• console.c - Xen과 도메인 0를 위한 콘솔 드라이버
• ns16550.c - 16550 시리즈 UART 드라이버
• serial.c - 시리얼 드라이버 프레임웍
• Makefile – Makefile

(XEN)/xen/drivers/cpufreq - CPU 주파수 제어 드라이버
• cpufreq.c - CPU 주파수 제어 드라이버 메인
• cpufreq_misc_governors.c - cpufreq 거버너 공통 코드
• cpufreq_ondemand.c - ondemand 방식 거버너. ondemand는 CPU 부하가 95%의 경우 동적으로 전환하는 방식이다.
• utility.c - cpufreq 드라이버와 Px 통계를 위한 Misc 함수
• Makefile - Makefile

(XEN)/xen/drivers/passthrough - 패쓰쓰루 드라이버
• io.c - 패쓰쓰루 I/O
• iommu.c - IOMMU 제어 루틴
• pci.c - PCI 드라이버
• Makefile- Makefile

(XEN)/xen/drivers/passthrough/amd - AMD의 IOMMU
• iommu_acpi.c – IOMMU ACPI
• iommu_detect.c - IOMMU 검출
• iommu_init.c - IOMMU 초기화
• iommu_intr.c - IOMMU 인터럽트
• iommu_map.c - IOMMU 맵
• pci_amd_iommu.c - PCI와 관련된 IOMMU 루틴
• Makefile - Makefile

(XEN)/xen/drivers/passthrough/vtd - 인텔의 VT-d
• dmar.c - DMA 리매핑 테이블
• dmar.h - DMAR 헤더 파일
• extern.h - 외부 참조 함수 헤더 파일
• intremap.c - 인터럽트 리매핑
• iommu.c - IOMMU 관련 루틴
• iommu.h - IOMMU 헤더 파일
• qinval.c - VT-d 큐 무효화 루틴
• quirks.c - VT-d 쿼크
• utils.c - VT-d 관련 유틸
• vtd.h - VT-d 헤더 파일
• Makefile - Makefile

(XEN)/xen/drivers/passthrough/vtd/ia64 - IA64 아키텍처 종속적인 VT-d
• ats.c - 주소 변환 서비스(Address Translation Service)
• vtd.c - IA64 아키텍처 VT-d 루틴
• Makefile - Makefile

(XEN)/xen/drivers/passthrough/vtd/x86 - x86 아키텍처 종속적인 VT-d
• ats.c - 주소 변환 서비스(Address Translation Service)
• vtd.c - x86 아키텍처 VT-d 루틴
• Makefile - Makefile

(XEN)/xen/drivers/pci - PCI 드라이버
• pci.c - 아키텍처 독립적인 PCI 접근 함수
• Makefile - Makefile

(XEN)/xen/drivers/video - VIDEO 드라이버
• font.h – 폰트 자료구조 헤더 파일
• font_8x8.c - 8x8 크기 폰트
• font_8x14.c- 8x14 크기 폰트
• font_8x16.c - 8x16 크기 폰트
• vesa.c - VESA 선형 프레임 버퍼 처리
• vga.c - VGA 지원 루틴
• Makefile - Makefile

(XEN)/xen/tools - Xen 하이퍼바이저 툴
• compat-build-header.py - 호환(compat) 버전 헤더 파일 생성 스크립트
• compat-build-source.py - 호환 버전 소스 파일 생성 스크립트
• get-fields.sh - xlat.h 헤더 생성 스크립트
• symbols.c - 심볼 정보가 담긴 어셈 코드 생성
• Makefile- Makefile

(XEN)/xen/tools/figlet - 아스키 문자열로 그림을 생성하는 툴
• figlet.c - figlet 소스 파일
• LICENSE - figlet 라이센스
• Makefile – Makefile
• README - figlet 설명서
• xen.flf - figlet으로 생성하는 아스키 그림 데이타 파일

(XEN)/xen/xsm - XSM(Xen Security Modules) 보안 모듈
• dummy.c – 다양한 더미 함수들
• xsm_core.c – XSM 코어 함수
• xsm_policy.c - XSM 정책 설정 루틴
• Makefile - Makefile

(XEN)/xen/xsm/acm - ACM(Access Control Module)
• acm_chinesewall_hooks.c - Chinese Wall 모델(Brewer and Nash 모델) 후킹 루틴
• acm_core.c - 도메인의 보안 식별자 초기화/해제 및 ACM 초기화 처리
• acm_null_hooks.c - 무정책 후킹 루틴
• acm_ops.c - 게스트 운영체제의 ACM 커맨드 요청 처리
• acm_policy.c - Xen의 접근 제어 정책 관리
• acm_simple_type_enforacment_hooks.c - STE(Simple Type Enforcement) 모델 후킹 루틴
• acm_xsm_hooks.c - 오리지널 ACM 후킹 기반으로 한 XSM의 후킹
• Makefile- Makefile

(XEN)/xen/xsm/flask - flask 툴 관련 소스 파일
• avc.c - AVC(Access Vector Cache) 구현
• flask_op.c - flask_op 하이퍼콜과 관련 함수
• hooks.c - flask 후킹 함수 구현
• Makefile - Makefile

(XEN)/xen/xsm/flask/ss
• avtab.c - 접근 벡터 테이블(Access Vector Table) 구현
• avtab.h - 접근 벡터 테이블 헤더 파일
• conditional.c - 조건부 처리 루틴 
• conditional.h - 조건부 처리 헤더 파일
• constraint.h - 제한된 표현
• context.h - 보안 컨텍스트
• ebitmap.c - 확장가능한 비트맵 구현
• ebitmap.h - 확장가능한 비트맵 헤더 파일
• hashtab.c - 해쉬 테이블
• hashtab.h - 해쉬 테이블 헤더 파일
• mls.c - MLS(Multi-Level Security) 정책 루틴
• mls.h - MLS 헤더 파일
• mis_types.h - MLS 정책 자료구조
• policydb.c - 정책 DB 구현
• policydb.h - 정책 DB 헤더 파일
• services.c - 보안 서비스 구현
• services.h - 보안 서비스 헤더 파일
• sidtab.c - SID(Security Identifier) 테이블 구현
• sidtab.h - SID 테이블 헤더 파일
• symtab.c - 심볼 테이블
• symtab.h - 심볼 테이블 헤더 파일
• Makefile - Makefile

2013년 6월 2일 일요일

ARM Cortex-A 페이징 (2)

ARM Cortex 페이징 (1)에서 이어지는 내용입니다.

2단계 페이징


작은 크기 페이지들인 Large Page(64KB)와 Small Page(4KB)를 사용하고자 할 때는 2단계 페이징을 합니다. 물론 크기가 작은 페이지도 하나의 페이지 테이블을 활용하는 1단계 페이징으로 할 수 있습니다만, 이것은 매우 비효율적입니다. 예를 들어, 4KB 페이지를 하나의 페이지 테이블로 페이징한다고 가정해 봅시다. 32비트 머신에서 가장 주소 공간은 4GB이므로, 총 1048576개의 엔트리가 필요합니다. 하나의 엔트리 크기는 4B이므로, 페이지 테이블의 크기는 총 4MB가 됩니다. 페이지 테이블은 프로세스마다 존재하므로 프로세스를 생성할때마다 페이지 테이블 용도로 4MB 메모리 공간을 할당하는 것은 낭비가 너무 심합니다. 이것을 2단계 페이징으로 변경하면, 16KB 크기의 1단계 페이지 테이블을 할당하고, 2단계 페이지 테이블은 가상 주소 영역에서 필요한 공간만 할당해서 사용할 수 있습니다. 이것이 다단계 페이징을 하는 이유입니다. 앞으로 2단계 페이지 테이블도 L1 페이지 테이블과 마찬가지로 L2 페이지 테이블(Level 2 Page Table)이라고 하겠습니다.


<그림 4. 2단계 페이징>

<그림 4>는 2단계 페이징 과정을 보여줍니다. MMU는 우선 TTBA(Translation Table Base Address)에서 L1 페이지 테이블의 시작 주소를 얻어옵니다. L1 페이지 테이블의 시작 주소에서 가상 주소 [31..20] 비트를 오프셋으로 사용하여 접근하려는 L1 페이지 테이블 엔트리의 주소를 구합니다(Level 1 Descriptor Address). L1 페이지 테이블 엔트리에서는 2단계 페이지 테이블 시작 주소가 저장되어 있습니다. L2 페이지 테이블 시작 주소(Level 2 Table Base Address)에서 가상 주소 [19..12] 비트를 오프셋으로 사용하여 L2 페이지 테이블 엔트리(L2 Page Table Entry)의 주소를 구합니다(Level 2 Descriptor Address). 메모리에서 L2 페이지 테이블 엔트리를 읽어오면, L2 페이지 테이블 엔트리에 저장된 가상 주소에 해당하는 페이지 프레임의 시작 주소(Small Page Base Address)를 알 수 있습니다. 마지막으로 페이지 프레임의 시작 주소에서 가상 주소 [11..0] 비트를 오프셋으로 사용하여 최종 접근하는 물리 주소(Physical Address)를 구하게 됩니다.

사실 2단계 페이징은 단계만 하나 늘었다뿐이지 1단계 페이징과 별반 다른점이 없습니다. <L1 페이지 테이블 시작 주소>, <L2 페이지 테이블 시작 주소>, <페이지 프레임 시작 주소>는 각각 <TTBA>, <L1 페이지 테이블 엔트리>, <L2 페이지 테이블 엔트리>에 저장되어 있고, 가상 주소를 나눠서 오프셋(인덱스)으로 사용한다는 점만 유념합시다.

2단계 페이징 과정도 좀 더 명확하게 이해하기 위해 예를 들어 볼까요? 아래와 같이 가정하고 2단계 페이징이 어떻게 되는지 보겠습니다.

  • 가상 주소 = 0xC0010010
  • 물리 주소 = 0x00010010
  • L1 페이지 테이블 시작 주소 = 0x4000
  • L2 페이지 테이블 시작 주소 = 0x9000

먼저 TTBA에 저장된 L1 페이지 테이블 시작 주소에서 L1 페이지 테이블 시작 주소를 얻어옵니다. L1 페이지 테이블 시작 주소에서 가상 주소의 [31..20] 비트를 오프셋으로 사용하여 L1 페이지 테이블 엔트리를 구합니다.

  • 0x4000(TTBA) + 0xC00(L1 PT Index) = 0x4C00(L1 Page Table Entry)

L1 페이지 테이블 엔트리에는 L2 페이지 테이블의 시작 주소가 저장되어 있습니다. L2 페이지 테이블 시작 주소에서 가상 주소 [19..12] 비트를 오프셋으로 사용하여 L2 페이지 테이블 엔트리를 구합니다.

  • 0x9000(L2 Page Table Base) + 0x10(L2 PT Index) = 0x9010(L2 Page Table Entry)

L2 페이지 테이블 엔트리에는 페이지 프레임의 주소가 저장되어 있습니다. 페이지 프레임의 주소에서 가상 주소 [11..0] 비트를 오프셋으로 사용하여 최종 물리 주소를 구할 수 있습니다.

  • 0x10000(Page Frame Base) + 0x10(Page Frame Index) = 0x10010(Physical Address)

페이지 크기가 작아질수록, 가상 주소 공간이 늘어 날수록 페이징 단계가 늘어나게 됩니다. ARM Cortex-A를 포함하여 전통적인 ARM 아키텍처는 32비트 가상 주소 공간을 제공합니다. 페이지 크기가 16MB, 1MB일때는 1단계 페이징, 페이지 크기가 64KB, 4KB일때는 2단계 페이징을 하였습니다. 반면에 우리가 PC로 많이 사용하고 있는 x86-64 아키텍처와 ARMv8의 aarch64 아키텍처는 64비트 가상 주소를 사용하는 64비트 아키텍처입니다. 64비트 아키텍처는 늘어난 가상 주소 공간을 지원하기 위해 주로 4단계 페이징을 합니다.(물론, 64비트 아키텍처도 페이지 크기에 따라 페이징 단계가 다릅니다.) 어쨋든, 페이징 단계가 늘어나더라도 각 단계별 페이지 테이블과 페이지 프레임의 오프셋으로 가상 주소를 분할하여 사용하는 것은 모두 동일합니다.

자 그럼, L2 페이지 테이블 엔트리들이 어떻게 생겼는지 한번 살펴 보겠습니다.

<그림 5. L2 페이지 테이블 엔트리의 종류>

L2 페이지 테이블 엔트리도 L1 페이지 테이블 엔트리와 마찬가지로 하위 2비트로 페이지 테이블 엔트리의 종류를 판별합니다. Lage page는 64KB 페이지를 사용하는 페이지 테이블 엔트리이고, Small page는 4KB 페이지를 사용하는 페이지 테이블 엔트리입니다. L2 페이지 테이블 엔트리는 페이지 프레임의 시작 주소를 저장하는 필드와 메모리 속성과 접근 권한을 설정하는 필드가 있습니다. 각 필드의 의미는 L1 페이지 테이블 엔트리의 것과 동일합니다. (참고> ARM Cortex 페이징 (1)) 2단계 페이징에 사용되는 L1 페이지 테이블 엔트리(Page table)에는 SectionSupersection에 있던 메모리 속성과 접근 권한을 설정하는 필드가 없고, L2 페이지 테이블 엔트리에 존재하는 것 입니다. 

메모리 속성


페이지 테이블 엔트리의 TEX, C, B등의 필드로 메모리 속성을 설정한다고 했는데, 메모리 속성을 도대체 어떻게 설정하는지 한번 알아보겠습니다. 우선, TEX, B, C 필드 값에 따라서 다음과 같이 메모리 속성이 설정 됩니다.

< 테이블 1. 페이지 테이블 엔트리 값에 따른 메모리 타입과 캐시 속성 설정 >
<테이블 1>의 메모리 타입은 메모리 오더링을 어떻게 하는지에 따라 달라집니다. ARM Cortex-A는 세가지 메모리 오더링 모델이 존재합니다.

  • Strongly-ordered: CPU로 부터 순차적으로 메모리 접근을 보장합니다. 캐싱(C)과 버퍼링(B)를 하지 않습니다.
  • Device: ARM은 MMIO을 사용하므로 해당 메모리 주소 영역이 디바이스 I/O 주소를 의미합니다.
  • Normal: 데이터를 저장하는 일반적인 메모리를 의미합니다. 

메모리 타입이 Normal의 Description를 보시면 Outer, Inner라는 것이 있는데, 이것은 캐시를 의미합니다. 만약, 시스템의 메모리 계층이 CORE <-> L1 명렁어/데이터 캐쉬 <-> L2 캐쉬 <-> 메모리  이렇게 가장 기본적인 형태로 되어 있다고 하면 일반적으로 L1 캐시를 Inner로 설정하고, L2 캐시를 Outer로 설정해서 사용합니다. 캐시는 시스템에 따라 많은 계층을 가질 수 있고, 시스템 프로그래머가 시스템 특성에 맞게 캐시를 Inner와 Outer로 설정할 수 있습니다. Inner와 Outer 별로 캐쉬 정책을 설정 할 수 있다는 것만 알아두도록 합시다.

write-throughwrite-back는 캐쉬 정책을 의미합니다. write-through는 쓰기 연산시 데이터를 캐쉬와 메모리에 동시에 반영하는 것이고, write-back은 캐시에만 반영하고 메모리에는 추후에 캐시에 저장된 데이터를 한번에 반영하는 것입니다. write-back 방식은 메모리 접근 횟수가 적으므로 성능상의 이점이 있지만 메모리에 쓰기 연산을 한 데이터가 적용되어 있지 않으므로 데이터 무결성 문제가 발생 할 수 있습니다.

TEX의 최상위 비트를 1로 설정하면 Outer, Inner 별로 캐시 정책을 설정 할 수 있습니다. (00: non-bacheable, 10: write-through, 11:write-back)


도메인


도메인은 ARM에서 메모리 영역을 구분하는 매커니즘입니다. 도메인은 L1 페이지 테이블 엔트리에서 총 16개의 도메인 ID로 설정 가능하며, 도메인 별로 접근 권한을 다르게 할 수 있습니다. 도메인 설정은 DACR(Domain Access Control Register)에서 하게 되는데, 2비트로 하나의 도메인을 설정합니다.

<그림 6. DACR>
다음과 같이 DACR의 값을 읽거나 설정 할 수 있습니다.

mrc    p15, 0, <Rt>, c3, c0, 0 ; DACR 값을 Rt에 읽어옵니다.
mcr    p15, 0, <Rt>, c3, c0, 0 ; Rt 값을 DACR에 설정합니다.


DACR의 각 도메인에 설정하는 2비트의 값에 따라 도메인은 다른 속성을 가집니다. DACR에 설정하는 2비트 값의 의미는 다음과 같습니다.

  • 0b00: No access. 해당 도메인에 접근하면 도메인 폴트(Domain Fault)가 발생합니다.
  • 0b01: Client. 페이지 테이블 엔트리의 접근 권한을 체크합니다.
  • 0b10: Resesrved.
  • 0b11: Manager. 페이지 테이블 엔트리의 접근 권한을 체크하지 않습니다.

페이지 테이블 엔트리에는 접근 권한을 설정하는 AP 필드가 있습니다(<그림 3>, <그림 5> 참고). 이 필드가 의미를 가지려면 도메인을 Client로 설정해야 합니다. 도메인을 Manager로 설정해두면 페이지 테이블 엔트리의 AP 필드 값에 따른 접근 권한을 체크하지 않고 해당 도메인은 무조건 접근이 가능합니다. 도메인을 Client로 설정하고, 페이지 테이블 엔트리의 AP 필드를 체크하여 도메인의 접근 권한을 위배하였을 경우에는 퍼미션 폴트(Permission Fault)가 발생합니다. 

ARM은 두단계를 거쳐서 메모리 접근 권한을 체크합니다. 먼저, 페이지 테이블 엔트리의 Domain 필드에서 도메인 ID를 가져와서 해당 도메인이 Manager 또는 Client인지 확인하고, Client이면 페이지 테이블 엔트리의 AP 필드를 확인하여 메모리 접근 권한을 체크합니다.

접근 권한


페이지 테이블 엔트리에 설정된 접근 권한은 해당 메모리가 속한 도메인이 Client일 경우에만 체크합니다. 페이지 테이블 엔트리의 APX, AP 필드 값에 따라 다음과 같이 접근 권한을 설정 할 수 있습니다.

<테이블 2. 페이지 테이블 엔트리 접근 권한 >

위 테이블은 페이지 테이블 엔트리의 APX/AP 필드 값에 따라 Privileged/Unprivileged의 접근 권한에 대해 나타냅니다. ARM은 총 7가지 모드(SVC/USR/SYS/ABT/IRQ/FIQ/UND)을 가지고 있는데, User 모드만 비특권(Unprivileged) 모드에 해당되고, 나머지 6가지 모드는 모두 특권(Privileged) 모드입니다. 커널은 Supervisor(SVC) 모드에서 주로 동작하며, 만약 인터럽트가 발생하면 Interrupt Request(IRQ) 모드로 진입하여 인터럽트 처리를 하고 다시 SVC 모드(혹은 USR 모드)로 되돌아가는 것이 모드 전환의 한가지 예로 들 수 있습니다. 특권 모드간, 특권 모드에서 User 모드로의 모드 전환은 CPSR 상태 레지스터 조작으로 할 수 있습니다.

커널에서 사용하는 데이터 영역은 특권 모드에서 Read/Write 할 수 있도록 설정하고, 유저 모드에서 동작하는 사용자 프로세스가 접근하지 못하도록 설정해야 합니다. 그리고, 프로그램 코드 영역으로 사용하는 메모리 공간은 수정이 되면 안되므로 Read만 가능하도록 설정해서 사용해야 합니다. 보안을 고려한 운영체제라면 사용하는 메모리 공간의 용도에 맞게 적절히 메모리 접근 권한을 설정해야 합니다.


< 참고자료 >
[1] Cortex -A Series Programmer’s Guide
[2] Cortex -A9 Technical Reference Manual
[3] ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition

2013년 4월 25일 목요일

Mailive: 실시간 메일링리스트 모니터링 시스템

소프트웨어 개발은 관리자, 기획자, 개발자, 디자이너등 개발에 관련된 다양한 사람들이 모여서 각자 맡은 역활을 충실해야 좋은 소프트웨어가 완성됩니다. 개발자 입장에서 보았을때 소프트웨어 개발은 소스 코드를 작성 하는 것과 일맥상통합니다. 즉, 좋은 소스 코드를 작성할수록 좋은 소프트웨어가 만들어집니다. 다른 직업군(특히, 관리자)분들은 이러한 점을 이해하지 못하고 단기간에 결과물만 내놓기를 바라셔서 눈가리고 아웅하는 쓰레기 결과물만 만들어지거나 프로젝트 자체가 드랍이 되어 버리는 안타까운 일이 비일비재하게 발생하고 있습니다.

어쨋든, 본인이 개발자라면 좀 더 좋은 코드를 작성하기 위해서 끊임없이 단련해야 할 텐대요. 그러기 위한 가장 좋은 방법이 오픈 소스 프로젝트에 참여하는 것 입니다. 좋은 소스 코드를 쓰는 것은 좋은 글 쓰는 것과 똑같습니다. 좋은 코드를 많이 보고, 좋은 코드를 쓰려고 의식하면서 코드를 많이 써보면 자연스럽게 늘게 됩니다.

저는 시스템 프로그래머이므로 리눅스 커널로 대표되는 오픈 소스 시스템 소프트웨어들(Linux Kernel, Xen, KVM/ARM, U-boot)에 많은 관심을 가지고 있습니다. DINOS 운영체제 프로젝트를 릴리즈하기 전에는 여유가 없어서 아직 적극적으로 참여하고 있는 프로젝트는 없지만 틈틈히 소스 코드를 보면서 배워나가고 있습니다.

특정 오픈 소스 프로젝트의 코드에 익숙해지고 나면, 현재 개발이 어떻게 진행되고 있는지 확인하려면 메일링리스트를 구독해야 합니다. 오픈 소스 프로젝트마다 개발 진행 방법이 모두 다르겠지만, 제가 관심을 가지고 있는 오픈 소스 프로젝트들은 모두 메일링리스트에서 개발이 이루어 집니다. 개발버전 원격 저장소 코드에서 기능을 추가하거나 버그를 고친 패치를 작성하여 담당자(=커미터=메인테이너)와 메일링리스트에 메일 보내고, 해당 모듈 담당자는 이메일로 수신한 패치 코드를 리뷰 한 뒤 이상이 없으면 승인하는 방식으로 진행됩니다. 물론, 코드 리뷰는 메일링리스트를 구독하고 있는 다른 개발자가 할 수도 있습니다. 그 외, 해당 프로젝트의 로드맵이나 이슈등이 메일링리스트에서 논의되고 있으므로 실제로 오픈 소스 프로젝트에 참여하기 위해서는 메일링리스트 구독이 필수 입니다.

그래서 관심있는 메일링리스트 내용을 실시간으로 보기 위해 Mailive라는 웹 페이지를 만들었습니다. 제가 관심을 가지고 있는 Linux ARM kernel, U-boot, KVM/ARM, QEMU, Xen등의 메일링리스트 내용을 실시간으로 볼 수 있습니다. 웹 소켓으로 구현한 웹 페이지이므로 웹 소켓을 지원하지 않는 브라우저는 실시간 업데이트가 되지 않습니다. (참고> 웹 소켓 지원 브라우저)

오류 보고 및 기능 추가 요청이나 멋진 아이디어가 있으시면 언제든지 저에게 연락주시기 바랍니다. :)

  • 추가

서버 호스팅이 만료되어 코드 공개합니다. -> https://github.com/Taehun/mailive

2013년 4월 5일 금요일

ARM Cortex-A 페이징 (1)

페이징

우리가 사용하는 어플리케이션은 모두 가상 주소(Virtual address)라는 실제 물리 메모리의 주소가 아닌 논리적 메모리 주소를 사용합니다. 그럼 이 가상 주소를 실제 메모리의 주소인 물리 주소(Physical Address)로 변환해야 할 필요가 있는데, 이러한 가상 주소를 물리 주소로 변환하는 매커니즘을 페이징(Paging)이라고 합니다. 페이징은 가상 주소를 사용하는 시스템이면 메모리 접근시마다 해야하므로 비교적 많은 오버헤드가 걸립니다. 그래서 페이징을 사용하는 시스템은 모두 CPU에 포함된 MMU(Memory Management Unit)에서 하드웨어적으로 처리하게 됩니다.

<그림 1. 가상 주소와 물리 주소>

어플리케이션을 좀 더 명확히 구분하기 위하여 컴퓨터 과학 분야에서 사용하는 용어로 바꾸겠습니다. 하드 디스크나 플래시 메모리등의 보조기억장치에서 실행가능한 파일 형태로 존재하는 프로그램(Program)을 실행 하기 위해 메모리 로드된 것을 프로세스(Process)라고 합니다. 모든 프로세스는 힙(Heap), 스택(Stack), 데이터(Data), 코드(Code)등의 영역을 가지고 있고 이것은 동일한 가상 주소를 사용합니다. <그림 1>에서 왼쪽 부분이 프로세스가 사용하는 가상 주소 공간의 메모리 맵을 나타냅니다. 하지만 실제 물리 메모리 주소는 <그림 1>의 오른쪽 부분처럼 되어 있습니다. 데이터를 저장하는 RAM 영역과 읽기만 가능한 ROM 영역, MMIO(Memory Mapped I/O)로 디바이스 I/O를 하기 위한 Peripheral 영역등이 물리 메모리 주소 공간에 존재합니다. 각 프로세스가 사용하는 가상 메모리 영역은 동일하지만 이것은 실제로는 각각 다른 물리 메모리 영역을 사용합니다. 물론, 커널 영역등의 시스템에서 전역적으로 사용하는 메모리 영역은 모든 프로세스가 같은 가상 주소에 같은 물리 주소가 매핑되어 있습니다.

사용자는 메모리를 연속된 공간으로 인식하여 사용하지만, 실제로 메모리를 관리하는 주체인 운영체제 커널이나 CPU는 메모리를 일정한 크기의 블록 단위로 관리합니다. 가상 메모리 공간을 일정한 크기로 나눈 것을 페이지(Page), 물리 메모리 공간을 일정한 크기로 나눈 것을 프레임(Frame) 혹은 페이지 프레임(Page Frame)이라고 합니다. 페이지와 페이지 프레임은 페이징 단위가 되므로 기억해 둡시다. 페이지와 프레임의 크기는 동일한 크기를 사용하며 일반적으로 4KB를 많이 사용하지만, 용도에 따라 다양한 크기로 설정 가능 합니다. 

페이징을 하기 위해서는 페이지 테이블(Page Table)이라는 가상 주소를 물리 주소로 변환하는 정보를 담고 있는 변환 테이블이 필요 합니다. 페이지 테이블은 메모리 공간 효율성 등의 이유로 여러 단계로 이루어져 있습니다. 운영체제 커널과 같은 시스템 프로그램은 초기화 과정에서 페이지 테이블을 작성하고, MMU에 페이지 테이블의 주소를 알려줍니다. 그리고 페이징을 활성화하게 되면 이제부터 MMU가 페이지 테이블을 참조하여 주소 변환을 하게 되므로 물리 주소가 아닌 가상 주소를 사용하게 되는 것 입니다. 페이징을 활성화하는 전반적인 흐름은 모두 이와 같습니다만 세부적인 방법은 아키텍처마다 모두 다릅니다. 이제부터 우리가 스마트폰이나 태블릿 PC등으로 널리 사용 중인 ARM Cortex-A 아키텍처의 페이징에 대해 알아 보겠습니다.

ARM Cortex-A 페이징

ARM 아키텍처를 처음 배우시는 분들은 ARM System Developer's Guide라는 걸출한 ARM 아키텍처 서적 때문에 ARM9 아키텍처(ARMv4 코어)로 공부하시는 분들이 많습니다. 애석하게도 ARM Cortex-A가 사용하는 ARMv7-A 코어는 ARMv4 코어에서 페이징 루틴이 변경되었으므로 유의해야 합니다.

ARM Cortex-A는 1단계 혹은 2단계 페이징을 지원합니다. 1단계 페이징은 1MB 페이지와 16MB 페이지를 지원합니다. 2단계 페이징은 4KB 페이지와 16KB 페이지를 지원합니다. 다양한 크기의 페이지를 용도에 따라 적절한 페이지 크기를 설정해서 사용 할 수 있습니다. 페이지 크기를 4KB로 하는 이유는 많이 알려져 있으니 페이지 크기를 크게 설정했을때 얻게되는 이점이 무엇인지 한번 알아 보겠습니다.

주소 변환은 CPU의 MMU에서 하드웨어적으로 처리한다고 하더라도 MMU가 주소 변환을 하기 위해서는 페이지 테이블을 참조해야 합니다. 즉, 페이징이 활성화 되어 가상 주소에 해당되는 물리 주소로 변환하려면 페이지 테이블을 참조하기 위해 메모리에 접근을 해야 한다는 것입니다. 1단계 페이징이면 1번의 메모리 접근을 하고, 2단계 페이징은 2번의 메모리 접근을 합니다. 당연히 메모리 접근 횟수가 늘어 날 수록 페이징에 걸리는 오버헤드가 커지게 됩니다. 페이지 크기를 크게 설정하면 페이징 단계가 줄어들게 되므로 MMU가 페이지 테이블을 참조하기 위해 메모리 접근 횟수를 줄일수 있다는 점이 첫번째 이점입니다.

마찬가지로 페이지 테이블 메모리 영역의 접근 횟수를 줄이기 위해 TLB(Translation Lookaside Buffer)라는 가상 주소를 물리 주소로 변환한 내용을 캐싱하는 캐시를 사용합니다. 최초에 가상 주소에 접근할때는 페이지 테이블을 참조하여 물리 주소로 변환합니다. 가상 주소를 물리 주소로 변환하는 작업이 완료되면 TLB에 <페이지 주소, 프레임 주소> 쌍으로 기입해두고 다음번에 동일한 페이지의 가상 주소를 접근할 시에는 TLB에 캐싱된 정보를 참조하여 곧바로 해당 물리 주소에 접근 하게 됩니다. 운영체제가 실행 도중에 문맥 교환(Context Switch)이 발생해 다른 프로세스로 전환 하게 되면 TLB에 저장된 가상 주소의 엔트리는 이전 프로세스가 사용하던 메모리 공간이 됩니다. 그래서 TLB의 캐싱 정보는 더이상 유효하지 않으므로 비워주어야 합니다.

주목해야 할 점은 TLB 크기는 한정적이라는 것입니다. 그래서 페이지 크기 단위가 클 수록 TLB에 많은 메모리 주소 영역을 캐싱 할 수 있습니다. 예를 들어, TLB에 10개의 엔트리만 저장 할 수 있다고 가정하면 1MB 페이지는 10MB 메모리 영역만 TLB에 캐싱 할 수 있습니다. 반면에 16MB 페이지는 160MB 메모리 영역을 TLB에 캐싱 할 수 있습니다. 이것이 페이지 크기를 크게 했을때 생기는 두번째 이점입니다.

ARMv7-A의 페이징은 크게 Short-descriptor translation과 Long-descriptor translation 두 종류로 나뉩니다. Long-descriptor translation은 큰 물리 메모리를 지원하는 LPAE(Large Physical Address Extension) 기능을 위한 것이므로 여기서는 Short-descriptor translation만 다루도록 하겠습니다.

앞서 ARM은 다양한 크기의 페이지를 사용할 수 있다고 하였습니다. 페이지 크기에 따라 다음과 같이 분류할 수 있습니다.
  • Supersection: 16MB 크기를 가진 페이지입니다. (1단계 페이징)
  • Section: 1MB 크기를 가진 페이지입니다. (1단계 페이징)
  • Large Page: 64KB 크기를 가진 페이지입니다. (2단계 페이징)
  • Small Page: 4KB 크기를 가진 페이지입니다. (2단계 페이징)

1 단계 페이징


1단계 페이지 테이블의 기본 주소는 코프로세서 CP15 c2 레지스터에 저장 되어 있습니다(페이징 초기화 루틴에서 설정합니다.). 1단계 페이지 테이블은 L1 페이지 테이블(Level 1 Page Table)라고 많이 부르니 앞으로 L1 페이지 테이블이라고 하겠습니다.

<그림 2. 1단계 페이징>

<그림 2>은 ARM Cortex-A의 1단계 페이징 과정을 나타냅니다. <그림 2>의 TTBA(Translation Table Base Address)는 L1 페이지 테이블의 시작 주소를 나타냅니다. TTBA의 상위 18비트에서 L1 페이지 테이블 시작 주소를 구하고, 여기에서 변환하고자 하는 가상 주소의 상위 12비트를 인덱스로 사용해서 L1 페이지 테이블 엔트리(L1 Page Table Entry)의 주소를 구합니다. <그림 2>의 'First Level Descriptor Address'가 이에 해당합니다. 0x000부터 0xfff까지 12비트를 인덱스로 사용하므로 L1 페이지 테이블의 엔트리의 갯수는 총 4096개가 됩니다. 이제 MMU는 메모리에서 L1 페이지 테이블 엔트리를 가져 옵니다(<그림 2>의 'Section Base Address Descriptor'). L1 페이지 테이블의 엔트리에는 가상 주소에 해당하는 페이지 프레임의 물리 주소가 저장되어 있습니다. 현재는 1MB 페이지를 예로 든것이므로 상위 12비트(1MB 단위)를 페이지 프레임의 주소로 사용합니다. 마지막으로 가상 주소의 하위 20비트를 페이지 프레임내의 오프셋으로 사용하면 가상 주소에 해당하는 물리 주소를 구할 수 있습니다.

이해를 돕기 위해 예를 들어 보겠습니다. 가상 주소는 0xC0001000번지, L1 페이지 테이블의 시작 주소는 물리 주소 0x4000, 매핑된 물리 주소는 0x00101000 번지라고 가정 하겠습니다. 우선 TTBA에서 0x4000를 읽어와 여기에 가상 주소의 상위 12 비트를 인덱스로 더합니다. 그러면 L1 페이지 테이블 엔트리의 주소는 0x4C00가 됩니다.

  • 0x4000(TTBA) + 0xC00(L1 Page Table Index) = 0x4C00(L1 Page Table Entry)

물리 주소 0x4C00에서 L1 페이지 테이블 엔트리를 읽어 옵니다. L1 페이지 엔트리에는 물리 메모리의 페이지 프레임 주소 0x100000가 저장되어 있습니다. 여기에 가상 주소의 하위 20비트를 더하면 물리 주소가 나오게 됩니다.

  • 0x100000(Base Address) + 0x01000(Offset) = 0x101000(Physical Address)

1단계 페이징 루틴을 작성시에 주의해야 할 점은 L1 페이지 테이블의 시작 주소는 14비트(16KB) 단위로 정렬된 주소가 되어야 한다는 점입니다. L1 페이지 테이블의 하나의 엔트리의 크기는 32비트(4B)이고, 4096개의 엔트리를 가지고 있으니, L1 페이지 테이블은 총 16KB를 할당해서 사용해야합니다. 만약 L1 페이지 테이블 시작 주소를 0x1000로 설정한다면, MMU는 TTBA의 상위 18비트만 사용하므로 0x0 번지를 L1 페이지 테이블의 시작 주소로 인식하게 됩니다. 이러면 당연히 엉뚱한 메모리 공간에 접근하였으므로 제대로 동작 할리가 없습니다.

<그림 3. L1 페이지 테이블 엔트리의 종류>

<그림 3>은 ARM Cortex-A에서 사용하는 L1 페이지 테이블 엔트리의 종류 입니다. ARM Cortex-A에는 사용하지 않는 Reserved를 포함해서 총 5개의 L1 페이지 테이블이 존재합니다. 눈치 빠른 분이라면 이미 예상 하셨겠지만, ARM은 페이지 테이블 엔트리의 하위 2비트 값에 따라 종류를 판별합니다. Section과 Supersesction은 하위 2비트가 동일하지만, 18비트가 0인지 1인지에 따라 구분합니다.

Fault는 해당 엔트리에 접근하면 MMU에서 Translation Fault가 발생합니다. 메모리를 할당하지 않고 페이지에 접근시 폴트 핸들러에서 동적으로 메모리를 할당하는 요구 페이징(Demand Paging)을 구현하는데 활용 할 수 있습니다. Page table은 2단계 페이징에 사용되는 L1 페이지 테이블의 엔트리 입니다. Page Table 엔트리는 2단계 페이지 테이블의 시작 주소를 담고 있습니다. 1단계 페이징에 사용되는 L1 페이지 테이블 엔트리는 Section과 Supersection 입니다. 1MB 페이지를 사용하려면 Section, 16MB 페이지를 사용하려면 Supersection을 사용합니다. 페이지 테이블 엔트리의 각 필드들이 무엇을 의미하는지 하나씩 살펴보겠습니다.
  • Section/Supersection Base Address:1MB/16MB 페이지 프레임의 물리 주소를 나타냅니다.
  • SBZ: Should Be Zero. 0
  • NG: Not Global. 글로벌 메모리 여부를 설정합니다. TLB 플러쉬 정책과 관련있습니다.
  • S: Shareable. 메모리 속성에 따라 공유 가능여부를 설정합니다.
  • APX/AP: Access Permission, 접근 권한을 설정하는 필드 입니다.
  • TEX: Type extension. 메모리 속성 설정에 사용됩니다.
  • P: Present. 운영체제 커널과 같은 시스템 소프트웨어에서 사용하는 필드입니다. 주로 스와핑에 사용됩니다.
  • Domain: 도메인을 설정합니다. 메모리는 도메인별로 접근 권한 확인 여부를 설정할 수 있습니다. No access, Client, Manager 세가지 타입으로 설정가능하며 총 16개 도메인이 존재합니다.
  • XN: Execute-Never 비트. CPU는 해당 메모리의 코드가 실행 가능한지 결정합니다.
  • C: Cacheable. 메모리 속성 설정에 사용됩니다.
  • B: Bufferable. 메모리 속성 설정에 사용됩니다.

페이지 테이블 엔트리의 각 필드들에 대한 내용은 뒤에 좀 더 자세히 알아 볼 것 입니다. 지금은 페이지 테이블의 각 엔트리는 페이지 프레임의 시작 주소 혹은 L2 페이지 테이블의 시작 주소를 담고 있고, 메모리의 속성과 접근 권한을 설정 할 수 있다는 것만 알아두시기 바랍니다.

이제부터 1단계 페이징을 직접 구현해 봅시다. 우선 L1 페이지 테이블 엔트리 자료 구조를 선언 합니다. 운영체제마다 가상 주소 맵이 다르고 운영체제 특성에 맞게 설계되어 있습니다. 우선 여기서는 물리 메모리 시작 주소에서 끝까지를 가상 주소 0xC0000000부터 선형 매핑하겠습니다. 물리 메모리 크기는 1GB, 시작 주소는 타겟 시스템마다 다르지만 0번지로 가정하겠습니다.

[코드 1] L1 페이지 테이블 엔트리 자료 구조 선언 <paging.h>
/* L1 Page table entry. */
typedef union {
    struct {
        u32 fixed:5;    /* {0,0,0,0,1} */
        u32 domain:4;   /* Memory protection domain info. */
        u32 p:1;        /* ECC enable. Should be zero. */
        u32 base:22;    /* L2 page table base address. */
    }__attribute__((__packed__)) pt; /* Page table */
    struct {
        u32 fixed:2;    /* {1,0}*/
        u32 b:1;        /* Buffer */
        u32 c:1;        /* Cache */
        u32 xn:1;       /* Execute never */
        u32 domain:4;   /* Domain for memory regions */
        u32 p:1;        /* Implementation Defined */
        u32 ap:2;       /* Access Permission */
        u32 tex:3;      /* Type Extension */
        u32 apx:1;      /* Access Permission 2 */
        u32 s:1;        /* Shareable */
        u32 ng:1;       /* Not Global */
        u32 sbz:2;      /* 0 */
        u32 base:12;    
    }__attribute__((__packed__)) s; /* Section */
    struct {
        u32 fixed:2;    /* {1,0} */
        u32 b:1;        /* Buffer */
        u32 c:1;        /* Cache */
        u32 xn:1;       /* Execute nerver */
        u32 domain:4;   /* Domain for memory regions */
        u32 p:1;        /* Implementation Defined */
        u32 ap:2;       /* Access Permission */
        u32 tex:3;      /* Type Extecsion */
        u32 apx:1;      /* Access Permission 2 */
        u32 s:1;        /* Shareable */
        u32 ng:1;       /* Not Global */
        u32 _1:1;       /* 1 */
        u32 sbz:5;      /* 0 */
        u32 base:8;     /* Base address */
    }__attribute__((__packed__)) ss; /* Supersection */
    u32 entry;
} l1_pte_t;

L1 페이지 테이블 엔트리는 크기는 모두 32비트이므로 세가지 타입의 엔트리를 공용체로 선언 하였습니다. __attribute__((__packed__))는 gcc 확장의 일종으로, 구조체 크기를 선언한 멤버 변수 크기와 동일하게 생성하라고 gcc 컴파일러에 일러주는 것입니다. 이 옵션을 생략하면 구조체의 크기는 멤버 변수 크기와 동일하게 생성되지 않으므로, 정확한 구조체 크기를 생성하고자 할 때는 항상 사용해야 합니다. (참고> C 구조체 정렬 제한 및 패딩)

이제 L1 페이지 테이블을 구축해야 합니다. 물리 메모리 시작 주소 0 번지부터 메모리 전체를 가상 주소 0xC0000000 번지부터 선형 매핑한다고 하였으니, 페이지 테이블 엔트리를 거기에 맞게 설정 해야 합니다.

[코드 2] L1 페이지 테이블 엔트리 설정 <paging.c>
#include <paging.h>
#include <stdlib.h>

#define L1_PT_SIZE (4*4096)      /* 페이지 테이블 엔트리 (크기)x(갯수) */
#define PAGE_SIZE  (0x100000)    /* Section 크기(1MB) */
#define MEM_SIZE   (0x40000000)  /* 메모리 크기(1GB) */
#define VIRT_START (0xC0000000)  /* 가상 메모리 시작 주소 */
#define PHY_START  (0x0)         /* 물리 메모리 시작 주소 */

void create_l1_pt(l1_pte_t *pt, u32 vaddr, u32 paddr, u32 page_size, u32 size)
{
    int i;
    char *base = (char *)pt;
    int offset = vaddr >> 20; /* 첫번째 페이지 테이블 엔트리 오프셋 */

         /* 매핑 할 메모리 크기 만큼의 L1 페이지 테이블 엔트리들을 설정 합니다. */
    for (i=0; i<size; i+=page_size, ++offset) {
        pt[offset].s.base   = (paddr+i)>>20; /* Page Frame base address*/
        pt[offset].s.sbz    = 0x0; /* 0 */
        pt[offset].s.ng     = 0x0; /* Not Global */
        pt[offset].s.s      = 0x0; /* Shareable */
        pt[offset].s.apx    = 0x0; /* Access Permission 2 */
        pt[offset].s.tex    = 0x0; /* Type Extension */
        pt[offset].s.ap     = 0x3; /* Access Permission */
        pt[offset].s.p      = 0x0; /* Implementation Defined */
        pt[offset].s.domain = 0x0; /* Domain for memory regions */
        pt[offset].s.xn     = 0x0; /* Execute never */
        pt[offset].s.c      = 0x0; /* Cache */
        pt[offset].s.b      = 0x0; /* Buffer */
        pt[offset].s.fixed  = 0x2; /* {1,0} */
    }
}

int main(void)
{
    l1_pte_t *l1_pt;
     
    /* L1 페이지 테이블 메모리 공간을 할당합니다. */
    l1_pt = (l1_pte_t *)malloc(L1_PT_SIZE);
    bzero(l1_pt, L1_PT_SIZE);
    /* L1 페이지 테이블을 구축합니다. */
    create_l1_pt(l1_pt, 0xC0000000, 0x0, PAGE_SIZE, MEM_SIZE);
    /* MMU를 활성화하여 가상 주소 사용을 시작합니다. */
    enable_paging(l1_pt);     

    /* 이제부터 물리 주소 0번지는 가상 주소 0xC0000000번지에 선형 매핑되었습니다. */

    /* Do something... */

    return 0;
}

먼저 L1 페이지 테이블 메모리 공간을 할당 해야 합니다. [코드 2]에서 malloc으로 L1 페이지 테이블 크기(16KB)만큼 할당 하였습니다. 페이지 테이블 관련 루틴을 구현하는 커널이나 펌웨어에는 표준 C 라이브러리인  malloc()가 지원하지 않는 경우가 많으므로 환경에 맞는 적절한 동적 메모리 할당자를 사용해야 합니다. 리눅스 커널을 예로 들면 malloc() 대신에 kmalloc()과 get_pages()등의 동적 메모리 할당자를 지원합니다. 다시한번 강조하지만 L1 페이지 테이블의 시작 주소는 16KB 단위로 정렬되어 있어야 한다는 점입니다. 즉, 일반적인 메모리 할당자가 아닌 정렬 기능을 지원하는 메모리 할당자를 사용해야 합니다. 

[코드 2]의 create_l1_pt() 함수는 인자로 받은 가상 주소와 물리 주소를 매핑하도록 L1 페이지 테이블을 생성하는 함수 입니다. 할당된 L1 페이지 테이블(l1_pt)를 인자로 넣어 가상 메모리 시작 주소의 상위 12비트에서 첫번째 L1 페이지 테이블 엔트리의 오프셋을 구합니다. 첫번째 L1 페이지 테이블 엔트리부터 시작하여 매핑 할 메모리 크기만큼 각 엔트리를 설정합니다. 현재는 가상 주소가 0xC0000000이고 , 메모리 크기가 0x40000000(1GB)이므로, 3072번째 엔트리부터 4095번째 엔트리까지 총 1024개의 엔트리를 설정하게 됩니다. 각 엔트리에는 물리 메모리 페이지 프레임 주소를 설정해야 하므로 물리 메모리 시작 주소(paddr)부터 페이지 크기(0x100000, 1MB) 만큼 증가하면서 엔트리의 base 필드를 설정합니다. 페이지 테이블 엔트리의 각 필드들이 의미하는 바는 앞으로 하나씩 자세히 알아 볼 것 입니다. 우선은 매핑하려는 가상 주소가 페이지 테이블 엔트리의 인덱스(오프셋)에 해당되고, 해당 엔트리에 매핑하려는 물리 주소를 설정한다는 점에 집중합시다. [그림 2]와 [그림 3]을 참조하면서 [코드 2]의 소스 코드를 보시면 한결 이해하기가 수월 할 것 입니다.

[코드 2]는 L1 페이지 테이블 엔트리를 l1_pte_t 타입에서 Section 구조체 s의 각 필드를 설정하도록 구현하였지만 다음과 같이 32비트 entry 멤버 변수에 엔트리에 설정 할 값을 한번에 설정해도 무방합니다. 가독성은 조금 떨어지지만 소스 코드 라인이 줄어들고 좀 더 최적화된 코드이므로 저는 이 방법을 애용합니다.

/* Base|ss|tex|ap|bc|fixed */
pt[offset].entry = (paddr+i)|(0x0<<18)|(0x0<<12)|(0x3<<10)|0x0<<2)|0x2;

[코드 2]의 enable_paging() 함수가 실제로 MMU를 제어하여 페이징을 활성화하는 함수입니다. 페이징 활성화 루틴은 아키텍처 종속적인 루틴이므로 어셈블리어로 작성해야 합니다.

[코드 3] 페이징 활성화 <paging.S>
.global enable_paging
.type   enable_paging, %function
.align  4
enable_paging:
    @ 페이징 활성화를 하기전에 TLB를 플러쉬 합니다.
    mov     r1, #0x0
    mcr     p15, 0, r1, c8, c7, 0

    @ TTBCR(Translation Table Base Control Register)을 설정합니다.
    mcr     p15, 0, r1, c2, c0, 2


         @ 도메인 접근 권한을 설정합니다.
    ldr     r1, =0xffffffff
    mcr     p15, 0, r1, c3, c0, 0

    @ TTBR(Translation Table Base Register) 0에 인자로 받은 L1 페이지 테이블
    @ 시작 주소(TTBA)를 설정합니다.
    mcr     p15, 0, r0, c2, c0, 0
        
    @ SCTLR(System Control Register)에서 MMU를 활성화하여 가상 주소를 사용합니다.
    mrc     p15, 0, r0, c1, c0, 0
    orr     r0, r0, #0x1 
    mcr     p15, 0, r0, c1, c0, 0
    mov     pc, lr

ARM의 함수 호출 규약(AAPCS)에 의하면 함수의 첫번째 인자는 r0 레지스터에 저장됩니다. 그래서 [코드 2]에서 enable_paging() 함수를 호출할때 인자로 넣은 L1 페이지 테이블의 시작 주소(l1_pt)가 r0 레지스터에 저장되어 있다는 점을 명심하고, r0를 제외한 다른 범용 레지스터를 사용해야 합니다. 먼저 TLB에 잘못된 값이 들어있으면 페이징이 오동작하므로 TLB를 플러쉬 해야 합니다. 그리고 도메인 접근 권한을 설정하는데, 현재는 모든 도메인을 Manager로 설정하여 접근 권한 체크를 하지 않도록 하였습니다. 도메인은 No access, Client, Manager 세 종류가 있고, 도메인을 Client로 설정하면 페이지 테이블 엔트리의 AP(Access Permission) 필드의 값을 확인하여 메모리 접근 권한 체크를 하게 됩니다. 도메인은 추후 메모리 접근 권한 설정에 대한 내용을 다룰때 좀 더 자세히 살펴보도록 하겠습니다.

TTBCR(Translation Table Base Control Register)은 LPAE 사용여부 설정과 TTBR0과 TTBR1 중에 어떤 것을 TTBA로 사용 할지를 설정합니다. TTBCR을 0으로 설정하면 TTBR0를 TTBA로 사용하고 LPAE를 사용하지 않습니다. [코드 2]에서 enable_paging() 함수를 호출하면서 인자로 받은(r0) L1 페이지 테이블의 시작 주소를 TTBR0에 설정하여 MMU가 참조하는 L1 페이지 테이블의 시작 주소를 설정합니다. 마지막으로 SCTLR의 첫번째 비트를 1로 설정하면 MMU가 활성화되어 가상 주소를 사용하게 됩니다. 

페이징을 활성화하게 되면 사용하는 심볼의 주소가 달라지므로 유의해야 합니다. 예를 들어, foo()라는 함수의 주소가 0x1000 번지라고 한다면, 가상 주소 0xC0001000 번지에 foo() 함수 코드가 들어 있습니다. 하지만, 링크 스크립트 설정에 따라 바이너리 이미지를 생성하면서 foo() 함수를 호출하면 0x1000 번지로 점프하도록 코드가 생성됩니다. 심볼이 올바른 (가상) 주소를 참조 할 수 있도록 링크 스크립트와 빌드시 바이너리 조작을 적절히 해주어야 합니다. 물리 주소를 사용하는 루틴과 가상 주소를 사용하는 루틴을 따로 바이너리를 생성해서 objcopy 하는 것도 하나의 방법이 될 수 있습니다.

다음 편에는 2단계 페이징 예제와 페이지 테이블의 각 필드들(접근 권한, 메모리 속성)을 하나씩 자세히 살펴 보겠습니다. 그리고, 페이징과 관련된 코프로세서 레지스터들도 자세히 살펴보도록 하겠습니다. 

< 참고자료 >
[1] Cortex -A Series Programmer’s Guide
[2] Cortex -A9 Technical Reference Manual
[3] ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition

2013년 3월 19일 화요일

"Xen으로 배우는 가상화 기술의 이해 - 메모리 가상화" 출간!






이번 편은 메모리 가상화에 대한 내용입니다. 우리가 사용하는 어플리케이션이 사용하는 메모리 주소는 실제 물리 메모리의 주소가 아닙니다. 모든 어플리케이션이 동일한 가상 주소를 사용하는대요. 이 가상 주소를 물리 주소로 변환하는 것을 '페이징'이라고 합니다. CPU의 MMU(Memory Management Unit)가 가상 주소를 물리 주소로 변환하는 페이징을 합니다.

자~그럼 가상화 환경에서 페이징은 어떻게 할까요? 가상 머신의 물리 주소가 물리 메모리의 물리 주소가 될까요?

그 해답에 대한 내용이 이 책에 담겨 있습니다.

전자책이므로 서점에는 없습니다. 한빛미디어 홈페이지에 접속하시면 저렴한 가격으로 pdf를 구매하실 수 있습니다.

2013년 3월 11일 월요일

"Xen으로 배우는 가상화 기술의 이해 - CPU 가상화" 출간!




매주 토요일 공저자 세 분과 함께 Xen 하이퍼바이저 소스 코드를 분석을 했던 내용을 책으로 엮었습니다. 최근 IT 분야의 핫 이슈라면 클라우드와 빅 데이터를 들 수가 있는데, 가상화 소프트웨어는 그 중 클라우드의 핵심이 되는 컴포넌트입니다. 번역서를 제외하고는 가상화 소프트웨어 활용이 아닌 내부 구조를 본격적으로 다루는 최초의 국내 서적이므로 가상화 기술에 관심이 있으시거나 공부하시는 분이라면 한번 보시길 바랍니다. 

많은 배경 지식과 경험이 필요한 시스템 소프트웨어 분야의 특성상 집필 과정에서 내용을 어느정도 수준까지 다루어야 하는지 설정하는 것이 가장 힘들었습니다. '이정도는 다들 알고 있겠지?' '이런 것까지 설명해야 되나?'등의 많은 고민을 거쳐서 집필 하였지만 독자의 입장에서는 잘 이해되지 않는 난해한 부분이 있을 수 있습니다. 더구나 소스 코드 분석에 대한 내용이 많다보니 소스 코드를 보는 것에 익숙하지 않는 독자라면 더욱 힘들지도 모르겠습니다. 그런 분들은 책을 집필한 저자들에게 질문을 주시면 저희가 아는 한도에서 최대한 성실하게 답변드리도록 하겠습니다.

전자책이므로 서점에는 없습니다. 한빛미디어 홈페이지에 접속하시면 저렴한 가격으로 pdf 파일로 구매해서 보실 수 있습니다.