Your SlideShare is downloading. ×
About Init In Android By Andstudy
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Introducing the official SlideShare app

Stunning, full-screen experience for iPhone and Android

Text the download link to your phone

Standard text messaging rates apply

About Init In Android By Andstudy

12,343
views

Published on

안드로이드 init 프로세스 분석

안드로이드 init 프로세스 분석


2 Comments
7 Likes
Statistics
Notes
No Downloads
Views
Total Views
12,343
On Slideshare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
320
Comments
2
Likes
7
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Init process in Android Created by Andstudy Andstudy Seminar 2009 08 22 김 연찬
  • 2. Android Study 2009 08 15 Android Study members 송형주 전형민 박지훈 황세희 이 백 전유진 김연찬 전승원 강명훈 임기영 박은병 이덕용 박은병 박주애 이덕용 구자관 윤동렬 김신수 김태연 Spring note : http://andstudy.springnote.com/ Study web page Android Pub: http://www.androidpub.com/devstudy_groupb 이 문서는 다음의 CCL (creative commons license) 을 따름니다. http://creativecommons.org/licenses/by-nc-sa/2.0/kr/ 저작자표시-비영리-동일조건변경허락 2.0 대한민국
  • 3. Change LOG Change history changes editor 2009년 08월 22일 최초작성 김 연찬 2009년 09월 05일 1차 교정 (PDF 배포용으로 수정) 김 연찬
  • 4. About Init. Init Process 는 PID 가 1인 프로세스이고 부팅과정에서 커널이 생성하 는 첫번째 프로세스이다. 그럼 Init Process는 언제 실행되는가? start_kernel() -> rest_init() -> kernel_thread() -> kernel_init() -> in it_post() 에서 "/init"을 수행 안드로이드 소스트리에서 Init process 코드는 어디에? /system/core/init/init.c 이 문서에서 다루는 부분의 코드는 /system/core/init/ 에서 찾아 볼 수 있다. Init.rc 의 경우는 /system/core/rootdir/init.rc 에 존재한다.
  • 5. Android Init의 기능 SIGCHLD Device 초 RC 파일 Property signal 기화 & 내용처리 설정 처리 관리
  • 6. Init Process (1) Kernal Init post() Init - 자식프로세스 처리를 위한 S main IGCHLD SIGNAL handler등록 act.sa_handler = sigchld_handler; sigaction(SIGCHLD, &act, 0); Sigchild handler() Zombi process mkdir("/dev", 0755); mkdir("/proc", 0755); / Make Special purpose mkdir("/sys", 0755); Device node files tmpfs mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755") /dev /proc /sys mkdir("/dev/pts", 0755); mkdir("/dev/socket", 0755); xxxxxx mount("devpts", "/dev/pts", "devpts", 0, NULL); Socket pts mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL); Make Special purpose open_devnull_stdio(); Device node files Fd[0] stdin Fd[0] stdin Fd[1] Fd[1] stdout stdout Fd[2] Fd[2] stderr stderr Fd[3] Fd[3] __null__ __null__ Fd[…] Fd[…] parent Fd[0] stdin log_init(); Fd[1] stdout Make __kmsg__ device file log Ex) FD_CLOEXEC Fd[2] stderr massage will be written to mknode(“/dev/__kmsg__”); Fd[3] a.txt “/dev/__kmsg__” Log_fd = open(“/dev/__kmsg__”); Child Fd[0] &set it as “FD_CLOEXEC” Fd[1] Fcntl(log_fd, F_SETFD, FD_CLOEXEC); Fd[2] Fd[3]
  • 7. Init Process (2) Service_list struct service struct service struct service Init.rc 파일을 Parsing 하여 Listnode slist Slist Slist Service_list 와 Action_list를 구 Char *name “console “ “Servicemanager” parse_config_file("/init.rc"); Char *classname “default” “default” 성한다. Int nargs 0 0 Struct Action onrestart “onrestart” “onrestart” Action_list Action_list Action_list Action_list Listnode alist listnode listnode Char *name “init” “boot” Struct listnode command Listnode command Listnode command Struct command *curtent QEMU란? Qemu 에서 사용하는 메모리 qemu_init(); 영역 초기화 커널 커맨드 라인(/proc/cmdli import_kernel_cmdline(0); Argument is ‘0’ : physical H/W ne )을 읽어서 필요한 내용을 Argument is ‘1’ : QEMU emulator 전역 변수에 저장한다 커널로 부터 H/W 정보를 얻어 get_hardware_name(); 와서 “init.H/W_name.rc 파일 default h/w configuration is goldfish. snprintf(tmp, sizeof(tmp),"/init.%s.rc",hardware) 을 Parsing 하여 Service_list parse_config_file(tmp); ->/system/core/rootdir/etc/init.goldfish.rc 와 Action_list 에 추가한다. Action list 에서 “early-init”이라 action_for_each_trigger("early-init“,action_a Init.rc 와 init.goldfish.rc 에 “early-init” 의 는 name의 노드를 dd_queue_tail); name의 항목이 없다. 따라서 여기서 하는 Action_queue에 삽입하고, drain_action_queue(); 일은 없다. Action_queue에 있는 노드를 실 행 시킨다.
  • 8. Init Process (3) /dev 이하에 장치 파일을 device_fd = device_init(); /dev 이하에 장치 파일을 uevent 파일을 uevent 파일을 이용하여 생성 이용하여 생성하고 접근권한을 설정한다. 하고 접근권한을 설정한다. Property_area memory map ashmem_create_region(/dev/ property_init(); ashmem) 을 사용하여 공유 메모리 공간을 생성한다. debuggable = property_get("ro.debuggable"); keychords 확인 if (debuggable && !strcmp(debuggable, "1")) Consols 확인 keychord_fd = open_keychord(); fd = open(console_name, O_RDWR); if (fd >= 0) have_console = 1; close(fd); if( load_565rle_image(INIT_IMAGE_FILE) ) { 부팅 이미지 출력 fd = open("/dev/tty0", O_WRONLY); 로고 파일이 있으면 로고를 출력하고, if (fd >= 0) { 로고 파일이 없거나 로딩에 실패하면 tty0 const char *msg; msg = "n ANDROID“; 에 “ANDROID” 문자열을 출력한다. write(fd, msg, strlen(msg)); close(fd); } Qemu 설정일 때, property에 if (qemu[0]) 'ro.kernel'이라는 접두어를 붙 여 property를 set 한다. import_kernel_cmdline(1);
  • 9. Init Process (4) if (!strcmp(bootmode,"factory")) Struct Property info property_set("ro.factorytest", "1"); Ro.bootmode 추가 Property 설정 else if (!strcmp(bootmode,"factory2")) name property_set("ro.factorytest", "2"); else property_set("ro.factorytest", "0"); serial Bootmode[0] property_set("ro.serialno", serialno[0] ? serialno : ""); value property_set("ro.bootmode", bootmode[0] ? bootmode : "unknown"); Ro.hardware property_set("ro.baseband", baseband[0] ? baseband : "u nknown"); property_set("ro.carrier", carrier[0] ? carrier : "unknown"); goldfish property_set("ro.bootloader", bootloader[0] ? bootloader : "unknown"); Ro.revision property_set("ro.hardware", hardware); snprintf(tmp, PROP_VALUE_MAX, "%d", revision); property_set("ro.revision", tmp); xxxxxxx Action list 에서 “init”이라는 action_for_each_trigger("init“,action_add_q name의 노드를 Action_queue에 ueue_tail); / 삽입하고, Action_queue에 있는 drain_action_queue(); 노드를 실행 시킨다. create basic FileSystem structure /dev /proc /sys /sdcard /system /data /cache 기타 property 파일 및 property_set_fd = start_property_service(); default 로 생성한 이미지의 /data/property 에 /data/property 경로에 저장된 는 4개의 로케일 셋팅 정보 파일이 있다. persistent property들을 시스템 persist.sys.country persist.sys.language property 영역에 로드 한다 persist.sys.localevar persist.sys.timezone if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) 시그널 처리를 위한 소켓 생성 signal_fd = s[0]; signal_recv_fd = s[1]; Init이 관리 하는 FD 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 ((device_fd < 0) || (property_set_fd < 0) | 부팅에 필요한 FD 확인 | (signal_recv_fd < 0)) { ERROR("init startup failuren"); return 1; }
  • 10. Init Process (5) Action list 에서 “early-boot”와 action_for_each_trigger("early-boot", - network init “boot”이라는 name의 노드를 action_add_queue_tail); - System Server and daemons 의 Permissions 설정 Action_queue에 삽입하고, action_for_each_trigger("boot", - 각 APP group 메모리 사용 설정 Action_queue에 있는 노드를 action_add_queue_tail); 실행 시킨다. drain_action_queue(); action_list 에 있는 노드중에 Action_list Action_list Action_list node.name 이 "property" 인 노 queue_all_property_triggers(); listnode Listnode alist 드들을 acttion queue 에 추가한 drain_action_queue(); Char *name “property” Struct listnode Listnode 다. command command Action_queue에 있는 노드를 실 Struct command 행 시킨다. *curtent ufds[0].fd = device_fd; init 프로세스가 poll함수로 감시 ufds[0].events = POLLIN; Ufds[ ] [0] fd=device_fd event = POLLIN 할 파일디스크립터 설정 ufds[1].fd = property_set_fd; ufds[1].events = POLLIN; [1] fd=property_set_fd event = POLLIN Init ufds[2].fd = signal_recv_fd; [2] fd=signal_recv_fd event = POLLIN ufds[2].events = POLLIN; ufds[3].fd = keychord_fd; [3] fd=keychord_fd event = POLLIN ufds[3].events = POLLIN; 무한 루프 문에서 action For(;;;) queue에 실행할 action이 있 으면 실행하고 재시작이 필요 drain_action_queue(); 한 프로세스가 있으면 재시작 restart_processes(); 해준다. ufds 를 감시하고 POLLIN 이 발생하면 해당 핸 들러를 통해 처리 한다. while (!wait_for_one_process(0)) handle_device_fd(device_fd); 해당 fd의 POLLIN 에 따라 4가 handle_property_set_fd(property_set_fd); 지 핸들러를 호출한다. handle_keychord(keychord_fd);
  • 11. Init.c 분석 (1) • File descriptor, Signal 구조체, Polling 을 위한 구조체 등 Init이 사용할 자료구조 선언 • int device_fd = -1; struct sigaction act; struct pollfd ufds[4]; • 자식프로세스 처리를 위한 SIGCHLD SIGNAL handler등록 SIGCHLD Handler등록 • /dev, /proc, /sys 디렉토리를 각각 생성 디렉토리를 생성 및 마운트 • tmpfs, devpts, proc, sysfs 마운트 • stdin, stdout, stderr File descriptor 를 문자 디바이스 파일 open_devnull_stdio(); /dev/__null__ 의 FD 로 연결한다. • /dev/__kmsg__ 디바이스 파일을 생성하고, 파일 디스크립터 log_init(); 를 log_fd에 저장 • /dev/__kmsg__에 log 내용을 기록 INFO("reading config filen");
  • 12. SIGCHILD SIGNAL SIGCHILD Signal - 자식 프로세스가 멈추거나 종료하거나 추적당하는 경우 부모프로세스가 받 는 시그널 Init process 는 왜 SIGCHILD 시그널을 사용할까? - 리눅스에서 자식프로세스 보다 부모 프로세스가 먼저 죽는다면, 자식프로세 스의 부모를 init process로 만들어준다. 그래서 고아 프로세스가 죽을 때 처리 해야 하는 일을 init process가 대신하게된다. 따라서 init process는SIGCHILD Signal 를 처리 해야만 한다. - init process 의 자식프로세스 중에 프로세스가 종료하고 재 시작 해야 하는 프로세스가 있다면, 재 시작 관련 설정을 해줘야 한다. (ex. Restart 옵션으로 시 작된 Service) SA_NOCLDSTOP flag: 만약 SIGCHLD의 시그널 핸들러일 경우 자식 프 로세스의 상태가 stop일 경우는 SIGCHLD 시그널이 발생 안됨.
  • 13. 디렉토리를 생성 및 마운트 / /dev /proc /SYS [tmpfs] [proc] [sysfs] /pts /socket [devpts] File System 설명 Tmpfs tmpfs는 램파일시스템의 일종(주로 성능 향상 목적) Devpts devpts는 가상 터미널을 위한 파일시스템 Proc Proc fs는 커널메모리에서 돌아가는 일종의 가상 파일시 스템 Sysfs sysfs 파일 시스템은 proc, devfs, devpts 파일 시스템을 하나로 통합한 파일 시스템 (Linux Kernel 2.6 에서 도입)
  • 14. open_devnull_stdio() static const char *name = "/dev/__null__"; if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) { 주번호 1 부번호 3 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가 어떠한 메시지 를 stdio로 보내더라도 null dev로 전달되어 stdio 불가능해진다. Fd[0] stdin Fd[0] stdin Fd[1] stdout Fd[1] stdout Fd[2] Fd[2] stderr stderr Fd[3] Fd[3] __null__ __null__ Fd[…] Fd[…]
  • 15. log_init() void log_init(void) static const char *name = "/dev/__kmsg__"; if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) { 주번호 1 부번호 11 log_fd = open(name, O_WRONLY); fcntl(log_fd, F_SETFD, FD_CLOEXEC); unlink(name); /dev/__kmsg__ 디바이스 파일을 생성하고, 파일 디스크립터를 log_fd에 저장 FD_CLOEXEC fd의 close-on-exec 플래그를 arg의 FD_CLOEXEC 비트에 의해 지정된 값으로 설정한다. close-on-exec 보통 프로세스에서 exec를 시켜서 새로운 프로세스를 실행시키면 새로운 프로세스는 기존의 프로세스의 이미지를 덮어쓰게 된다. 그러면서 기존 프로세스가 열었던 파일 지정자를 그대로 넘겨주게 된다. 그러나 기존 프로세스가 열었던 파일 디스크립터의 close- on-exec가 set됐을 경우 해당 파일은 새로운 프로세스로는 상속이 되지 않는다. Init 에서 close-on- /dev/__kmsg__는 init이 unlink를 했기 때문에 파일의 데 exec 의 의미 이터는 남아있지만 접근할 이름이 없어졌다. 그리고 close-on-exec를 set 함으로써 fork를 통해서도 파일 디스 크립터는 상속이 되지 않는다. 따라서 /dev/__kmsg__는 Init process 만 접근 가능하다.
  • 16. close-on-exec flag close-on-exec = unSET close-on-exec = SET Parent 가 “a.txt”를 open() 후에 Fork()를 이용하여 child를 생성할 때 fd 값의 차이. Fd[0] stdin Fd[0] stdin parent parent Fd[1] stdout Fd[1] stdout Fd[2] stderr Fd[2] stderr Fd[3] a.txt Fd[3] a.txt Fd[0] Fd[0] Child Child Fd[1] Fd[1] Fd[2] Fd[2] Fd[3] Fd[3]
  • 17. 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 파일을 추가적으로 파싱 parse_config_file(tmp); • snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
  • 18. 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 - network init [boot action 정의] - System Server and daemons 의 Permissions 설정 - 각 APP group 메모리 사용 설정 class_start default - service 정의 형식 [서비스 시작] - service <name> <pathname> [ <argument> ]* <option> <option> Service_list struct service struct service struct service Listnode slist Slist Slist Char *name “console “ “Servicemanager” Char *classname “default” “default” Int nargs 0 0 Stuct Action onrestart “onrestart” “onrestart” Action_list Action_list Action_list Action_list Listnode alist listnode listnode Char *name “init” “boot” Struct listnode command Listnode command Listnode command CMD CMD CMD Struct command *curtent CMD CMD CMD
  • 19. APP group & 메모리 설정 APP group ADJ Define the mem value ory 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] 시작화면에 등록되는 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)
  • 20. Service Start [service 정의 형식] service <name> <pathname> [ <argument> ]* <option> <option> [code] service servicemanager /system/bin/servicemanager user system critical onrestart restart zygote onrestart restart media - servicemanager 는 /system/bin/servicemanager 경로에 존재 - critcal 옵션 [ 4분 안에 4번의 오류가 발생 한다면 reboot 하겠다는 안드로이드의 시 스템 운영정책] - onrestart 옵션 -> servicemanager 가 재시작되면 zygoto & media 도 재시작해라 라 는 의미 -ini.rc 에서 시작하는 서비스 리스트 Console adbd vold servicemanager ril-daemon zygote media bootsound dbus hcid hfag hsag installd flash_recovery ※ Init.rc 에 대한 보다 많은 정보는 /system/core/init/readme.txt 문서 참조
  • 21. 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 QEMU Linux Kernel ARM core Goldfish Emulator or Hardware Simulator Hardware Simulator Network
  • 22. import_kernel_cmdline(0) 커널 커맨드 라인(/proc/cmdline )을 읽어서 필요한 내용을 전역 변수에 저장한다 “0”을 인자로 넘길 경우는 실제 타겟을 위한 몇몇 커맨드 라인의 내용이 init 프로세스의 전역변수에 저장 “1”을 인자로 넘길 경우는 QEMU 에뮬레이터를 위해 모든 커맨드 라인의 내 용을 "ro.kernel" 접두어를 붙여서 property 값을 set한다.(unix domain socket 이용)
  • 23. 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 에 해당 항목이 있다면 추 가한다. Service_list struct service struct service struct service Listnode slist Slist Slist Char *name “console “ “Servicemanager” Char *classname “default” “default” Int nargs 0 0 Steuct Action onrestart “onrestart” “onrestart” Action_list Action_list Action_list Action_list Action_list Listnode alist listnode listnode listnode Char *name “init” “boot” “xxx” Struct listnode command Listnode command Listnode command Listnode command CMD CMD Struct command *curtent CMD CMD CMD CMD CMD CMD CMD CMD
  • 24. Init 분석 (2,3) action_for_each_trigger("early-in • action리스트에서 name이 “early-init”인 노드를 ACTION queue에 추 가한다. it“,action_add_queue_tail); • ACTION queue에 저장된 커맨드를 순차적으로 실행한다. drain_action_queue(); •uevent 파일들에 'add' 명령을 write해서 디바이스 추가 이벤트를 발생 시키고 이렇게 발생된 이벤트는 uevent 소켓을 통해 수신해서 파싱 device_fd = device_init(); • uevent메시지를 수신하기 위해 사용한 소켓을 리턴함. •system property 영역을 생성하고, default property들을 저장함 property_init();
  • 25. Action_queue에 실행할 노드 추가 & 실행 action_for_each_trigger("early-init“,action_add_queue_tail) action리스트에서 name이 “early-init”인 노드를 ACTION queue에 추가한다. Action-list에 name=“early-init”인 노드가 없기 때문에 아무 일도 하지 않는다. (init.rc 와 init.goldfish.rc 에 “early-init” 항목이 없다) init 프로세스에서는 다음과 같은 4개의 boot action을 정의 할수 있다. early-init 정의된 action 없음 init create basic filesystem structure early-boot, 정의된 action 없음 Boot System Server and daemons 의 Permissions 설정 early-init, early-boot, 추후 시스템의 확장을 고려해서 설계한 것이라 추측된다. drain_action_queue() action queue에 있는 내용(커맨드)를 실행한다. “early-init”에서 추가한 내용이 없을것 이므로 여기서 실행되는 내용도 없을 것 이다.
  • 26. 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 프로세스에서 생성하는데, 어떠한 디바이스들이 있는지를 확인하기 위해서 netlink socket (device_fd) 를 생성하고, 디바이스 드라이버와 uevent 메세지를 주고 받는다. 과정은 디렉토리 "/sys/class" , "/sys/block", "/sys/devices" 를 각각 검색하여, uevent 파일을 열어서 "add" 를 write 한 후, 응답 하는 uevent 메세지를 수신하고, 해당 디바이스의 노드 생성 및 접근 권한을 설정한 다. 수신된 uevent구조체의 subsysem과 event가 firmware subsystem 관련이고, path 필드의 정보를 참조하여 /dev 디렉 uevent->acttion 이 'ADD'이면 fork()를 토리 이하의 장치 관련 서브 디렉토리 및 통해 새로운 프로세스를 만들고 irmware 장치 파일을 생성 혹은 삭제하고 접근 권 관련 uevent는 새로운 자식 프로세스에 한을 설정한다. 서 처리한다. (process_firmware_event(uevent);)
  • 27. uevent - LDM(Linux Device Model)에서는 커널 이벤트를 사용자공간으로 전달하기 위한 인터페이스를 제공하고 있다. 이것이 바로 uevent다. - uevent는 커널에서 유저 프로세스에게 디바이스 관련 메시지를 전 달하는 netlink socket의 한 종류이다. netlink socket ? netlink socket은 커널과 유저 영역 사이의 통신(IPC) 방법이다 <커널> <-------( netlink socket ) ------> <유저프로세스> netlink socket 장점 -netlink의 경우는 커널 모듈로 추가 가능. - 다른 IPC에 반해, netlink는 여러 프로세스 그룹으로 멀티캐스트 전송이 가능 - 시스템콜과 ioctl의 경우, 유저 애플리케이션에 의해 시작 가능 , 이에 반해 netlink는 커널에 의해서 시작 가능
  • 28. 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; memset( &server_addr, 0, sizeof( server_addr)); server_addr.sun_family = AF_UNIX; strcpy( server_addr.sun_path, "/tmp/test_server.dat");
  • 29. property_init() • ashmem_create_region(/dev/ashmem) 을 사용하여 공유 메모리 공 간을 생성한다. (anonymous shared memory)
  • 30. Init 분석 (3,4) •Keychord open 조건을 확인하고 참인 조건이면 open keychords 확인 •keychord_fd = open_keychord(); •console을 open해서 동작 유무를 체크한다. Consols 확인 •정상동작을 한다면 have_console = 1 로 셋팅 •565rle image(로고) 파일을 프레임 버퍼에 로딩한다. 부팅 이미지 출력 • image 파일이 없으면 텍스트 모드로 프레임 버퍼에 “ANDORID” 출력 • if (qemu[0]) import_kernel_cmdline(1); QEMU설정에 따라 CMDLINE •커널 커맨드 라인의 옵션들을 QEMU에서 참조하게끔 'ro.kernel'이라는 접두어 변경 를 붙여 property를 생성한다. •각 커널 커맨드에 대한 중요 옵션들을 property로 만든다. 추가 Property 설정 •Factory mode, ro.bootmode, ro.baseband등등
  • 31. 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 변수를 셋 한다.
  • 32. 부팅 이미지 출력 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“ “ 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 이다.
  • 33. QEMU설정에 따라 CMDLINE 변경 Qemu 환경이라면 이에 맞는 셋팅을 해준다. [code] if (qemu[0]) import_kernel_cmdline(1); import_kernel_cmdline(1) 인자가 1이면 qemu 관련 환경이고, 내부에서 property_set() 을 하게된다. Property 는 'ro.kernel'이라는 접두어를 붙여 property를 생성한다 Property 관련 함수 property_init() Property_area 로 사용할 공유메모리 공간을 생성한다 property_get() /property_set() start_property_service() property_service() 를 시작해야 하는 서비스 내용을 소 켓을 이용하여 시스템에 알린다. property service(handle_property 위에서 써진 소켓 내용을 통해 서비스를 시작한다. _set_fd())
  • 34. 추가 Property 설정 Struct Property info 각 커널 커맨드에 대한 중요 옵션들을 property로 만든다. name [code] serial if (!strcmp(bootmode,"factory")) value property_set("ro.factorytest", "1"); ro.factorytest else if (!strcmp(bootmode,"factory2")) property_set("ro.factorytest", "2"); 1 else property_set("ro.factorytest", "0"); Ro.hardware property_set("ro.serialno", serialno[0] ? serialno : ""); property_set("ro.bootmode", bootmode[0] ? bootmode : "unknown"); goldfish property_set("ro.baseband", baseband[0] ? baseband : "unknown"); Ro.revision property_set("ro.carrier", carrier[0] ? carrier : "unknown"); xxxxxxx 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를 채운다.
  • 35. Init 분석 ( 4 ) •전체 action list에서 'init'에 해당하는 action에 관계되는 커맨드 내용을 뽑아내 action_for_each_trigger("init ACTION queue에 저장 “,action_add_queue_tail); •ACTION queue에 저장된 커맨드를 순차적으로 실행한다. drain_action_queue(); •기타 property 파일 및 /data/property 디렉토리에 저장된 persistent property property_set_fd = start_pro 들을 시스템 property 영역에 로드한다. 그리고 property service를 위한 서버용 perty_service(); unix domain socket을 생성하고, 리턴한다. •sockpair 시스템 콜을 이용해 서로 연결된 unix domain socket 쌍을 생성한다 시그널 처리를 위한 소켓 생 성 •device_fd , property_set_fd, signal_recv_fd 값이 모두 0 보다 커야 한다. 부팅에 필요한 FD 확인 •그 외의 경우는 ERROR()호출 후 return 1
  • 36. “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 basic filesystem structure / /dev /proc /sys /sdcard /system /data /cashe
  • 37. 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.language persist.sys.localevar persist.sys.timezone start_property_service() 함수에서 참조하는 property fils list /system/build.prop /system/default.prop /data/local.prop
  • 38. 시그널 처리를 위한 소켓 생성 서로 연결된 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_SETFL, O_NONBLOCK); } - sockpair 시스템 콜은 서로 연결된 unix domain socket 쌍을 생성한다. 생성된 소켓 쌍은 서로 직접 연결되어 있어 바인딩이 필요 없으며. 4번째 인자에 저장된다. S[] 는 init이 SIGCHLD시그널 핸들러에서 발생한 시그널번호를 수신하는 소켓이다.
  • 39. 부팅에 필요한 FD 확인 device_fd, property_set_fd, signal_recv_fd가 정상적이어야만 부팅이 진행된다. if ((device_fd < 0) || (property_set_fd < 0) || (signal_recv_fd < 0)) { ERROR("init startup failuren"); return 1; } ERROR() 함수는 __kmsg__ 에 Level =3 으로 로그를 남기는 함수이다. stdin Init이 관리 하는 FD Fd[0] stdout Fd[1] stderr Fd[2] __null__ Fd[x] __kmsg__ Init Fd[x] Fd[x] Uevent Socket device_fd Fd[x] Unix domain Socket Property_set_fd Fd[x] Unix domain Socket Signal_recv_fd Fd[…] /dev/keychord keychord_fd
  • 40. Init 분석 ( 5 ) • action_for_each_trigger("early-boot", action_add_queue_tail); ‘early-boot'와 'boot' action에 • action_for_each_trigger("boot", action_add_queue_tail); 관계되는 커맨드 실행 • drain_action_queue(); queue_all_property_triggers(); • 아직까지 셋팅 되지 않은 property 를 queue에 추가하고 실행한다. drain_action_queue(); • ufds[0] : uevent 메시지 체크 init 프로세스가 poll함수로 감 • ufds[1] : property set관련 Unix Domain 소켓 메시지 체크 시할 파일디스크립터 설정 • ufds[2] : SIGCHLD 시그널 발생 체크 • ufds[3] : keychord 발생 체크 • ufds에서 정의한 파일 디스크립터들의 입력을 감시한다 무한 루프 문 • 4개의 FD에서 POLLIN이 뜨면 해당 이벤트 처리를 한다.
  • 41. 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(); drain_action_queue(); Init.rc. 내용에서 보았듯이 early-boot의 내용은 없었기 때문에 이부분은 실행 되는 것이 없을 것이다. Boot 섹션은 다음과 같은 일을 한다. - network init - System Server and daemons 의 Permissions 설정 - 각 APP group 메모리 사용 설정 queue_all_property_triggers() 함수에서 아직까지 셋팅 되지 않은 property 를 queue에 추가하고 실행한다.
  • 42. 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[1] : property set관련 Unix Domain 소켓 메시지 체크 ufds[2] : SIGCHLD 시그널 발생 체크 ufds[3] : keychord 발생 체크 [0] fd=device_fd event = POLLIN Ufds[ ] [1] fd=property_set_fd event = POLLIN Init [2] fd=signal_recv_fd event = POLLIN [3] fd=keychord_fd event = POLLIN
  • 43. 무한 루프 문 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) 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);
  • 44. 마름모 안에 텍스트는 drain_action_queue(); “ufds[x].revents == POLLIN ?” restart_processes(); 대신 “dfds[x]”로 표현 Nr > 0 no nr = poll( ufds); yes yes ufds[0] wait_for_one_process(0) no yes handle_device_fd(device_fd) ufds[1] no yes ufds[2] handle_property_set_fd(property_set_fd) no yes handle_keychord(keychord_fd); ufds[3]
  • 45. Ufds[ ] handle action wait_for_one_process(0) SIGCHID 시그널에 발생 했다는 것은 child process가 종료 했다는 것을 의미한 다. 따라서 이 함수에서는 process 종료 시에 parent 가 처리해야 하는 내용이 있 다. Wait() 하고, 종료된 process 속성 값에 따라 재 시작 해줘야 하는지 확인하고 옵션에 따라 기능을 수행한다. handle_device_fd(device_fd) Device_fd에 Uevent가 발생한것은 디바이스 드라이버에서 핫 플러그 등의 디바 이스 관련한 이벤트가 발생할 경우이다. 따라서 init procrss는 이런 디바이스 메 시지를 처리해야한다. 이 루틴은 앞서서 devicd_init()에서 이미 호출된 적이 있다. - uevent는 커널에서 유저 프로세스에게 디바이스 관련 메시지를 전달하는 netlink socket의 한 종류이다. handle_property_set_fd(property_set_fd); Property가 변경되어야 할때, 소켓을 통해 전달된 정보를 바탕으로 권한 체크를 하고, 문제가 없다면 property_set()을 한다. handle_keychord(keychord_fd); Keychord 정보를 읽고 나서, 읽어온 keychord 에 매칭되는 서비스가 있으면 실 행한다.