精华内容
下载资源
问答
  • before-init加载配置文件

    千次阅读 2013-08-16 22:49:32
    思想:让实现类,初始化的时候加载需要文件 1、建立一个工具类 public class SessionUtils { public static String url = "";...public void init(){ Configuration configuration =

    思想:让实现类,初始化的时候加载需要文件

    1、建立一个工具类

    public class SessionUtils {

    public static String url = "";

    public  static SessionFactory sessionFactory;

    @Before

    public void init(){

    Configuration configuration = new Configuration();

    configuration.configure(url);

    sessionFactory =  configuration.buildSessionFactory();

    }

    }

    2、继承该工具类,并赋值给要初始化的文件目录

    public class LazyTest extends SessionUtils{

    static{

    url="config/hibernate.cfg.xml";

    }

    展开全文
  • context-param与init-param的区别与作用

    千次阅读 2013-10-09 17:47:40
    作用: web.xml的配置中配置作用 1. 启动一个WEB项目的时候,容器(如:Tomcat)会去读它的配置文件web.xml.读两个节点: 和 2.紧接着,容器创建一个ServletContext(上下文),这个WEB项目所有部分都将共享这个上下文....

    <context-param>的作用:
    web.xml
    的配置中<context-param>配置作用

    1. 启动一个WEB项目的时候,容器(:Tomcat)会去读它的配置文件web.xml.读两个节点:<listener></listener> <context-param></context-param>

    2.紧接着,容器创建一个ServletContext(上下文),这个WEB项目所有部分都将共享这个上下文.

    3.容器将<context-param></context-param>转化为键值对,并交给ServletContext.

    4.容器创建<listener></listener>中的类实例,即创建监听.

    5.在监听中会有contextInitialized(ServletContextEvent args)初始化方法,在这个方法中获得ServletContext =ServletContextEvent.getServletContext();
    context-param
    的值 =ServletContext.getInitParameter("context-param的键");

    6.得到这个context-param的值之后,你就可以做一些操作了.注意,这个时候你的WEB项目还没有完全启动完成.这个动作会比所有的Servlet都要早.
    换句话说,这个时候,你对<context-param>中的键值做的操作,将在你的WEB项目完全启动之前被执行.

    7.举例.你可能想在项目启动之前就打开数据库.
    那么这里就可以在<context-param>中设置数据库的连接方式,在监听类中初始化数据库的连接.

    8.这个监听是自己写的一个类,除了初始化方法,它还有销毁方法.用于关闭应用前释放资源.比如说数据库连接的关闭.

    :
    <!--
    加载spring的配置文件
    -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
       <param-value>/WEB-INF/applicationContext.xml,/WEB-INF/action-servlet.xml,/WEB-

    INF/jason-servlet.xml</param-value>
    </context-param>
    <listener>
       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    又如: --->自定义context-param,且自定义listener来获取这些信息

    <context-param>
        <param-name>urlrewrite</param-name>
        <param-value>false</param-value>
    </context-param>
    <context-param>
        <param-name>cluster</param-name>
        <param-value>false</param-value>
    </context-param>
    <context-param>
        <param-name>servletmapping</param-name>
        <param-value>*.bbscs</param-value>
    </context-param>
    <context-param>
        <param-name>poststoragemode</param-name>
        <param-value>1</param-value>
    </context-param>
    <listener>
       <listener-class>com.laoer.bbscs.web.servlet.SysListener</listener-class>
    </listener>

    public classSysListener extendsHttpServlet implements ServletContextListener {

    private staticfinal Log logger = LogFactory.getLog(SysListener.class);

    public void contextDestroyed(ServletContextEventsce) {

       //用于在容器关闭时,操作
    }

    //用于在容器开启时,操作

    public void contextInitialized(ServletContextEventsce) {
       String rootpath =
    sce.getServletContext().getRealPath("/");

      System.out.println("-------------rootPath:"+rootpath);

       if(rootpath != null) {
        rootpath = rootpath.replaceAll("\\\\","/");
       } else {
        rootpath = "/";
       }
       if (!rootpath.endsWith("/")) {
        rootpath = rootpath + "/";
       }
       Constant.ROOTPATH = rootpath;
       logger.info("Application Run Path:" + rootpath);
       String urlrewrtie =
    sce.getServletContext().getInitParameter("urlrewrite");

       boolean burlrewrtie = false;
       if (urlrewrtie != null) {
        burlrewrtie = Boolean.parseBoolean(urlrewrtie);
       }
       Constant.USE_URL_REWRITE = burlrewrtie;
       logger.info("Use Urlrewrite:" + burlrewrtie);
      
    其它略之....

       }

    }
       /*
    最终输出

       -------------rootPath:D:\tomcat_bbs\webapps\BBSCS_8_0_3\
       2009-06-09 21:51:46,526[com.laoer.bbscs.web.servlet.SysListener]-[INFO]

    Application RunPath:D:/tomcat_bbs/webapps/BBSCS_8_0_3/
       2009-06-09 21:51:46,526[com.laoer.bbscs.web.servlet.SysListener]-[INFO]

    UseUrlrewrite:true
       2009-06-09 21:51:46,526[com.laoer.bbscs.web.servlet.SysListener]-[INFO]

    UseCluster:false
       2009-06-09 21:51:46,526[com.laoer.bbscs.web.servlet.SysListener]-[INFO]

    SERVLETMAPPING:*.bbscs
       2009-06-09 21:51:46,573 [com.laoer.bbscs.web.servlet.SysListener]-[INFO]

    Post StorageMode:1
       */

    context-paraminit-param区别
    web.xml
    里面可以定义两种参数:
    (1)application范围内的参数,存放在servletcontext中,在web.xml中配置如下:
    <context-param>
              <param-name>context/param</param-name>
              <param-value>avalible during application</param-value>
    </context-param>

    (2)servlet范围内的参数,只能在servletinit()方法中取得,web.xml中配置如下:
    <servlet>
        <servlet-name>MainServlet</servlet-name>
       <servlet-class>com.wes.controller.MainServlet</servlet-class>
        <init-param>
          <param-name>param1</param-name>
           <param-value>avalible in servletinit()</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>

    servlet中可以通过代码分别取用:
    package com.wes.controller;

    importjavax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;

    public classMainServlet extends HttpServlet ...{

       public MainServlet() ...{
            super();
         }
        public void init() throws ServletException ...{
             System.out.println("
    下面的两个参数param1是在servlet中存放的
    ");
            System.out.println(this.getInitParameter("param1"));

             System.out.println("
    下面的参数是存放在servletcontext中的");
           System.out.println(getServletContext().getInitParameter("context/param"));

          }
    }

    第一种参数在servlet里面可以通过getServletContext().getInitParameter("context/param")得到
    第二种参数只能在servletinit()方法中通过this.getInitParameter("param1")取得.

     

     

     

    定义

      context-param元素含有一对参数名和参数值,用作应用的ServletContext上下文初始化参数。参数名在整个Web应用中必须是惟一的。

      <!ELEMENT context-param (param-name, param-value,description?)>

      <!ELEMENT param-name (#PCDATA)>

      <!ELEMENT param-value (#PCDATA)>

      <!ELEMENT description (#PCDATA)>

    编辑本段描述参数

      param-name 子元素包含有参数名,而param-value子元素包含的是参数值。作为选择,可用description子元素来描述参数。

      下面是一个含有context-param元素的有效部署描述符:

      <?xml version="1.0"encoding="ISO-8859-1"?>

      <!DOCTYPE web-app

      PUBLIC "-//Sun Microsystems, Inc.//DTD WebApplication 2.3//EN"

      <web-app>

      <context-param>

      <param-name>jdbcDriver</param-name>

      <param-value>com.mysql.jdbc.Driver</param-value>

      </context-param>

    </web-app>

     

     

     

    The load-on-startup element indicates that this servletshould be loaded (instantiated and have its init() called) on the startup ofthe web application. The optional contents of these element must be an integerindicating the order in which the servlet should be loaded. If the value is anegative integer, or the element is not present, the container is free to loadthe servlet whenever it chooses.  If the value is a positive integer or 0,the container must load and initialize the servlet as the application isdeployed. The container must guarantee that servlets marked with lower integersare loaded before servlets marked with higher integers. The container maychoose the order of loading of servlets with the same load-on-start-up value.
    这个 load-on-startup 元素 在 web 应用启动的时候指定了servlet被加载的顺序,它的值必须是一个整数。如果它的值是一个负整数或是这个元素不存在,那么容器会在该servlet被调用的时候,加载这个servlet 。如果值是正整数或零,容器在配置的时候就加载并初始化这个servlet,容器必须保证值小的先被加载。如果值相等,容器可以自动选择先加载谁。

     

     

     

     web.xml 中的listener filterservlet 加载顺序及其详解

    一、概述

    1、启动一个WEB项目的时候,WEB容器会去读取它的配置文件web.xml,读取<listener><context-param>两个结点。 

    2、紧急着,容创建一个ServletContextservlet上下文),这个web项目的所有部分都将共享这个上下文。 

    3、容器将<context-param>转换为键值对,并交给servletContext 

    4、容器创建<listener>中的类实例,创建监听器。 

    load-on-startup

    load-on-startup 元素在web应用启动的时候指定了servlet被加载的顺序,它的值必须是一个整数。如果它的值是一个负整数或是这个元素不存在,那么容器会在该 servlet被调用的时候,加载这个servlet 。如果值是正整数或零,容器在配置的时候就加载并初始化这个servlet,容器必须保证值小的先被加载。如果值相等,容器可以自动选择先加载谁。  

    servlet的配置当中,<load-on-startup>5</load-on-startup>的含义是: 

    标记容器是否在启动的时候就加载这个servlet 

    当值为0或者大于0时,表示容器在应用启动时就加载这个servlet 

    当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。 

    正数的值越小,启动该servlet的优先级越高。 

      、加载顺序

    首先可以肯定的是,加载顺序与它们在 web.xml 文件中的先后顺序无关。即不会因为 filter 写在 listener 的前面而会先加载 filter。最终得出的结论是:ServletContext -> listener ->filter -> servlet

            同时还存在着这样一种配置节:context-param,它用于向 ServletContext 提供键值对,即应用程序上下文信息。我们的 listener, filter 等在初始化时会用到这些上下文中的信息,那么 context-param 配置节是不是应该写在 listener 配置节前呢?实际上 context-param 配置节可写在任意位置,因此真正的加载顺序为:context-param -> listener ->filter -> servlet

            对于某类配置节而言,与它们出现的顺序是有关的。以 filter 为例,web.xml 中当然可以定义多个 filter,与 filter 相关的一个配置节是 filter-mapping,这里一定要注意,对于拥有相同 filter-name filter filter-mapping 配置节而言,filter-mapping 必须出现在 filter 之后,否则当解析到 filter-mapping 时,它所对应的 filter-name 还未定义。web 容器启动时初始化每个 filter 时,是按照 filter 配置节出现的顺序来初始化的,当请求资源匹配多个 filter-mapping 时,filter 拦截资源是按照 filter-mapping 配置节出现的顺序来依次调用 doFilter() 方法的。

            servlet filter 类似,此处不再赘述。

           由此,可以看出,web.xml 的加载顺序是:ServletContext -> context-param-> listener -> filter -> servlet ,而同个类型之间的实际程序调用的时候的顺序是根据对应的 mapping 的顺序进行调用的。

    web.xml文件详解 

     

    我将自己知道的web.xml的元素整理了一下:

    1web.xml首先是肯定要包含它的schema.

    <web-appxmlns="http://java.sun.com/xml/ns/j2ee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
        version="2.4">

    </web-app>

    其它的元素都放在<web-app></web-app>之中。

    <discription></discription> 是对站台的描述
    <display-name></display-name>
    定义站台的名称
    <distributable/>
    是指定该站台是否可分布式处理
    <context-param></context-param>
    用来设定web站台的环境参数,它包含两个子元素:
        <param-name></param-name>
    用来指定参数的名称
        <param-value></param-value>
    用来设定参数值

    比如:
    <context-param>
        <param-name>my_param</param-name>
        <param-value>hello</param-value>
    </context-param>

    在此设定的参数,可以在servlet中用getServletContext().getInitParameter("my_param") 来取得

    2listener

    <listener></listener> 用来设定Listener接口,它的主要子元素为
        <listener-class></listener-class>
    定义Listener的类名称

    比如:
    <listener>
       <listener-class>com.myTest.ContextListener</listener-class> 
    </listener>

    3.1filter
    <filter></filter>
    是用来声明filter的相关设定,它包含以下子元素:
        <filter-name></filter-name>
    这当然就是指定filter的名字
        <filter-class></filter-class>
    这是用来定义filter的类的名称
        <init-param></init-param>
    用来定义参数,它有两个子元素:
           <param-name></param-name>
    用来指定参数的名称
            <param-value></param-value>
    用来设定参数值

    比如:
    <filter>
           <filter-name>setCharacterEncoding</filter-name>
           <filter-class>com.myTest.setCharacterEncodingFilter</filter-class>
            <init-param>
               <param-name>encoding</param-name>
               <param-value>GB2312</param-value>
            </init-param>
    </filter>

    3.2filter-mapping

    <filter></filter>同时使用的是<filter-mapping></filter-mapping>用来定义filter所对应的URL,它有两个子元素:
        <filter-name></filter-name>
    指定filter的名字
        <url-pattern></url-pattern>
    指定filter所对应的URL

    比如:
    <filter-mapping>
           <filter-name>setCharacterEncoding</filter-name>
           <url-pattern>/*</url-pattern>
    </filter-mapping>

    4.1servlet

    <servlet></servlet> 用来声明一个servlet的数据,主要有以下子元素:
        <servlet-name></servlet-name>
    指定servlet的名称
        <servlet-class></servlet-class>
    指定servlet的类名称
        <jsp-file></jsp-file>
    指定web站台中的某个JSP网页的完整路径
        <init-param></init-param>
    用来定义参数,和前面的<init-param>差不多

    4.2servlet-mapping

    <servlet></servlet>一起使用的是<servlet-mapping></servlet-mapping>用来定义servlet所对应的URL,包含两个子元素:
        <servlet-name></servlet-name>
    指定servlet的名称
        <url-pattern></url-pattern>
    指定servlet所对应的URL

    比如:
    <servlet>
           <servlet-name>ShoppingServlet</servlet-name>
           <servlet-class>com.myTest.ShoppingServlet</servlet-class>
    </servlet>
    <servlet-mapping>
           <servlet-name>ShoppingServlet</servlet-name>
           <url-pattern>/shop/ShoppingServlet</url-pattern>
    </servlet-mapping>

    5session-config
    <session-config></session-config>
    用来定义web站台中的session参数,包含一个子元素:
        <session-timeout></session-timeout>
    用来定义这个web站台所有session的有效期限,单位为分钟

    6mime-mapping
    <mime-mapping></mime-mapping>
    定义某一个扩展名和某一个MIME Type做对映,包含两个子元素:
        <extension></extension>
    扩展名的名称
        <mime-type></mime-type> MIME
    格式

    比如:
    <mime-mapping>
        <extension>doc</extension>
        <mime-type>application/vnd.ms-word</mime-type>
    </mime-mapping> 
    <mime-mapping>
        <extension>xls</extension>
        <mime-type>application/vnd.ms-excel</mime-type>
    </mime-mapping>

    7welcom-file-list
    <welcome-file-list></welcom-file-list>
    用来定义首页的列单,包含一个子元素:
        <welcome-file></welcome-file>
    指定首页的文件名称

    比如:
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>index.html</welcome-file>
    </welcom-file-list>

    8error-page
    <error-page></error-page>
    用来处理错误代码或异常的页面,有三个子元素:
        <error-code></error-code>
    指定错误代码
        <exception-type></exception-type>
    指定一个JAVA异常类型
        <location></location>
    指定在web站台内的相关资源路径

    比如:
    <error-page>
        <error-code>404</error-code>
        <location>/error404.jsp</location>
    </error-page>
    <error-page>
        <exception-type>java.lang.Exception</exception-type>
        <location>/exception.jsp</location>
    </error-page>

    9taglib
    <taglib></taglib>
    用来设定JSP网页所用到的Tag Library路径,有两个子元素:
        <taglib-uri></taglib-uri>
    定义TLD文件的URI,在JSP网页中用taglib指令便可取得该URI TLD文件
        <taglib-location></taglib-location>
    指定TLD文件相对于web站台的存放位置

    比如:
    <taglib>
          <taglib-uri>myTaglib</taglib-uri>
         <taglib-location>/WEB-INF/tlds/MyTaglib.tld</taglib-location>
    </taglib>

    10resource-ref
    <resource-ref></resource-ref>
    定义利用JNDI取得站台可利用的资源,有五个子元素:
        <description></description>
    资源说明
        <rec-ref-name></rec-ref-name>
    资源名称
        <res-type></res-type>
    资源种类
        <res-auth></res-auth>
    资源经由ApplicationContainer来许可
        <res-sharing-scope></res-sharing-scope>
    资源是否可以共享,有ShareableUnshareable两个值,默认为Shareable

    比如,配置数据库连接池就可在此配置:
    <resource-ref>
            <description>JNDI JDBCDataSource of shop</description>
           <res-ref-name>jdbc/sample_db</res-ref-name>
           <res-type>javax.sql.DataSource</res-type>
            <res-auth>Container</res-auth>
    </resource-ref>

    11jsp-config
    <jsp-config>
    包括<taglib> <jsp-property-group> 两个子元素。
       
    其中<taglib>元素在JSP 1.2时就已经存在;而<jsp-property-group>JSP 2.0 新增的元素。
        <jsp-property-group>
    元素主要有八个子元素,它们分别为:
        1).<description>
    :设定的说明;
        2).<display-name>
    :设定名称;
        3).<url-pattern>
    :设定值所影响的范围,如:/CH2 /*.jsp
        4).<el-ignored>
    :若为true,表示不支持EL 语法;
        5).<scripting-invalid>
    :若为true,表示不支持<% scripting %>语法;
        6).<page-encoding>
    :设定JSP 网页的编码;
        7).<include-prelude>
    :设置JSP 网页的抬头,扩展名为.jspf
        8).<include-coda>
    :设置JSP 网页的结尾,扩展名为.jspf
    一个简单的<jsp-config>元素完整配置:

    <jsp-config>
        <taglib>
           <taglib-uri>Taglib</taglib-uri>
           <taglib-location>/WEB-INF/tlds/MyTaglib.tld</taglib-location>
        </taglib>
        <jsp-property-group>
            <description>Special propertygroup for JSP Configuration JSP example.</description>
           <display-name>JSPConfiguration</display-name>
            <url-pattern>/jsp/*</url-pattern>
           <el-ignored>true</el-ignored>
           <page-encoding>GB2312</page-encoding>
            <scripting-invalid>true</scripting-invalid>
           <include-prelude>/include/prelude.jspf</include-prelude>
           <include-coda>/include/coda.jspf</include-coda>
        </jsp-property-group>
    </jsp-config> 
    配置web.xml来限制对某些servlet的请求

    有时我们只希望通过认证的用户才能请求某些servlet的话,就可以在web.xml中来进行相应的配置,来达到此目的。

    这就要用到<security-constraint></security-constraint>元素。
    对于 tomcat,中web.xml使用security-constraint元素需要在位于<Tomcat-installation-directory>/conf/tomcat-users.xmlXML文件中创建用户名和密码。比如下面的这个tomcat- users.xml文件:

    <?xml version='1.0' encoding='utf-8'?>
    <tomcat-users>
      <role rolename="tomcat"/>
      <role rolename="manager"/>
      <role rolename="admin"/>
      <user username="tomcat" password="tomcat"roles="tomcat"/>
      <user username="both" password="tomcat"roles="tomcat,manager"/>
      <user username="admin" password="admin"roles="admin"/>
    </tomcat-users>

    XML片段包括一个tomcat-users根元素,它包含一个或多个roleuser元素。
    然后在Web应用程序的web.xml中创建security-constraintlogin-configsecurity-role元素。

    <security-constraint>
          <web-resource-collection>
             <web-resource-name>HelloServlet</web-resource-name>
             <url-pattern>/HelloServlet</url-pattern>
             <http-method>GET</http-method>
             <http-method>POST</http-method>
          </web-resource-collection>
          <auth-constraint>
              <description>Thisapplies only to the "tomcat" security role</description>
             <role-name>admin</role-name>
          </auth-constraint>
          <user-data-constraint>
             <transport-guarantee>NONE</transport-guarantee>
          </user-data-constraint>
      </security-constraint>
      <login-config>
          <auth-method>BASIC</auth-method>
      </login-config>
      <security-role>
          <role-name>admin</role-name>
      </security-role>

    其中security-constraint元素包含一个或多个web-resource-collection元素,它是描述Web应用程序中的哪些web资源受到指定安全限制的保护。http-method元素指定安全限制覆盖的HTTP方法。上面的例子中,当我们对/HelloServlet GETPOST请求时将触发配置的安全机制。
    auth-constraint
    元素用于描述允许访问Web组件的安全角色。此例中安全角色的例子有tomcatmanageradmin。而只有当作为admin角色的用户才可以访问HelloServlet

    Web应用程序通过login-config元素来认证用户,并确认该用户是否为正确的角色。
    longin-config
    包含的 transport-guarantee子元素用来指定认证方法,BASIC是一种常见的Web认证方式,浏览器给用户提示一个对话框,要求输入用户名和密码,随后Tomcat将给出的用户名和密码与tomcat-users.xml中的用户名和密码进行比较,然后使用前面的security- constraint配置来确定用户是否可访问受保护的servlet

    (除BASIC外,还可以是FORMCLIENT-CERTDIGEST等)

    其实这种认证方法实际上有两个步骤:
    1
    、检查提供的用户名和密码是否正确。
    2
    、判断用户是否映射到特定的安全角色。例如,用户可能提供了正确的用户名和密码,但没有映射到特定的安全角色,也将被禁止访问特定的Web资源。

     

     

     

     

    展开全文
  • Thinkphp当中的模型事件在处理缓存方面使用起来是非常方便的,比如 1.在更新和添加的时候将缓存删除 2.在删除数据的时候将缓存删除 在事件当中写入后,就避免了在每个控制... self::beforeWrite(function ($user) {

    Thinkphp当中的模型事件在处理缓存方面使用起来是非常方便的,比如

    1.在更新和添加的时候将缓存删除

    2.在删除数据的时候将缓存删除


    在事件当中写入后,就避免了在每个控制器当中再分别写了,在升级到5.0+以后出现的问题是,

    protected static function init(){
        self::beforeWrite(function ($user) {
            //这里有作用
            cache('menu', NULL);
        });
        self::afterDelete(function ($user) {
            //这里不起作用
            cache('menu', NULL);
        });
    }
    

    查询数据库,数据也是正常删除的,可是事件就是不起作用,删除也是实例化模型后删除的,如下:

    $del_result = $this->menu_model->where('id',$id)->delete();

    在尝试多次,准备放弃的时候,突然在手册上看到了这么一句话,如下:

    //或者通过数据库类的查询条件删除
    
    User::where('id','>',10)->delete();

    也就是说,这种我们熟悉的3.0删除的方式是通过数据库类的方式来进行删除,而非模型的方式,在事件提示当中,有这么一句话,如下:

    //模型事件只可以在调用模型的方法才能生效,使用查询构造器通过Db类操作是无效的

    将原有的删除方式更改为:

    $del_result = $this->menu_model->destroy($id);

    事件正常触发

    展开全文
  • Android Framework学习(一)之init进程解析

    千次阅读 2017-04-30 16:38:42
    init进程是Android系统中用户空间的第一个进程,它被赋予了很多极其重要的工作职责,init进程相关源码位于system/core/init,本篇博客我们就一起来学习init进程(基于Android 7.0)。init入口函数分析init的入口函数...

    init进程是Android系统中用户空间的第一个进程,它被赋予了很多极其重要的工作职责,init进程相关源码位于system/core/init,本篇博客我们就一起来学习init进程(基于Android 7.0)。

    init入口函数分析

    init的入口函数为main,位于system/core/init/init.cpp

    int main(int argc, char** argv) {
        if (!strcmp(basename(argv[0]), "ueventd")) {
            return ueventd_main(argc, argv);
        }
    
        if (!strcmp(basename(argv[0]), "watchdogd")) {
            return watchdogd_main(argc, argv);
        }
    
        // Clear the umask.
        umask(0);
    
        add_environment("PATH", _PATH_DEFPATH);
    
        bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);
    
        // Get the basic filesystem setup we need put together in the initramdisk
        // on / and then we'll let the rc file figure out the rest.
        //1.创建一些文件夹,并挂载设备,这些都是与Linux相关
        if (is_first_stage) {
            mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
            mkdir("/dev/pts", 0755);
            mkdir("/dev/socket", 0755);
            mount("devpts", "/dev/pts", "devpts", 0, NULL);
            #define MAKE_STR(x) __STRING(x)
            mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
            mount("sysfs", "/sys", "sysfs", 0, NULL);
        }
    
        // We must have some place other than / to create the device nodes for
        // kmsg and null, otherwise we won't be able to remount / read-only
        // later on. Now that tmpfs is mounted on /dev, we can actually talk
        // to the outside world.
        //2.重定向标准输入,输出,错误输出到/dev/_null_
        open_devnull_stdio();
        3.初始化内核log系统 
        klog_init();
        klog_set_level(KLOG_NOTICE_LEVEL);
    
        NOTICE("init %s started!\n", is_first_stage ? "first stage" : "second stage");
    
        if (!is_first_stage) {
            // Indicate that booting is in progress to background fw loaders, etc.
            close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
            //4.初始化和属性相关资源
            property_init();
    
            // If arguments are passed both on the command line and in DT,
            // properties set in DT always have priority over the command-line ones.
            process_kernel_dt();
            process_kernel_cmdline();
    
            // Propagate the kernel variables to internal variables
            // used by init as well as the current required properties.
            export_kernel_boot_props();
        }
    
        // Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
        5.完成SELinux相关工作
        selinux_initialize(is_first_stage);
    
        // If we're in the kernel domain, re-exec init to transition to the init domain now
        // that the SELinux policy has been loaded.
        if (is_first_stage) {
            6.重新设置属性
            if (restorecon("/init") == -1) {
                ERROR("restorecon failed: %s\n", strerror(errno));
                security_failure();
            }
            char* path = argv[0];
            char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
            if (execv(path, args) == -1) {
                ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
                security_failure();
            }
        }
    
        // These directories were necessarily created before initial policy load
        // and therefore need their security context restored to the proper value.
        // This must happen before /dev is populated by ueventd.
        NOTICE("Running restorecon...\n");
        restorecon("/dev");
        restorecon("/dev/socket");
        restorecon("/dev/__properties__");
        restorecon("/property_contexts");
        restorecon_recursive("/sys");
        7.创建epoll句柄 
        epoll_fd = epoll_create1(EPOLL_CLOEXEC);
        if (epoll_fd == -1) {
            ERROR("epoll_create1 failed: %s\n", strerror(errno));
            exit(1);
        }
        8.装载子进程信号处理器
        signal_handler_init();
    
        property_load_boot_defaults();
        export_oem_lock_status();
        //9.启动属性服务
        start_property_service();
    
        const BuiltinFunctionMap function_map;
        Action::set_function_map(&function_map);
    
        Parser& parser = Parser::GetInstance();
        parser.AddSectionParser("service",std::make_unique<ServiceParser>());
        parser.AddSectionParser("on", std::make_unique<ActionParser>());
        parser.AddSectionParser("import", std::make_unique<ImportParser>());
        //10.解析init.rc配置文件
        parser.ParseConfig("/init.rc");
    
        ActionManager& am = ActionManager::GetInstance();
    
        am.QueueEventTrigger("early-init");
    
        // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
        am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
        // ... so that we can start queuing up actions that require stuff from /dev.
        am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
        am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits");
        am.QueueBuiltinAction(keychord_init_action, "keychord_init");
        am.QueueBuiltinAction(console_init_action, "console_init");
    
        // Trigger all the boot actions to get us started.
        am.QueueEventTrigger("init");
    
        // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
        // wasn't ready immediately after wait_for_coldboot_done
        am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    
        // Don't mount filesystems or start core system services in charger mode.
        std::string bootmode = property_get("ro.bootmode");
        if (bootmode == "charger") {
            am.QueueEventTrigger("charger");
        } else {
            am.QueueEventTrigger("late-init");
        }
    
        // Run all property triggers based on current state of the properties.
        am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
    
        while (true) {
            if (!waiting_for_exec) {
                am.ExecuteOneCommand();
                restart_processes();
            }
    
            int timeout = -1;
            if (process_needs_restart) {
                timeout = (process_needs_restart - gettime()) * 1000;
                if (timeout < 0)
                    timeout = 0;
            }
    
            if (am.HasMoreCommands()) {
                timeout = 0;
            }
    
            bootchart_sample(&timeout);
    
            epoll_event ev;
            int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
            if (nr == -1) {
                ERROR("epoll_wait failed: %s\n", strerror(errno));
            } else if (nr == 1) {
                ((void (*)()) ev.data.ptr)();
            }
        }
    
        return 0;
    }
    

    从上面代码中可以精简归纳init的main方法做的事情:
    1.创建文件系统目录并挂载相关的文件系统
    2.屏蔽标准的输入输出
    3.初始化内核log系统
    4.调用property_init初始化属性相关的资源
    5.完成SELinux相关工作
    6.重新设置属性
    7.创建epoll句柄
    8.装载子进程信号处理器
    9.通过property_start_service启动属性服务
    10.通过parser.ParseConfig(“/init.rc”)来解析init.rc
    接下来对上述部分步骤,进行详细解析。

    1.创建文件系统目录并挂载相关的文件系统

    //清除屏蔽字(file mode creation mask),保证新建的目录的访问权限不受屏蔽字影响。
    umask(0);
    
    add_environment("PATH", _PATH_DEFPATH);
    
    bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);
    
    // Get the basic filesystem setup we need put together in the initramdisk
    if (is_first_stage) {
        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
        mkdir("/dev/pts", 0755);
        mkdir("/dev/socket", 0755);
        mount("devpts", "/dev/pts", "devpts", 0, NULL);
        #define MAKE_STR(x) __STRING(x)
        mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
        mount("sysfs", "/sys", "sysfs", 0, NULL);
    }

    该部分主要用于创建和挂载启动所需的文件目录。
    需要注意的是,在编译Android系统源码时,在生成的根文件系统中,并不存在这些目录,它们是系统运行时的目录,即当系统终止时,就会消失。

    在init初始化过程中,Android分别挂载了tmpfs,devpts,proc,sysfs这4类文件系统。

    2.屏蔽标准的输入输出

    open_devnull_stdio();

    前文生成/dev目录后,init进程将调用open_devnull_stdio函数,屏蔽标准的输入输出。
    open_devnull_stdio函数会在/dev目录下生成null设备节点文件,并将标准输入、标准输出、标准错误输出全部重定向到null设备中。

    void open_devnull_stdio(void)
    {
        // Try to avoid the mknod() call if we can. Since SELinux makes
        // a /dev/null replacement available for free, let's use it.
        int fd = open("/sys/fs/selinux/null", O_RDWR);
        if (fd == -1) {
            // OOPS, /sys/fs/selinux/null isn't available, likely because
            // /sys/fs/selinux isn't mounted. Fall back to mknod.
            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 == -1) {
                exit(1);
            }
        }
    
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        if (fd > 2) {
            close(fd);
        }
    }

    open_devnull_stdio函数定义于system/core/init/util.cpp中。

    这里需要说明的是,dup2函数的作用是用来复制一个文件的描述符,通常用来重定向进程的stdin、stdout和stderr。它的函数原形是:

    int dup2(int oldfd, int targetfd)

    该函数执行后,targetfd将变成oldfd的复制品。

    因此上述过程其实就是:创建出null设备后,将0、1、2绑定到null设备上。因此init进程调用open_devnull_stdio函数后,通过标准的输入输出无法输出信息。

    4.初始化属性域

    if (!is_first_stage) {
        .......
        property_init();
        .......
    }

    调用property_init初始化属性域。在Android平台中,为了让运行中的所有进程共享系统运行时所需要的各种设置值,系统开辟了属性存储区域,并提供了访问该区域的API。

    需要强调的是,在init进程中有部分代码块以is_first_stage标志进行区分,决定是否需要进行初始化,而is_first_stage的值,由init进程main函数的入口参数决定。 其原因在于,在引入selinux机制后,有些操作必须要在内核态才能完成;
    但init进程作为android的第一个进程,又是运行在用户态的。
    于是,最终设计为用is_first_stage进行区分init进程的运行状态。init进程在运行的过程中,会完成从内核态到用户态的切换。

    void property_init() {
        if (__system_property_area_init()) {
            ERROR("Failed to initialize property area\n");
            exit(1);
        }
    }

    property_init函数定义于system/core/init/property_service.cpp中,如上面代码所示,最终调用_system_property_area_init函数初始化属性域。

    5.完成SELinux相关工作

    // Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
    selinux_initialize(is_first_stage);

    init进程进程调用selinux_initialize启动SELinux。从注释来看,init进程的运行确实是区分用户态和内核态的。

    static void selinux_initialize(bool in_kernel_domain) {
        Timer t;
    
        selinux_callback cb;
        //用于打印log的回调函数
        cb.func_log = selinux_klog_callback;
        selinux_set_callback(SELINUX_CB_LOG, cb);
        //用于检查权限的回调函数
        cb.func_audit = audit_callback;
        selinux_set_callback(SELINUX_CB_AUDIT, cb);
    
        if (in_kernel_domain) {
            //内核态处理流程
            INFO("Loading SELinux policy...\n");
            //用于加载sepolicy文件。该函数最终将sepolicy文件传递给kernel,这样kernel就有了安全策略配置文件,后续的MAC才能开展起来。
            if (selinux_android_load_policy() < 0) {
                ERROR("failed to load policy: %s\n", strerror(errno));
                security_failure();
            }
            //内核中读取的信息
            bool kernel_enforcing = (security_getenforce() == 1);
            //命令行中得到的数据
            bool is_enforcing = selinux_is_enforcing();
            if (kernel_enforcing != is_enforcing) {
                //用于设置selinux的工作模式。selinux有两种工作模式:
                //1、”permissive”,所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志
                //2、”enforcing”,所有操作都会进行权限检查。在一般的终端中,应该工作于enforing模式
                if(security_setenforce(is_enforcing)) {
                    ........
                    //将重启进入recovery mode
                    security_failure();
                }
            }
            if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) {
                security_failure();
            }
    
            NOTICE("(Initializing SELinux %s took %.2fs.)\n",
                   is_enforcing ? "enforcing" : "non-enforcing", t.duration());
        } else {
            selinux_init_all_handles();
        }
    }

    6.重新设置属性

    // If we're in the kernel domain, re-exec init to transition to the init domain now that the SELinux policy has been loaded.
    if (is_first_stage) {
        //按selinux policy要求,重新设置init文件属性
        if (restorecon("/init") == -1) {
            ERROR("restorecon failed: %s\n", strerror(errno));
            security_failure();
        }
        char* path = argv[0];
        char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
        //这里就是前面所说的,启动用户态的init进程,即second-stage
        if (execv(path, args) == -1) {
            ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
            security_failure();
        }
    }
    
    // These directories were necessarily created before initial policy load
    // and therefore need their security context restored to the proper value.
    // This must happen before /dev is populated by ueventd.
    INFO("Running restorecon...\n");
    restorecon("/dev");
    restorecon("/dev/socket");
    restorecon("/dev/__properties__");
    restorecon_recursive("/sys");

    上述文件节点在加载Sepolicy之前已经被创建了,因此在加载完Sepolicy后,需要重新设置相关的属性。

    9.启动配置属性的服务端

    start_property_service();

    init进程在共享内存区域中,创建并初始化属性域。其它进程可以访问属性域中的值,但更改属性值仅能在init进程中进行。这就是init进程调用start_property_service的原因。其它进程修改属性值时,要预先向init进程提交值变更申请,然后init进程处理该申请,并修改属性值。在访问和修改属性时,init进程都可以进行权限控制。

    void start_property_service() {
        //创建了一个非阻塞socket
        property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0666, 0, 0, NULL);
        if (property_set_fd == -1) {
            ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
            exit(1);
        }
        //调用listen函数监听property_set_fd, 于是该socket变成一个server
        listen(property_set_fd, 8);
        //监听server socket上是否有数据到来
        register_epoll_handler(property_set_fd,  handle_property_set_fd);
    }

    我们知道,在create_socket函数返回套接字property_set_fd时,property_set_fd是一个主动连接的套接字。此时,系统假设用户会对这个套接字调用connect函数,期待它主动与其它进程连接。

    由于在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接,于是需要调用listen函数使用主动连接套接字变为被连接套接字,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。

    因此,调用listen后,init进程成为一个服务进程,其它进程可以通过property_set_fd连接init进程,提交设置系统属性的申请。

    listen函数的第二个参数,涉及到一些网络的细节。

    在进程处理一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态。有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。

    因此,内核会在自己的进程空间里维护一个队列,以跟踪那些已完成连接但服务器进程还没有接手处理的用户,或正在进行的连接的用户。这样的一个队列不可能任意大,所以必须有一个上限。listen的第二个参数就是告诉内核使用这个数值作为上限。因此,init进程作为系统属性设置的服务器,最多可以同时为8个试图设置属性的用户提供服务。

    在启动配置属性服务的最后,调用函数register_epoll_handler。该函数将利用之前创建出的epoll句柄监听property_set_fd。当property_set_fd中有数据到来时,init进程将利用handle_property_set_fd函数进行处理。

    static void handle_property_set_fd() {
        ..........
        if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
            return;
        }
        ........
        r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));
        .........
        switch(msg.cmd) {
        .........
        }
        .........
    }

    handle_propery_set_fd函数实际上是调用accept函数监听连接请求,接收property_set_fd中到来的数据,然后利用recv函数接受到来的数据,最后根据到来数据的类型,进行设置系统属性等相关操作,在此不做深入分析。

    介绍一下系统属性改变的一些用途。
    在init.rc中定义了一些与属性相关的触发器。当某个条件相关的属性被改变时,与该条件相关的触发器就会被触发。举例来说,如下面代码所示,debuggable属性变为1时,将执行启动console进程等操作。

    on property:ro.debuggable=1
        # Give writes to anyone for the trace folder on debug builds.
        # The folder is used to store method traces.
        chmod 0773 /data/misc/trace
        start console

    总结一下,其它进程修改系统属性时,大致的流程如下图所示:其它的进程像init进程发送请求后,由init进程检查权限后,修改共享内存区。
    这里写图片描述

    10.解析配置文件init.rc

    init.rc是系统配置文件,位于system/core/rootdir/init.rc,Android 7.0中对init.rc文件进行了拆分,每个服务一个rc文件。

    init.rc文件是在init进程启动后执行的启动脚本,文件中记录着init进程需执行的操作。在Android系统中,使用init.rc和init.{ hardware }.rc两个文件。

    其中init.rc文件在Android系统运行过程中用于通用的环境设置与进程相关的定义,init.{hardware}.rc(例如,高通有init.qcom.rc,MTK有init.mediatek.rc)用于定义Android在不同平台下的特定进程和环境设置等。

    init.rc文件大致分为两大部分,一部分是以“on”关键字开头的动作列表(action list):

    on early-init
        # Set init and its forked children's oom_adj.
        write /proc/1/oom_score_adj -1000
        .........
        start ueventd

    另一部分是以“service”关键字开头的服务列表(service list):

    service ueventd /sbin/ueventd
        class core
        critical
        seclabel u:r:ueventd:s0

    动作列表用于创建所需目录,以及为某些特定文件指定权限,而服务列表用来记录init进程需要启动的一些子进程。如上面代码所示,service关键字后的第一个字符串表示服务(子进程)的名称,第二个字符串表示服务的执行路径。

    接下来,我们从ParseConfig函数入手,逐步分析整个解析过程(函数定义于system/core/init/ Init_parser.cpp中):

    bool Parser::ParseConfig(const std::string& path) {
        if (is_dir(path.c_str())) {
            //传入参数为目录地址
            return ParseConfigDir(path);
        }
        //传入参数为文件地址
        return ParseConfigFile(path);
    }
    bool Parser::ParseConfigDir(const std::string& path) {
        ...........
        std::unique_ptr<DIR, int(*)(DIR*)> config_dir(opendir(path.c_str()), closedir);
        ..........
        //看起来很复杂,其实就是递归目录
        while ((current_file = readdir(config_dir.get()))) {
            std::string current_path = android::base::StringPrintf("%s/%s", path.c_str(), current_file->d_name);
            if (current_file->d_type == DT_REG) {
                //最终还是靠ParseConfigFile来解析实际的文件
                if (!ParseConfigFile(current_path)) {
                    .............
                }
            }
        }
    }

    从上面的代码可以看出,解析init.rc文件的函数是ParseConfigFile:

    bool Parser::ParseConfigFile(const std::string& path) {
        INFO("Parsing file %s...\n", path.c_str());
        Timer t;
        std::string data;
        //读取路径指定文件中的内容,保存为字符串形式
        if (!read_file(path.c_str(), &data)) {
            return false;
        }
    
        data.push_back('\n'); // TODO: fix parse_config.
        //解析获取的字符串
        ParseData(path, data);
        for (const auto& sp : section_parsers_) {
            sp.second->EndFile(path);
        }
    
        // Turning this on and letting the INFO logging be discarded adds 0.2s to
        // Nexus 9 boot time, so it's disabled by default.
        if (false) DumpState();
    
        NOTICE("(Parsing %s took %.2fs.)\n", path.c_str(), t.duration());
        return true;
    }

    ParseData函数定义于system/core/init/init_parser.cpp中,根据关键字解析出服务和动作。动作与服务会以链表节点的形式注册到service_list与action_list中,service_list与action_list是init进程中声明的全局结构体

    void Parser::ParseData(const std::string& filename, const std::string& data) {
        .......
        parse_state state;
        .......
        std::vector<std::string> args;
    
        for (;;) {
            //next_token以行为单位分割参数传递过来的字符串
            //最先走到T_TEXT分支
            switch (next_token(&state)) {
            case T_EOF:
                if (section_parser) {
                    //EOF,解析结束
                    section_parser->EndSection();
                }
                return;
            case T_NEWLINE:
                state.line++;
                if (args.empty()) {
                    break;
                }
                //创建parser时,会为init.rc中以service,on,import开头的都定义了对应的解析parser 
                //这里就是根据第一个参数,判断是否有对应的parser
                if (section_parsers_.count(args[0])) {
                    if (section_parser) {
                        //结束上一个parser的工作,将构造出的对象加入到对应的service_list与action_list中
                        section_parser->EndSection();
                    }
                    //获取参数对应的parser
                    section_parser = section_parsers_[args[0]].get();
                    std::string ret_err;
                    //调用实际parser的ParseSection函数
                    if (!section_parser->ParseSection(args, &ret_err)) {
                        parse_error(&state, "%s\n", ret_err.c_str());
                        section_parser = nullptr;
                    }
                } else if (section_parser) {
                    std::string ret_err;
                    //如果第一个参数不是service,on,import
                    //则调用前一个parser的ParseLineSection函数
                    //这里相当于解析一个参数块的子项
                    if (!section_parser->ParseLineSection(args, state.filename, state.line, &ret_err)) {
                        parse_error(&state, "%s\n", ret_err.c_str());
                    }
                }
                //清空本次解析的数据
                args.clear();
                break;
            case T_TEXT:
                //将本次解析的内容写入到args中
                args.emplace_back(state.text);
                break;
            }
        }
    }

    这里的解析看起来比较复杂,在6.0以前的版本中,整个解析是面向过程的。init进程统一调用一个函数来进行解析,然后在该函数中利用switch-case的形式,根据解析的内容进行相应的处理。
    在Android 7.0中,为了更好地封装及面向对象,对于不同的关键字定义了不同的parser对象,每个对象通过多态实现自己的解析操作。

    在init进程main函数中,创建各种parser的代码如下:

    ...........
    Parser& parser = Parser::GetInstance();
    parser.AddSectionParser("service",std::make_unique<ServiceParser>());
    parser.AddSectionParser("on", std::make_unique<ActionParser>());
    parser.AddSectionParser("import", std::make_unique<ImportParser>());
    ...........

    看看三个Parser的定义:

    class ServiceParser : public SectionParser {......}
    class ActionParser : public SectionParser {......}
    class ImportParser : public SectionParser {.......}

    可以看到三个Parser均是继承SectionParser,具体的实现各有不同,我们以比较常用的ServiceParser和ActionParser为例

    ServiceParser
    ServiceParser定义于system/core/init/service.cpp中。从前面的代码,我们知道,解析一个service块,首先需要调用ParseSection函数,接着利用ParseLineSection处理子块,解析完所有数据后,调用EndSection。
    因此,我们着重看看ServiceParser的这三个函数:

    bool ServiceParser::ParseSection(.....) {
        .......
        const std::string& name = args[1];
        .......
        std::vector<std::string> str_args(args.begin() + 2, args.end());
        //主要根据参数,构造出一个service对象
        service_ = std::make_unique<Service>(name, "default", str_args);
        return true;
    }
    //注意这里已经在解析子项了
    bool ServiceParser::ParseLineSection(......) const {
        //调用service对象的HandleLine
        return service_ ? service_->HandleLine(args, err) : false;
    }
    bool Service::HandleLine(.....) {
        ........
        //OptionHandlerMap继承自keywordMap<OptionHandler>
        static const OptionHandlerMap handler_map;
        //根据子项的内容,找到对应的handler函数
        //FindFunction定义于keyword模块中,FindFunction方法利用子类生成对应的map中,然后通过通用的查找方法,即比较键值找到对应的处理函数
        auto handler = handler_map.FindFunction(args[0], args.size() - 1, err);
    
        if (!handler) {
            return false;
        }
        //调用handler函数
        return (this->*handler)(args, err);
    }
    
    class Service::OptionHandlerMap : public KeywordMap<OptionHandler> {
        ...........
        Service::OptionHandlerMap::Map& Service::OptionHandlerMap::map() const {
        constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
        static const Map option_handlers = {
            {"class",       {1,     1,    &Service::HandleClass}},
            {"console",     {0,     0,    &Service::HandleConsole}},
            {"critical",    {0,     0,    &Service::HandleCritical}},
            {"disabled",    {0,     0,    &Service::HandleDisabled}},
            {"group",       {1,     NR_SVC_SUPP_GIDS + 1, &Service::HandleGroup}},
            {"ioprio",      {2,     2,    &Service::HandleIoprio}},
            {"keycodes",    {1,     kMax, &Service::HandleKeycodes}},
            {"oneshot",     {0,     0,    &Service::HandleOneshot}},
            {"onrestart",   {1,     kMax, &Service::HandleOnrestart}},
            {"seclabel",    {1,     1,    &Service::HandleSeclabel}},
            {"setenv",      {2,     2,    &Service::HandleSetenv}},
            {"socket",      {3,     6,    &Service::HandleSocket}},
            {"user",        {1,     1,    &Service::HandleUser}},
            {"writepid",    {1,     kMax, &Service::HandleWritepid}},
        };
        return option_handlers;
    }
    
    //以class对应的处理函数为例,可以看出其实就是填充service对象对应的域
    bool Service::HandleClass(const std::vector<std::string>& args, std::string* err) {
        classname_ = args[1];
        return true;
    }
    
    //注意此时service对象已经处理完毕
    void ServiceParser::EndSection() {
        if (service_) {
            ServiceManager::GetInstance().AddService(std::move(service_));
        }
    }
    
    void ServiceManager::AddService(std::unique_ptr<Service> service) {
        Service* old_service = FindServiceByName(service->name());
        if (old_service) {
            ERROR("ignored duplicate definition of service '%s'",
                  service->name().c_str());
            return;
        }
        //将service对象加入到services_里
        //7.0里,services_已经是个vector了
        services_.emplace_back(std::move(service));
    }

    总结一下:ServiceParser中,首先根据第一行的名字和参数创建出service对象,然后根据选项域的内容填充service对象,最后将创建出的service对象加入到vector类型的service链表中。

    ActionParser
    ActionParser定义于system/core/init/action.cpp中。Action的解析过程,其实与Service一样,也是先后调用ParseSection, ParseLineSection和EndSection。

    bool ActionParser::ParseSection(....) {
        ........
        //创建出新的action对象
        auto action = std::make_unique<Action>(false);
        //根据参数,填充action的trigger域,不详细分析了
        if (!action->InitTriggers(triggers, err)) {
            return false;
        }
        .........
    }
    
    bool ActionParser::ParseLineSection(.......) const {
        //构造Action对象的command域
        return action_ ? action_->AddCommand(args, filename, line, err) : false;
    }
    
    bool Action::AddCommand(.....) {
        ........
        //找出action对应的执行函数
        auto function = function_map_->FindFunction(args[0], args.size() - 1, err);
        ........
        //利用所有信息构造出command,加入到action对象中
        AddCommand(function, args, filename, line);
        return true;
    }
    
    void Action::AddCommand(......) {
        commands_.emplace_back(f, args, filename, line);
    }
    
    void ActionParser::EndSection() {
        if (action_ && action_->NumCommands() > 0) {
            ActionManager::GetInstance().AddAction(std::move(action_));
        }
    }
    
    void ActionManager::AddAction(.....) {
        ........
        auto old_action_it = std::find_if(actions_.begin(),
                         actions_.end(),
                         [&action] (std::unique_ptr<Action>& a) {
                             return action->TriggersEqual(*a);
                         });
    
        if (old_action_it != actions_.end()) {
            (*old_action_it)->CombineAction(*action);
        } else {
            //加入到action链表中,类型也是vector,其中装的是指针
            actions_.emplace_back(std::move(action));
        }
    }

    可以看出,加载action块的逻辑和service一样,不同的是需要填充trigger和command域。当然,最后解析出的action也需要加入到action链表中。

    这里最后还剩下一个问题,那就是哪里定义了Action中command对应处理函数?
    答案就是在init.cpp的main函数中:

    .......
    const BuiltinFunctionMap function_map;
    Action::set_function_map(&function_map);
    .......

    Action中调用function_map_->FindFunction时,实际上调用的是BuiltinFunctionMap的FindFunction函数。FindFunction是keyword定义的通用函数,重点是重构的map函数。所以需要看BuiltinFunctionMap,其定义在system/core/init/builtins.cpp:

    BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const {
        constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
        static const Map builtin_functions = {
            {"bootchart_init",          {0,     0,    do_bootchart_init}},
            {"chmod",                   {2,     2,    do_chmod}},
            {"chown",                   {2,     3,    do_chown}},
            {"class_reset",             {1,     1,    do_class_reset}},
            {"class_start",             {1,     1,    do_class_start}},
            {"class_stop",              {1,     1,    do_class_stop}},
            {"copy",                    {2,     2,    do_copy}},
            {"domainname",              {1,     1,    do_domainname}},
            {"enable",                  {1,     1,    do_enable}},
            {"exec",                    {1,     kMax, do_exec}},
            {"export",                  {2,     2,    do_export}},
            {"hostname",                {1,     1,    do_hostname}},
            {"ifup",                    {1,     1,    do_ifup}},
            {"init_user0",              {0,     0,    do_init_user0}},
            {"insmod",                  {1,     kMax, do_insmod}},
            {"installkey",              {1,     1,    do_installkey}},
            {"load_persist_props",      {0,     0,    do_load_persist_props}},
            {"load_system_props",       {0,     0,    do_load_system_props}},
            {"loglevel",                {1,     1,    do_loglevel}},
            {"mkdir",                   {1,     4,    do_mkdir}},
            {"mount_all",               {1,     kMax, do_mount_all}},
            {"mount",                   {3,     kMax, do_mount}},
            {"powerctl",                {1,     1,    do_powerctl}},
            {"restart",                 {1,     1,    do_restart}},
            {"restorecon",              {1,     kMax, do_restorecon}},
            {"restorecon_recursive",    {1,     kMax, do_restorecon_recursive}},
            {"rm",                      {1,     1,    do_rm}},
            {"rmdir",                   {1,     1,    do_rmdir}},
            {"setprop",                 {2,     2,    do_setprop}},
            {"setrlimit",               {3,     3,    do_setrlimit}},
            {"start",                   {1,     1,    do_start}},
            {"stop",                    {1,     1,    do_stop}},
            {"swapon_all",              {1,     1,    do_swapon_all}},
            {"symlink",                 {2,     2,    do_symlink}},
            {"sysclktz",                {1,     1,    do_sysclktz}},
            {"trigger",                 {1,     1,    do_trigger}},
            {"verity_load_state",       {0,     0,    do_verity_load_state}},
            {"verity_update_state",     {0,     0,    do_verity_update_state}},
            {"wait",                    {1,     2,    do_wait}},
            {"write",                   {2,     2,    do_write}},
        };
        return builtin_functions;
    }

    上述代码的第四项就是Action每个command对应的执行函数。

    11.向执行队列中添加其它action

    ActionManager& am = ActionManager::GetInstance();
    
    am.QueueEventTrigger("early-init");
    
    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
    m.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    // ... so that we can start queuing up actions that require stuff from /dev.
    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits");
    am.QueueBuiltinAction(keychord_init_action, "keychord_init");
    am.QueueBuiltinAction(console_init_action, "console_init");
    
    // Trigger all the boot actions to get us started.
    am.QueueEventTrigger("init");
    
    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
    // wasn't ready immediately after wait_for_coldboot_done
    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    
    // Don't mount filesystems or start core system services in charger mode.
    std::string bootmode = property_get("ro.bootmode");
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");
    } else {
        am.QueueEventTrigger("late-init");
    }
    
    // Run all property triggers based on current state of the properties.
        am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");

    从上面的代码可以看出,接下来init进程中调用了大量的QueueEventTrigger和QueueBuiltinAction函数。

    void ActionManager::QueueEventTrigger(const std::string& trigger) {
        trigger_queue_.push(std::make_unique<EventTrigger>(trigger));
    }

    此处QueueEventTrigger函数就是利用参数构造EventTrigger,然后加入到trigger_queue_中。后续init进程处理trigger事件时,将会触发相应的操作。根据前文的分析,我们知道实际上就是将action_list中,对应trigger与第一个参数匹配的action,加入到运行队列action_queue中。

    void ActionManager::QueueBuiltinAction(BuiltinFunction func, const std::string& name) {
        //创建action
        auto action = std::make_unique<Action>(true);
        std::vector<std::string> name_vector{name};
    
        //保证唯一性
        if (!action->InitSingleTrigger(name)) {
            return;
        }
    
        //创建action的cmd,指定执行函数和参数
        action->AddCommand(func, name_vector);
    
        trigger_queue_.push(std::make_unique<BuiltinTrigger>(action.get()));
        actions_.emplace_back(std::move(action));
    }

    QueueBuiltinAction函数中构造新的action加入到actions_中,第一个参数作为新建action携带cmd的执行函数;第二个参数既作为action的trigger name,也作为action携带cmd的参数。

    12.处理添加到运行队列的事件

    while (true) {
        //判断是否有事件需要处理
        if (!waiting_for_exec) {
            //依次执行每个action中携带command对应的执行函数
            am.ExecuteOneCommand();
            //重启一些挂掉的进程
            restart_processes();
        }
    
        //以下决定timeout的时间,将影响while循环的间隔
        int timeout = -1;
        //有进程需要重启时,等待该进程重启
        if (process_needs_restart) {
            timeout = (process_needs_restart - gettime()) * 1000;
            if (timeout < 0)
                timeout = 0;
        }
    
        //有action待处理,不等待
        if (am.HasMoreCommands()) {
            timeout = 0;
        }
    
        //bootchart_sample应该是进行性能数据采样
        bootchart_sample(&timeout);
    
        epoll_event ev;
        //没有事件到来的话,最多阻塞timeout时间
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
        if (nr == -1) {
            ERROR("epoll_wait failed: %s\n", strerror(errno));
        } else if (nr == 1) {
            //有事件到来,执行对应处理函数
            //epoll句柄(即epoll_fd)主要监听子进程结束,及其它进程设置系统属性的请求。
            ((void (*)()) ev.data.ptr)();
        }
    }

    init进程将所有需要操作的action加入运行队列后, 进入无限循环过程,不断处理运行队列中的事件,同时进行重启service等操作。

    ExecuteOneCommand中的主要部分如下所示

    void ActionManager::ExecuteOneCommand() {
        // Loop through the trigger queue until we have an action to execute
        //当有可执行action或trigger queue为空时结束
        while (current_executing_actions_.empty() && !trigger_queue_.empty()) {
            //轮询actions链表
            for (const auto& action : actions_) {
                //依次查找trigger表
                if (trigger_queue_.front()->CheckTriggers(*action)) {
                    //当action与trigger对应时,就可以执行当前action
                    //一个trigger可以对应多个action,均加入current_executing_actions_
                    current_executing_actions_.emplace(action.get());
                }
            }
            //trigger event出队
            trigger_queue_.pop();
        }
    
        if (current_executing_actions_.empty()) {
            return;
        }
    
        //每次只执行一个action,下次init进程while循环时,跳过上面的while循环,接着执行
        auto action = current_executing_actions_.front();
    
        if (current_command_ == 0) {
            std::string trigger_name = action->BuildTriggersString();
            INFO("processing action (%s)\n", trigger_name.c_str());
        }
    
        //实际的执行过程,此处仅处理当前action中的一个cmd
        action->ExecuteOneCommand(current_command_);
    
        //适当地清理工作,注意只有当前action中所有的command均执行完毕后,才会将该action从current_executing_actions_移除
        // If this was the last command in the current action, then remove
        // the action from the executing list.
        // If this action was oneshot, then also remove it from actions_.
        ++current_command_;
        if (current_command_ == action->NumCommands()) {
            current_executing_actions_.pop();
            current_command_ = 0;
            if (action->oneshot()) {
                auto eraser = [&action] (std::unique_ptr<Action>& a) {
                    return a.get() == action;
                };
                actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser));
            }
        }
    }
    
    void Action::ExecuteCommand(const Command& command) const {
        Timer t;
        //执行该command对应的处理函数
        int result = command.InvokeFunc();
        ........
    }
    void Action::ExecuteCommand(const Command& command) const {
        Timer t;
        //执行该command对应的处理函数
        int result = command.InvokeFunc();
        ........
    }

    从代码可以看出,当while循环不断调用ExecuteOneCommand函数时,将按照trigger表的顺序,依次取出action链表中与trigger匹配的action。
    每次均执行一个action中的一个command对应函数(一个action可能携带多个command)。
    当一个action所有的command均执行完毕后,再执行下一个action。
    当一个trigger对应的action均执行完毕后,再执行下一个trigger对应action。

    restart_processes函数负责按需重启service

    static void restart_processes() {
        process_needs_restart = 0;
        ServiceManager::GetInstance().ForEachServiceWithFlags(
            SVC_RESTARTING,
            [] (Service* s) {
                s->RestartIfNeeded(process_needs_restart);
            });
    }

    该函数轮询service对应的链表,对于有SVC_RESTARING标志的service执行RestartIfNeeded(当子进程终止时,init进程会将可被重启进程的服务标志位置为SVC_RESTARTING)。

    RestartIfNeeded可以重新启动服务。

    void Service::RestartIfNeeded(time_t& process_needs_restart)(struct service *svc)
    {
        time_t next_start_time = svc->time_started + 5;
    
        //两次服务启动时间的间隔要大于5s
        if (next_start_time <= gettime()) {
            svc->flags &= (~SVC_RESTARTING);
            //满足时间间隔的要求后,重启服务
            //Start将会重新fork服务进程,并做相应的配置
            Start(svc, NULL);
            return;
        }
    
        //更新main函数中,while循环需要等待的时间
        if ((next_start_time < process_needs_restart) ||
            (process_needs_restart == 0)) {
            process_needs_restart = next_start_time;
        }
    }

    Bootchart 是一个能对 GNU/Linux boot 过程进行性能分析并把结果直观化的工具。它在 boot 过程中搜集资源利用情况及进程信息然后以PNG, SVG或EPS格式来显示结果。BootChart 包含数据收集工具和图像产生工具。数据收集工具在原始的BootChart中是独立的shell程序,但在Android中,数据收集工具被集成到了init 程序中。

    展开全文
  • Android7.0 init进程源码分析

    万次阅读 多人点赞 2016-08-07 20:07:26
    主要对Android 7.0中,init进程的源码进行分析。
  • 到了Android6.0,Init进程使用c++来写了,不过没有关系,它和c写的init没有太大的区别。 Init进程的入口代码是:system\core\init\init.cpp main函数: int main(int argc, char** argv) { if (!strcmp(basename...
  • Init 进程详解

    千次阅读 2017-11-13 11:16:13
    Android 内核加载完成后,就会启动init进程,init进程是Android系统用户空间的第一个进程。init程序放在系统根目录下,init进程代码位于源码的目录“system/core/init”下面。下面我们来分析init进程的启动过程1. ...
  • init进程源码分析

    千次阅读 2017-02-21 21:57:28
    主要作用是初始化基本的硬件环境(如CPU,内存,Flash等),为装载Linux内核准备合适的运行环境。一旦Linux内核装载完毕,bootloader将会从内存中清除掉。  Fastboot 是android设计的一套通过USB更新手机分
  • init-param 的param-name 就是参数名 param-value就是参数值, 支持多个参数 每一个 filter 都有一个 init 方法 ,可以再这个 方法中通过 getInitParamter("key"); key 就是 param-name的值,来获取
  • 作为天字第一号进程,代码羞涩难懂,但是也极其重要,熟悉init的原理对后面Zygote -- SystemServer -- 核心服务等一些列源码的研究是有很大作用的,所以既然说研究Android源码,就先拿init “庖丁解牛”!...
  • Android 的init过程详解

    千次阅读 2014-09-12 16:32:57
    init进程是用户空间执行的第一个进程,直接进入code:/system/core/init/init.c
  • init进程【1】——init启动过程

    万次阅读 2014-04-05 20:41:11
    众所周知,Linux中的所有进程都是有init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程。Android是基于Linux...
  • Android系统init进程启动及init.rc全解析

    万次阅读 多人点赞 2017-11-23 16:14:40
    system/core/init/init.c文件main函数中parse_config_file(init.rc)读取并解析init.rc文件内容。将service信息放置到system/core/init/init_parser.cpp的service_list中 system/core/init/init.c文
  • u-boot 的 init_sequence解析

    千次阅读 2019-02-26 22:16:11
    start_armboot (void)是u-boot开始执行的第一个C函数,该 函数自然要进行各种初始化工作,而init_sequence则是进行各种初始化的 函数数组,该函数定义在/lib_arm/board.c中,如下所示: typedef int (init_fnc_t) ...
  • init进程启动二
  • Android init进程(长文)

    千次阅读 2017-03-31 15:44:11
    android init
  • ,然后去解析init.rc文件,init.rc位于/bootable/recovery/etc/init.rc;需要注意的是这就是一个脚本文件,就像Android打包用到的gradle脚本一样 rc文件规则 这个文件解析具体实现在 init_parser.cpp 文件中,一...
  • # This should occur before anything else (e.g. ueventd) is started. setcon u:r:init:s0 start ueventd # create mountpoints mkdir /mnt 0775 root system 其中early-init是触发器,下面几行是command。...
  • init.rc文件的分析

    千次阅读 2014-06-01 17:23:38
    init.rc文件有两份,一个对应于normal mode,一个是recovery
  • lowlevel.init函数分析

    千次阅读 2018-04-15 15:33:22
     *///以下几行比较重要 : 代码的作用,就是判定当前代码执行的位置在sram中还是ddr中:为什么要判断:原因:  ldr r0, =0xff000fff //BL1(uboot的前一部分)在sram ,ddr都有存在,因此,如果是冷启动,...
  • kernel_thread函数调用do_fork创建了2号内核线程kthreadd、1号内核线程init,他们的父进程是内核0号进程,2号内核线程kthreadd作用是管理调度其他内核线程,而init内核线程通过调用init可执行程序转变成init进程,...
  • linux系统 initrd.img中init启动脚本分析

    千次阅读 2017-02-23 17:43:28
    FIXME: /dev/.initramfs-tools 的作用? usplash 会使 用 /dev/.initramfs 目录。usplash 会在机器启动的时候提供类似 windows 的启动画面,ubuntu linux 的启 动画面就是通过 usplash 实现的。由于在 /sbin 目录...
  • fs_initcall、early_initcall、__init

    千次阅读 2015-12-08 20:20:51
    在内核代码中经常可以看到类似fs_initcall\early_initcall\late_initcall这样的宏,这些宏有什么作用?如何实现的?下面来具体分析 具体定义 在include/linux/init.h中可以找到这些宏的定义 /* * Early ...
  • 本说明文件位于system/core/init/readme.txt 本文参考深入解析安卓系统一书,进行翻译,版权部分归书的作者 刘超,资深Android专家,系统架构师。 博客地址:...
  • 深入理解Android-Init理解

    千次阅读 2018-08-09 00:35:37
    这篇文章的意义在于理解Android的启动流程,作为一名...其中init进程起着承上启下的作用,android本身是基于Linux而来的,init进程是Linux系统中用户空间的第一个进程,在Android中,它也是Android用户空间的第一...
  • SSM框架原理,作用及使用方法

    万次阅读 多人点赞 2017-09-06 09:17:06
    作用: SSM框架是spring MVC ,spring和mybatis框架的整合,是标准的MVC模式,将整个系统划分为表现层,controller层,service层,DAO层四层 使用spring MVC负责请求的转发和视图管理 spring实现业务对象管理,...
  • Android Init进程源码分析

    万次阅读 热门讨论 2013-06-07 14:41:07
    Init 进程源码分析 基于Linux内核的android系统,在内核启动完成后将创建一个Init用户进程,实现了内核空间到用户空间的转变。在Android 启动过程介绍一文中介绍了Android系统的各个启动阶段,init进程启动后会...
  • # This should occur before anything else (e.g. ueventd) is started. "【这段脚本的意思是init进程启动之后就马上调用函数setcon将自己的安全上下文设置为“u:r:init:s0”,即将init进程的domain指定为init。】...
  • init进程【2】——解析配置文件

    千次阅读 2014-04-07 11:27:52
    在前面的一篇文章中分析了init进程的启动过程和main函数

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 55,535
精华内容 22,214
关键字:

beforeinit的作用