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

  1. 2017.02.19 [android framework] init 프로세스 #2
반응형

이전 포스팅에 이어서 Android의 init 프로세스에 대해서 더 알아보겠다.


4. 디바이스 초기화

 디바이스 초기화는 두가지 방식이 있다. 1) 부팅과 동시에 init 프로세스가 일괄적으로 디바이스 노드 파일을 생성하는 방식을  Cold Plug 라고 한다. 2) USB 장치와 같이 동작 중에 연결되는 방식을 Hot Plug 라고 한다.


다음은 부팅과 동시에 init 프로세스가 일괄적으로 디바이스 노드 파일을 생성하는 Cold Plug에 대한 설명이다.


 device 초기화는 ueventd 에 의해서 진행된다. ueventd의 메인 함수는 uevent_main 인데 다음과 같다.


int ueventd_main(int argc, char **argv)

{

    struct pollfd ufd;

    int nr;

    char tmp[32];


    /*

     * init sets the umask to 077 for forked processes. We need to

     * create files with exact permissions, without modification by

     * the umask.

     */

    umask(000);


    /* Prevent fire-and-forget children from becoming zombies.

     * If we should need to wait() for some children in the future

     * (as opposed to none right now), double-forking here instead

     * of ignoring SIGCHLD may be the better solution.

     */

    signal(SIGCHLD, SIG_IGN);


    open_devnull_stdio();

    klog_init();


    INFO("starting ueventd\n");


    /* Respect hardware passed in through the kernel cmd line. Here we will look

     * for androidboot.hardware param in kernel cmdline, and save its value in

     * hardware[]. */

    import_kernel_cmdline(0, import_kernel_nv);


    get_hardware_name(hardware, &revision);


    ueventd_parse_config_file("/ueventd.rc");


    snprintf(tmp, sizeof(tmp), "/ueventd.%s.rc", hardware);

    ueventd_parse_config_file(tmp);


    device_init();


    ufd.events = POLLIN;

    ufd.fd = get_device_fd();


    while(1) {

        ufd.revents = 0;

        nr = poll(&ufd, 1, -1);

        if (nr <= 0)

            continue;

        if (ufd.revents == POLLIN)

               handle_device_fd();

    }

}

 여기에서 중요한 부분은 ueventd_parse_config_file와 device_init();이다. 


 ueventd_parse_config_file는 system/core/rootdir/ueventd.rc 나 ueventd.{hardware}.rc 를 읽어서 디바이스 노드를 생성에 필요한 정보를 얻는다. system/core/rootdir/ueventd.rc 파일은 다음과 같다. 노드와 퍼미션, uid, gid가 기술되어 있다.

/dev/null                 0666   root       root

/dev/zero                 0666   root       root

/dev/full                 0666   root       root

/dev/ptmx                 0666   root       root

/dev/tty                  0666   root       root

/dev/random               0666   root       root

/dev/urandom              0666   root       root

/dev/ashmem               0666   root       root

/dev/binder               0666   root       root


# Anyone can read the logs, but if they're not in the "logs"

# group, then they'll only see log entries for their UID.

/dev/log/*                0666   root       log


# the msm hw3d client device node is world writable/readable.

/dev/msm_hw3dc            0666   root       root


# gpu driver for adreno200 is globally accessible

/dev/kgsl                 0666   root       root


# kms driver for drm based gpu

/dev/dri/*                0666   root       graphics


# these should not be world writable

/dev/diag                 0660   radio      radio

/dev/diag_arm9            0660   radio      radio

/dev/android_adb          0660   adb        adb

/dev/android_adb_enable   0660   adb        adb

/dev/ttyMSM0              0600   bluetooth  bluetooth

/dev/uhid                 0660   system     net_bt_stack 


 device_init 함수는 uevent 를 수신하기 위한 소켓을 생성하고 coldboot 함수로 /sys 드렉토리에 정보를 등록한 드라이버에 대해 콜드 플러그 처리한다. (do_coldboot 호출)


void device_init(void)

{

    suseconds_t t0, t1;

    struct stat info;

    int fd;

    sehandle = NULL;

    if (is_selinux_enabled() > 0) {

        sehandle = selinux_android_file_context_handle();

    }

    /* is 256K enough? udev uses 16MB! */

    device_fd = uevent_open_socket(256*1024, true);

    if(device_fd < 0)

        return;


    fcntl(device_fd, F_SETFD, FD_CLOEXEC);

    fcntl(device_fd, F_SETFL, O_NONBLOCK);


    if (stat(coldboot_done, &info) < 0) {

        t0 = get_usecs();

        coldboot("/sys/class");

        coldboot("/sys/block");

        coldboot("/sys/devices");

        t1 = get_usecs();

        fd = open(coldboot_done, O_WRONLY|O_CREAT, 0000);

        close(fd);

        log_event_print("coldboot %ld uS\n", ((long) (t1 - t0)));

    } else {

        log_event_print("skipping coldboot, already done\n");

    }

}


do_coldboot에서는 uevent를 add 메시지로 강제 발생 시킨다.

static void do_coldboot(DIR *d)

{

    struct dirent *de;

    int dfd, fd;


    dfd = dirfd(d);


    fd = openat(dfd, "uevent", O_WRONLY);

    if(fd >= 0) {

        write(fd, "add\n", 4);

        close(fd);

        handle_device_fd();

    }


    while((de = readdir(d))) {

        DIR *d2;


        if(de->d_type != DT_DIR || de->d_name[0] == '.')

            continue;


        fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY);

        if(fd < 0)

            continue;


        d2 = fdopendir(fd);

        if(d2 == 0)

            close(fd);

        else {

            do_coldboot(d2);

            closedir(d2);

        }

    }

}


 handle_device_fd에서는 uevent 를 처리한다. uevent 구조체에 메시지를 파싱하고, handle_device_event와 handle_firmware_event를 호출한다.

 void handle_device_fd()

{

    char msg[UEVENT_MSG_LEN+2];

    int n;

    while ((n = uevent_kernel_multicast_recv(device_fd, msg, UEVENT_MSG_LEN)) > 0) {

        if(n >= UEVENT_MSG_LEN)   /* overflow -- discard */

            continue;


        msg[n] = '\0';

        msg[n+1] = '\0';


        struct uevent uevent;

        parse_event(msg, &uevent);


        handle_device_event(&uevent);

        handle_firmware_event(&uevent);

    }

}

 이떄 uevent 구조체의 const char *action 필드는 "add"로 설정이 된다.


 handle_device_event함수에서는 장치를 비교해서 관련 event를 발생시켜 준다.

static void handle_device_event(struct uevent *uevent)

{

    if (!strcmp(uevent->action,"add") || !strcmp(uevent->action, "change"))

        fixup_sys_perms(uevent->path);


    if (!strncmp(uevent->subsystem, "block", 5)) {

        handle_block_device_event(uevent);

    } else if (!strncmp(uevent->subsystem, "platform", 8)) {

        handle_platform_device_event(uevent);

    } else {

        handle_generic_device_event(uevent);

    }

 예를 들어 block device의 경우에는 handle_block_device_event 함수가 호출된다.

static void handle_block_device_event(struct uevent *uevent)

{

    const char *base = "/dev/block/";

    const char *name;

    char devpath[96];

    char **links = NULL;


    name = parse_device_name(uevent, 64);//1)

    if (!name)

        return;


    snprintf(devpath, sizeof(devpath), "%s%s", base, name);

    make_dir(base, 0755);//2)


    if (!strncmp(uevent->path, "/devices/", 9))

        links = parse_platform_block_device(uevent);


    handle_device(uevent->action, devpath, uevent->path, 1,

            uevent->major, uevent->minor, links);

 여기에서는 device name을 파싱(1)하고 해당 디렉토리를 생성(2)한다. 그리고 handle_device 함수를 호출한다. 다음은 handle_device 함수이다.

static void handle_device(const char *action, const char *devpath,

        const char *path, int block, int major, int minor, char **links)

{

    int i;


    if(!strcmp(action, "add")) {

        make_device(devpath, path, block, major, minor);

        if (links) {

            for (i = 0; links[i]; i++)

                make_link(devpath, links[i]);

        }

    }


    if(!strcmp(action, "remove")) {

        if (links) {

            for (i = 0; links[i]; i++)

                remove_link(devpath, links[i]);

        }

        unlink(devpath);

    }


    if (links) {

        for (i = 0; links[i]; i++)

            free(links[i]);

        free(links);

    }

}

 이 함수에서 make_device 함수를 실행한다. 

 static void make_device(const char *path,

                        const char *upath,

                        int block, int major, int minor)

{

    unsigned uid;

    unsigned gid;

    mode_t mode;

    dev_t dev;

    char *secontext = NULL;


    mode = get_device_perm(path, &uid, &gid) | (block ? S_IFBLK : S_IFCHR);


    if (sehandle) {

        selabel_lookup(sehandle, &secontext, path, mode);

        setfscreatecon(secontext);

    }


    dev = makedev(major, minor);

    /* Temporarily change egid to avoid race condition setting the gid of the

     * device node. Unforunately changing the euid would prevent creation of

     * some device nodes, so the uid has to be set with chown() and is still

     * racy. Fixing the gid race at least fixed the issue with system_server

     * opening dynamic input devices under the AID_INPUT gid. */

    setegid(gid);

    mknod(path, mode, dev);

    chown(path, uid, -1);

    setegid(AID_ROOT);


    if (secontext) {

        freecon(secontext);

        setfscreatecon(NULL);

    }

}

 이 함수에서  최종적으로 디바이스 노드를 만들고 관련 설정을 진행한다.


Hot Plug는 ueventd_main 함수의 마지막 while 문에서 poll 하고 있다가 handle_device_fd 함수에 의해서 처리 된다. 

 

 Hot Plug와 Cold Plug의 차이는 Hot Plug는 ueventd_main의 while문에서 poll 하고 있다가 관련 처리를 하는 것이고, Cold Plug는 강제적으로 uevent 이벤트를 do_coldboot 함수에서 발생시킨다는 것에 차이가 있다. 다음 그림은 조금 다르긴 한데 참고할만하다. init.c 의 main에서 device_init을 실행시키는 것이 아니고 init.c 의 main에서 ueventd_main 을 실행시키고, ueventd_main에서 device_init을 실행시킨다고 보면 된다.

 





반응형
Posted by alias
,