Android Emulator의 Internals<?xml:nampace prefix = o ns = "un:schemas-microsoft-com:offioffice" /><?xml:namespace prefix = o /><?xml:namespace prefix = o />
Android의 코어에서 에뮬레이터는 QEMU Ver 0.8.2를 사용하고 있다. Android 팀은 효율적인 인수Naming과 Parsing을 가지는 main()이라는 Wrapper 함수를 제공하고 있다. 그리고 전통적인 QEMU 엔트리 포인트를 호출하면서 끝낸다. 내부적으로 main의 변화는 새로운 플랫폼인 “GoldFish”가 통합 플랫폼으로 추가되어 있다.
새로운 플랫폼에 다른 OS가 포팅이 될 때, 첫번째로 인트럽트 컨트롤러, 시리얼 콘솔, 타이머, 처럼 기본적인 디바이스 드라이버들이 확보되어야 될 필요가 있다. 일반적으로, 메뉴얼 또는 스펙 시트가 발행된 것을 참고하는 방법이 있으나, 불행히도 “GoldFish”를 위한 어떠한 스팩 시트도 제공되지 않는 다. 그러나 우리는 Goldfish 커널 소스(2.6.23)를 이용하여 필요한 정보들을 얻을수 있다. 여기서는 안드로이드 플랫폼의 몇가지 중요한 부분(예를 들면, Keypad, Famebuffer, Etc)을 공유하고자 한다.
물리 메모리의 레이아웃은 비교적 간단하다. RAM이 0번지에서 Start되고, 한 개의 인접하는 블록의 RAM 사이즈의 끝에 도달할 때까지 계속된다.
데이터 캐쉬는 16kbytes(4 way set associative, 32 byte lines)이다. 더 높은 결합성질을 가지고 있을 지라도 이것은 표준이다. ( 물론, 시뮬레이션 조건하에 많은 다른 것을 만들지는 않는다. 그래서, 이 캐쉬 레이아웃이 실제 Phone에서 사용되는 몇가지 SOC와 유사하다.)
인트럽트 제어기는 0xff000000에 위치하는 레지스터의 4kbyte 블록을 가지고 있다. 이것은 5개의 32비트 레지스터들을 구성하고 있다. offset 0x0의 STATUS는 Pending되어 있는 인트럽트의 개수들을 포함한다. 이것은 읽기전용 레지스터이다. offset 0x4의 NUMBER는 가장 낮은 Pending되어 있는 Enable된 인트럽트 개수 구성한다. 이것은 읽기 전용 레지스터이다.. offset 0x8의 DISABLE_ALL는 쓰기 전용 레지스터이다. 쓰기가능한 값은 모든 인트럽트들을 Disable한다 offset 0xC의 DISABLE은 쓰기 전용 레지스터이다. 쓰기가능한 인트럽트 번호는 지정된 인트럽트를 Disable한다. offset 0x10의 ENABLE 는 쓰기 전용 레지스터이다. 쓰기가능한 인트럽트 번호는 지정된 인트럽트를 Enable한다 이것은 인트럽트 컨트롤러에 가장 적합한 인터페이스이다. Job이 수행된 것을 얻기 위하여 다중 레지스터들이 갱신된다. 각 함수들은 해당 드라이버에서 구현을 하기 위해 필요하다.
시리얼 컨트롤러는0xff002000에 위치하는 4kbyte 블록의 레지스터들을 가지고 있다. offset 0x0의 PUT_CHAR는 쓰기 전용 레지스터이다. 쓰기가능한 값은 콘솔에 문자로 놓여진다. offset 0x4의 BYTES_READY는 콘솔로부터 읽기위하여 대기중인 문자들의 수를 반환한다. 이 레지스터는 읽기 전용이다 offset 0x8 의 CMD는 쓰기 전용 레지스터이다. 명령을 쓰는 것은 4가지 액션중의 하나를 수행한다.
- CMD_INT_DISABLE (0) 은 콘솔 인트럽트를 disable한다.
- CMD_INT_ENABLE (1) 은 콘솔 인트럽트를 enables 한다.
- CMD_WRITE_BUFFER (2) 는 콘솔에 가상주소 DATA_PRT부터 DATA_LEN 바이트까지를 복사한다.
- CMD_READ_BUFFER (3) 는 콘솔에서부터 가상주소 DATA_PTR까지 DATA_LEN 바이트들을 복사한다. 바이트들의 수는 BYTES_READY에 의해 지정된 것을 초과하지 않을 것이다.
offset 0x10 의 DATA_PTR 는 쓰기전용 레지스터이다. 이 레지스터의 값은 읽기와 쓰기 버퍼명령에 사용된 가상주소이다. offset 0x14의 DATA_LEN는 쓰기전용 레지스터이다. 이 레지스터의 값은 읽기 또는 쓰기 버퍼명령에서 복사하기 위한 수량이다. 이것을 정말 상당히 좋은 인터페이스이다. 한 개의 예약어는 PUT_CHAR을보ㅜ터 읽기를 수행한다면 사용가능시에 문자를 반환한다. (물론, 이것은 PUT_CHAR으로부터 재명명될것이다. ) 이것을 버퍼를 위해서 물리 주소를 사용하는 것보다 차라리 가상주소들을 사용하는 상당히 흥미있는 결정이다. 이것은 지금까지 보아왔던 대부분의 하드웨어와는 다를 것이다.
시리얼 컨트롤러는 0xff003000에 존재하는 레지스터의 4k바이트 블록을 가지고 있다. 이것은 32비트 레지스터 5개를 구성하고 있다. Time은 64비트 카운터에 의해 표현된다.
offset 0x0의 TIME_LOW는 64비트 카운터으로부터 가장낮은 32비트를 반환한다. 이것은 또한 TIME_HIGH 에 높은 32비트들을 잡고 놓지 않는다. 여러분은 일관성있는 값들을얻기위하여 TIME_HIGH를 읽기전에 TIME_LOW를 읽어야만 한다. 이것은 읽기전용 레지스터이다.
TIME_HIGH at offset 0x4의 TIME_HIGH는 64비트 카운터의 Top 32비트들을 저장하는 읽기전용 레지스터이다. 이것은 TIME_LOW값을 읽은 후에 읽을 것이다. 0x8의 ALARM_LOW은 다음 알람값의 가장낮은 32비트들을 저장하는 쓰기전용 레지스터이다. ALARM_HIGH으로부터 알람 값을 위한 Top 32비트들을 가져왔을 때 내부 레지스터의 값을 저장한다. 일관성 있는 결과를 얻기위해서 ALARM_HIGH는 알람이 셋팅되었을 때 처음으로 저장이 될 것이다. 카운터 값이 도착했을떄, 알람 값과 인트럽트는 Trigger처리된다.
오프셋 0xc의 ALARM_HIGH는 다음 알람 값의 Top 32비트들을 저장하는 쓰기 전용 레지스터이다.
이 레지스터를 쓰는 동안에 64비트 알람 레지스터을 갱신하지는 않는다. 이것은 ALARM_LOW를 쓰도록 한다.
오프셋 0x10의 CLEAR_INTERRUPT은 쓰기 전용 레지스터이다. 쓰기로 되었을떄 알람에 의해 이전에 작성된 인트럽트를 Clear 할것이다.
이것은 단지 OS 타이머를 처리하는 단순한 방법이다. 단지, 이것의 오류는 “Periodic Mode”이므로, 다음 알람 값은 계산된 각 시간을 필요로 하지 않는다. ( 물론, “Periodic Tick”은 모두 소모되어 치명적이지 않는다. )
최초에 릴리즈된 Android Emulator의 QEMU에는 컴파일시의 버그를 가지고 있다. 이것은 대부분 죽은 코드로 보이는 것을 제거하는 것을 포함하고 있다. Emulator을 빌드할 때 SDL이 사용된다. 그러나, 빌드 시스템은 오직 지정된 파일과 관련된 파일들 에서 SDL을 셋업한다.
#include <SDL.h>를 사용하는 것이 올바른 방법인데 이코드가 사용되지 않았다. 그래서 우리는 아래와 같이 해당 부분의 코드를 모두 제거해야만 한다. 이것은 Android를 사용하는 어느 누구에게도 문제가 되지는 않으므로, 아래와 같이 수정후에 재컴파일을 할것을 권장한다.
|
--- android-emulator-20071111.orig/qemu/vl.c <?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /><?xml:namespace prefix = st1 /><?xml:namespace prefix = st1 />2007-11-12 17:58:42.000000000 +1100
+++ android-emulator-20071111/qemu/vl.c 2007-11-29 00:29:35.000000000 +1100
@@ -78,12 +78,6 @@
extern void android_emulation_setup( void );
extern void android_emulation_teardown( void );
-#ifdef CONFIG_SDL
-#ifdef __APPLE__
-#include
-#endif
-#endif /* CONFIG_SDL */
-
#ifdef CONFIG_COCOA
#undef main
#define main qemu_main
|
다음 버그는 아주 사소한 것이다. 근본적으로 리얼타임 알람을 등록하기 위해 많은 문제를 가지는 부분이다. 그리고 이것을 서비스하기 위해서 시그널 핸들러를 후킹한다. ( 이것은 타이머 인트럽트들이 에뮬레이트된 기계에 어떻게 도입될수있는 지에 관한 것이다. )
블락된 SIGALRM을 가지는 것처럼 보이는 기본적인 sigprogmaks가 문제이다. 이것은 우리가 타이머 인트럽트들을 얻을수 없거나 적어도 에뮬레이트된 코드가 실행중인 상태임을 의미한다. 이 버그는 안드로이드를 사용하여 모든 사람들에게 적용될수 있다. 이 경우에 타이머 인트럽트들은 잘못처리되 가능성이 있게 되고, 시스템은 무응답이 된다. 이 부분은 아마도 향후 좀더 분석되어 개선되어야 될 부분이다.
|
diff -ru android-emulator-20071111.orig/qemu/vl.c android-emulator-20071111/qemu/vl.c
--- android-emulator-20071111.orig/qemu/vl.c 2007-11-12 17:58:42.000000000 +1100
+++ android-emulator-20071111/qemu/vl.c 2007-11-29 00:43:34.000000000 +1100
@@ -1282,6 +1276,7 @@
{
struct sigaction act;
struct itimerval itv;
+ sigset_t nset;
sigfillset(&act.sa_mask);
act.sa_flags = 0;
@@ -1304,6 +1299,10 @@
/* we probe the tick duration of the kernel to inform the user if
the emulated kernel requested a too high timer frequency */
getitimer(ITIMER_REAL, &itv);
+
+ sigemptyset(&nset);
+ sigaddset(&nset, SIGALRM);
+ sigprocmask(SIG_UNBLOCK, &nset, NULL);
}
#endif
}
|
다음 버그는 사실 QEMU 0.9 버전에서 수정된 것이다. ARM MMU으로 처리되는 것으로 오직 MMU(Memory Management Unit)을 모두 사용하는 커널에 의해 표시된다.
|
diff -ru android-emulator-20071111.orig/qemu/target-arm/helper.c android-emulator-20071111/qemu/target-arm/helper.c
--- android-emulator-20071111.orig/qemu/target-arm/helper.c 2007-11-12 17:58:42.000000000 +1100
+++ android-emulator-20071111/qemu/target-arm/helper.c 2007-11-29 00:26:44.000000000 +1100
@@ -247,7 +247,7 @@
switch (ap) {
case 0:
- if (access_type != 1)
+ if (access_type == 1)
return 0;
switch ((env->cp15.c1_sys >> 8) & 3) {
case 1:
@@ -428,6 +428,7 @@
break;
case 3: /* MMU Domain access control. */
env->cp15.c3 = val;
+ tlb_flush(env, 1);
break;
case 4: /* Reserved. */
goto bad_reg; |
하여튼, 에뮬레이터를 수정해야 만 하는 경우에 쉽게 몇가지 피쳐를 추가할수 있다. QEMU는 리눅스커널을 적재하는 기능이 있다. 하지만 모든 커널들이 리눅스만은 아닐수 있다. 그리고, 그것들이 어디에서 적재되었는지와 데이터가 무엇인지에 대한 다른 기대결과를 가진다.
지정된 OS로 에뮬레이트 하도록 -os-type이라는 새로운 플래그를 지원하기 위한 Wrapper를 확장해보도록 하자. 기본적으로 이것은 리눅스를 셋한다. 그리고 일반적인 리눅스 커널의 적재 알고리즘이 적용된다. 만약에 이외의 어떤 것이 셋된다면 그것은 메모리의 시작주소에서 직접 지정된 커널을 적재한다.
|