• Android init代码分析


     

    Android init代码分析

    分类: Google Android 343人阅读 评论(1) 收藏 举报

    转: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是表示子进程结束时不要向父进程发送SIGCHLDsigchld_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

     

    /devdevfs(设备文件系统)或者udev的挂在点所在。在使用devfs的内核里如果没有/dev,根本见不到Shell启动的信息,因为内核找不到/dev/console;在使用udev的系统里,也事先需要在/dev下建立consolenull这两个节点。关于devfsudev的区别,网上很多文章说。当然如果你的内核已经不支持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下面,文件系统的名称是tmpfstmpfs是一个虚拟内存文件系统,它不同于传统的用块设备形式来实现的Ramdisk,也不同于针对物理内存的RamfsTmpfs可以使用物理内存,也可以使用交换分区。在Linux内核中,虚拟内存资源由物理内存(RAM)和交换分区组成,这些资源是由内核中的虚拟内存子系统来负责分配和管理。Tmpfs向虚拟内存子系统请求页来存储文件,它同Linux的其它请求页的部分一样,不知道分配给自己的页是在内存中还是在交换分区中。同Ramfs一样,其大小也不是固定的,而是随着所需要的空间而动态的增减。接着创建ptssocket目录,在/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();

    这段代码是加载systemdata目录下的属性,并启动属性监听服务。

     

     

    #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_fdsignal_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-bootboot属性的命令。

     

     

    #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图片文件,由于androidLOGO图片加载到内存里,并且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的目录。

  • 相关阅读:
    vray学习笔记(5)-学习资料
    vray学习笔记(4)混合材质是个什么东西
    vray学习笔记(3)-多维子材质是个什么东西
    vray学习笔记(2)vray工作流程
    vray学习笔记(1)vray介绍
    怎么让一个东西看起来比较亮?
    怎么给一个贴图创建透明通道
    BMP是可以保存alpha通道的。
    逆向分析底纹
    关于photoshop处理图片的自动化
  • 原文地址:https://www.cnblogs.com/yuzaipiaofei/p/4124235.html
Copyright © 2020-2023  润新知