오늘은 안드로이드 초기화 과정인 init 에 대해서 이야기를 할까 합니다.
다들 알고 계시겠지만, 안드로이는 linux 커널이 시작되면서 init daemon 을 실행시켜서 뭐 이 initd 에서 대충 서비스를 시작한다 정도로 알고 있을 것입니다.
하지만 어떻게 Kernel 로 시작해서 linux 의 데몬인 initd 가 Android 에 framewokr 에 기본이 되는 서비스들을 살릴 수 있을까요?
이 것에 대한 의문으로 오늘 이야기는 시작되었습니다. 물론 ODE 실행 후 FOTA 업데이트가 제대로 안되어서 fota update 를 보다가 fota.rc 를 수정해야 하고 또 이 .rc 파일들이 linux 에서 사용하는 그냥 init 파일과 다르게 Android Init Language (AIL) 이라는 문법에 따라서 처리가 된다는 것을 알아서 메일을 보내는 것은 아니라고 우겨봅니다 ^^;
* 이번 이야기는 kernel 단과 system 단에 대한 이야기들이 많습니다. 제가 조사한다고 했는데, 그래도 혹시 제가 잘못 이해하고 있는 부분이 있으면, kernel 이나 system 쪽 잘아시는 분들이 지적해주셨으면 합니다. 그러면서 정보는 더욱 더 공유가 될 수 있을 것 같습니다.
따라서 오늘 이야기의 핵심은 android/system/core/init/ 에 있는 init.c 가 주가 될 것입니다. 이놈이 결국 compile 되어서 initd 가 되는 놈입니다.
먼저 안드로이드 폰의 부팅은 개략적으로 다음과 같습니다.
Bootloader 가 kernel 을 로드 -> android/kernel/arch/arm/kernel/head-common.S (어셈블리 어로 작성되어 있는 코드입니다 ㅡㅡ;) 에서
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ARM( ldmia r3, {r4, r5, r6, r7, sp})
THUMB( ldmia r3, {r4, r5, r6, r7} )
THUMB( ldr sp, [r3, #16] )
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
str r2, [r6] @ Save atags pointer
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r7, {r0, r4} @ Save control register values
b start_kernel // 커널이 start 되는 부분.
ENDPROC(__mmap_switched)
이런 코드가 있는데, 위에서 b start_kernel 로 해서 바로 kernel 초기화를 시작하게 됩니다.
-> 바로 이 start_kernel 은 android/kernel/init/main.c 에 있는 start_kernel() 이되고, 이 함수 끝에 rest_init() 호출 ->
rest_init() 함수에서 kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); 부분에서 다시 kernel_init() 호출 ->
kernel_init() 함수에서 init process 를 위한 제반 작업을 준비하고 init_post() 함수를 호출 ->
init_post() 함수를 보면,
static noinline int init_post(void)
__releases(kernel_lock)
{
-- need to finish all async __init code before freeing the memory --
async_synchronize_full();
free_initmem();
unlock_kernel();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
current->signal->flags |= SIGNAL_UNKILLABLE;
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command); // init process 실행.
printk(KERN_WARNING "Failed to execute %s ",
ramdisk_execute_command);
}
--
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
--
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults... ", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
위 코드에서 푸른색이 바로 오늘의 중심 이야기가 되는 init process 즉, android/system/core/init/init.c 가 됩니다.
그 다음부터는 바로 이 init.c 의 main() 가 처리를 하는 것이죠. 이 함수는 다음과 같습니다 (설명을 위해서 소스의 많은 부분을 삭제했습니다).
int main(int argc, char **argv)
{
....
....
....
// 필요한 .rc 파일 읽어서 파싱하기
if(factorytest==1)
init_parse_config_file("/factorytest.rc");
else if(factorytest==2)
init_parse_config_file("/recovery.rc");
else if(factorytest==3)
init_parse_config_file("/fota.rc");
else if(check_charging_mode())
init_parse_config_file("/lpm.rc");
else
init_parse_config_file("/init.rc");
....
....
....
// Action 을 Queue 에 넣기 (그런데 알고 보면 이놈은 linked list 구조)
action_for_each_trigger("early-init", action_add_queue_tail);
queue_builtin_action(property_init_action, "property_init");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(set_init_properties_action, "set_init_properties");
-- execute all the boot actions to get us started --
action_for_each_trigger("init", action_add_queue_tail);
action_for_each_trigger("early-fs", action_add_queue_tail);
if(emmc_boot) {
action_for_each_trigger("emmc-fs", action_add_queue_tail);
} else {
action_for_each_trigger("fs", action_add_queue_tail);
}
action_for_each_trigger("post-fs", action_add_queue_tail);
-- move actions here to eliminate delay of "wait_for_coldboot_done" --
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
queue_builtin_action(console_init_action, "console_init");
queue_builtin_action(property_service_init_action, "property_service_init");
queue_builtin_action(signal_init_action, "signal_init");
queue_builtin_action(check_startup_action, "check_startup");
-- execute all the boot actions to get us started --
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
-- run all property triggers based on current state of the properties --
queue_builtin_action(queue_property_triggers_action, "queue_propety_triggers");
for(;;) {
int nr, i, timeout = -1;
execute_one_command(); // Action 실행
restart_processes(); // Service 실행
if (!property_set_fd_init && get_property_set_fd() > 0) {
ufds[fd_count].fd = get_property_set_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
property_set_fd_init = 1;
}
......
......
......
}
}
return 0;
}
소스에서 보시면 알겠지만, 바로 이놈이 init.rc 나 fota 업데이트를 위한 fota.rc 등 rc 파일을 읽어와서 순서에 맞게 실행시키는 것을 볼 수 있습니다.
이런 init.rc 파일들을 보면 대충 다음과 같이 생겼는데,
on early-init
start ueventd
on init
on fs
# mount mtd partitions
on emmc-fs
chmod 0666 /dev/block/mmcblk0p14
mount ext4 /dev/block/mmcblk0p16 /cache nosuid nodev barrier=0
on boot
# basic network init
ifup lo
hostname localhost
domainname localdomain
service tvout /system/bin/tvoutserver
user system
group system
service dhcpcd /system/bin/logwrapper /system/bin/dhcpcd -d eth0
disabled
oneshot
여기서 주로 보이는게 on 으로 시작하면서 어쩌고 저쩌고 하는 것들과 service 입니다.
그 런데 자세히 보면 chmod 나 exec 같은 linux 명령과 같은 것들은 줄을 좀 들여쓰기가 되어 있는 것을 볼 수 있습니다. 바로 여기서 .rc 파일을 이해하기 위해서 우리는 Android Init Language (AIL) 라는 것을 알아야 합니다.
AIL 은 하나의 문법인데, 이놈은 크게 Action / Service / Command / Option / Property 정도로 나뉘어져 있습니다. 좀 쉬게 말해서 Action 은 C 의 switch 문이라고 보시면 됩니다. Command 는 명령인데, 이것은 항상 Action 이나 Service 안에서 호출이 되어야 합니다. 자세한 설명은 android/system/core/init/readme.txt 파일에 자세히 적혀있습니다 (첨부도 했습니다).
어라 그런데 자세히 보니 init.c main 에서 다음과 같은 것들이 보이네요,
action_for_each_trigger("early-init", action_add_queue_tail);
queue_builtin_action(property_init_action, "property_init");
queue_builtin_action(keychord_init_action, "keychord_init");
즉, init.rc 에서 on 으로 시작되는 놈들은 init.c 에서 정의한 순서대로 호출이 된다는 것입니다 (소스에서는 queue 라고 함수이름을 해놓았는데, Linked List 구조체를 사용해서 큐를 만들어 두었더군요).
init.rc 에 on A, on B 가 순서대로 있다고 이것이 A 호출되고 B 되고 하는게 아니라 init.c main 에서 B 를 먼저 queue 에 넣고 다음에 A 를 넣으면, B, A 순으로 호출이 된다는 것입니다.
그럼 소스에서는 Action 을 queue 에 넣는데, 어디에서 실행이 될까요? 바로 그 밑인 이 부분에서 실행이 됩니다.
execute_one_command(); // Action 실행
restart_processes(); // Service 실행
execute_one_command() 함수에서 큐에 들어가 있는 순서대로 action 을 호출해주고,
restart_processes() 함수에서 service 가 init.rc 에 적혀진 순서대로 (service 는 init.rc 위에서부터 아래 순으로 순서대로 호출됩니다) 호출이 됩니다.
그리고 참고로 아래와 같은 코드들이 있는데,
on property:persist.service.adb.enable=1
start adbd
이 뜻은 조건부로 on property 라는 action 을 실행하겠다는 것입니다. 즉, persist.service.adb.enable 값이 1 이면 start adbd 를 실행한다는 것입니다.
이 persist.service.adb.enable 는 property_get() 으로 받아오는 환경변수입니다. 왜 adb 로 연결해서 getprop 하면 지금 폰에 설정된 환경변수값들이 모두 반환될 때 나오는 그놈입니다. 이 환경변수에 대해서도 좀 이야기를 하자면, 보통은 기본적으로 /system/build.prop 에 정의된 것들이 환경분수에 들어가는데, getprop 해서 얻은 정보와 build.prop 를 비교해보면 getprop 명령으로 얻은 정보가 더 많음을 볼 수 있습니다.
이 는 각 프로그램마다 property_set() 를 써서 환경변수값을 등록하고, build.prop 말고도 기본적으로 폰이 부팅되면서 property 가지고 오는 부분이 더 있기 때문입니다. 어디에 더 prop 가 정의되어 있는지는 android/system/core/init/property_service.c 에서 start_property_service() 함수를 보시면 됩니다. 참고로 이런 환경변수는 prop_info 라는 구조체에 저장됩니다.
이 렇게 init process 는 init.rc 에 있는 것을 실행하고 그 생을 마감(?)합니다. 그럼 어떻게 Android 의 기본이 되는 서비스들 (소위 말해서 우리가 DDMS 를 연결해서 볼 수 있는 process 들, Audio Flinger, Surface Flinger, Headset Observer 등등) 을 시작하는 SystemServer.java 의 ServerThread class 를 시작할 수 있을까요?
이 것은 바로 init.rc 에서 시작시키는 zygote 에 의해서 시작이 됩니다. zygote 이 시작되면서 android 의 system_server 라는 것을 실행시키고, system_server 의 android/framework/base/cmds/system_server/library/system_init.cpp system_init() 함수에서 JNI 를 거쳐서 android/framework/base/services/java/com/android/server /SystemServer.java 의 init2() 함수에서
public static final void init2() {
Slog.i(TAG, "Entered the Android system server!");
Thread thr = new ServerThread();
thr.setName("android.server.ServerThread");
thr.start(); // Android 기본이 되는 서비스들 시작 시킴.
}
에서 실행을 하게 됩니다.
휴...오늘도 역시 이야기가 많이 길어졌네요. 전 재능있는 이야기꾼은 아닌가 봅니다. 명확한 내용을 이렇게 장황하게 이야기를 늘여놓았으니깐요. ㅠㅠ
하지만 조금이라도 제가 아는 것을 공유하다보니 이렇게 되었습니다. 너그러이 이해해주시길 부탁드립니다.
이상입니다.
출처 : http://blog.naver.com/mad_ai/130142483781
'IT > 리눅스마스터1급' 카테고리의 다른 글
ps 명령어 (0) | 2022.05.03 |
---|---|
SurfaceFlinger,AudioFlinger (0) | 2022.05.03 |
Ethernet frame format etc.. (0) | 2022.05.03 |
SFD jam sequence (0) | 2022.05.03 |
csma/cd (0) | 2022.05.03 |