about init in android by andstudy
DESCRIPTION
안드로이드 init 프로세스 분석TRANSCRIPT
Init process in Androidin Android
Created by Andstudy
Andstudy Seminar2009 08 22
김 연찬
Android Study
Android Study members
송형주 전형민 박지훈 황세희
이 백 전유진 김연찬 전승원
강명훈 임기영 박은병 이덕용
박은병 박주애 이덕용 구자관
2009 08 15
윤동렬 김신수 김태연
Study web page Spring note : http://andstudy.springnote.com/Android Pub: http://www.androidpub.com/devstudy_groupb
이 문서는 다음의 CCL (creative commons license) 을 따름니다.
http://creativecommons.org/licenses/by-nc-sa/2.0/kr/저작자표시-비영리-동일조건변경허락 2.0 대한민국
Change LOG
Change history changes editor
2009년 08월 22일 최초작성 김 연찬
2009년 09월 05일 1차 교정 (PDF 배포용으로 수정) 김 연찬
About Init.
Init Process 는 PID 가 1인 프로세스이고 부팅과정에서 커널이 생성하는 첫번째 프로세스이다.
그럼 Init Process는 언제 실행되는가?
start_kernel() -> rest_init() -> kernel_thread() -> kernel_init() -> init_post() 에서 "/init"을 수행
안드로이드 소스트리에서 Init process 코드는 어디에?
/system/core/init/init.c
이 문서에서 다루는 부분의 코드는 /system/core/init/ 에서 찾아 볼수 있다.Init.rc 의 경우는 /system/core/rootdir/init.rc 에 존재한다.
Android Init의 기능
SIGCHLD SIGCHLD Device 초Device 초SIGCHLD
signal
처리
SIGCHLD
signal
처리
RC 파일
내용처리
RC 파일
내용처리
Device 초
기화 &
관리
Device 초
기화 &
관리
Property
설정
Property
설정
xxxxxx
KernalKernalInit post()
InitInitmain
act.sa_handler = sigchld_handler;sigaction(SIGCHLD, &act, 0);act.sa_handler = sigchld_handler;sigaction(SIGCHLD, &act, 0); Sigchild handler()Sigchild handler() Zombi processZombi process
- 자식프로세스 처리를 위한 SIGCHLD SIGNAL handler등록- 자식프로세스 처리를 위한 SIGCHLD SIGNAL handler등록
Make Special purposeDevice node filesMake Special purposeDevice node files
mkdir("/dev", 0755); mkdir("/proc", 0755); mkdir("/sys", 0755);mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755")mkdir("/dev/pts", 0755); mkdir("/dev/socket", 0755);mount("devpts", "/dev/pts", "devpts", 0, NULL);
mkdir("/dev", 0755); mkdir("/proc", 0755); mkdir("/sys", 0755);mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755")mkdir("/dev/pts", 0755); mkdir("/dev/socket", 0755);mount("devpts", "/dev/pts", "devpts", 0, NULL);
/
/dev
Socket pts
/proc /sys
tmpfs
Init Process (1)
xxxxxxmount("devpts", "/dev/pts", "devpts", 0, NULL); mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL);
mount("devpts", "/dev/pts", "devpts", 0, NULL); mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL);
Socket pts
Make Special purposeDevice node filesMake Special purposeDevice node files
open_devnull_stdio();open_devnull_stdio();Fd[0]
Fd[1]
Fd[2]
Fd[3]
Fd[…]
stdin
stdout
stderr
__null__
Fd[0]
Fd[1]
Fd[2]
Fd[3]
Fd[…]
stdin
stdout
stderr
__null__
Make __kmsg__ device file log massage will be written to “/dev/__kmsg__”&set it as “FD_CLOEXEC”
Make __kmsg__ device file log massage will be written to “/dev/__kmsg__”&set it as “FD_CLOEXEC”
log_init();log_init();
mknode(“/dev/__kmsg__”);Log_fd = open(“/dev/__kmsg__”);Fcntl(log_fd, F_SETFD, FD_CLOEXEC);
mknode(“/dev/__kmsg__”);Log_fd = open(“/dev/__kmsg__”);Fcntl(log_fd, F_SETFD, FD_CLOEXEC);
Fd[0]Fd[1]
Fd[3]Fd[2]
stdinstdout
a.txtstderr
parent
Fd[0]Fd[0]Fd[1]Fd[1]Fd[2]Fd[2]
ChildChild
Fd[3]Fd[3]
Ex) FD_CLOEXEC
qemu_init();qemu_init();Qemu 에서 사용하는 메모리영역 초기화Qemu 에서 사용하는 메모리영역 초기화
Init.rc 파일을 Parsing 하여Service_list 와 Action_list를 구성한다.
Init.rc 파일을 Parsing 하여Service_list 와 Action_list를 구성한다. parse_config_file("/init.rc");parse_config_file("/init.rc");
Init Process (2)
Service_listService_list struct servicestruct service
Action_listAction_list Action_listAction_list Action_listAction_list Action_listAction_list
struct servicestruct service
Listnode slistChar *nameChar *classnameInt nargsStruct Action onrestart
Listnode slistChar *nameChar *classnameInt nargsStruct Action onrestart
Slist“console ““default”0“onrestart”
Slist“console ““default”0“onrestart”
struct servicestruct service
Slist“Servicemanager”“default”0“onrestart”
Slist“Servicemanager”“default”0“onrestart”
Listnode alistChar *nameStruct listnode commandStruct command *curtent
Listnode alistChar *nameStruct listnode commandStruct command *curtent
listnode“init”Listnode command
listnode“init”Listnode command
listnode“boot”Listnode command
listnode“boot”Listnode command
QEMU란?
커널 커맨드 라인(/proc/cmdline )을 읽어서 필요한 내용을전역 변수에 저장한다
커널 커맨드 라인(/proc/cmdline )을 읽어서 필요한 내용을전역 변수에 저장한다
get_hardware_name();snprintf(tmp, sizeof(tmp),"/init.%s.rc",hardware)parse_config_file(tmp);
get_hardware_name();snprintf(tmp, sizeof(tmp),"/init.%s.rc",hardware)parse_config_file(tmp);
커널로 부터 H/W 정보를 얻어와서 “init.H/W_name.rc 파일을 Parsing 하여 Service_list와 Action_list 에 추가한다.
커널로 부터 H/W 정보를 얻어와서 “init.H/W_name.rc 파일을 Parsing 하여 Service_list와 Action_list 에 추가한다.
import_kernel_cmdline(0);import_kernel_cmdline(0);
Action list 에서 “early-init”이라는 name의 노드를Action_queue에 삽입하고, Action_queue에 있는 노드를 실행 시킨다.
Action list 에서 “early-init”이라는 name의 노드를Action_queue에 삽입하고, Action_queue에 있는 노드를 실행 시킨다.
action_for_each_trigger("early-init“,action_add_queue_tail);drain_action_queue();
action_for_each_trigger("early-init“,action_add_queue_tail);drain_action_queue();
Argument is ‘0’ : physical H/WArgument is ‘1’ : QEMU emulatorArgument is ‘0’ : physical H/WArgument is ‘1’ : QEMU emulator
Init.rc 와 init.goldfish.rc 에 “early-init” 의name의 항목이 없다. 따라서 여기서 하는일은 없다.
Init.rc 와 init.goldfish.rc 에 “early-init” 의name의 항목이 없다. 따라서 여기서 하는일은 없다.
default h/w configuration is goldfish.->/system/core/rootdir/etc/init.goldfish.rcdefault h/w configuration is goldfish.->/system/core/rootdir/etc/init.goldfish.rc
property_init();property_init();ashmem_create_region(/dev/ashmem) 을 사용하여 공유메모리 공간을 생성한다.
ashmem_create_region(/dev/ashmem) 을 사용하여 공유메모리 공간을 생성한다.
keychords 확인keychords 확인 debuggable = property_get("ro.debuggable"); if (debuggable && !strcmp(debuggable, "1")) debuggable = property_get("ro.debuggable"); if (debuggable && !strcmp(debuggable, "1"))
/dev 이하에 장치 파일을uevent 파일을 이용하여 생성하고 접근권한을 설정한다.
/dev 이하에 장치 파일을uevent 파일을 이용하여 생성하고 접근권한을 설정한다.
device_fd = device_init();device_fd = device_init();
Init Process (3)
/dev 이하에 장치 파일을 uevent 파일을이용하여 생성하고 접근권한을 설정한다./dev 이하에 장치 파일을 uevent 파일을이용하여 생성하고 접근권한을 설정한다.
Property_area memory map
keychords 확인Consols 확인keychords 확인Consols 확인
if( load_565rle_image(INIT_IMAGE_FILE) ) {fd = open("/dev/tty0", O_WRONLY); if (fd >= 0) {
const char *msg; msg = "\n ANDROID“;write(fd, msg, strlen(msg));close(fd); }
if( load_565rle_image(INIT_IMAGE_FILE) ) {fd = open("/dev/tty0", O_WRONLY); if (fd >= 0) {
const char *msg; msg = "\n ANDROID“;write(fd, msg, strlen(msg));close(fd); }
부팅 이미지 출력부팅 이미지 출력
debuggable = property_get("ro.debuggable"); if (debuggable && !strcmp(debuggable, "1"))
keychord_fd = open_keychord(); fd = open(console_name, O_RDWR); if (fd >= 0)
have_console = 1; close(fd);
debuggable = property_get("ro.debuggable"); if (debuggable && !strcmp(debuggable, "1"))
keychord_fd = open_keychord(); fd = open(console_name, O_RDWR); if (fd >= 0)
have_console = 1; close(fd);
Qemu 설정일 때, property에'ro.kernel'이라는 접두어를 붙여 property를 set 한다.
Qemu 설정일 때, property에'ro.kernel'이라는 접두어를 붙여 property를 set 한다.
if (qemu[0]) import_kernel_cmdline(1);
if (qemu[0]) import_kernel_cmdline(1);
로고 파일이 있으면 로고를 출력하고, 로고 파일이 없거나 로딩에 실패하면 tty0에 “ANDROID” 문자열을 출력한다.
로고 파일이 있으면 로고를 출력하고, 로고 파일이 없거나 로딩에 실패하면 tty0에 “ANDROID” 문자열을 출력한다.
action_for_each_trigger("init“,action_add_queue_tail);drain_action_queue();
action_for_each_trigger("init“,action_add_queue_tail);drain_action_queue();
Action list 에서 “init”이라는name의 노드를 Action_queue에삽입하고, Action_queue에 있는
Action list 에서 “init”이라는name의 노드를 Action_queue에삽입하고, Action_queue에 있는
추가 Property 설정추가 Property 설정
if (!strcmp(bootmode,"factory")) property_set("ro.factorytest", "1");
else if (!strcmp(bootmode,"factory2")) property_set("ro.factorytest", "2");
else property_set("ro.factorytest", "0"); property_set("ro.serialno", serialno[0] ? serialno : ""); property_set("ro.bootmode", bootmode[0] ? bootmode : "unknown"); property_set("ro.baseband", baseband[0] ? baseband : "unknown"); property_set("ro.carrier", carrier[0] ? carrier : "unknown"); property_set("ro.bootloader", bootloader[0] ? bootloader: "unknown"); property_set("ro.hardware", hardware); snprintf(tmp, PROP_VALUE_MAX, "%d", revision); property_set("ro.revision", tmp);
if (!strcmp(bootmode,"factory")) property_set("ro.factorytest", "1");
else if (!strcmp(bootmode,"factory2")) property_set("ro.factorytest", "2");
else property_set("ro.factorytest", "0"); property_set("ro.serialno", serialno[0] ? serialno : ""); property_set("ro.bootmode", bootmode[0] ? bootmode : "unknown"); property_set("ro.baseband", baseband[0] ? baseband : "unknown"); property_set("ro.carrier", carrier[0] ? carrier : "unknown"); property_set("ro.bootloader", bootloader[0] ? bootloader: "unknown"); property_set("ro.hardware", hardware); snprintf(tmp, PROP_VALUE_MAX, "%d", revision); property_set("ro.revision", tmp);
Init Process (4)
Ro.bootmodeRo.bootmode
Bootmode[0]Bootmode[0]
Ro.hardwareRo.hardware
goldfishgoldfish
Ro.revisionRo.revision
xxxxxxxxxxxxxx
namenameserialserial
valuevalue
Struct Property infoStruct Property info
/drain_action_queue();drain_action_queue();삽입하고, Action_queue에 있는
노드를 실행 시킨다.create basic FileSystem structure
삽입하고, Action_queue에 있는노드를 실행 시킨다.create basic FileSystem structure
기타 property 파일 및/data/property 경로에 저장된persistent property들을 시스템property 영역에 로드 한다
기타 property 파일 및/data/property 경로에 저장된persistent property들을 시스템property 영역에 로드 한다
if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) signal_fd = s[0]; signal_recv_fd = s[1]; fcntl(s[0], F_SETFD, FD_CLOEXEC); fcntl(s[0], F_SETFL, O_NONBLOCK); fcntl(s[1], F_SETFD, FD_CLOEXEC); fcntl(s[1], F_SETFL, O_NONBLOCK); }
if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) signal_fd = s[0]; signal_recv_fd = s[1]; fcntl(s[0], F_SETFD, FD_CLOEXEC); fcntl(s[0], F_SETFL, O_NONBLOCK); fcntl(s[1], F_SETFD, FD_CLOEXEC); fcntl(s[1], F_SETFL, O_NONBLOCK); }
시그널 처리를 위한 소켓 생성시그널 처리를 위한 소켓 생성
property_set_fd = start_property_service();property_set_fd = start_property_service();
부팅에 필요한 FD 확인부팅에 필요한 FD 확인if ((device_fd < 0) || (property_set_fd < 0) || (signal_recv_fd < 0)) { ERROR("init startup failure\n"); return 1; }
if ((device_fd < 0) || (property_set_fd < 0) || (signal_recv_fd < 0)) { ERROR("init startup failure\n"); return 1; }
default 로 생성한 이미지의 /data/property 에는 4개의 로케일 셋팅 정보 파일이 있다.persist.sys.country persist.sys.languagepersist.sys.localevar persist.sys.timezone
default 로 생성한 이미지의 /data/property 에는 4개의 로케일 셋팅 정보 파일이 있다.persist.sys.country persist.sys.languagepersist.sys.localevar persist.sys.timezone
/dev /proc /sys /sdcard /system /data /cache
Init이 관리하는 FD
queue_all_property_triggers(); drain_action_queue(); queue_all_property_triggers(); drain_action_queue();
action_list 에 있는 노드중에node.name 이 "property" 인 노드들을 acttion queue 에 추가한다.Action_queue에 있는 노드를 실행 시킨다.
action_list 에 있는 노드중에node.name 이 "property" 인 노드들을 acttion queue 에 추가한다.Action_queue에 있는 노드를 실행 시킨다.
init 프로세스가 poll함수로 감시할 파일디스크립터 설정init 프로세스가 poll함수로 감시할 파일디스크립터 설정
ufds[0].fd = device_fd; ufds[0].events = POLLIN;ufds[1].fd = property_set_fd;
ufds[0].fd = device_fd; ufds[0].events = POLLIN;ufds[1].fd = property_set_fd;
Action list 에서 “early-boot”와“boot”이라는 name의 노드를Action_queue에 삽입하고, Action_queue에 있는 노드를실행 시킨다.
Action list 에서 “early-boot”와“boot”이라는 name의 노드를Action_queue에 삽입하고, Action_queue에 있는 노드를실행 시킨다.
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
drain_action_queue();
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
drain_action_queue();
Init Process (5)
- network init- System Server and daemons 의 Permissions 설정- 각 APP group 메모리 사용 설정
- network init- System Server and daemons 의 Permissions 설정- 각 APP group 메모리 사용 설정
Action_listAction_list Action_listAction_list Action_listAction_list
Listnode alistChar *nameStruct listnode
commandStruct command
*curtent
Listnode alistChar *nameStruct listnode
commandStruct command
*curtent
listnode“property”Listnode
command
listnode“property”Listnode
command
[0] fd=device_fd event = POLLINUfds[ ]
For(;;;)drain_action_queue(); restart_processes();
For(;;;)drain_action_queue(); restart_processes();
무한 루프 문에서 action queue에 실행할 action이 있으면 실행하고 재시작이 필요한 프로세스가 있으면 재시작해준다. ufds 를 감시하고POLLIN 이 발생하면 해당 핸들러를 통해 처리 한다.
무한 루프 문에서 action queue에 실행할 action이 있으면 실행하고 재시작이 필요한 프로세스가 있으면 재시작해준다. ufds 를 감시하고POLLIN 이 발생하면 해당 핸들러를 통해 처리 한다.
ufds[1].fd = property_set_fd; ufds[1].events = POLLIN; ufds[2].fd = signal_recv_fd; ufds[2].events = POLLIN;ufds[3].fd = keychord_fd; ufds[3].events = POLLIN;
ufds[1].fd = property_set_fd; ufds[1].events = POLLIN; ufds[2].fd = signal_recv_fd; ufds[2].events = POLLIN;ufds[3].fd = keychord_fd; ufds[3].events = POLLIN;
해당 fd의 POLLIN 에 따라 4가지 핸들러를 호출한다.해당 fd의 POLLIN 에 따라 4가지 핸들러를 호출한다.
while (!wait_for_one_process(0))handle_device_fd(device_fd);handle_property_set_fd(property_set_fd);handle_keychord(keychord_fd);
while (!wait_for_one_process(0))handle_device_fd(device_fd);handle_property_set_fd(property_set_fd);handle_keychord(keychord_fd);
Init [1] fd=property_set_fd event = POLLIN
[2] fd=signal_recv_fd event = POLLIN
[3] fd=keychord_fd event = POLLIN
Init.c 분석 (1)
• File descriptor, Signal 구조체, Polling 을 위한 구조체 등
• int device_fd = -1; struct sigaction act; struct pollfd ufds[4];Init이 사용할 자료구조 선언
• 자식프로세스 처리를 위한 SIGCHLD SIGNAL handler등록SIGCHLD Handler등록
• /dev, /proc, /sys 디렉토리를 각각 생성
• tmpfs, devpts, proc, sysfs 마운트디렉토리를 생성 및 마운트
• tmpfs, devpts, proc, sysfs 마운트
• stdin, stdout, stderr File descriptor 를 문자 디바이스 파일
/dev/__null__ 의 FD 로 연결한다.open_devnull_stdio();
• /dev/__kmsg__ 디바이스 파일을 생성하고, 파일 디스크립터
를 log_fd에 저장log_init();
• /dev/__kmsg__에 log 내용을 기록INFO("reading config file\n");
SIGCHILD SIGNAL
SIGCHILD Signal- 자식 프로세스가 멈추거나 종료하거나 추적당하는 경우 부모프로세스가 받는 시그널
Init process 는왜 SIGCHILD 시그널을사용할까?- 리눅스에서 자식프로세스 보다 부모 프로세스가 먼저 죽는다면, 자식프로세
스의 부모를 init process로 만들어준다. 그래서 고아 프로세스가 죽을 때 처리해야 하는 일을 init process가 대신하게된다. 따라서 init process는SIGCHILD 해야 하는 일을 init process가 대신하게된다. 따라서 init process는SIGCHILD Signal 를 처리 해야만 한다.- init process 의 자식프로세스 중에 프로세스가 종료하고 재 시작 해야 하는프로세스가 있다면, 재 시작 관련 설정을 해줘야 한다. (ex. Restart 옵션으로 시작된 Service)
SA_NOCLDSTOP flag: 만약 SIGCHLD의 시그널 핸들러일 경우 자식 프로세스의 상태가 stop일 경우는 SIGCHLD 시그널이 발생 안됨.
디렉토리를 생성 및 마운트
/
/dev
[tmpfs]
/pts /socket
/proc
[proc]
/SYS
[sysfs]
[devpts]
File System 설명
Tmpfs tmpfs는 램파일시스템의 일종(주로 성능 향상 목적)
Devpts devpts는 가상 터미널을 위한 파일시스템
Proc Proc fs는 커널메모리에서 돌아가는 일종의 가상 파일시스템
Sysfs sysfs 파일 시스템은 proc, devfs, devpts 파일 시스템을하나로 통합한 파일 시스템 (Linux Kernel 2.6 에서 도입)
open_devnull_stdio()
static const char *name = "/dev/__null__";if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {
fd = open(name, O_RDWR);unlink(name);if (fd >= 0) {dup2(fd, 0); dup2(fd, 1); dup2(fd, 2);if (fd > 2) {
close(fd); } 이후로는 stdio 가 null dev로 설정되어 init process가 어떠한 메시지
주번호 1 부번호 3주번호 1 부번호 3
이후로는 stdio 가 null dev로 설정되어 init process가 어떠한 메시지를 stdio로 보내더라도 null dev로 전달되어 stdio 불가능해진다.
Fd[0]
Fd[1]
Fd[2]
Fd[3]
Fd[…]
stdin
stdout
stderr
__null__
Fd[0]
Fd[1]
Fd[2]
Fd[3]
Fd[…]
stdin
stdout
stderr
__null__
log_init()
void log_init(void)static const char *name = "/dev/__kmsg__";
if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {log_fd = open(name, O_WRONLY);
fcntl(log_fd, F_SETFD, FD_CLOEXEC);unlink(name);
/dev/__kmsg__ 디바이스 파일을 생성하고, 파일 디스크립터를 log_fd에 저장
주번호 1 부번호 11주번호 1 부번호 11
FD_CLOEXEC fd의 close-on-exec 플래그를 arg의 FD_CLOEXEC FD_CLOEXEC fd의 close-on-exec 플래그를 arg의 FD_CLOEXEC 비트에 의해 지정된 값으로 설정한다.
close-on-exec 보통 프로세스에서 exec를 시켜서 새로운 프로세스를 실행시키면새로운 프로세스는 기존의 프로세스의 이미지를 덮어쓰게 된다. 그러면서 기존 프로세스가 열었던 파일 지정자를 그대로 넘겨주게된다. 그러나 기존 프로세스가 열었던 파일 디스크립터의 close-on-exec가 set됐을 경우 해당 파일은 새로운 프로세스로는 상속이되지 않는다.
Init 에서 close-on-exec 의 의미
/dev/__kmsg__는 init이 unlink를 했기 때문에 파일의 데이터는 남아있지만 접근할 이름이 없어졌다. 그리고close-on-exec를 set 함으로써 fork를 통해서도 파일 디스크립터는 상속이 되지 않는다. 따라서 /dev/__kmsg__는Init process 만 접근 가능하다.
close-on-exec flag
close-on-exec = unSET close-on-exec = SET
Parent 가 “a.txt”를 open() 후에 Fork()를이용하여 child를 생성할 때 fd 값의 차이.
Fd[0]
Fd[1]
stdin
stdoutparent
Fd[0]
Fd[1]
stdin
stdoutparent
Fd[1]
Fd[3]
Fd[2]
stdout
a.txt
stderr
Fd[0]Fd[0]
Fd[1]Fd[1]
Fd[3]Fd[3]
Fd[2]Fd[2]
ChildChild
Fd[1]
Fd[3]
Fd[2]
stdout
a.txt
stderr
Fd[0]Fd[0]
Fd[1]Fd[1]
Fd[2]Fd[2]
ChildChild
Fd[3]Fd[3]
Init.c 분석 (2)
• init.rc 파일을 파싱해서 각 action, service 섹션별로 연
결 리스트를 생성한다parse_config_file("/init.rc");
• 안드로이드의 에뮬레이터로 사용되는 QEMU와 관련된
접근 권한 관련 변수 초기화qemu_init();
• 커널 커맨드 라인을 읽어서 필요한 내용을 전역 변수에• 커널 커맨드 라인을 읽어서 필요한 내용을 전역 변수에
저장한다. import_kernel_cmdline(0);
• 커널로부터 CPU정보를 읽어와 hardware와 revision 정
보를저장get_hardware_name();
• hardware에 해당하는 init.rc 파일을 추가적으로 파싱
• snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);parse_config_file(tmp);
parse_config_file("/init.rc“)INIT.RC 내용 (/system/core/rootdir/init.rc)
Init.rc 파일을 Parsing 하여 Service_list 와 Action_list를 구성한다.
RC file ? [runtime configuration files] runtime시에 환경설정을 할 수 있도록 설정내용을 정의한 파일
on init [환경 설정]
- 글로벌 변수 초기화- mount point 생성 /sdcard, /system, /data, /cache-MTD 파티션 마운드 (기본적으로 yaffs2 사용)- create basic filesystem structure
on boot[boot action 정의]
- network init- System Server and daemons 의 Permissions 설정- 각 APP group 메모리 사용 설정
class_start default[서비스 시작]
- service 정의 형식- service <name> <pathname> [ <argument> ]*<option><option>
Service_listService_list struct servicestruct service
Action_listAction_list Action_listAction_list Action_listAction_list Action_listAction_list
struct servicestruct service
Listnode slistChar *nameChar *classnameInt nargsStuct Action onrestart
Listnode slistChar *nameChar *classnameInt nargsStuct Action onrestart
Slist“console ““default”0“onrestart”
Slist“console ““default”0“onrestart”
struct servicestruct service
Slist“Servicemanager”“default”0“onrestart”
Slist“Servicemanager”“default”0“onrestart”
Listnode alistChar *nameStruct listnode commandStruct command *curtent
Listnode alistChar *nameStruct listnode commandStruct command *curtent
listnode“init”Listnode command
listnode“init”Listnode command
listnode“boot”Listnode command
listnode“boot”Listnode command
CMD CMDCMD
CMD CMDCMD
APP group & 메모리 설정
APP group ADJvalue
Define the memory thresholds [4k pages ]
FOREGROUND_APP 0 1536 [6M] 전면에 있는 프로그램
VISIBLE_APP 1 2048 [8M] 화면에 보이지만 실행되지 않는APP
SECONDARY_SERVER 2 4096 [16M] Service Demon
HOME_APP 4 4096 [16M] 시작화면에 등록되는 APPHOME_APP 4 4096 [16M] 시작화면에 등록되는 APP
HIDDEN_APP_MIN 7 5120 [20M] 최소화 시킨 APP
CONTENT_PROVIDER 14 5632 [22M] CONTENT_PROVIDER
EMPTY_APP 15 6144 [24M]
About ADJ Value ?- Define the oom_adj values for the classes of processes that can be killed by the kernel- OOM Killer 가 동작해야 할때 계산되는 score 값에 영향을 미치는 값- 숫자가 높을 수록 OOM killer에 의해 죽을 가능성이 높다. - OOM_killer에 절대 죽지 않는 ADJ value 는 -17 이다. (ex init process)
Service Start [service 정의 형식]service <name> <pathname> [ <argument> ]*<option><option>
[code]service servicemanager /system/bin/servicemanager
user systemcriticalonrestart restart zygoteonrestart restart zygoteonrestart restart media
- servicemanager 는 /system/bin/servicemanager 경로에 존재- critcal 옵션 [ 4분 안에 4번의 오류가 발생 한다면 reboot 하겠다는 안드로이드의 시스템 운영정책]- onrestart 옵션 -> servicemanager 가 재시작되면 zygoto & media 도 재시작해라 라는 의미
-ini.rc 에서 시작하는 서비스 리스트Console adbd vold servicemanager ril-daemon zygote mediabootsound dbus hcid hfag hsag installd flash_recovery
※ Init.rc 에 대한 보다 많은 정보는 /system/core/init/readme.txt 문서 참조
qemu_init()
Func 기능 : qemu_perms 영역을 memset() 을 이용하여 초기화 한다.
What is QEMU & Goldfish?
Google은 app 개발자를 위해 SDK 배포시 emulator 를 포함시켰고, 이emulator 에서 동작하도록 하기위한 가상의 device configuration을goldfish 라 합니다. 그리고 이 emulator 를 QEMU라 합니다.
Applications
APP Framework
Android Run- Time
Libraries
Linux Kernel
Emulator or Hardware
Network
QEMUQEMU
ARM coreSimulatorARM coreSimulator
GoldfishHardware Simulator
GoldfishHardware Simulator
import_kernel_cmdline(0)
커널 커맨드 라인(/proc/cmdline )을 읽어서 필요한 내용을 전역 변수에 저장한다
“0”을 인자로 넘길 경우는 실제 타겟을 위한 몇몇 커맨드 라인의 내용이 init 프로세스의 전역변수에 저장
“1”을 인자로 넘길 경우는 QEMU 에뮬레이터를 위해 모든 커맨드 라인의 내용을 "ro.kernel" 접두어를 붙여서 property 값을 set한다.(unix domain socket 이용)socket 이용)
Init.H/W_name.rc 파싱
커널(proc 파일시스템)로부터 CPU정보를 읽어와 hardware와 revision 정보를 전역변수에 저장하고 이를 이용하여 hardware 관련 rc 파일을 추가적으로 파싱한다. 현재는default로 hardware가 goldfish로 되어 있기 때문에 init.goldfish.rc 파일이 파싱 된다.
" /system/core/rootdir/etc/init.goldfish.rc”
[code]get_hardware_name();snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);parse_config_file(tmp);
Init.rc 파일을 파싱해서 만들어놓은 Service_list 와 Action_list 에 해당 항목이 있다면 추Init.rc 파일을 파싱해서 만들어놓은 Service_list 와 Action_list 에 해당 항목이 있다면 추가한다.
Service_listService_list struct servicestruct service
Action_listAction_list Action_listAction_list Action_listAction_list Action_listAction_list
struct servicestruct service
Listnode slistChar *nameChar *classnameInt nargsSteuct Action onrestart
Listnode slistChar *nameChar *classnameInt nargsSteuct Action onrestart
Slist“console ““default”0“onrestart”
Slist“console ““default”0“onrestart”
struct servicestruct service
Slist“Servicemanager”“default”0“onrestart”
Slist“Servicemanager”“default”0“onrestart”
Listnode alistChar *nameStruct listnode commandStruct command *curtent
Listnode alistChar *nameStruct listnode commandStruct command *curtent
listnode“init”Listnode command
listnode“init”Listnode command
listnode“boot”Listnode command
listnode“boot”Listnode command
Action_listAction_list
listnode“xxx”Listnode command
listnode“xxx”Listnode command
CMD CMDCMD
CMD CMDCMD
CMD
CMD
CMD CMD
Init 분석 (2,3)
• action리스트에서 name이 “early-init”인 노드를 ACTION queue에 추
가한다.action_for_each_trigger("early-in
it“,action_add_queue_tail);
• ACTION queue에 저장된 커맨드를 순차적으로 실행한다.
drain_action_queue();
•uevent 파일들에 'add' 명령을 write해서 디바이스 추가 이벤트를 발생
시키고 이렇게 발생된 이벤트는 uevent 소켓을 통해 수신해서 파싱
• uevent메시지를 수신하기 위해 사용한 소켓을 리턴함.device_fd = device_init();
•system property 영역을 생성하고, default property들을 저장함
property_init();
Action_queue에 실행할 노드 추가 & 실행
action리스트에서 name이 “early-init”인 노드를 ACTION queue에 추가한다.
Action-list에 name=“early-init”인 노드가 없기 때문에 아무 일도 하지 않는다.(init.rc 와 init.goldfish.rc 에 “early-init” 항목이 없다)
init 프로세스에서는 다음과 같은 4개의 boot action을 정의 할수 있다.
early-initinit early-boot,
정의된 action 없음create basic filesystem structure정의된 action 없음
action_for_each_trigger("early-init“,action_add_queue_tail)action_for_each_trigger("early-init“,action_add_queue_tail)
early-boot,Boot
정의된 action 없음System Server and daemons 의 Permissions 설정
early-init, early-boot, 추후 시스템의 확장을 고려해서 설계한 것이라 추측된다.
action queue에 있는 내용(커맨드)를 실행한다.
“early-init”에서 추가한 내용이 없을것 이므로 여기서 실행되는 내용도 없을 것이다.
drain_action_queue()drain_action_queue()
device_init()uevent 메시지용 소켓 오픈 하고 3번의 coldboot() 함수를 호출한다.
[code]Fd = open_uevent_socket(); coldboot(fd, "/sys/class");coldboot(fd, "/sys/block");coldboot(fd, "/sys/devices");
cold boot는 무슨일을 하나?
/dev/ 이하 파일들을 init 프로세스에서 생성하는데, 어떠한 디바이스들이 있는지를/dev/ 이하 파일들을 init 프로세스에서 생성하는데, 어떠한 디바이스들이 있는지를확인하기 위해서 netlink socket (device_fd) 를 생성하고, 디바이스 드라이버와uevent 메세지를 주고 받는다. 과정은 디렉토리 "/sys/class" , "/sys/block", "/sys/devices" 를 각각 검색하여, uevent 파일을 열어서 "add" 를 write 한 후, 응답하는 uevent 메세지를 수신하고, 해당 디바이스의 노드 생성 및 접근 권한을 설정한다.
수신된 uevent구조체의 subsysem과path 필드의 정보를 참조하여 /dev 디렉토리 이하의 장치 관련 서브 디렉토리 및장치 파일을 생성 혹은 삭제하고 접근 권한을 설정한다.
event가 firmware subsystem 관련이고, uevent->acttion 이 'ADD'이면 fork()를통해 새로운 프로세스를 만들고 irmware관련 uevent는 새로운 자식 프로세스에서 처리한다. (process_firmware_event(uevent);)
uevent
- LDM(Linux Device Model)에서는 커널 이벤트를 사용자공간으로전달하기 위한 인터페이스를 제공하고 있다. 이것이 바로 uevent다.- uevent는 커널에서 유저 프로세스에게 디바이스 관련 메시지를 전달하는 netlink socket의 한 종류이다.
netlink socket ?netlink socket은 커널과 유저 영역 사이의 통신(IPC) 방법이다
<커널> <-------( netlink socket ) ------> <유저프로세스>
netlink socket 장점
-netlink의 경우는 커널 모듈로 추가 가능.- 다른 IPC에 반해, netlink는 여러 프로세스 그룹으로 멀티캐스트 전송이 가능- 시스템콜과 ioctl의 경우, 유저 애플리케이션에 의해 시작 가능 , 이에 반해netlink는 커널에 의해서 시작 가능
Unix domain socket
Unix domain socket socket
Unix Domain 소켓은 같이 동일 PC내의프로세스끼리 통신을 하기 위해서 사용
네트워크 프로그래밍에서 네트워크로 연결된 서로 다른 PC간의 통신을 위해 사용
파일명을 가지고 바인딩 ip주소와 포트로 바인딩
sock = socket( PF_FILE, SOCK_DGRAM, 0); sock = socket( PF_INET, SOCK_DGRAM, 0);
Unix domain 사용 예struct sockaddr_un server_addr;struct sockaddr_un server_addr;memset( &server_addr, 0, sizeof( server_addr));server_addr.sun_family = AF_UNIX;strcpy( server_addr.sun_path, "/tmp/test_server.dat");
property_init()
• ashmem_create_region(/dev/ashmem) 을 사용하여 공유 메모리 공간을 생성한다. (anonymous shared memory)
• ashmem_create_region(/dev/ashmem) 을 사용하여 공유 메모리 공간을 생성한다. (anonymous shared memory)
Init 분석 (3,4)•Keychord open 조건을 확인하고 참인 조건이면 open
•keychord_fd = open_keychord();keychords 확인
•console을 open해서 동작 유무를 체크한다.
•정상동작을 한다면 have_console = 1 로 셋팅Consols 확인
•565rle image(로고) 파일을 프레임 버퍼에 로딩한다. •565rle image(로고) 파일을 프레임 버퍼에 로딩한다.
• image 파일이 없으면 텍스트 모드로 프레임 버퍼에 “ANDORID” 출력부팅 이미지 출력
• if (qemu[0]) import_kernel_cmdline(1);
•커널 커맨드 라인의 옵션들을 QEMU에서 참조하게끔 'ro.kernel'이라는 접두어
를 붙여 property를 생성한다.
QEMU설정에 따라 CMDLINE
변경
•각 커널 커맨드에 대한 중요 옵션들을 property로 만든다.
•Factory mode, ro.bootmode, ro.baseband등등추가 Property 설정
open_keychord(), Consols 확인
Keychord 란?
Keychord는 핸드폰에 있는 단축키와 조합키 와 같은 특수키와 조합 키를 지원하기 위한 구조이다.
[code]debuggable = property_get("ro.debuggable"); if (debuggable && !strcmp(debuggable, "1"))
keychord_fd = open_keychord();
debuggable 셋팅값에 따라 keychord를 오픈한다.
Consols 동작 확인
[code]fd = open(console_name, O_RDWR); if (fd >= 0)
have_console = 1; close(fd);
Consols 동작 확인을 하고 have_console 변수를 셋 한다.
부팅 이미지 출력
565rle image(로고) 파일을 프레임 버퍼에 로딩한다.
[code]if( load_565rle_image(INIT_IMAGE_FILE) ) {
fd = open("/dev/tty0", O_WRONLY); if (fd >= 0) {
const char *msg; msg = "\n"
"\n“"\n““ A N D R O I D ";
write(fd, msg, strlen(msg)); close(fd); }
-/initlogo.rle 파일이 있다면 이미지를 로딩하여 LCD에 출력한다.-/initlogo.rle 파일이 없거나, image file 로딩이 실패하면 -1을 리턴하고 tty0 에텍스트(“A N D R O I D”)를 출력한다.- 로고이미지는 565rle format 이다.
QEMU설정에 따라 CMDLINE 변경
Qemu 환경이라면 이에 맞는 셋팅을 해준다.
[code]if (qemu[0])
import_kernel_cmdline(1);
import_kernel_cmdline(1) 인자가 1이면 qemu 관련 환경이고, 내부에서property_set() 을 하게된다. Property 는 'ro.kernel'이라는 접두어를 붙여property를 생성한다
property_init() Property_area 로 사용할 공유메모리 공간을 생성한다
property_get() /property_set()
start_property_service() property_service() 를 시작해야 하는 서비스 내용을 소켓을 이용하여 시스템에 알린다.
property service(handle_property_set_fd())
위에서 써진 소켓 내용을 통해 서비스를 시작한다.
Property 관련 함수
추가 Property 설정
각 커널 커맨드에 대한 중요 옵션들을 property로 만든다.
[code]if (!strcmp(bootmode,"factory"))
property_set("ro.factorytest", "1"); else if (!strcmp(bootmode,"factory2"))
property_set("ro.factorytest", "2"); else property_set("ro.factorytest", "0"); property_set("ro.serialno", serialno[0] ? serialno : ""); property_set("ro.bootmode", bootmode[0] ? bootmode : "unknown");
ro.factorytestro.factorytest
11
Ro.hardwareRo.hardware
goldfishgoldfish
namenameserialserial
valuevalue
Struct Property infoStruct Property info
property_set("ro.bootmode", bootmode[0] ? bootmode : "unknown"); property_set("ro.baseband", baseband[0] ? baseband : "unknown"); property_set("ro.carrier", carrier[0] ? carrier : "unknown"); property_set("ro.bootloader", bootloader[0] ? bootloader : "unknown"); property_set("ro.hardware", hardware); snprintf(tmp, PROP_VALUE_MAX, "%d", revision); property_set("ro.revision", tmp);
Property_area에 property_info 구조체의 형태로Property_area를 채운다.
goldfishgoldfish
Ro.revisionRo.revision
xxxxxxxxxxxxxx
Init 분석 ( 4 )
•전체 action list에서 'init'에 해당하는 action에 관계되는 커맨드 내용을 뽑아내
ACTION queue에 저장action_for_each_trigger("init
“,action_add_queue_tail);
•ACTION queue에 저장된 커맨드를 순차적으로 실행한다.
drain_action_queue();
•기타 property 파일 및 /data/property 디렉토리에 저장된 persistent propertyproperty_set_fd = start_pro •기타 property 파일 및 /data/property 디렉토리에 저장된 persistent property
들을 시스템 property 영역에 로드한다. 그리고 property service를 위한 서버용
unix domain socket을 생성하고, 리턴한다.
property_set_fd = start_pro
perty_service();
•sockpair 시스템 콜을 이용해 서로 연결된 unix domain socket 쌍을 생성한다시그널 처리를 위한 소켓 생
성
•device_fd , property_set_fd, signal_recv_fd 값이 모두 0 보다 커야 한다.
•그 외의 경우는 ERROR()호출 후 return 1부팅에 필요한 FD 확인
“Init” action 실행
-Action리스트에서 name이 “init”인 노드를 ACTION queue에 추가한다.-추가한 action queue에 있는 노드를 실행시킨다.
[code]action_for_each_trigger("init", action_add_queue_tail); drain_action_queue();
Init.rc. “on init “ 섹션에서 정의한 Action이 시작되는 시점이고, 다음과 같은 내용의 명령어를 수행한다.
- 글로벌 변수 초기화- 글로벌 변수 초기화- mount point 생성 /sdcard, /system, /data, /cashe- MTD 파티션 마운드 (기본적으로 yaffs2 사용)- create basic filesystem structure
/
/dev /proc /sys /sdcard /system /data /cashe
basic filesystem structure basic filesystem structure
start_property_service()
property_set_fd = start_property_service();
- 기타 property 파일 및 /data/property 디렉토리에 저장된 persistent property들을 시스템 property 영역에 로드한다. - property service를 위한 서버용 unix domain socket을 생성하고, 리턴한다.- 이후에 property 를 변경할 경우 여기서 생성한 unix domain socket을 이용한다.
/data/property/file 은 안드로이드의 로케일 설정 파일이 있다.persist.sys.country persist.sys.languagepersist.sys.localevar persist.sys.timezonepersist.sys.localevar persist.sys.timezonestart_property_service() 함수에서 참조하는 property fils list
/system/build.prop/system/default.prop/data/local.prop
시그널 처리를 위한 소켓 생성
서로 연결된 unix domain socket 쌍을 생성한다. S[0], S[1]
[code]if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) { signal_fd = s[0]; signal_recv_fd = s[1]; fcntl(s[0], F_SETFD, FD_CLOEXEC); fcntl(s[0], F_SETFL, O_NONBLOCK); fcntl(s[1], F_SETFD, FD_CLOEXEC);fcntl(s[1], F_SETFD, FD_CLOEXEC);fcntl(s[1], F_SETFL, O_NONBLOCK); }
- sockpair 시스템 콜은 서로 연결된 unix domain socket 쌍을 생성한다. 생성된소켓 쌍은 서로 직접 연결되어 있어 바인딩이 필요 없으며. 4번째 인자에 저장된다.
S[] 는 init이 SIGCHLD시그널 핸들러에서 발생한 시그널번호를 수신하는 소켓이다.
부팅에 필요한 FD 확인
device_fd, property_set_fd, signal_recv_fd가 정상적이어야만 부팅이 진행된다.
if ((device_fd < 0) || (property_set_fd < 0) || (signal_recv_fd < 0)) {ERROR("init startup failure\n"); return 1; }
ERROR() 함수는 __kmsg__ 에 Level =3 으로 로그를 남기는 함수이다.
Fd[0]stdin
Init이 관리
Init
Fd[0]Fd[1]
Fd[2]Fd[x]
Fd[…]
stdinstdout
stderr
__null__
Fd[x]
Fd[x]
Fd[x]
Fd[x]
/dev/keychord
Uevent Socket
Unix domain Socket
Unix domain Socket
device_fd
Property_set_fd
Signal_recv_fdkeychord_fd
__kmsg__
Init이 관리하는 FD
Init 분석 ( 5 )
• action_for_each_trigger("early-boot", action_add_queue_tail);
• action_for_each_trigger("boot", action_add_queue_tail);
• drain_action_queue();
‘early-boot'와 'boot' action에
관계되는 커맨드 실행
• 아직까지 셋팅 되지 않은 property 를
queue에 추가하고 실행한다.
queue_all_property_triggers();
drain_action_queue(); queue에 추가하고 실행한다.drain_action_queue();
• ufds[0] : uevent 메시지 체크
• ufds[1] : property set관련 Unix Domain 소켓 메시지 체크
• ufds[2] : SIGCHLD 시그널 발생 체크
• ufds[3] : keychord 발생 체크
init 프로세스가 poll함수로 감
시할 파일디스크립터 설정
• ufds에서 정의한 파일 디스크립터들의 입력을 감시한다
• 4개의 FD에서 POLLIN이 뜨면 해당 이벤트 처리를 한다.무한 루프 문
action_for_each_trigger(“boot“,action_add_queue_tail)
Action list에서 name이 'early-boot'와 'boot‘ 인 커맨드 내용을 뽑아내ACTION queue에 추가한 후, 각 커맨드를 실행한다
[code]action_for_each_trigger("early-boot", action_add_queue_tail); action_for_each_trigger("boot", action_add_queue_tail);drain_action_queue();
queue_all_property_triggers(); queue_all_property_triggers(); drain_action_queue();
Init.rc. 내용에서 보았듯이 early-boot의 내용은 없었기 때문에 이부분은 실행되는 것이 없을 것이다. Boot 섹션은 다음과 같은 일을 한다.- network init- System Server and daemons 의 Permissions 설정- 각 APP group 메모리 사용 설정
queue_all_property_triggers() 함수에서 아직까지 셋팅 되지 않은 property 를queue에 추가하고 실행한다.
Ufds[ ] 파일디스크립터 설정init 프로세스가 poll함수(I/O multiplexing)로 감시할 파일 디스크립터 설정
[code]ufds[0].fd = device_fd; ufds[0].events = POLLIN; ufds[1].fd = property_set_fd; ufds[1].events = POLLIN; ufds[2].fd = signal_recv_fd; ufds[2].events = POLLIN; fd_count = 3; ufds[3].fd = keychord_fd; ufds[3].events = POLLIN; fd_count++;
ufds[0] : uevent 메시지 체크ufds[0] : uevent 메시지 체크ufds[1] : property set관련 Unix Domain 소켓 메시지 체크ufds[2] : SIGCHLD 시그널 발생 체크ufds[3] : keychord 발생 체크
Init [1] fd=property_set_fd event = POLLIN
[0] fd=device_fd event = POLLIN
[2] fd=signal_recv_fd event = POLLIN
[3] fd=keychord_fd event = POLLIN
Ufds[ ]
무한 루프 문Loop를 돌면서 action queue 에 처리해야 하는 action이 있다면 실행하고, restart 해야 하는 prosess가 있으면 재시작 해준다.Poll() 함수를 이용하여 ufds에서 정의한 파일 디스크립터들의 입력을 감시한다
[code]for (i = 0; i < fd_count; i++)
ufds[i].revents = 0; drain_action_queue(); restart_processes(); nr = poll(ufds, fd_count, timeout); if (nr <= 0) if (nr <= 0)
continue; if (ufds[2].revents == POLLIN) {
read(signal_recv_fd, tmp, sizeof(tmp)); while (!wait_for_one_process(0)) ; continue; }
if (ufds[0].revents == POLLIN) handle_device_fd(device_fd); if (ufds[1].revents == POLLIN) handle_property_set_fd(property_set_fd); if (ufds[3].revents == POLLIN) handle_keychord(keychord_fd);
drain_action_queue(); restart_processes();
ufds[0]ufds[0] wait_for_one_process(0)wait_for_one_process(0)
nr = poll(ufds);
nr = poll(ufds);
Nr > 0
yes
no
yes
yesno
마름모 안에 텍스트는“ufds[x].revents == POLLIN ?”대신 “dfds[x]”로 표현
ufds[1]ufds[1]
ufds[2]ufds[2]
ufds[3]ufds[3]
handle_device_fd(device_fd)handle_device_fd(device_fd)
handle_property_set_fd(property_set_fd)handle_property_set_fd(property_set_fd)
handle_keychord(keychord_fd); handle_keychord(keychord_fd);
yes
yes
yes
no
no
Ufds[ ] handle actionwait_for_one_process(0)SIGCHID 시그널에 발생 했다는 것은 child process가 종료 했다는 것을 의미한다. 따라서 이 함수에서는 process 종료 시에 parent 가 처리해야 하는 내용이 있다. Wait() 하고, 종료된 process 속성 값에 따라 재 시작 해줘야 하는지 확인하고옵션에 따라 기능을 수행한다.
handle_device_fd(device_fd)Device_fd에 Uevent가 발생한것은 디바이스 드라이버에서 핫 플러그 등의 디바이스 관련한 이벤트가 발생할 경우이다. 따라서 init procrss는 이런 디바이스 메이스 관련한 이벤트가 발생할 경우이다. 따라서 init procrss는 이런 디바이스 메시지를 처리해야한다. 이 루틴은 앞서서 devicd_init()에서 이미 호출된 적이 있다.- uevent는 커널에서 유저 프로세스에게 디바이스 관련 메시지를 전달하는netlink socket의 한 종류이다.
handle_property_set_fd(property_set_fd); Property가 변경되어야 할때, 소켓을 통해 전달된 정보를 바탕으로 권한 체크를하고, 문제가 없다면 property_set()을 한다.
handle_keychord(keychord_fd); Keychord 정보를 읽고 나서, 읽어온 keychord 에 매칭되는 서비스가 있으면 실행한다.