이전 프스팅들에 이어서 본 포스팅에서도 안드로이드의 init 프로세스를 계속 분석한다.
5. 프로퍼티 서비스
안드로이드 플랫폼은 시스템이 동작하는 데 필요한 각종 설정 값을 동작 중인 모든 프로세스에서 공유하기 위한 Key=Value 공간을 제공하는데 이를 프로퍼티라고 한다. 이 값들은 Android Share Memory 이라는 공유 메모리에 저장된다.
프로퍼티는 모든 프로세스에서 조회 가능하지만 프로퍼티의 값을 변경하는 것은 오직 init 프로세스만 가능하며 init 프로세스는 다른 프로세스에서의 프로퍼티 값 변경 요청에 대해서 접근 권한을 검사하여 변경을 하게 된다.
<프로퍼티 초기화>
프로퍼티 초기화는 init.c의 main 함수의 다음의 코드들에서 초기화를 진행한다.
<init.c> int main(int argc,char **argv) { [-------------------중략------------------] property_init() [-------------------중략------------------] if(!is_charger) property_load_boot_defaults(); [-------------------중략------------------] queue_builtin_action(property_service_init_action, "property_service_init"); [-------------------중략------------------] } |
property_init 은 다음과 같다.
void property_init(void) { init_property_area(); } |
init_property_area()에서는 공유 메모리 영역을 초기화 한다.
property_load_boot_defaults()는 다음과 같다.
void property_load_boot_defaults(void) { load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT); } |
파일에서 초기값을 읽어서 프로퍼티 값을 설정한다.
이후 property_service_init_action 함수롤 호출하게 되는데, 이 함수에서는 start_property_service를 호출한다.
void start_property_service(void) { int fd; load_properties_from_file(PROP_PATH_SYSTEM_BUILD); load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT); load_override_properties(); /* Read persistent properties after all default values have been loaded. */ load_persistent_properties(); fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0); if(fd < 0) return; fcntl(fd, F_SETFD, FD_CLOEXEC); fcntl(fd, F_SETFL, O_NONBLOCK); listen(fd, 8); property_set_fd = fd; } |
이 함수에서는 파일에서 설정된 프로퍼티를 읽어서 설정하고, 프로퍼티 서비스를 시작하는데 필요한 UNIX 소켓을 생성한다. 그리고 property_set_fd에 해당 File Descriptor를 저장한다. property_service.c에서는 get_property_fd 함수에서 property_set_fd를 리턴한다.
init.c 의 main 함수를 보면 for문이 다음과 같이 property_set_fdd의 이벤트에 대해서 handle_property_set_fd() 함수를 호출하도록 되어 있다.
int main(int argc, char ((argv) { [-------------------중략------------------] for (i = 0; i < fd_count; i++) { if (ufds[i].revents == POLLIN) { if (ufds[i].fd == get_property_set_fd()) handle_property_set_fd(); else if (ufds[i].fd == get_keychord_fd()) handle_keychord(); else if (ufds[i].fd == get_signal_fd()) handle_signal(); } } return 0; } |
handle_property_set 함수는 다음과 같다.
void handle_property_set_fd() { prop_msg msg; int s; int r; int res; struct ucred cr; struct sockaddr_un addr; socklen_t addr_size = sizeof(addr); socklen_t cr_size = sizeof(cr); char * source_ctx = NULL; if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) { return; } /* Check socket options here */ if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) { //권한 확인을 위하여 소켓으로부터 SO_PEERCRED 값을 얻어 온다. // struct ucred 구조체 내부에 전송한 프로세스의 uid,pid,gid 값이 채워진다. close(s); ERROR("Unable to receive socket options\n"); return; } r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), 0)); if(r != sizeof(prop_msg)) { ERROR("sys_prop: mis-match msg size received: %d expected: %d errno: %d\n", r, sizeof(prop_msg), errno); close(s); return; } switch(msg.cmd) { case PROP_MSG_SETPROP: msg.name[PROP_NAME_MAX-1] = 0; msg.value[PROP_VALUE_MAX-1] = 0; if (!is_legal_property_name(msg.name, strlen(msg.name))) { ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name); close(s); return; } getpeercon(s, &source_ctx); if(memcmp(msg.name,"ctl.",4) == 0) { // Keep the old close-socket-early behavior when handling // ctl.* properties. close(s); if (check_control_perms(msg.value, cr.uid, cr.gid, source_ctx)) { //접근 권한을 검사, control은 시스템 프로퍼티 값을 변경하는 것이 아니라 프로세스의 // 시작과 종료를 요청하는 메시지로, system server와 root 그리고 해당 프로세스만 가능하다. handle_control_message((char*) msg.name + 4, (char*) msg.value); } else { ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n", msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid); } } else { if (check_perms(msg.name, cr.uid, cr.gid, source_ctx)) { // 리눅스의 uid 를 사용하여 접근 권한 확인 property_set((char*) msg.name, (char*) msg.value); } else { ERROR("sys_prop: permission denied uid:%d name:%s\n", cr.uid, msg.name); } // Note: bionic's property client code assumes that the // property server will not close the socket until *AFTER* // the property is written to memory. close(s); } freecon(source_ctx); break; default: close(s); break; } }
|
상기 코드의 check_perms 의 퍼미션확인은 property_perms[] 구조체에서 확인 가능하며 다음과 같다.
struct { const char *prefix; unsigned int uid; unsigned int gid; } property_perms[] = { { "net.rmnet0.", AID_RADIO, 0 }, { "net.gprs.", AID_RADIO, 0 }, { "net.ppp", AID_RADIO, 0 }, { "net.qmi", AID_RADIO, 0 }, { "net.lte", AID_RADIO, 0 }, { "net.cdma", AID_RADIO, 0 }, { "ril.", AID_RADIO, 0 }, { "gsm.", AID_RADIO, 0 }, { "persist.radio", AID_RADIO, 0 }, { "net.dns", AID_RADIO, 0 }, { "sys.usb.config", AID_RADIO, 0 }, { "net.", AID_SYSTEM, 0 }, { "dev.", AID_SYSTEM, 0 }, { "runtime.", AID_SYSTEM, 0 }, { "hw.", AID_SYSTEM, 0 }, { "sys.", AID_SYSTEM, 0 }, { "sys.powerctl", AID_SHELL, 0 }, { "service.", AID_SYSTEM, 0 }, { "wlan.", AID_SYSTEM, 0 }, { "bluetooth.", AID_BLUETOOTH, 0 }, { "dhcp.", AID_SYSTEM, 0 }, { "dhcp.", AID_DHCP, 0 }, { "debug.", AID_SYSTEM, 0 }, { "debug.", AID_SHELL, 0 }, { "log.", AID_SHELL, 0 }, { "service.adb.root", AID_SHELL, 0 }, { "service.adb.tcp.port", AID_SHELL, 0 }, { "persist.sys.", AID_SYSTEM, 0 }, { "persist.service.", AID_SYSTEM, 0 }, { "persist.security.", AID_SYSTEM, 0 }, { "persist.service.bdroid.", AID_BLUETOOTH, 0 }, { "selinux." , AID_SYSTEM, 0 }, { NULL, 0, 0 } }; |
실제 구현하려는 시스템이 동작 중에 프로퍼티 설정을 변경할 필요가 있다면 각 프로퍼티에 대한 접근 권한을 고려해야 한다.
init.rc에는 프로퍼티가 변경될 경우 수행해야할 동작이 기술되어 있다.
on property:vold.decrypt=trigger_reset_main class_reset main on property:vold.decrypt=trigger_load_persist_props load_persist_props on property:vold.decrypt=trigger_post_fs_data trigger post-fs-data [중략] on property:selinux.reload_policy=1 restart ueventd restart installd |
해당 property의 key 조건에 해당되면 조건에 따른 명령어를 실행한다. 예를 들어 selinux.reload_policy가 1로 설정되면 restart ueventd, restart installd 를 실행하게 된다.
6. 프로세스 재시작
init.c의 main 함수의 마지막에 보면 get_signal_fd() 와 동일한 이벤트가 발생하였을 경우 handle_signal을 호출하도록 되어 있다. handle_signal 코드는 다음과 같다.
void handle_signal(void) { char tmp[32]; /* we got a SIGCHLD - reap and restart as needed */ read(signal_recv_fd, tmp, sizeof(tmp)); while (!wait_for_one_process(0)) ; } |
wait_for_one_process는 다음과 같은데.
static int wait_for_one_process(int block) { pid_t pid; int status; struct service *svc; struct socketinfo *si; time_t now; struct listnode *node; struct command *cmd; while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR ); // waitpid()는 Signal을 발생시킨 자식 프로세스가 종료되면 해당 프로세스의 자원을 회수한다. if (pid <= 0) return -1; INFO("waitpid returned pid %d, status = %08x\n", pid, status); svc = service_find_by_pid(pid); // 서비스 리스트에서 종료된 프로세스에 해당하는 서비스 항목을 가져온다. if (!svc) { ERROR("untracked pid %d exited\n", pid); return 0; } NOTICE("process '%s', pid %d exited\n", svc->name, pid); if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) { kill(-pid, SIGKILL); NOTICE("process '%s' killing any children in process group\n", svc->name); } // SVC_ONESHOT 은 한번만 실행되고 종료되는 것이므로 재시작되지 않고 kill 로 종료시킨다. /* remove any sockets we may have created */ for (si = svc->sockets; si; si = si->next) { char tmp[128]; snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name); unlink(tmp); } // 해당 프로세스의 소켓 디스크립터를 해제한다. svc->pid = 0; svc->flags &= (~SVC_RUNNING); /* oneshot processes go into the disabled state on exit, * except when manually restarted. */ if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) { svc->flags |= SVC_DISABLED; } //SVC_ONESHOT 은 SVC_DISABLE을 설정 /* disabled and reset processes do not get restarted automatically */ if (svc->flags & (SVC_DISABLED | SVC_RESET) ) { notify_service_state(svc->name, "stopped"); return 0; } // SVC_DISABLED 되었을 경우 함수 종료 now = gettime(); if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) { if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) { if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) { ERROR("critical process '%s' exited %d times in %d minutes; " "rebooting into recovery mode\n", svc->name, CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60); android_reboot(ANDROID_RB_RESTART2, 0, "recovery"); return 0; } } else { svc->time_crashed = now; svc->nr_crashed = 1; } } svc->flags &= (~SVC_RESTART); svc->flags |= SVC_RESTARTING; /* Execute all onrestart commands for this service. */ list_for_each(node, &svc->onrestart.commands) { cmd = node_to_item(node, struct command, clist); cmd->func(cmd->nargs, cmd->args); } //onrestart 옵션이 있을 경우 프로세스 재시작 시 실행할 명령어를 실행한다. notify_service_state(svc->name, "restarting"); return 0; } |
onrestart 는 init.rc에 기술되어 있다. 다음은 그 예이다.
service servicemanager /system/bin/servicemanager class core user system group system critical onrestart restart healthd onrestart restart zygote onrestart restart media onrestart restart surf |
servicemanager가 재시작 되면 healthd, zygote, media, surf 를 재시작한다.