Init process in Android Created by Andstudy Andstudy Seminar 2009 08 22 김연찬
Android Study 2009 08 15 Android Study members 송형주 전형민 박지훈 황세희 이백 전유진 김연찬 전승원 강명훈 임기영 박은병 이덕용 박은병 박주애 이덕용 구자관 윤동렬 김신수 김태연 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() -> in it_post() 에서 "/init" 을수행 안드로이드소스트리에서 Init process 코드는어디에? /system/core/init/init.c 이문서에서다루는부분의코드는 /system/core/init/ 에서찾아볼수있다. Init.rc 의경우는 /system/core/rootdir/init.rc 에존재한다.
Android Init 의기능 SIGCHLD signal 처리 RC 파일 내용처리 Device 초기화 & 관리 Property 설정
Init Process (1) Kernal Init post() - 자식프로세스처리를위한 S IGCHLD SIGNAL handler 등록 Init main act.sa_handler = sigchld_handler; sigaction(sigchld, &act, 0); Sigchild handler() Zombi process Make Special purpose Device 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", xxxxxx 0, NULL); mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL); tmpfs /dev Socket pts / /proc /sys Make Special purpose Device node files open_devnull_stdio(); Fd[0] stdin Fd[0] stdin Fd[1] stdout Fd[1] stdout Fd[2] stderr Fd[2] stderr Fd[3] Fd[ ] null Fd[3] Fd[ ] null Make kmsg device file log massage will be written to /dev/ kmsg &set it as FD_CLOEXEC log_init(); mknode( /dev/ kmsg ); Log_fd = open( /dev/ kmsg ); Fcntl(log_fd, F_SETFD, FD_CLOEXEC); Ex) FD_CLOEXEC parent Child Fd[0] Fd[1] Fd[2] Fd[3] Fd[0] Fd[1] Fd[2] Fd[3] stdin stdout stderr a.txt
Init Process (2) Init.rc 파일을 Parsing 하여 Service_list 와 Action_list 를구성한다. parse_config_file("/init.rc"); Service_list struct service Listnode slist Char *name Char *classname Int nargs Struct Action onrestart struct service Slist console default 0 onrestart struct service Slist Servicemanager default 0 onrestart Action_list Action_list Action_list Action_list Listnode alist Char *name Struct listnode command Struct command *curtent listnode init Listnode command listnode boot Listnode command Qemu 에서사용하는메모리영역초기화 qemu_init(); QEMU 란? 커널커맨드라인 (/proc/cmdli ne ) 을읽어서필요한내용을전역변수에저장한다 import_kernel_cmdline(0); Argument is 0 : physical H/W Argument is 1 : QEMU emulator 커널로부터 H/W 정보를얻어와서 init.h/w_name.rc 파일을 Parsing 하여 Service_list 와 Action_list 에추가한다. get_hardware_name(); snprintf(tmp, sizeof(tmp),"/init.%s.rc",hardware) parse_config_file(tmp); default h/w configuration is goldfish. ->/system/core/rootdir/etc/init.goldfish.rc Action list 에서 early-init 이라는 name 의노드를 Action_queue 에삽입하고, Action_queue 에있는노드를실행시킨다. action_for_each_trigger("early-init,action_a dd_queue_tail); drain_action_queue(); Init.rc 와 init.goldfish.rc 에 early-init 의 name 의항목이없다. 따라서여기서하는일은없다.
Init Process (3) /dev 이하에장치파일을 uevent 파일을이용하여생성하고접근권한을설정한다. device_fd = device_init(); /dev 이하에장치파일을 uevent 파일을이용하여생성하고접근권한을설정한다. Property_area memory map ashmem_create_region(/dev/ ashmem) 을사용하여공유메모리공간을생성한다. property_init(); keychords 확인 Consols 확인 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); 부팅이미지출력 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); } 로고파일이있으면로고를출력하고, 로고파일이없거나로딩에실패하면 tty0 에 ANDROID 문자열을출력한다. Qemu 설정일때, property 에 'ro.kernel' 이라는접두어를붙여 property 를 set 한다. if (qemu[0]) import_kernel_cmdline(1);
Init Process (4) 추가 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 : "u nknown"); 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); Struct Property info name serial value Ro.bootmode Bootmode[0] Ro.hardware goldfish Ro.revision xxxxxxx Action list 에서 init 이라는 name 의노드를 Action_queue 에삽입하고, Action_queue 에있는노드를실행시킨다. create basic FileSystem structure action_for_each_trigger("init,action_add_q ueue_tail); / drain_action_queue(); /dev /proc /sys /sdcard /system /data /cache 기타 property 파일및 /data/property 경로에저장된 persistent property 들을시스템 property 영역에로드한다 시그널처리를위한소켓생성 부팅에필요한 FD 확인 property_set_fd = start_property_service(); 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 ((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.language persist.sys.localevar persist.sys.timezone Init 이관리하는 FD
Action list 에서 early-boot 와 boot 이라는 name 의노드를 Action_queue 에삽입하고, Action_queue 에있는노드를실행시킨다. action_list 에있는노드중에 node.name 이 "property" 인노드들을 acttion queue 에추가한다. Action_queue 에있는노드를실행시킨다. Init Process (5) 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(); - network init - System Server and daemons 의 Permissions 설정 - 각 APP group 메모리사용설정 Action_list Action_list Listnode alist Char *name Struct listnode command Struct command *curtent Action_list listnode property Listnode command init 프로세스가 poll 함수로감시할파일디스크립터설정 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; ufds[3].fd = keychord_fd; ufds[3].events = POLLIN; Init Ufds[ ] [0] fd=device_fd event = POLLIN [1] fd=property_set_fd event = POLLIN [2] fd=signal_recv_fd event = POLLIN [3] fd=keychord_fd event = POLLIN 무한루프문에서 action queue 에실행할 action 이있으면실행하고재시작이필요한프로세스가있으면재시작해준다. ufds 를감시하고 POLLIN 이발생하면해당핸들러를통해처리한다. 해당 fd 의 POLLIN 에따라 4 가지핸들러를호출한다. For(;;;) drain_action_queue(); restart_processes(); while (!wait_for_one_process(0)) handle_device_fd(device_fd); handle_property_set_fd(property_set_fd); handle_keychord(keychord_fd);
Init.c 분석 (1) Init 이사용할자료구조선언 File descriptor, Signal 구조체, Polling 을위한구조체등 int device_fd = -1; struct sigaction act; struct pollfd ufds[4]; SIGCHLD Handler 등록 자식프로세스처리를위한 SIGCHLD SIGNAL handler 등록 디렉토리를생성및마운트 /dev, /proc, /sys 디렉토리를각각생성 tmpfs, devpts, proc, sysfs 마운트 open_devnull_stdio(); stdin, stdout, stderr File descriptor 를문자디바이스파일 /dev/ null 의 FD 로연결한다. log_init(); /dev/ kmsg 디바이스파일을생성하고, 파일디스크립터 를 log_fd 에저장 INFO("reading config file\n"); /dev/ kmsg 에 log 내용을기록
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 시그널이발생안됨.
디렉토리를생성및마운트 / /dev [tmpfs] /proc [proc] /SYS [sysfs] /pts /socket [devpts] File System 설명 Tmpfs tmpfs 는램파일시스템의일종 ( 주로성능향상목적 ) Devpts Proc Sysfs devpts 는가상터미널을위한파일시스템 Proc fs 는커널메모리에서돌아가는일종의가상파일시스템 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); } 주번호 1 부번호 3 이후로는 stdio 가 null dev 로설정되어 init process 가어떠한메시지를 stdio 로보내더라도 null dev 로전달되어 stdio 불가능해진다. Fd[0] stdin Fd[0] stdin Fd[1] Fd[2] Fd[3] Fd[ ] stdout stderr null Fd[1] Fd[2] Fd[3] Fd[ ] 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) { 주번호 1 부번호 11 log_fd = open(name, O_WRONLY); fcntl(log_fd, F_SETFD, FD_CLOEXEC); unlink(name); /dev/ kmsg 디바이스파일을생성하고, 파일디스크립터를 log_fd에저장 FD_CLOEXEC close-on-exec Init 에서 close-onexec 의의미 fd 의 close-on-exec 플래그를 arg 의 FD_CLOEXEC 비트에의해지정된값으로설정한다. 보통프로세스에서 exec 를시켜서새로운프로세스를실행시키면새로운프로세스는기존의프로세스의이미지를덮어쓰게된다. 그러면서기존프로세스가열었던파일지정자를그대로넘겨주게된다. 그러나기존프로세스가열었던파일디스크립터의 closeon-exec 가 set 됐을경우해당파일은새로운프로세스로는상속이되지않는다. /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 값의차이. parent Fd[0] Fd[1] Fd[2] stdin stdout stderr parent Fd[0] Fd[1] Fd[2] stdin stdout stderr Fd[3] a.txt Fd[3] a.txt Child Fd[0] Fd[1] Fd[2] Fd[3] Child Fd[0] Fd[1] Fd[2] Fd[3]
Init.c 분석 (2) parse_config_file("/init.rc"); init.rc 파일을파싱해서각 action, service 섹션별로연 결리스트를생성한다 qemu_init(); 안드로이드의에뮬레이터로사용되는 QEMU 와관련된 접근권한관련변수초기화 import_kernel_cmdline(0); 커널커맨드라인을읽어서필요한내용을전역변수에 저장한다. get_hardware_name(); 커널로부터 CPU 정보를읽어와 hardware 와 revision 정 보를저장 parse_config_file(tmp); hardware 에해당하는 init.rc 파일을추가적으로파싱 snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
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 [ 환경설정 ] on boot [boot action 정의 ] class_start default [ 서비스시작 ] - 글로벌변수초기화 - mount point 생성 /sdcard, /system, /data, /cache -MTD 파티션마운드 ( 기본적으로 yaffs2 사용 ) - create basic filesystem structure - network init - System Server and daemons 의 Permissions 설정 - 각 APP group 메모리사용설정 - service 정의형식 - service <name> <pathname> [ <argument> ]* <option> <option> Service_list struct service struct service struct service Listnode slist Char *name Char *classname Int nargs Stuct Action onrestart Slist console default 0 onrestart Slist Servicemanager default 0 onrestart Action_list Action_list Action_list Action_list Listnode alist Char *name Struct listnode command Struct command *curtent listnode init Listnode command CMD listnode boot Listnode command CMD CMD CMD CMD CMD
APP group APP group & 메모리설정 ADJ value Define the mem 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)
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 문서참조
qemu_init() Func 기능 : qemu_perms 영역을 memset() 을이용하여초기화한다. What is QEMU & Goldfish? Google 은 app 개발자를위해 SDK 배포시 emulator 를포함시켰고, 이 emulator 에서동작하도록하기위한가상의 device configuration 을 goldfish 라합니다. 그리고이 emulator 를 QEMU 라합니다. ARM core Simulator QEMU Goldfish Hardware Simulator Applications APP Framework Android Run- Time Libraries Linux Kernel Emulator or Hardware Network
import_kernel_cmdline(0) 커널커맨드라인 (/proc/cmdline ) 을읽어서필요한내용을전역변수에저장한다 0 을인자로넘길경우는실제타겟을위한몇몇커맨드라인의내용이 init 프로세스의전역변수에저장 1 을인자로넘길경우는 QEMU 에뮬레이터를위해모든커맨드라인의내용을 "ro.kernel" 접두어를붙여서 property 값을 set한다.(unix domain 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 에해당항목이있다면추가한다. Service_list struct service struct service struct service Listnode slist Char *name Char *classname Int nargs Steuct Action onrestart Slist console default 0 onrestart Slist Servicemanager default 0 onrestart Action_list Action_list Action_list Action_list Action_list Listnode alist Char *name Struct listnode command Struct command *curtent listnode init Listnode command listnode boot Listnode command CMD CMD listnode xxx Listnode command CMD CMD CMD CMD CMD CMD CMD CMD
Init 분석 (2,3) action_for_each_trigger("early-in it,action_add_queue_tail); action 리스트에서 name 이 early-init 인노드를 ACTION queue 에추 가한다. ACTION queue 에저장된커맨드를순차적으로실행한다. drain_action_queue(); device_fd = device_init(); uevent 파일들에 'add' 명령을 write 해서디바이스추가이벤트를발생 시키고이렇게발생된이벤트는 uevent 소켓을통해수신해서파싱 uevent 메시지를수신하기위해사용한소켓을리턴함. system property 영역을생성하고, default property 들을저장함 property_init();
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 init early-boot, Boot 정의된 action 없음 create basic filesystem structure 정의된 action 없음 System Server and daemons 의 Permissions 설정 early-init, early-boot, 추후시스템의확장을고려해서설계한것이라추측된다. drain_action_queue() action queue 에있는내용 ( 커맨드 ) 를실행한다. early-init 에서추가한내용이없을것이므로여기서실행되는내용도없을것이다.
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 과 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 소켓은같이동일 PC 내의프로세스끼리통신을하기위해서사용 파일명을가지고바인딩 Unix domain socket socket 네트워크프로그래밍에서네트워크로연결된서로다른 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");
property_init() ashmem_create_region(/dev/ashmem) 을사용하여공유메모리공간을생성한다. (anonymous shared memory)
Init 분석 (3,4) keychords 확인 Keychord open 조건을확인하고참인조건이면 open keychord_fd = open_keychord(); Consols 확인 console 을 open 해서동작유무를체크한다. 정상동작을한다면 have_console = 1 로셋팅 부팅이미지출력 565rle image( 로고 ) 파일을프레임버퍼에로딩한다. image 파일이없으면텍스트모드로프레임버퍼에 ANDORID 출력 QEMU 설정에따라 CMDLINE 변경 if (qemu[0]) import_kernel_cmdline(1); 커널커맨드라인의옵션들을 QEMU 에서참조하게끔 'ro.kernel' 이라는접두어 를붙여 property 를생성한다. 추가 Property 설정 각커널커맨드에대한중요옵션들을 property 로만든다. Factory mode, ro.bootmode, ro.baseband 등등
Keychord 란? open_keychord(), Consols 확인 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 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 관련함수 property_init() property_get() /property_set() start_property_service() property service(handle_property _set_fd()) Property_area 로사용할공유메모리공간을생성한다 property_service() 를시작해야하는서비스내용을소켓을이용하여시스템에알린다. 위에서써진소켓내용을통해서비스를시작한다.
추가 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"); 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 를채운다. Struct Property info name serial value ro.factorytest 1 Ro.hardware goldfish Ro.revision xxxxxxx
Init 분석 ( 4 ) action_for_each_trigger("init,action_add_queue_tail); 전체 action list 에서 'init' 에해당하는 action 에관계되는커맨드내용을뽑아내 ACTION queue 에저장 drain_action_queue(); ACTION queue 에저장된커맨드를순차적으로실행한다. property_set_fd = start_pro perty_service(); 기타 property 파일및 /data/property 디렉토리에저장된 persistent property 들을시스템 property 영역에로드한다. 그리고 property service를위한서버용 unix domain socket을생성하고, 리턴한다. 시그널처리를위한소켓생 성 sockpair 시스템콜을이용해서로연결된 unix domain socket 쌍을생성한다 부팅에필요한 FD 확인 device_fd, property_set_fd, signal_recv_fd 값이모두 0 보다커야한다. 그외의경우는 ERROR() 호출후 return 1
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
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
시그널처리를위한소켓생성 서로연결된 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 시그널핸들러에서발생한시그널번호를수신하는소켓이다.
부팅에필요한 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 으로로그를남기는함수이다. Init 이관리하는 FD Init Fd[0] Fd[1] Fd[2] Fd[x] Fd[x] Fd[x] Fd[x] Fd[x] Fd[ ] stdin stdout stderr null kmsg Uevent Socket Unix domain Socket Unix domain Socket /dev/keychord device_fd Property_set_fd Signal_recv_fd keychord_fd
Init 분석 ( 5 ) early-boot' 와 'boot' action 에 관계되는커맨드실행 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(); 아직까지셋팅되지않은 property 를 queue 에추가하고실행한다. init 프로세스가 poll 함수로감 시할파일디스크립터설정 ufds[0] : uevent 메시지체크 ufds[1] : property set관련 Unix Domain 소켓메시지체크 ufds[2] : SIGCHLD 시그널발생체크 ufds[3] : keychord 발생체크 무한루프문 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(); 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[1].fd = property_set_fd; ufds[2].fd = signal_recv_fd; fd_count = 3; ufds[3].fd = keychord_fd; fd_count++; ufds[0].events = POLLIN; ufds[1].events = POLLIN; ufds[2].events = POLLIN; ufds[3].events = POLLIN; ufds[0] : uevent 메시지체크 ufds[1] : property set 관련 Unix Domain 소켓메시지체크 ufds[2] : SIGCHLD 시그널발생체크 ufds[3] : keychord 발생체크 Init Ufds[ ] [0] fd=device_fd event = POLLIN [1] fd=property_set_fd event = POLLIN [2] fd=signal_recv_fd event = POLLIN [3] fd=keychord_fd event = POLLIN
무한루프문 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);
drain_action_queue(); restart_processes(); nr = poll( ufds); ufds[0] yes Nr > 0 no yes 마름모안에텍스트는 ufds[x].revents == POLLIN? 대신 dfds[x] 로표현 wait_for_one_process(0) ufds[1] no yes handle_device_fd(device_fd) ufds[2] no yes handle_property_set_fd(property_set_fd) ufds[3] no yes handle_keychord(keychord_fd);
wait_for_one_process(0) Ufds[ ] handle action 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 에매칭되는서비스가있으면실행한다.