IT/리눅스마스터1급

init.rc

알콩달콩아빠 2022. 5. 3. 18:26
728x90
반응형

오늘은 안드로이드 초기화 과정인 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 

 

728x90
반응형

'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