'2017/02/20'에 해당되는 글 1건

  1. 2017.02.20 [android framework] init 프로세스 #3
반응형

이전 프스팅들에 이어서 본 포스팅에서도 안드로이드의 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 를 재시작한다.

반응형
Posted by alias
,