转:http://blog.csdn.net/caimouse/
android系统的初始化过程是从那里开始呢?它在加载linux基本内核后,就开始运行一个初始化进程,叫做init进程,那么怎么样知道它是加载init进程的呢?难道上天就注定的吗?呵呵,不是的,原来是从android加载linux内核时,就设置了下面的参数:
Kernelcommand line: noinitrd root=/dev/nfs console=ttySAC0 init=/initnfsroot=192.168.1.103:/nfsbootip=192.168.1.20:192.168.1.103:192.168.1.1:255.255.255.0::eth0:on
在这行命令里,就是告诉linux内核初始化完成后开始运行init进程,由于init进程就是放在系统根目录下面。而这个进程的代码,就是位于源码的目录system/core/init下面,现在就来仔细地分析这个进程到底做了什么事情,以便理解整个系统运行情况。在分析过程中,会学习很多有用知识,甚至linux编程知识。这么有用,还等什么呢?现在就开始,找到目录system/core/init/init.c代码,先从main函数开始,如下:
#001 intmain(int argc, char **argv)
#002 {
#003 intdevice_fd = -1;
#004 intproperty_set_fd = -1;
#005 intsignal_recv_fd = -1;
#006 intkeychord_fd = -1;
#007 intfd_count;
#008 ints[2];
#009 intfd;
#010 structsigaction act;
#011 chartmp[PROP_VALUE_MAX];
#012 structpollfd ufds[4];
#013 char*tmpdev;
#014 char*debuggable;
#015
#016
#017 act.sa_handler= sigchld_handler;
#018 act.sa_flags= SA_NOCLDSTOP;
#019 act.sa_mask= 0;
#020 act.sa_restorer= NULL;
#021 sigaction(SIGCHLD,&act, 0);
在上面这段代码里,调用函数sigaction来设置处理子进程发送回来的关闭信号,其中SIGCHLD是设置子进程信号处理,SA_NOCLDSTOP是表示子进程结束时不要向父进程发送SIGCHLD,sigchld_handler是信号SIGCHLD的处理函数。这样做的作用,就是如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。因此需要对SIGCHLD信号做出处理,回收僵尸进程的资源,避免造成不必要的资源浪费。
#022
#023 /*clear the umask */
#024 umask(0);
在上面这段代码里,调用函数umask来设置屏蔽位为0值。这样的意思是什么呢?是告诉系统做了那些工作呢?要了解这个,就得深入查看一下linux函数大全了,因为它的作用就一目了然了,它的解释如下:
linux中的 umask 函数主要用于:在创建新文件或目录时 屏蔽掉新文件或目录不应有的访问允许权限。文件的访问允许权限共有9种,分别是:rw x r w x r w x(它们分别代表:用户读 用户写 用户执行 组读 组写 组执行 其它读 其它写 其它执行)。
其实这个函数的作用,就是设置允许当前进程创建文件或者目录最大可操作的权限,比如这里设置为0,它的意思就是0取反再创建文件时权限相与,也就是:(~0)& mode 等于八进制的值0777&mode了,这样就是给后面的代码调用函数mkdir给出最大的权限,避免了创建目录或文件的权限不确定性,指定明确的标志,可谓是开发人员对代码健壮性深刻的反映,高度明确性。
#025
#026 /*Get the basic filesystem setup we need put
#027 *together in the initramdisk on / and then we'll
#028 *let the rc file figure out the rest.
#029 */
#030 mkdir("/dev",0755);
#031 mkdir("/proc",0755);
#032 mkdir("/sys",0755);
在上面这段代码里,主要就是在当前内存模拟磁盘里建立一个基本的文件系统,以便后面加载rc文件来做其它事情。其中就是创建设备目录dev,进程文件系统目录proc,系统目录sys。
/dev是devfs(设备文件系统)或者udev的挂在点所在。在使用devfs的内核里如果没有/dev,根本见不到Shell启动的信息,因为内核找不到/dev/console;在使用udev的系统里,也事先需要在/dev下建立console和null这两个节点。关于devfs和udev的区别,网上很多文章说。当然如果你的内核已经不支持devfs了(2.6.12以后),可以使用纯纯的静态节点。也就是用mknod人工生成。
/proc是用来挂载存放系统信息虚拟文件系统——“proc文件系统”,“proc文件系统”在内核里面可以选。如果没有“proc文件系统”,很多Shell自己的命令就没有办法运行,比如ifconfig。“proc文件系统”不像devfs可以自动挂载,它需要使用初始化脚本挂载。另外,udev也需要“proc文件系统”的支持。
/sys用于挂载“sysfs文件系统”,“sysfs文件系统”在内核里面可以选。
#033
#034 mount("tmpfs","/dev", "tmpfs", 0, "mode=0755");
#035 mkdir("/dev/pts",0755);
#036 mkdir("/dev/socket",0755);
#037 mount("devpts","/dev/pts", "devpts", 0, NULL);
#038 mount("proc","/proc", "proc", 0, NULL);
#039 mount("sysfs","/sys", "sysfs", 0, NULL);
在 Linux 中将一个文件系统与一个存储设备关联起来的过程称为挂装(mount)。使用 mount 命令将一个文件系统附着到当前文件系统层次结构中(根)。在执行挂装时,要提供文件系统类型、文件系统和一个挂装点。因此,这里就是把tmpfs文件系统加到目录/dev下面,文件系统的名称是tmpfs。tmpfs是一个虚拟内存文件系统,它不同于传统的用块设备形式来实现的Ramdisk,也不同于针对物理内存的Ramfs。Tmpfs可以使用物理内存,也可以使用交换分区。在Linux内核中,虚拟内存资源由物理内存(RAM)和交换分区组成,这些资源是由内核中的虚拟内存子系统来负责分配和管理。Tmpfs向虚拟内存子系统请求页来存储文件,它同Linux的其它请求页的部分一样,不知道分配给自己的页是在内存中还是在交换分区中。同Ramfs一样,其大小也不是固定的,而是随着所需要的空间而动态的增减。接着创建pts和socket目录,在/dev/pts挂装devpts虚拟文件系统,在目录/proc挂装proc文件系统,在目录/sys挂装sysfs文件系统。
#040
#041 /*We must have some place other than / to create the
#042 *device nodes for kmsg and null, otherwise we won't
#043 *be able to remount / read-only later on.
#044 *Now that tmpfs is mounted on /dev, we can actually
#045 *talk to the outside world.
#046 */
#047 open_devnull_stdio();
这段代码是创建空的设备节点(/dev/null)。
#048 log_init();
这段代码是创建kmsg(/dev/kmsg)节点,主要用来输出LOG信息。比如把LOG信息输出到开发板的串口上,再在电脑上打印出来,方便跟踪和调试系统的功能。
#049
#050 //caijs addtest. 2010-07-13
#051 ERROR("Init::main()'%s'/n", "caijunsheng 2010-07-13");
这里是我测试系统引导输出的一行LOG代码。
#052
#053 INFO("readingconfig file/n");
#054 parse_config_file("/init.rc");
这段代码是分析根目录下面的init.rc配置文件,并且把里面的参数组成链表的方式,以便后面使用,后面再来仔细地分析init.rc文件的格式和内容。
#055
#056 /*pull the kernel commandline and ramdisk properties file in */
#057 qemu_init();
这里初始化qemu模拟器运行计数,这里是指模拟ARM指令的虚拟系统。
#058 import_kernel_cmdline(0);
这段代码是从linux内核里获取引导系统给内核的引导参数,并保存到全局变量,以便使用。
#059
#060 get_hardware_name();
这段代码是获取当前android系统运行的硬件信息,比如硬件的CPU名称。主要从/proc/cpuinfo里读到相关的信息。
#061 snprintf(tmp,sizeof(tmp), "/init.%s.rc", hardware);
#062 parse_config_file(tmp);
这段代码是从前面获取到的硬件名称,然后以硬件的名称(/init.硬件名称.rc)来获取相应硬件的配置文件,并且把配置文件参数加载到链表里。
#063
#064 action_for_each_trigger("early-init",action_add_queue_tail);
#065 drain_action_queue();
这段代码是先把有early-init标识的命令提前添加到一个命令队列,以便函数drain_action_queue一个一个命令地执行配置文件里的函数,这样可以在不同的配置文件里,只要标明是最先执行的函数,就可以优先地运行。
#066
#067 INFO("deviceinit/n");
#068 device_fd= device_init();
这段代码是遍历为/sys,添加设备事件响应,创建设备节点。
#069
#070 property_init();
这段代码是进行属性初始化。每个属性都有一个名称和值,它们都是字符串格式。属性被大量使用在Android系统中,用来记录系统设置或进程之间的信息交换。属性是在整个系统中全局可见的。每个进程可以get/set属性。在系统初始化时,Android将分配一个共享内存区来存储的属性,这里主要是从/default.prop属性文件读取属性。这个有点像Windows下的注册表的作用。
#071
#072 //only listen for keychords if ro.debuggable is true
#073 debuggable= property_get("ro.debuggable");
#074 if(debuggable && !strcmp(debuggable, "1")) {
#075 keychord_fd= open_keychord();
#076 }
这段代码是从属性里获取调试标志,如果是可以调试,就打开组合按键输入驱动程序。
#077
#078 if(console[0]) {
#079 snprintf(tmp,sizeof(tmp), "/dev/%s", console);
#080 console_name= strdup(tmp);
#081 }
#082
#083 fd= open(console_name, O_RDWR);
#084 if(fd >= 0)
#085 have_console= 1;
#086 close(fd);
这段代码是判断是否有控制台,如果没有,就尝试是否是可以打缺省的控制台。
#087
#088 if(load_565rle_image(INIT_IMAGE_FILE) ) {
#089 fd= open("/dev/tty0", O_WRONLY);
#090 if(fd >= 0) {
#091 constchar *msg;
#092 msg= "/n"
#093 "/n"
#094 "/n"
#095 "/n"
#096 "/n"
#097 "/n"
#098 "/n" //console is 40 cols x 30 lines
#099 "/n"
#100 "/n"
#101 "/n"
#102 "/n"
#103 "/n"
#104 "/n"
#105 "/n"
#106 " AN D R O I D ";
#107 write(fd,msg, strlen(msg));
#108 close(fd);
#109 }
#110 }
这段代码是先调用load_565rle_image函数来尝试加载定制的显示的LOGO图片,如果不成功,就直接在屏幕上显示字符串android。通过这里可以定制不同厂家的LOGO图片显示,以便在系统初始化时,进行更人性化的等待,更加漂亮个性。
#111
#112 if(qemu[0])
#113 import_kernel_cmdline(1);
这段代码是用来判断是否使用模拟器运行,如果时,就加载内核命令行参数。
#114
#115 if(!strcmp(bootmode,"factory"))
#116 property_set("ro.factorytest","1");
#117 elseif (!strcmp(bootmode,"factory2"))
#118 property_set("ro.factorytest","2");
#119 else
#120 property_set("ro.factorytest","0");
这段代码是根据内核命令行参数来设置工厂模式测试,比如在工厂生产手机过程里需要自动化演示功能,就可以根据这个标志来进行特别处理。
#121
#122 property_set("ro.serialno",serialno[0] ? serialno : "");
这段代码是设置手机序列号到属性里保存,以便上层应用程序可以识别这台手机。
#123 property_set("ro.bootmode",bootmode[0] ? bootmode : "unknown");
这段代码是保存启动模式到属性里。
#124 property_set("ro.baseband",baseband[0] ? baseband : "unknown");
这段代码是保存手机基带频率到属性里。
#125 property_set("ro.carrier",carrier[0] ? carrier : "unknown");
这段代码是保存手机硬件载波的方式到属性里。
#126 property_set("ro.bootloader",bootloader[0] ? bootloader : "unknown");
这里是保存引导程序的版本号到属性里,以便系统知道引导程序有什么特性。
#127
#128 property_set("ro.hardware",hardware);
这里是保存硬件信息到属性里,其实就是获取CPU的信息。
#129 snprintf(tmp,PROP_VALUE_MAX, "%d", revision);
#130 property_set("ro.revision",tmp);
这里是保存硬件修订的版本号到属性里,这样可以方便应用程序区分不同的硬件版本。
#131
#132 /*execute all the boot actions to get us started */
#133 action_for_each_trigger("init",action_add_queue_tail);
#134 drain_action_queue();
这段代码是先把所有init命令添加队列,然后再执行。
#135
#136 /*read any property files on system or data and
#137 *fire up the property service. This must happen
#138 *after the ro.foo properties are set above so
#139 *that /data/local.prop cannot interfere with them.
#140 */
#141 property_set_fd= start_property_service();
这段代码是加载system和data目录下的属性,并启动属性监听服务。
#142
#143 /*create a signalling mechanism for the sigchld handler */
#144 if(socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {
#145 signal_fd= s[0];
#146 signal_recv_fd= s[1];
#147 fcntl(s[0],F_SETFD, FD_CLOEXEC);
#148 fcntl(s[0],F_SETFL, O_NONBLOCK);
#149 fcntl(s[1],F_SETFD, FD_CLOEXEC);
#150 fcntl(s[1],F_SETFL, O_NONBLOCK);
#151 }
这段代码是创建一个全双工的通讯机制的两个SOCKET,信号可以在signal_fd和signal_recv_fd双向通讯,从而建立起沟通的管道。其实这个信号管理,就是用来让init进程与它的子进程进行沟通的,子进程从signal_fd写入信息,init进程从signal_recv_fd收到信息,然后再做处理。
#152
#153 /*make sure we actually have all the pieces we need */
#154 if((device_fd < 0) ||
#155 (property_set_fd< 0) ||
#156 (signal_recv_fd< 0)) {
#157 ERROR("initstartup failure/n");
#158 return1;
#159 }
这段代码是判断关键的几个组件是否成功初始化,主要就是设备文件系统是否成功初始化,属性服务是否成功初始化,信号通讯机制是否成功初始化。
#160
#161 /*execute all the boot actions to get us started */
#162 action_for_each_trigger("early-boot",action_add_queue_tail);
#163 action_for_each_trigger("boot",action_add_queue_tail);
#164 drain_action_queue();
这段代码是执行early-boot和boot属性的命令。
#165
#166 /*run all property triggers based on current state of the properties */
#167 queue_all_property_triggers();
#168 drain_action_queue();
这段代码是根据当前属性,运行属性命令。
#169
#170 /*enable property triggers */
#171 property_triggers_enabled= 1;
这段代码是标明属性触发器已经初始化完成。
#172
#173 ufds[0].fd= device_fd;
#174 ufds[0].events= POLLIN;
#175 ufds[1].fd= property_set_fd;
#176 ufds[1].events= POLLIN;
#177 ufds[2].fd= signal_recv_fd;
#178 ufds[2].events= POLLIN;
#179 fd_count= 3;
这段代码是保存三个重要的服务socket,以便后面轮询使用。
#180
#181 if(keychord_fd > 0) {
#182 ufds[3].fd= keychord_fd;
#183 ufds[3].events= POLLIN;
#184 fd_count++;
#185 }else {
#186 ufds[3].events= 0;
#187 ufds[3].revents= 0;
#188 }
这段代码是判断是否处理组合键轮询。
#189
#190 #ifBOOTCHART
#191 bootchart_count= bootchart_init();
#192 if(bootchart_count < 0) {
#193 ERROR("bootchartinginit failure/n");
#194 }else if (bootchart_count > 0) {
#195 NOTICE("bootchartingstarted (period=%d ms)/n",bootchart_count*BOOTCHART_POLLING_MS);
#196 }else {
#197 NOTICE("bootchartingignored/n");
#198 }
#199 #endif
这段代码是初始化linux程序启动速度的性能分析工具,这个工具有一个好处,就是图形化显示每个进程启动顺序和占用时间,如果想优化系统的启动速度,记得启用这个工具。
#200
#201 for(;;){
#202 intnr, i, timeout = -1;
这段代码是进入死循环处理,以便这个init进程变成一个服务。
#203
#204 for(i = 0; i < fd_count; i++)
#205 ufds[i].revents= 0;
这段代码是清空每个socket的事件计数。
#206
#207 drain_action_queue();
这段代码是执行队列里的命令。
#208 restart_processes();
这句代码是用来判断那些服务需要重新启动。
#209
#210 if(process_needs_restart) {
#211 timeout= (process_needs_restart - gettime()) * 1000;
#212 if(timeout < 0)
#213 timeout= 0;
#214 }
这段代码是用来判断那些进程启动超时。
#215
#216 #ifBOOTCHART
#217 if(bootchart_count > 0) {
#218 if(timeout < 0 || timeout > BOOTCHART_POLLING_MS)
#219 timeout= BOOTCHART_POLLING_MS;
#220 if(bootchart_step() < 0 || --bootchart_count == 0) {
#221 bootchart_finish();
#222 bootchart_count= 0;
#223 }
#224 }
#225 #endif
这段代码是用来计算运行性能。
#226 nr= poll(ufds, fd_count, timeout);
#227 if(nr <= 0)
#228 continue;
这段代码用来轮询几个socket是否有事件处理。
#229
#230 if(ufds[2].revents == POLLIN) {
#231 /*we got a SIGCHLD - reap and restart as needed */
#232 read(signal_recv_fd,tmp, sizeof(tmp));
#233 while(!wait_for_one_process(0))
#234 ;
#235 continue;
#236 }
这段代码是用来处理子进程的通讯,并且能删除任何已经退出或者杀死死进程,这样做可以保持系统更加健壮性,增强容错能力。
#237
#238 if(ufds[0].revents == POLLIN)
#239 handle_device_fd(device_fd);
这段代码是处理设备事件。
#240
#241 if(ufds[1].revents == POLLIN)
#242 handle_property_set_fd(property_set_fd);
这段代码是处理属性服务事件。
#243 if(ufds[3].revents == POLLIN)
#244 handle_keychord(keychord_fd);
这段代码是处理调试模式下的组合按键。
#245 }
#246
#247 return0;
#248 }
#249
到这里已经分析完成init进程的主流程,后面再来详细地其它功能实现。
在主函数main里调用这个函数来做什么呢?而这个函数是怎么样实现的呢?下面就来了解这个函数的功能与产现,具代码如下:
#001 voidopen_devnull_stdio(void)
#002 {
#003 intfd;
#004 staticconst char *name = "/dev/__null__";
这段代码是指明创建设备节点的名称,这是空设备。
#005 if(mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {
这行是调用函数mknod来创建设备节点/dev/__null__。空节点当作输出的黑洞,只进不出,写入它的字符永远不会满。
#006 fd= open(name, O_RDWR);
这行代码是打开空设备,进行读写。
#007 unlink(name);
这行代码是从文件系统中删除一个名称。如果名称是文件的最后一个连接,并且没有其它进程将文件打开,名称对应的文件会实际被删除。
#008 if(fd >= 0) {
#009 dup2(fd,0);
#010 dup2(fd,1);
#011 dup2(fd,2);
#012 if(fd > 2) {
#013 close(fd);
#014 }
#015 return;
#016 }
这段代码是用来检查是否打开空设备成功,是否重定向设备成功,如果成功就返回。
#017 }
#018
#019 exit(1);
这行代码是当创建空设备节点出错时退出。
#020 }
#021
通过上面的代码,就可以创建/dev/__null__空设备,并测试是否创建成功。
parse_config_file函数是分析*.rc配置文件,并且把里面的参数组成链表的方式。下面来仔细地分析代码,如下:
#001 intparse_config_file(const char *fn)
#002 {
输入来的参数是文件名称的路径。
#003 char*data;
#004 data= read_file(fn, 0);
#005 if(!data) return -1;
这段代码是从文件里读取数据,并保存数据缓冲区的指针在data里。
#006
#007 parse_config(fn,data);
这行代码是分析data数据里,然后把里面的参数组成链表的方式。
#008 DUMP();
这行代码是用来调试时输出配置文件里的内容。
#009 return0;
#010 }
接着下来分析函数read_file的代码,如下:
#001 /*reads a file, making sure it is terminated with /n /0 */
#002 void*read_file(const char *fn, unsigned *_sz)
#003 {
#004 char*data;
#005 intsz;
#006 intfd;
#007
#008 data= 0;
#009 fd= open(fn, O_RDONLY);
#010 if(fd< 0) return 0;
这段代码是打开文件路径为fn的文件,使用只读的方式打开。
#011
#012 sz= lseek(fd, 0, SEEK_END);
#013 if(sz< 0) goto oops;
这段代码是移动文件指针到文件末尾,然后计算出文件的长度。
#014
#015 if(lseek(fd,0, SEEK_SET) != 0) goto oops;
这段代码是移动到文件头开始位置。
#016
#017 data= (char*) malloc(sz + 2);
#018 if(data== 0) goto oops;
这段代码是分配文件所需内存大小。
#019
#020 if(read(fd,data, sz) != sz) goto oops;
#021 close(fd);
#022 data[sz]= '/n';
#023 data[sz+1]= 0;
这段代码是读取文件数据到缓冲区,并设置缓冲区最后的结束字符为换行符和0字符。
#024 if(_sz)*_sz = sz;
#025 returndata;
这段代码是返回文件的长度和文件缓冲区的指针。
#026
#027 oops:
#028 close(fd);
#029 if(data!= 0) free(data);
#030 return0;
#031 }
这段代码是读取文件出错,删除缓冲区。
再接着来分析函数parse_config,主要实现从缓冲区里分析配置数据,生成链表。它的代码如下:
#001 staticvoid parse_config(const char *fn, char *s)
#002 {
#003 structparse_state state;
#004 char*args[SVC_MAXARGS];
#005 intnargs;
#006
#007 nargs= 0;
#008 state.filename= fn;
#009 state.line= 1;
#010 state.ptr= s;
#011 state.nexttoken= 0;
#012 state.parse_line= parse_line_no_op;
这段代码是设置分析开始状态,其实state.filename指向文件名称;state.line是第一行;state.ptr是指向数据缓冲区;state.nexttoken是下一个词位置;state.parse_line是指向空操作行函数。
下面开始循环适别所有配置文件。
#013 for(;;) {
下面根据三种状态进行处理。
#014 switch(next_token(&state)) {
#015 caseT_EOF:
已经到了文件结尾字符处理。
#016 state.parse_line(&state,0, 0);
#017 return;
新一行配置文件处理。
#018 caseT_NEWLINE:
#019 if(nargs) {
查找这一行里配置的关键字。
#020 intkw = lookup_keyword(args[0]);
#021 if(kw_is(kw, SECTION)) {
是关键字处理,分近这一行字符。
#022 state.parse_line(&state,0, 0);
调用函数parse_new_section来分析这一节配置文件的意思,比如服务或者动作,或者参数等等。
#023 parse_new_section(&state,kw, nargs, args);
#024 }else {
保存一行的参数。
#025 state.parse_line(&state,nargs, args);
#026 }
#027 nargs= 0;
#028 }
#029 break;
保存命令配置的参数。
#030 caseT_TEXT:
#031 if(nargs < SVC_MAXARGS) {
#032 args[nargs++]= state.text;
#033 }
#034 break;
#035 }
#036 }
#037 }
在上面函数主要识别的关键字有:
copycapability chdir chroot class class_start class_stop console chownchmod critical disabled domainname device exec export group hostname
ifupinsmod import keycodes loglevel mkdir mount on oneshot onrestartrestart service setenv setkey setprop setrlimit socket start stop
symlinksysclktz trigger user write。
也就是配置文件只能使用上面的关键字,其它都是作为标识符的。这些关键的作用,其实是通过预先定义的操作来决定的,如下代码所示:
#001 #defineKEYWORD(symbol, flags, nargs, func) K_##symbol,
#002 enum{
#003 K_UNKNOWN,
#004 #endif
#005 KEYWORD(capability, OPTION, 0,0)
这个关键字是用来执行linux服务之前检查linux内核的兼容性,它是一个选项。
#006 KEYWORD(chdir, COMMAND,1, do_chdir)
这个关键字是用来改变当前工作的目录,它是一个命令。
#007 KEYWORD(chroot, COMMAND,1, do_chroot)
这个关键字是用来更改某个进程所能看到的根目录,即将某进程限制在指定目录中,保证该进程只能对该目录及其子目录的文件有所动作,从而保证整个服务器的安全,它是一个命令。
#008 KEYWORD(class, OPTION, 0,0)
这个关键字是为一个服务指明一个类名称,它是一个选项。
#009 KEYWORD(class_start,COMMAND, 1, do_class_start)
这个关键字是启动所有指定服务类下的未运行服务,它是一个命令。
#010 KEYWORD(class_stop, COMMAND,1, do_class_stop)
这个关键字是停止指定服务类下的所有已运行的服务,它是一个命令。
#011 KEYWORD(console, OPTION, 0,0)
这个关键字是控制台选项,它是一个选项。
#012 KEYWORD(critical, OPTION, 0,0)
这个关键字是说明这是一个对于设备关键的服务。如果他四分钟内退出大于四次,系统将会重启并进入recovery(恢复)模式。
#013 KEYWORD(disabled, OPTION, 0,0)
这个关键字是说明这个服务不会同与他同trigger(触发器)下的服务自动启动。他必须被明确的按名启动。。
#014 KEYWORD(domainname, COMMAND,1, do_domainname)
这个关键字是设置域名,它是一个命令。
#015 KEYWORD(exec, COMMAND,1, do_exec)
这个关键字是创建或执行一个程序,它是一个命令。
#016 KEYWORD(export, COMMAND,2, do_export)
这个关键字是用来设置全局环境变量的值,它是一个命令。
#017 KEYWORD(group, OPTION, 0,0)
这个关键字是用来改服务的组名,它是一个选项。
#018 KEYWORD(hostname, COMMAND,1, do_hostname)
这个关键字是用来主机名称,它是一个命令。
#019 KEYWORD(ifup, COMMAND,1, do_ifup)
这个关键字是用来启动网络接口,它是一个命令。
#020 KEYWORD(insmod, COMMAND,1, do_insmod)
这个关键字是用来加载指定路径的模块,它是一个命令。
#021 KEYWORD(import, COMMAND,1, do_import)
这个关键字是用来加载一个init能识别的rc文件,它是一个命令。
#022 KEYWORD(keycodes, OPTION, 0,0)
这个关键字是用来定义按键码的选项。
#023 KEYWORD(mkdir, COMMAND,1, do_mkdir)
这个关键字是用来建立一个目录,它是一个命令。
#024 KEYWORD(mount, COMMAND,3, do_mount)
这个关键字是用来指定目录加载设备,它是一个命令。
#025 KEYWORD(on, SECTION,0, 0)
这个关键字是用来设置一段命令按什么事件进行触发运行,它是一个段描述符。
#026 KEYWORD(oneshot, OPTION, 0,0)
这个关键字是用来设置服务器只运行一次就关闭,它是一个选项。
#027 KEYWORD(onrestart, OPTION, 0,0)
这个关键字是用来设置当服务重启动,执行一个命令,它是一个选项。
#028 KEYWORD(restart, COMMAND,1, do_restart)
这个关键字是用来重新启动服务,它是一个命令。
#029 KEYWORD(service, SECTION,0, 0)
这个关键字是用来设置一段服务的命令,往往一段服务里需要有多个选项组成。
#030 KEYWORD(setenv, OPTION, 2,0)
这个关键字是用来设置环境变量,它是一个选项。
#031 KEYWORD(setkey, COMMAND,0, do_setkey)
这个关键字是用来设置按键的索引和键值,它是一个命令。
#032 KEYWORD(setprop, COMMAND,2, do_setprop)
这个关键字是用来设置系统属性名称为某个值,它是一个命令。
#033 KEYWORD(setrlimit, COMMAND,3, do_setrlimit)
这个关键字是用来设置系统资源限制,它是一个命令。
#034 KEYWORD(socket, OPTION, 0,0)
这个关键字是用来设置socket给一个应用程序,它是一个选项。
#035 KEYWORD(start, COMMAND,1, do_start)
这个关键字是用来启动一个服务,它是一个命令。
#036 KEYWORD(stop, COMMAND,1, do_stop)
这个关键字是用来停止一个服务,它是一个命令。
#037 KEYWORD(trigger, COMMAND,1, do_trigger)
这个关键字是用来标志一个触发命令,它是一个命令。
#038 KEYWORD(symlink, COMMAND,1, do_symlink)
这个关键字是用来设置一个路径的符号连接,它是一个命令。
#039 KEYWORD(sysclktz, COMMAND,1, do_sysclktz)
这个关键字是用来设置系统时钟基准,它是一个命令。
#040 KEYWORD(user, OPTION, 0,0)
这个关键字是用来设置服务、文件或目录所属的用户,它是一个选项。
#041 KEYWORD(write, COMMAND,2, do_write)
这个关键字是用来打开一个文件写多个字符串,它是一个命令。
#042 KEYWORD(copy, COMMAND,2, do_copy)
这个关键字是用来拷贝文件,它是一个命令。
#043 KEYWORD(chown, COMMAND,2, do_chown)
这个关键字是用来改变文件或目录的属某个用户的属性,它是一个命令。
#044 KEYWORD(chmod, COMMAND,2, do_chmod)
这个关键字是用来改变文件或目录的访问权限,它是一个命令。
#045 KEYWORD(loglevel, COMMAND,1, do_loglevel)
这个关键字是用来设置log输出的级别,它是一个命令。
#046 KEYWORD(device, COMMAND,4, do_device)
这个关键字是用来设置设备的名称,它是一个命令。
#047 #ifdef__MAKE_KEYWORD_ENUM__
#048 KEYWORD_COUNT,
#049 };
Android初始化语言包含了四种类型的声明:Actions(行动)、Commands(命令)、Services(服务)和Options(选 项)。
通上面的函数就可以把服务和事件触发的命令添加队列里。其实是在文件parser.c头部,就声明了下面三个链表,如下:
staticlist_declare(service_list);
staticlist_declare(action_list);
staticlist_declare(action_queue);
service_list是定义添加分析所有资源文件里的服务,action_list是定义添加分析所有资源文件里的事件触发的命令列表,action_queue是定义添加当前需要执行操作的命令动作或者服务等。
那么又有下面一个问题了,init进程是在那里添加这些服务或命令到运行队列呢?在init.c文件看到下面的代码:
action_for_each_trigger("early-init",action_add_queue_tail);
drain_action_queue();
原来是通过函数action_for_each_trigger来把所有需要按事件触发运行的命令添加到队列里,函数action_add_queue_tail把需要执行命令放到队列,函数drain_action_queue把添加到队列里的命令进行运行。
#001 voidaction_for_each_trigger(const char *trigger,
#002 void(*func)(struct action *act))
#003 {
#004 structlistnode *node;
#005 structaction *act;
#006 list_for_each(node,&action_list) {
这行代码是遍历链表所有项。
#007 act= node_to_item(node, struct action, alist);
这行代码是把动作节点转换为动作项。
#008 if(!strcmp(act->name, trigger)) {
#009 func(act);
这段代码是当找到与用户输入相同事件触发的动作,比如上面是查找“early-init”事件。这时就把动作项添加到将要运行的队列里。
#010 }
#011 }
#012 }
在init初始化进程里,设备初始化是怎么进行的呢?如果要了解这方面,就需要仔细分析下面的代码,如下:
#001 intdevice_init(void)
#002 {
#003 suseconds_tt0, t1;
#004 intfd;
#005
#006 fd= open_uevent_socket();
#007 if(fd< 0)
#008 return-1;
这段代码是调用函数open_uevent_socket来创建一个用户事件空间的socket。
#009
#010 fcntl(fd,F_SETFD, FD_CLOEXEC);
#011 fcntl(fd,F_SETFL, O_NONBLOCK);
这段代码是设置socket的属性,FD_CLOEXEC用来设置文件的close-on-exec状态标准。在exec()调用后,close-on-exec标志为0的情况,此文件不被关闭。非零则在exec()后被关闭。默认close-on-exec状态为0,需要通过FD_CLOEXEC设置。O_NONBLOCK设置这个socket是异步的通讯方式。
#012
#013 t0= get_usecs();
这行代码是用来获取当前用户的时间。
#014 coldboot(fd,"/sys/class");
#015 coldboot(fd,"/sys/block");
#016 coldboot(fd,"/sys/devices");
这段代码是用来遍历所有在init进程前已经创建的设备,并找到设备的事件文件发送事件通知。
#017 t1= get_usecs();
这行代码是用来获取当前用户的时间。
#018
#019 log_event_print("coldboot%ld uS/n", ((long) (t1 - t0)));
这行代码是用来打印输出冷启动的时间是多少秒。
#020
#021 returnfd;
#022 }
#023
在android系统里,设计有一种系统叫做属性系统,它是用来做什么呢?这样设计有什么优势呢?其实这个属性系统主要是用来保存系统配置,或者用来交换不同进程的信息。这样的系统最大的优势是统一了系统配置的方式,统一了信息交换方式,通过共享内存的方式提高系统的性能。
下面就来分析属性系统的初始化函数,代码如下:
#001 voidproperty_init(void)
#002 {
#003 init_property_area();
这行代码是调用函数init_property_area来设置属性内存的区域。
#004 load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
这行代码是从ramdisk盘里加载属性文件。
#005 }
在这个函数里,需要查看一下宏定义,如下:
#definePROP_PATH_RAMDISK_DEFAULT "/default.prop"
也就是从内存盘里加载属性文件/default.prop,并把这些属性放到属性系统里。
接着来分析函数init_property_area是怎么创建共享内存,并把属性放到里面给所有进程共享使用的,代码如下:
#001 staticint init_property_area(void)
#002 {
#003 prop_area*pa;
#004
#005 if(pa_info_array)
#006 return-1;
这段代码是判断当属性信息数组已经初始化,就直接返回。
#007
#008 if(init_workspace(&pa_workspace,PA_SIZE))
#009 return-1;
这段代码是调用函数init_workspace创建共享内存。
#010
#011 fcntl(pa_workspace.fd,F_SETFD, FD_CLOEXEC);
这行代码是设置共享内存的执行结束后关闭。
#012
#013 pa_info_array= (void*) (((char*) pa_workspace.data) + PA_INFO_START);
这行代码是保存创建共享内存指针。
#014
#015 pa= pa_workspace.data;
#016 memset(pa,0, PA_SIZE);
这段代码是清空属性共享的内存。
#017 pa->magic= PROP_AREA_MAGIC;
#018 pa->version= PROP_AREA_VERSION;
这段代码是设置属性共享内存的版本号。
#019
#020 /*plug into the lib property services */
#021 __system_property_area__= pa;
这行代码是设置属性共享内存可以给库的属性共享服务使用。
#022
#023 return0;
#024 }
#025
从上面的函数里可以看到一个创建共享内存的函数,它是怎么样实现创建共享内存的呢?现在来分析它的代码
前面学习了属性系统的初始化和加载,还有保存到属性文件等功能,下面来学习属性服务的方面,它主要用来提供一种服务的方式给java虚拟机上层使用,或者java应用程序使用。start_property_service函数的代码如下:
#001 intstart_property_service(void)
#002 {
#003 intfd;
#004
#005 load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
#006 load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
#007 load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
这段代码是用来加载下面几个缺省的属性文件:
/system/build.prop
/system/default.prop
/data/local.prop
#008 /*Read persistent properties after all default values have been loaded.*/
#009 load_persistent_properties();
这行代码是用来加载保存为属性文件的属性。
#010
#011 fd= create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
#012 if(fd< 0) return -1;
#013 fcntl(fd,F_SETFD, FD_CLOEXEC);
#014 fcntl(fd,F_SETFL, O_NONBLOCK);
这段代码是用来创建服务的socket,并设置相应的属性。
#015
#016 listen(fd,8);
这行代码是让属性服务进入监听状态。
#017 returnfd;
#018 }
在初始化过程里,会显示一个LOGO图片,那么它是怎么实现显示这个图片的呢?它的代码如下:
#001 /*565RLE image format: [count(2 bytes), rle(2 bytes)] */
#002
#003 intload_565rle_image(char *fn)
#004 {
这段代码是加载565RLE格式的LOGO图片,函数的参数是图片路径。
#005 structFB fb;
#006 structstat s;
#007 unsignedshort *data, *bits, *ptr;
#008 unsignedcount, max;
#009 intfd;
#010
#011 if(vt_set_mode(1))
#012 return-1;
这段代码是设置显示输出为图片模式。
#013
#014 fd= open(fn, O_RDONLY);
#015 if(fd < 0) {
#016 ERROR("cannotopen '%s'/n", fn);
#017 gotofail_restore_text;
#018 }
这段代码是打开要显示的图片。
#019
#020 if(fstat(fd, &s) < 0) {
#021 gotofail_close_file;
#022 }
这段代码是获取打开文件的信息,比如文件的大小。
#023
#024 data= mmap(0, s.st_size, PROT_READ, MAP_SHARED, fd, 0);
#025 if(data == MAP_FAILED)
#026 gotofail_close_file;
这段代码是映射图片文件到内存。
#027
#028 if(fb_open(&fb))
#029 gotofail_unmap_data;
这段代码是打开显示缓存内存,以便把图片数据放到显示缓存里。
#030
#031 max= fb_width(&fb) * fb_height(&fb);
#032 ptr= data;
#033 count= s.st_size;
#034 bits= fb.bits;
#035 while(count > 3) {
#036 unsignedn = ptr[0];
#037 if(n > max)
#038 break;
#039 android_memset16(bits,ptr[1], n << 1);
#040 bits+= n;
#041 max-= n;
#042 ptr+= 2;
#043 count-= 4;
#044 }
这段代码是把图片数据填入显示缓存。
#045
#046 munmap(data,s.st_size);
#047 fb_update(&fb);
这段代码是更新显示缓存,并把LOGO显示出来。
#048 fb_close(&fb);
#049 close(fd);
#050 unlink(fn);
这段代码关闭上面打开的资源,并删除LOGO图片文件,由于android把LOGO图片加载到内存里,并且LOGO显示完成后,再没有作用了,及时回收资源。如果想不删除这个图片,就需要把这行代码删除掉,或者每次显示前动态地拷贝到这里。
#051 return0;
#052
#053 fail_unmap_data:
#054 munmap(data,s.st_size);
#055 fail_close_file:
#056 close(fd);
#057 fail_restore_text:
#058 vt_set_mode(0);
#059 return-1;
这段代码是处理失败情况。
#060 }
通过上面的分析,可以知道LOGO图片是RLE编码的565格式的图片,也就是采用行程编码的方式,颜色位数采用16位的方式(红色5位,绿色6位,蓝色5位)。因此,所有其它图片的格式都需要转换为这种标准的格式,初始化进程才可以显示出来。下面就来详细地介绍定制一个LOGO图片的显示过程。
这个图片的像素大小为480X272,显示屏的大小为4.3寸。直接在Windows平台里使用画笔就可以制作出来,然后保存为png文件,比如保存为xiyang.png。然后在linux里打开windows共享的文件夹,再把这个文件拷贝到linux的目录里,进行如下操作:
1. 下载imgageMagick工具,通过命令进行:sudoapt-get install imagemagick,这样就安装好图片文件转换工具,它是用来把png文件转换为rgb原始格式的文件。
2. 把png格式的logo图片转换为raw原始格式图片:
convert-depth 8 xiyang.png rgb: xiyang.raw
3. 把raw原始格式图片转换为rle格式图片:
Android-2.0/out/host/linux-x86/bin/rgb2565–rle < xiyang.raw > initlogo.rle
当转换成功时,就会输出多少个像素的提示。
把文件initlogo.rle拷贝到nfsboot的目录。