init 订阅
init是Linux系统操作中不可缺少的程序之一。所谓的init进程,它是一个由内核启动的用户级进程。内核会在过去曾使用过init的几个地方查找它,它的正确位置(对Linux系统来说)是/sbin/init。如果内核找不到init,它就会试着运行/bin/sh,如果运行失败,系统的启动也会失败。内核自行启动(已经被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后,就通过启动一个用户级程序init的方式,完成引导进程。所以init始终是第一个进程(其进程编号始终为1)。 展开全文
init是Linux系统操作中不可缺少的程序之一。所谓的init进程,它是一个由内核启动的用户级进程。内核会在过去曾使用过init的几个地方查找它,它的正确位置(对Linux系统来说)是/sbin/init。如果内核找不到init,它就会试着运行/bin/sh,如果运行失败,系统的启动也会失败。内核自行启动(已经被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后,就通过启动一个用户级程序init的方式,完成引导进程。所以init始终是第一个进程(其进程编号始终为1)。
信息
外文名
init
属    于
Linux系统操作中不可缺少的程序
进    程
一个由内核启动的用户级进程
正确位置
是/sbin/init
init运行级别
那么,到底什么是运行级呢?简单的说,运行级就是操作系统当前正在运行的功能级别。这个级别从1到6 ,具有不同的功能。不同的运行级定义如下:(可以参考Red Hat Linux 里面的/etc/inittab)# 0 - 停机(千万不能把initdefault 设置为0 )# 1 - 单用户模式# 2 - 多用户,没有 NFS# 3 - 完全多用户模式(标准的运行级)# 4 - 没有用到# 5 - X11 (xwindow)# 6 - 重新启动 (千万不要把initdefault 设置为6 )这些级别在/etc/inittab 文件里指定。这个文件是init 程序寻找的主要文件,最先运行的服务是放在/etc/rc.d 目录下的文件。在大多数的Linux 发行版本中,启动脚本都是位于 /etc/rc.d/init.d中的。这些脚本被用ln 命令连接到 /etc/rc.d/rcn.d 目录。(这里的n 就是运行级0-6)
收起全文
精华内容
下载资源
问答
  • init
    千次阅读
    2022-03-28 09:20:26

    Kubernetes Init Container

    在很多场景中,应用在启动之前都需要进行如下初始化操作。

    • 等待其他关联组件正确运行(例如数据库或某个后台服务)。
    • 基于环境变量或配置模板生成配置文件。
    • 从远程数据库获取本地所需配置,或者自身注册到某个中央数据中。
    • 下载某个依赖包,或者对系统进行一些预配置操作。

    Kubernetes v1.3 引入了一个 Alpha 版本的新特新 init container(在 Kubernetes v1.5 时被更新为 Beta 版本),用于在启动应用容器(app container)之前启动一个或多个 “初始化” 容器,完成应用容器所需的预置条件。Init container 与应用容器本质上一样的,但它们是仅运行一次就结束的任务,而且必须在成功执行完成之后,系统才会继续执行下一个容器。根据 Pod 的重启策略(RestartPolicy),当 init container 执行失败,在设置了 RestartPolicy=Never 时,Pod 将会启动失败;而设置 RestartPolicy=Always 时,Pod 将会被启动自动重启。

    v1.8 版本之后,Init Container 特性完全成熟,其定义被放入了 Pod 的 spec.initContainers。

    下面以 Nginx 应用为例,在启动 Nginx 之前,通过初始化容器 busybox 为 Nginx 创建一个 index.html 主页文件。这里为 init container 和 Nginx 设置了一个共享的 volume,以提供 Nginx 访问 init container 设置的 index.html。

    nginx-init-containers.yaml

    apiVersion: v1

    kind: Pod

    metadata:

      name: nginx

      annotations:

    spec:

      initContainers:

      - name: install

        image: busybox

        command:

        - wget

        "-O"

        "/work-dir/index.html"

        - http://kubernetes.io

        volumeMounts:

        - name: workdir

          mountPath: "/work-dir"

      containers:

      - name: nginx

        image: nginx

        ports:

        - containerPort: 80

        volumeMounts:

        - name: workdir

          mountPath: "/usr/share/nginx/html"

      dnsPolicy: Default

      volumes:

      - name: workdir

        emptyDir: {}

    创建这个 Pod:

    $ kubectl apply -f nginx-init-containers.yaml

    在运行 init container 的过程中,查看 Pod 的状态,可见 init 过程还未完成:

    $ kubectl get po nginx

    NAME      READY     STATUS     RESTARTS   AGE

    nginx     0/1       Init:0/1   0          23s

    # or

    $ kubectl get po

    NAME      READY     STATUS            RESTARTS   AGE

    nginx     0/1       PodInitializing   0          1m

    在 init container 成功执行完成之后,系统继续期待 Nginx 容器,再次查看 Pod 的状态:

    $  kubectl get po nginx

    NAME      READY     STATUS    RESTARTS   AGE

    nginx     1/1       Running   0          1m

    查看 Pod 的时间,可以看到系统首先创建并运行 init container 容器(名为 install),成功之后继续创建运行 Nginx 容器:

    $ kubectl describe po nginx

    执行过程 展开源码

    启动完成之后,进入 Nginx 容器,可以看到挂载的目录下已经有了 index.html 文件为 init container 所生产,其内容为:

    $ kubectl exec -it nginx cat /usr/share/nginx/html/index.html

    执行结果 展开源码

    init container 与应用容器的区别如下:

    (1)init container 的运行方式与应用容器不同,它们必须先于应用容器执行完成,当设置了多个 init container 时,将按顺序逐个运行,并且只有前一个 init container 运行成功之后才能运行后一个 init container。当所有 init container 都成功运行后,Kubernetes 才会初始化 Pod 的各种信息,并开始创建和运行应用容器。

    (2)在 init container 的定义中也可以设置资源限制、volume 的使用和安全策略,等等。但资源限制的设置与应用容器略有不同。

    • 如果多个 init container 都定义了资源请求/资源限制,则取最大的值作为所有 init container 的资源请求值/资源限制值。
    • Pod 的有效 (efective) 资源请求值/资源限制值取以下二者中的较大值。
      a)所有应用容器的资源请求值/资源限制值之和。
      b)init container 的有效资源请求值/资源限制值。
    • 调度算法将基于 Pod 的有效资源请求值/资源限制值进行计算,也就是说 init container 可以为初始化操作预留系统资源,及时后续应用容器无须使用这些资源。
    • Pod 的有效 QoS 等级适用于 init container 和应用容器。
    • 资源配额和限制将根据 Pod 的有效资源请求/限制,与调度机制一致。
    • init container 不能设置 readinessProbe 探针,因为必须在它们成功运行后才能继续运行 Pod 中定义的普通容器。

    在 Pod 重新启动(Restart)时,init container 将会重新运行,场景的 Pod 重启场景如下:

    • init container 的镜像被更新时,init container 将会重新运行,导致 Pod 重启。仅更新应用容器的进行只会应用容器被重启。
    • Pod 的 infrastructure 容器(pause)更新时,Pod 将会重启。
    • 若 Pod 中的所有应用容器都停止了,并且 RestartPolicy=Always,则 Pod 将会重启。
    更多相关内容
  • 安卓11 init初始化以及init.rc的解析执行过程详解

    千次阅读 热门讨论 2021-01-07 16:12:47
    最近做了一个高通平台安卓的需求,功能使得data分区在第一次启动时,自动适配emmc/ufs的实际大小,在此过程中对init的执行以及.rc文件的解析流程有了一些理解,趁热打铁!!在这里总结一下!!! 这里以mtk平台为例...

            最近做了一个高通平台安卓的需求,功能使得data分区在第一次启动时,自动适配emmc/ufs的实际大小,在此过程中对init的执行以及.rc文件的解析流程有了一些理解,但是对于一些细节的东西还不清楚,在这里提出几个自己疑惑的关键问题,趁热打铁!!梳理并寻找答案!!!

    这里以高通平台为例,基于最新的安卓11,init这块的代码mtk与高通基本是一模一样的(差异很小),都是中间层的东西;

    1,init进程在第二初始阶段如何加载init.rc,在整个工程中有几个地方存放rc文件,存放有什么规律??

    2,根据目前的理解,rc文件中的cmd最终映射到了对应的函数执行,如下图,他们是什么时候被调用的?? 例如:rc文件中cmd  mount_all  最终会映射执行do_mount_all()函数;

    3,*.rc文件加载的时候就伴随执行吗,还是是分开的??

    4,目前看分区并不是一次性挂载的,分区分了几次挂载,这几次区分有什么规律??

            对于init在整个系统中(宏观)的执行流程想必大家都很清楚了,init进程是linux内核启动后创建的第一个进程,地位非常重要,init进程在初始化过程中会启动很多重要的守护进程,因此理解init进程的启动过程可以使我们更好的理解安卓系统,init本身也是一个守护进程,linux内核加载完毕后,会首先启动init进程,启动过程中会解析linux配置脚本init.rc文件,根据init.rc文件的内容,init进程会装载Android的文件系统,创建系统目录,初始化属性系统,启动android系统重要的守护进程, 这些进程包括USB守护进程,adb守护进程,vold守护进程,rild守护进程等;

            最后init进程也会作为守护进程来执行修改属性请求,重启崩溃的进程操作;

    init进程初始化过程

            init进程的源码位于目录system/core/init 下,程序的入口函数main()位于文件main.cpp中;

    main函数的流程;

    int main(int argc, char** argv) {
    #if __has_feature(address_sanitizer)
        __asan_set_error_report_callback(AsanReportCallback);
    #endif
    
        if (!strcmp(basename(argv[0]), "ueventd")) {
            return ueventd_main(argc, argv);//uevent的初始化
        }
    
        if (argc > 1) {
            if (!strcmp(argv[1], "subcontext")) {
                android::base::Initialling(argv, &android::base::KernelLogger);
                const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();//命令映射表
                
                return SubcontextMain(argc, argv, &function_map);
            }
    
            if (!strcmp(argv[1], "selinux_setup")) {//selinux的初始化设置
                return SetupSelinux(argv);
            }
    
            if (!strcmp(argv[1], "second_stage")) {
                return SecondStageMain(argc, argv); //init的第二阶段初始化
            }
        }
    
        return FirstStageMain(argc, argv); //init的第一阶段初始化
    }
    system/core/init/main.cpp

    main函数一开始,首先初始化了守护进程uevent,然后紧接着的就是init的初始化过程;

    init引导序列分为三个主要阶段:1 first_stage_init;

                                                        2 selinux_setup;(可选)

                                                        3 second_stage_init;

    其中 first_stage_init负责设置最低限度的基本需求用以加载系统其余部分,具体来说,包括“/dev”,“/proc”的挂载,挂载“early mount”分区(这包括所有包含系统代码的分区,例如system和vendor)对于有ramdisk的设备,将system.img挂载到“/”;

    一旦first_stage_init完成接着执行 execs /system/bin/init 并以“selinux_setup”作为参数,在这个阶段,SELinux可选地编译并加载到系统中,这个阶段主要是加载初始化selinux相关的东西,关于此更多的细节在Selinux.cpp中;

    最后,一旦该阶段结束,它将再次使用"second_stage"参数执行/system/bin/init。此时,init的主要阶段将运行并通过init继续引导init.rc脚本

    由此我们可以知道rc文件的解析执行均在init的第二阶段,因此我们需要重点关注init初始化的第二阶段

    second_stage_init;

    ...

    初始化属性系统;

    初始化信号;

    LoadBootScripts(am, sm); //加载*.rc文件

    进入while(1)循环,监听处理到达的事件;

    init.rc语言

    这部分将介绍init.rc文件的格式这是理解解析过程的根本;

    Android Init语言由5大类语句组成:
    Actions, Commands, Services, Options, 和 Imports.

    以下是对各个语句的简单解释

    actions

    actions其实就是以序列的commands的集合,每个actions都有一个trigger,它用于决定action的执行时机,当一个符合action触发条件的事件发生了,此action会加入到执行队列的末尾,除非它已经在队列里;

    每一个action都将以此从队列中取出,此action的每个command都将依次执行,在这些命令执行时init还同时处理这其他活动(设备节点的创建和销毁,设置属性,重启进程);

    services:

    services是一个后台程序,在init中启动,如果退出了可以由系统重启(可选);

    options

    options是services的修饰项,它们决定一个services何时以及如何运行;

    triggers

    Triggers是一个用于匹配某种事件类型的字符串,它将使对应的actions执行;

    触发器分为事件触发器和属性触发器。

    事件触发器
    是由'trigger'命令或init可执行文件中的QueueEventTrigger()函数触发的字符串。它们采用简单字符串的形式,如'boot'或'late-init'。

    属性触发器
    是指指定属性将值更改为给定的新值或指定属性将值更改为任何新值时触发的字符串。它们分别采用'property:='和'property:=\*'的形式。属性触发器将在init的初始引导阶段额外计算并相应地触发。

    一个操作可以有多个属性触发器,但可能只有一个事件触发器。

    commands

    command是actions的命令列表中的命令,或者是service的选项参数命令;

    import

    一般用作 “import <path>”,扩展当前配置。如果path是一个目录,该目录中的每个文件都被解析为一个配置文件。它不是递归的,嵌套的目录将不会被解析。

    import关键字不是命令,而是它自己的部分,这意味着它不是作为action的一部分发生的,而是在解析文件时处理导入;

    第一级安装设备的实际顺序是:
        1. 解析/init.rc,然后递归地解析它的每个导入(此处递归是import的递归,不是文件夹的递归,文件夹不支持递归);
        2. /system/etc/init/的内容按字母顺序排列并按顺序解析,在每个文件解析后递归地进行导入;
        3. 步骤2重复/vendor/etc/init,然后是/odm/etc/init;

    -----------------

    /init.rc是主要的.rc文件,由init可执行文件在开始执行时加载。它负责系统的初始设置。

    在加载主目录/init.rc后,init立即加载包含在/{system,vendor,odm}/etc/init/目录中的所有文件。

    rc文件的存放目录以及目的:
        1 /system/etc/init/  用于核心系统项,例如 SurfaceFlinger, MediaService和logd。
        2 /vendor/etc/init/  是针对SoC供应商的项目,如SoC核心功能所需的actions或守护进程。
        3 /odm/etc/init/      用于设备制造商的项目,如actions或运动传感器或其他外围功能所需的守护进程。

    以下是个人认为理解init.rc脚本重要的几点内容:

    1,init.rc文件是以section为单位组织的,一个section可以包含多行,section可以分为两大类,一类是action,另一类是service;action以关键字on开始,表示一组命令的集合,service以关键字service开始,表示启动某个进程的方式和参数;

    2,section以on或service开始,直到下一个on或者service结束,中间的所有行都属于这一个section(空行或者注释不具有分割作用),截取init.rc部分如下

    on property:apexd.status=ready && property:ro.product.cpu.abilist32=*
        exec_start boringssl_self_test_apex32
    on property:apexd.status=ready && property:ro.product.cpu.abilist64=*
        exec_start boringssl_self_test_apex64
    
    service boringssl_self_test32 /system/bin/boringssl_self_test32
        setenv BORINGSSL_SELF_TEST_CREATE_FLAG true # Any nonempty value counts as true
        reboot_on_failure reboot,boringssl-self-check-failed
        stdio_to_kmsg

    根据1,2可以得出,上图有3个section,其中两个是action,一个是service;

    3,无论是action还是service,并不是按照文件的编排顺序执行的,他们只是一份定义,至于执行与否以及什么时候执行取决于init在运行时的操作.
     

    脚本文件的解析过程:

    init进程启动时最重要的工作就是解析并执行启动文件init.rc,本节介绍init进程解析脚本文件的流程;

    这里我们主要以解析action为例讲解,service的解析过程同理,首先讲解基本流程,然后列举实例进行解析分析;

    init.rc文件以及*.rc文件加载是从下面这个函数开始的:

    static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
        Parser parser = CreateParser(action_manager, service_list);
    
        std::string bootscript = GetProperty("ro.boot.init_rc", "");
        if (bootscript.empty()) {
            parser.ParseConfig("/system/etc/init/hw/init.rc");
            if (!parser.ParseConfig("/system/etc/init")) {
                late_import_paths.emplace_back("/system/etc/init");
            }
            // late_import is available only in Q and earlier release. As we don't
            // have system_ext in those versions, skip late_import for system_ext.
            parser.ParseConfig("/system_ext/etc/init");
            if (!parser.ParseConfig("/product/etc/init")) {
                late_import_paths.emplace_back("/product/etc/init");
            }
            if (!parser.ParseConfig("/odm/etc/init")) {
                late_import_paths.emplace_back("/odm/etc/init");
            }
            if (!parser.ParseConfig("/vendor/etc/init")) {
                late_import_paths.emplace_back("/vendor/etc/init");
            }
        } else {
            parser.ParseConfig(bootscript);
        }
    }

    以下是主要的解析流程:

    ParseConfig 函数传入需要解析的rc文件的路径,如果是目录,则遍历该目录取出所有的 rc 文件并调用 ParseConfigFile 函数进行解析,如果是文件路径,则直接调用 ParseConfigFile 函数进行解析。

    从代码中可以看出,init 解析 rc 文件的过程中,首先调用 ReadFile 函数将 rc 文件的内容全部保存为字符串,存在 data 中,然后调用 ParseData 进行解析。ParseData 函数会根据关键字解析出service和action,最终挂在到 service_list 与 action_manager  的向量(vector)上。

    下面分析一下 ParseData 函数,根据关键字的不同会调用不同的 parser 去解析(多态),action 使用 ActionParser,而 service 使用 ServiceParser 解析,该部分定义在LoadBootScrip()函数的第一行,parser.AddSectionParser()方法为parser的map成员section_parsers_创建了三个SectionParser,分别用来解析service,on,import的section;

    下面重点分析ParseData函数:

    next_token(&parse_state)处理数据类型使用parse_state的构体返回:

    struct parse_state
    {
        char *ptr;      // 要解析的字符串
        char *text;     // 解析到的字符串,可以理解为返回一行的数据
        int line;       // 解析到第行数
        int nexttoken;  // 解析状态,有 T_EOF T_NEWLINE T_TEXT
    };//其中 T_EOF 表示字符串解析结束,T_NEWLINE 表示解析完一行的数据,T_TEXT 表示解析到一个单词

    其中 T_EOF 表示字符串解析结束,T_NEWLINE 表示解析完一行的数据,T_TEXT 表示解析到一个单词,

    void Parser::ParseData(const std::string& filename, std::string* data) {
        data->push_back('\n');  // TODO: fix tokenizer
        data->push_back('\0');
    
        parse_state state;
        state.line = 0;
        state.ptr = data->data();
        state.nexttoken = 0;
    
        SectionParser* section_parser = nullptr;
        int section_start_line = -1;
        std::vector<std::string> args;
    
        // If we encounter a bad section start, there is no valid parser object to parse the subsequent
        // sections, so we must suppress errors until the next valid section is found.
        bool bad_section_found = false;
    
        auto end_section = [&] {//lambda类似于一个函数指针
            bad_section_found = false;
            if (section_parser == nullptr) return;
            //同样是调用相应的section的EndSection()结束该section的解析;
            if (auto result = section_parser->EndSection(); !result.ok()) {
                parse_error_count_++;
                LOG(ERROR) << filename << ": " << section_start_line << ": " << result.error();
            }
    
            section_parser = nullptr;
            section_start_line = -1;
        };
    
        for (;;) {
            switch (next_token(&state)) {
                case T_EOF:
                    end_section();
    
                    for (const auto& [section_name, section_parser] : section_parsers_) {
                        section_parser->EndFile();//解析到文件末尾,则结束
                    }
    
                    return;
                case T_NEWLINE: { // 开始处理新的一行
                    state.line++;
                    if (args.empty()) break;
                    // If we have a line matching a prefix we recognize, call its callback and unset any
                    // current section parsers.  This is meant for /sys/ and /dev/ line entries for
                    // uevent.
                /*    auto line_callback = std::find_if(
                        line_callbacks_.begin(), line_callbacks_.end(),
                        [&args](const auto& c) { return android::base::StartsWith(args[0], c.first); });
                    if (line_callback != line_callbacks_.end()) {
                        end_section();
    
                        if (auto result = line_callback->second(std::move(args)); !result.ok()) {
                            parse_error_count_++;
                            LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                        }
                */              //这部分是uevent的暂时跳过
                //section_parsers_ 是一个map,是在LoadBootScrip()第一行创建了三个键值对  "on"      ---> ActionParser
                //                                                                 "import"  ---> ImportParser
                //                                                                 "service" ---> ServiceParser
                //args[0]为某一行的第一个单词,该行由可能为section的开头,也有可能为command行,count()方法判断该行的第一个单词是不是为on/import/serivce,如果是则返回1
                    } else if (section_parsers_.count(args[0])) { 
                        end_section();//在处理新的section前,结束之前的section;
                        section_parser = section_parsers_[args[0]].get(); //依据args[0]是on/import/service取出其对应的解析方法的地址ActionParser/ImportParser/ServiceParser
                        section_start_line = state.line;
                        if (auto result =
                                    section_parser->ParseSection(std::move(args), filename, state.line); //调用相应的section解析函数(ActionParser/ImportParser/ServiceParser)解析section
                            !result.ok()) {
                            parse_error_count_++;
                            LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                            section_parser = nullptr;
                            bad_section_found = true;
                        }
                    } else if (section_parser) {//该行的第一个单词不是新的section的开头,则使用上一次的解析方法的函数ActionParser/ImportParser/ServiceParser解析其命令行
                        if (auto result = section_parser->ParseLineSection(std::move(args), state.line);//解析命令
                            !result.ok()) {
                            parse_error_count_++;
                            LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                        }
                    } else if (!bad_section_found) {
                        parse_error_count_++;
                        LOG(ERROR) << filename << ": " << state.line
                                   << ": Invalid section keyword found";
                    }
                    args.clear();
                    break;
                }
                case T_TEXT: //解析到一个单词,就把它存入args中
                    args.emplace_back(state.text);
                    break;
            }
        }
    }

    next_token()函数的作用就是寻找单词结束或者行结束标志,如果是单词结束标志就将单词push到args中,如果是行结束标志,则根据第一个单词来判断是否是一个section,section的标志只有三个"on","service","import",如果是"section",则调用相应的ParseSection()来处理一个新的section,否则把这一行继续作为前“section”所属的行来处理。

    ParseSection()被用来解析一个新的section,ParseLineSection()被用来解析该section下的命令行,依据注释内容我们可以绘制出解析的基本流程图如下:

    针对action,import,service分别定义了其对应的三个函数(不列举import了),这三个函数在解析中扮演了重要的角色;

    ActionParser::ParseSection()                               ServiceParser::ParseSection()                  ....

    ActionParser::ParseLineSection()                        ServiceParser::ParseLineSection() 

    ActionParser::EndSection()                                  ServiceParser::EndSection()

    还是以下面action为例进行说明,

    on boot
        ifup io
        start sshd

     首先next_token解析到 on boot这一行,根据args[0] 值为“on” 取出ActionParser的指针,首先调用ActionParser::ParseSection处理这个新的action;

    Result<void> ActionParser::ParseSection(std::vector<std::string>&& args,
                                            const std::string& filename, int line) {
        std::vector<std::string> triggers(args.begin() + 1, args.end());
        if (triggers.size() < 1) {
            return Error() << "Actions must have a trigger";
        }
        Subcontext* action_subcontext = nullptr;
        if (subcontext_ && subcontext_->PathMatchesSubcontext(filename)) {
            action_subcontext = subcontext_;
        }
        std::string event_trigger;
        std::map<std::string, std::string> property_triggers;
        if (auto result =
                    ParseTriggers(triggers, action_subcontext, &event_trigger, &property_triggers);
            !result.ok()) {
            return Error() << "ParseTriggers() failed: " << result.error();
        }
    #ifdef G1122717
        for (const auto& [property, _] : property_triggers) {
            action_manager_->StartWatchingProperty(property);
        }
    #endif
        auto action = std::make_unique<Action>(false, action_subcontext, filename, line, event_trigger,
                                               property_triggers);
        action_ = std::move(action);
        return {};
    }

     ActionParser::ParseSection函数首先解析trigger,以trigger作为参数(triggle分为事件触发和属性触发,都会被赋值给action对象的相应事件成员)构造一个结构action 并赋值给ActionParser的成员函数action_,之后使用ActionParser::ParseLineSection处理下一行命令,需要处理该on boot下的行命令ifup io,调用ActionParser::ParseLineSection对命令行进行处理,这个函数首先会根据ifup这个命令查询BuiltinFunctionMap表找到ifup对应的处理函数,

    Result<void> Action::AddCommand(std::vector<std::string>&& args, int line) {
        if (!function_map_) {
            return Error() << "no function map available";
        }
        auto map_result = function_map_->Find(args);
        if (!map_result.ok()) {
            return Error() << map_result.error();
        }
        commands_.emplace_back(map_result->function, map_result->run_in_subcontext, std::move(args),
                               line);
        return {};
    }

    如上图ifup对应的处理函数为do_ifup,找到该函数后,根据该函数以及其参数 “io” 构造出一个command实例,并把它push到第一步构造出的action_的成员commands_中,接着继续调用ActionParser::ParseLineSection()处理下一行命令start sshd同样最后把其push到commands_中;

    Result<void> ActionParser::EndSection() {
        if (action_ && action_->NumCommands() > 0) {
            action_manager_->AddAction(std::move(action_));
        }
        return {};
    }

    此时,ActionParser的成员函数action_ 就代表这个解析出来的action的整体,(这里可以看出action类就是对一个整个action的抽象)最后调用ActionParser::EndSection()把这个解析出来的整体的action_添加到ActionParser的单例成员(ActionManager类型)action_manager_中,ActionManager单例成员action_manager_中包含了所有解析出来的action,相当于之前使用的链表,这就是一个action的解析过程;

    执行action

    action和service解析完成之后,所有的action和service都被挂在ActionParser->action_manager_->actions_ServiceParser->service_list_->services_ 这两个向量里;向量中的每一个action和service都完整的描述了一个action和service的section,在解析完成之后SecondStageMain()函数会调用如下代码:

        am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
        am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
        am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
        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(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
        am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
        Keychords keychords;
        am.QueueBuiltinAction(
                [&epoll, &keychords](const BuiltinArguments& args) -> Result<void> {
                    for (const auto& svc : ServiceList::GetInstance()) {
                        keychords.Register(svc->keycodes());
                    }
                    keychords.Start(&epoll, HandleKeychord);
                    return {};
                },
                "KeychordInit");
    
        // Trigger all the boot actions to get us started.
        am.QueueEventTrigger("init");

    am.QueueEventTrigger("early-init");意为early-init时间已经到来,可以执行triggle只为early-init的action了,QueueEventTrigger()函数只把triggle加入到ActionManager的event_queue_中,对于 am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups") 该函数在创建action的同时,把该事件触发添加到ActionManager的事件队列中,后续会遍历ActionManager的event_queue_找到的triggle对应的action的command会被依次执行,过程如下:

    while (true) {
            // By default, sleep until something happens.
            // 决定timeout的时间
            auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
    
            auto shutdown_command = shutdown_state.CheckShutdown();
            // 判断是否执行了关机
            if (shutdown_command) {
                HandlePowerctlMessage(*shutdown_command);
            }
            //判断是否有事件需要处理
            if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
                am.ExecuteOneCommand();
            }
            if (!IsShuttingDown()) {
                auto next_process_action_time = HandleProcessActions();
    
                // If there's a process that needs restarting, wake up in time for that.
                if (next_process_action_time) {
                    epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
                            *next_process_action_time - boot_clock::now());
                    if (*epoll_timeout < 0ms) epoll_timeout = 0ms;
                }
            }
    
            if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
                // If there's more work to do, wake up again immediately.
                if (am.HasMoreCommands()) epoll_timeout = 0ms;
            }
    
            auto pending_functions = epoll.Wait(epoll_timeout);
            if (!pending_functions.ok()) {
                LOG(ERROR) << pending_functions.error();
            } else if (!pending_functions->empty()) {
                // We always reap children before responding to the other pending functions. This is to
                // prevent a race where other daemons see that a service has exited and ask init to
                // start it again via ctl.start before init has reaped it.
                ReapAnyOutstandingChildren();
                for (const auto& function : *pending_functions) {
                    (*function)();
                }
            }
            if (!IsShuttingDown()) {
                HandleControlMessages();
                SetUsbController();
            }
        }
    
        return 0;
    }

    init最终会进入了无限循环的监听状态,可以看到这里面一个核心函数就是 am.ExecuteOneCommand();该函数具体如下:

    void ActionManager::ExecuteOneCommand() {
        {
            auto lock = std::lock_guard{event_queue_lock_};
            // 编译event_queue_ 队列直到有事件处理
            while (current_executing_actions_.empty() && !event_queue_.empty()) {
                //遍历action的向量(链表),包括所有解析出来的action,每一个action都包含了完整的信息(command,triggle等)
                for (const auto& action : actions_) {
                // 一个action是否要执行,事件trigger和属性trigger都必须要满足,这里检查event_queue_的第一个元素的属性事件是不是满足,会从属性map表里查找其值,如果满足才会执行下一步
                    if (std::visit([&action](const auto& event) { return action->CheckEvent(event); },
                                   event_queue_.front())) {
                        //如果满足这证明该action需要执行,把action压入current_executing_actions_当前执行队列中;
                        current_executing_actions_.emplace(action.get());
                    }
                }
                event_queue_.pop();
            }
        }
    
        if (current_executing_actions_.empty()) {
            return;
        }
        // 从当前需要执行的action队列中取出第一个要执行的action
        auto action = current_executing_actions_.front();
    
        if (current_command_ == 0) {
            std::string trigger_name = action->BuildTriggersString();
            LOG(INFO) << "processing action (" << trigger_name << ") from (" << action->filename()
                      << ":" << action->line() << ")";
        }
        // 开始执行action
        action->ExecuteOneCommand(current_command_);
    
        // 如果这是当前需要执行的action的最后一个命令,则从current_executing_actions_队列中移除该action
        // 如果这个action只执行依次,则从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),
                               actions_.end());
            }
        }
    }

    在上一步中,QueueEventTrigger("early-init")把early-init加入到event_queue_的队列中,ExecuteOneCommand()一开始就遍历之前解析的action向量表,使用每一个action自己的eventtrigger和event_queue_队列中的第一个trigger对比,如果一样,则继续判断该action中的PropertyTriggers,遍历PropertyTriggers的map表查找当前action的PropertyTriggers值是否满足条件,如果在eventtrigger相同且PropertyTriggers满足条件的情况下,就把当前action push到current_executing_actions_队列中;

    接下来,从current_executing_actions_队列中取出第一个要执行的action,调用action->ExecuteOneCommand(current_command_); 执行该命令,如下代码

    Result<void> Command::InvokeFunc(Subcontext* subcontext) const {
        if (subcontext) {
            if (execute_in_subcontext_) {
                return subcontext->Execute(args_);
            }
    
            auto expanded_args = subcontext->ExpandArgs(args_);
            if (!expanded_args.ok()) {
                return expanded_args.error();
            }
            return RunBuiltinFunction(func_, *expanded_args, subcontext->context());
        }
    
        return RunBuiltinFunction(func_, args_, kInitContext);
    }

    该action下的所有命令被执行完成,;

    当一个 action 对象所有的 command 均执行完毕后,再执行下一个action。

    当一个 trigger 触发对应的所有 action 对象均执行完毕后(一个action有且仅有一个eventtrigger,但是一个eventtrigger可以对应多个action,他们可以由不同的属性),再执行下一个 trigger 对应 action。

    到此就是一个action被解析以及到被执行的整个过程!!因为service和action存在自有的特性,因此解析执行稍有不同,需要理解的可以自行分析;

    文章一开始的四个问题,前三个都在解析分析的过程中解开了;

    关于分区挂载的后续碰到再另行补充吧!

    展开全文
  • Android-深入理解init

    千次阅读 2022-03-07 12:10:16
    文章目录initinit介绍学习路线init分析解析配置文件 init init介绍 init是一个进程,它是Linux系统中用户空间的第一个进程,因为Android是基于Linux内核的,所以init也是Android系统中用户空间的第一个进程,它的...

    1. init

    1.1 init介绍

      init是一个进程,它是Linux系统中用户空间的第一个进程,因为Android是基于Linux内核的,所以init也是Android系统中用户空间的第一个进程,它的进程号是1,本节关注几个重要的职责:
      1.init进程负责创建系统中的几个关键进程,尤其是zygote(后面会讲)
      2.Android系统有很多属性,于是init就提供了一个property service(属性服务)来管理他们,那么这个属性服务是怎么工作的呢?

    1.2 学习路线

      1.init如何创建zygote
      2.init的属性服务是如何工作

    1.3 init分析

    init进程的入口函数main,代码如下:

    int main(int argc, char **argv)
    {
    	int device_fd = -1;
    	int property_set_fd = -1;
    	int signal_recv_fd = -1;
    	int keychord_fd = -1;
    	int fd_count;
    	int s[2];
    	int fd;
    	struct sigaction act;
    	char tmp[PROP_VALUE_MAX];
    	struct pollfd ufds[4];
    	char *tmpdec;
    	char* debuggable;
    
    	//设置进程退出的信号处理函数,该函数为sigchld_handler
    	act.sa_handler = sigchld_handler;
    	act.sa_flags = SA_NOCLDSTOP;
    	act.sa_mask = 0;
    	act.sa_restorer = NULL;
    	sigaction(SIGCHLD, &act, 0);
    
    	//...
    	//创建一些文件夹,并挂载设备,这些是与Linux相关的
    	mkdir("/dec/socker", 0755);
    	mount("devpts", "/dev/pts", "devpts", 0, NULL);
    	mount("proc", "/proc", "proc", 0, NULL);
    	mount("sysfs", "/sys", "sysfs", 0, NULL);
    
    	//重定向标准输入/输出/错误输出到/dev/_null_
    	open_devnull_stdio();
    
    	//设置init的日志输出设备为/dev/_kmsg_,不过该文件打开后,会立即被unlink了
    	//这样,其他进程就无法打开这个文件读取日志信息了
    	log_init();
    
    	//解析init.rc配置文件
    	parse_config_file("/init.cr");
    
    	//.....
    	//下面这个函数通过读取/proc/cpuinfo得到机器的Hardware名
    	get_hardware_name();
    	snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
    	//解析这个和机器相关的配置文件 这里对应的文件为init.bravo.rc
    	parse_config_file(tmp);
    
    	/*
    	 * 解析完上述两个配置文件后,会得到一系列的Action(动作),下面两句
    	 * 代码将执行那些处于early-init阶段的Action. init将动作执行的时间划
    	 * 分为四个阶段,所以就有了先后之分。那些动作属于哪个阶段由配置文件
    	 * 决定,后面会介绍配置文件的相关知识。
    	 *
    	 * */
    
    	action_for_each_trigger("early-init", action_add_queue_tail);
    	drain_action_queue();
    
    	/* 创建利用Uevent与Linux内核交互的socket,关于Uevent的知识,第九章
    	 * 中对Vold进行分析时再说
    	 * /
    
    	 device_fd = device_init();
    	 //初始化和属性相关的资源
    	 property_init();
    	 //初始化/dev/keychord设备,这与调试有关
    	 keychord_fd = open_keychord();
    
    	 /*
    	  * INIT_IMAGE_FILE定义为"/initlogo.rle", 下面这个函数将加载这个文
    	  * 件所谓系统的开机画面,注意,他不是开机动画控制程序 bootanimation
    	  * 加载的开机动画文件
    	  * */
    	if(load_565rle_image(INIT_IMAGE_FILE))
    	{
    		/*
    		 * 如果加载initlogo.rle文件失败(可能是没有这个文件),则会
    		 * 打开/dev/ty0设备,并输出"ANDROID"的字样作为开机画面,在模
    		 * 拟器上看到的开机画面就是它
    		 * */
    		//.....
    	}
    
    	if(qemu[0])
    		import_kernel_cmdline(1);
    
    	//调用property_set函数设置属性项,一个属性项包括属性名和属性值
    	property_set("ro.bootloader", bootloader[0] ? bootloader : "unknown");
    
    	//执行位于init阶段的动作
    	action_for_each_trigger("init", action_add_queue_tail);
    	drain_action_queue();
    
    	//启动属性服务
    	property_set_fd = start_property_service();
    
    	/*
    	 * 调用sockerpair函数创建两个已经connect好的socker,socketpair是Linux
    	 * 的系统调用,man socketpair可以查到
    	 * */
    	if(sockerpair(AF_UNIX, SOCK_STREAM, 0, s) == 0)
    	{
    		signal_fd = s[0];
    		signal_recv_fd = s[1];
    		//.....
    	}
    
    	//.......
    	//执行配置文件中early-boot和boot阶段的动作
    	action_for_each_trigger("early-boot", action_add_queue_tail);
    	action_for_each_trigger("boot", action_add_queue_tail);
    	drain_action_queue();
    	//......
    	//init 关注来自四个方面的事情
    
    	ufds[0].fd = device_fd;//device_fd用于监听来自内核的Uevent事件
    	ufds[0].events = POLLIN;
    	ufds[1].fd = property_set_fd;//property_set_fd用于监听来自属性服务器的事件
    	ufds[1].events = POLLIN;
    	//signal_recv_fd 由socketpair创建,它的事件来自另一个socket
    	ufds[2].fd = signal_recv_fd;
    	ufds[2].events = POLLIN;
    	fd_count = 3;
    	if(keychord_fd > 0)
    	{
    		//如果keychord设备初始化成功,则init也会关注来自这个设备的事件
    		ufds[3].fd = keychord_fd;
    		ufds[3].events = POLLIN;
    		fd_count++;
    	}
    
    	//......
    #if BOOTCHART
    	//与Boot char相关
    	/*
    	 * Boot chart是一个小工具,它能对系统的性能进行分析,并生成系统启动过程
    	 * 的图表,以提供一些有价值的信息,而这些信息最大的用处就是帮助提升系统
    	 * 的启动速度
    	 * */
    
    #endif
    	for(;;)
    	{
    		//从此init将进入一个无限循环
    		int nr, i, timeout = -1;
    
    		for(i = 0; i < fd_count; i++)
    			ufds[i].revents = 0;
    
    		//在循环中执行动作
    		drain_action_queue();
    		restart_processes();// 重启那些已经死去的进程
    
    		//......
    #if BOOTCHART
    		//Boot Chart相关
    #endif
    		//调用poll等待一些事情的发生
    		nr = poll(ufds, fd_count, timeout);
    
    		//ufds[2]保存的是signal_recv_fd,用于接受来自socket的消息
    		if(ufds[2].revents == POLLIN)
    		{
    			//有一个子进程去世,init要处理这个事情
    			read(signal_recv_fd, tmp, sizeof(tmp));
    			while(!wait_for_one_process(0))
    				;
    			continue;
    		}
    
    		if(ufds[0].revents == POLLIN)
    			handle_device_fd(device_fd);//处理Uevent事件
    		if(ufds[1].revents == POLLIN)
    			handle_property_set_fd(property_set_fd);//处理属性服务的事件
    		if(ufds[3].revents == POLLIN)
    			handle_keychord(keychord_fd);//处理keychord事件
    	}
    
    	return 0;
    }
    

    根据本章学习内容,可以把init的工作流程分为以下四点:
      1.解析两个配置文件,我们将分析其中对init.rc文件的解析
      2.执行各个阶段的动作,创建zygote的工作就是其中的某个阶段完成的
      3.调用property_init初始化属性相关的资源,并且通过property_start_service启动属性服务
      4.init进入一个无限循环,并且等待一些事情的发生,重点关注init如何处理来自socket和来自属性服务器的相关事情。

    1.3.1 解析配置文件

      在init中会解析两个配置文件,其中一个是系统配置文件init.rc,另一个是与硬件平台相关的配置文件,以HTC G7手机为例,这个配置文件为init.bravo.rc,其中bravo是硬件平台的名称,对于这两个配置文件进行解析,调用的是同一个parse_config_file函数。

    int parse_config_file(const char* fn)
    {
    	char* data;
    	data = read_file(fn, 0);//读取配置文件的内容,这个文件时init.rc
    	if(!data) return -1;
    	parse_config(fn, data);//调用parse_config做到真正的解析
    	return 0;
    }
    

      读取完文件的内容后,将调用parse_config进行解析,这个函数的代码如下:
    在这里插入图片描述
    在这里插入图片描述
      上面就是parse_config函数,这个函数首先会找到配置文件的一个section,然后针对不同的section使用不同的解析函数去解析,section和init.rc文件的组织结构有关。

    1.关键字定义
    keyword.h的定义
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    keywords.h做了两件事:
      1.第一次包含keywords.h时,声明了一些诸如do_class_start的函数,另外还定义了一个枚举,枚举值为K_class, K_mkdir等关键字
      2.第二次包含keywords.h后,得到了一个keyword_info结构体数组,这个keyword_info结构体数组以前面定义的枚举值为索引,存储对应的关键字信息,这些信息包括关键字名称,处理函数,处理函数的参数个数,以及属性。
      目前,关键字信息中最重要的就是symbol和flags了,什么样的关键字被认为是section呢,根据keywords.h的定义,当symbol为service的时候表示section:

    KEYWORD (on, SECTION, 0, 0)
    KEYWORD (service, SECTION, 0, 0)
    

    2.解析init.rc
    在这里插入图片描述
    分析可知:
      1.一个section的内容从这个标识section的关键字开始,到下一个标识section的地方结束
      2.init.rc中出现了名为boot和init的section,这里的boot和init就是前面介绍的4个动作执行阶段中的boot和init,也就是说,在boot阶段执行的动作都是由boot这个section定义的。

    1.3.2 解析service

    解析service的入口函数是parse_new_section,代码如下:
    在这里插入图片描述
      其中,解析service时,用到了parse_service和parse_line_service这两个函数,先看init怎么组织service的

    1.service结构体
      init中使用了一个叫service的结构体来保存与service section相关的信息,代码如下:

    在这里插入图片描述
    service中使用的这个action结构体
    在这里插入图片描述
      parse_line_service 将根据配置文件的内容填充service结构体,那么,zygote解析完后会得到什么呢?

    在这里插入图片描述
    图中可知:
      1.service_list链表将解析后的service全部链接到一起,并且是一个双向链表,前向节点用prev表示,后向节点用next表示
      2.socketinfo也是一个双向链表,因为zygote只有一个socket,所以画了一个虚框socker作为链表的示范
      3.onrestart通过commands指向了一个commands链表,zygote有三个commands

    1.3.3 init控制service

    1.启动zygote
    init.rc中有这样一句话

    #class_start是一个COMMAND,对应的函数为do_class_start,很重要,切记
    class_start default
    

      class_start标识一个COMMAND,对应的处理函数为do_class_start,它位于bootsection的范围内,当init进程执行到下面几句话时,do_class_start就会被执行了。

    //将boot section节的command加入到执行队列
    action_for_each_trigger("boot", action_add_queue_tail);
    //执行队列里的命令,class是一个COMMAND,所以它对应的do_class_start会被执行。
    drain_action_queue();
    

    2.重启zygote

      onrestart是在zygote重启时使用的,zygote死后,父进程init的动作:

    static void sigchld_handler(int s)
    {
    	//当子进程退出时,init的这个信号处理函数会被调用
    	write(signal_fd, &s, 1);//往signal_fd中写数据
    }
    

      signal_fd就是在init中通过socketpair创建的两个socket中的一个,既然会往这个signal_fd中发送数据,那么另一个socket就一定能接受到,这样就会导致init从poll函数中返回

    1.3.4 属性服务

      Windows平台上面有一个叫做注册表的东西,注册表可以存储一些类型key/value的键值对,一般而言,系统或某些引用程序会把自己的一些属性存储在注册表中,即使系统重启或应用程序重启,它还能够根据之前在注册表中设置的属性进行相应的初始化工作,Android平台也提供了一个类似的机制,称为属性服务,应用程序可以通过这个属性机制,查询或设置属性,读者可以用adb shell登录到真机或模拟器上,然后用getprop命令查看当前系统中有哪些属性。

      其中与init.c和属性服务有关的代码有下面两行:

    proprety_init();
    property_set_fd = start_property_service();
    

    1.属性服务初始化
      创建存储空间,proprety_init函数代码如下:

    void property_init(void)
    {
    	init_property_area();//初始化属性存储区域
    	//加载default.prop文件
    	load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
    }
    

      在properyty_init函数中,先调用init_property_area函数,创建一块用于存储属性的存储区域,然后加载default.prop文件中的内容,再看init_property_area是如何工作的,代码如下:
    在这里插入图片描述
    在这里插入图片描述
      最后的赋值语句,_system_property_area_是bionic libc库中输出的一个变量,为啥要给他赋值?
      因为虽然属性区域是由init进程创建的,但Android系统希望其他进程也能读取这块内存里的东西,为了做到这一点,他便做了以下两项工作:
      1.把属性区域创建在共享内存上,而共享内存是可以跨进程的,这一点,已经在上面的代码中可以看见了,init_workspace函数内部将创建这个共享内存。
      2.如何让别人知道这个共享内存呢?Android利用了gcc的constructor属性,这个属性指明了一个_libc_prenit函数,当bionic lib库被加载时,将自动调用这个_libc_prenit,这个函数内部就将完成共享内存到本地进程的映射工作。

    客户端进程获取存储空间

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

      上面代码的很多地方都和共享内存有关,后面讲,通过这种方式,客户端进程可以直接读取属性空间,但没有权限设置属性,客户端进程要如何设置属性呢?

    2.属性服务器的分析
    启动属性服务器
      init进程会启动一个属性服务器,而客户端之只能通过与属性服务器交互来设置属性,先来看属性服务器的内容,它由start_property_service函数启动。
    在这里插入图片描述
      属性服务创建了一个用来接受请求的socket,可这个请求在哪里被处理呢,事实上,在init中的for循环处已经进行了相关处理了。

    处理设置属性请求
      接受请求的地方在init进程中,代码如下:

    if (ufds[1].revents == POLLIN)
    	handle_property_set_fd(property_set_fd);
    

      当属性服务器收到了客户端请求时,init会调用handle_property_set_fd进行处理,代码如下:
    在这里插入图片描述
    在这里插入图片描述
      当客户端的权限满足要求时,init就调用property_set进行相关处理,代码如下:
    在这里插入图片描述
    在这里插入图片描述
    客户端发送请求
      客户端通过property_set发送请求,property_set由libcutils库提供的。
    在这里插入图片描述
    在这里插入图片描述

    1.4 总结

      介绍了init进程如何解析zygote,以及属性服务器的工作原理,init同时还涉及很多Linux系统相关的知识。

    展开全文
  • init命令详解

    千次阅读 2021-03-12 20:52:03
    #init 0 - 停机(千万不能把initdefault 设置为0 ) #init 1 - 单用户模式 #init 2 - 多用户,没有 NFS 不联网 #init 3 - 完全多用户模式(标准的运行级) #init 4 - 没有用到 #init 5 - X11 (xwindow) ...

    1.手动输入命令会执行相关操作

      #init 0 - 停机(千万不能把initdefault 设置为0 )

      #init 1 - 单用户模式

      #init 2 - 多用户,没有 NFS 不联网

      #init 3 - 完全多用户模式(标准的运行级)

      #init 4 - 没有用到

      #init 5 - X11 (xwindow) 图形化界面模式

      #init 6 - 重新启动 (千万不要把initdefault 设置为6 )
    12345678910111213

    2.用法:init命令很简单。直接输入init + 你想要的模式  回车就行。
    比如输入:  init 0  就是关机
                    init 3  就是切换到多用户-命令行模式
                    init 5  就是切换到图形化界面
                    init 6  就是重启

    3.开机默认模式设置方式
    当我们已界面模式退出后,再次开机进入时,还是界面模式,我们想不管我们以什么模式退出,在每次开机进入时默认是命令行模式,该如何设置呢?
    方法如下:在CentOS系统下有个跟文件目录:/etc/inittab

    1.  # inittab       This file describes how the INIT process should set up   
    2.  #               the system in a certain run-level.   
    3.  #   
    4.  # Author:       Miquel van Smoorenburg, <miquels@drinkel.nl.mugnet.org>   
    5.  #               Modified for RHS Linux by Marc Ewing and Donnie Barnes   
    6.  #  
    7.  # Default runlevel. The runlevels used by RHS are:   
    8.  #   0 - halt (Do NOT set initdefault to this)   
    9.  #   1 - Single user mode   
    10. #   2 - Multiuser, without NFS (The same as 3, if you do not have networking)   
    11. #   3 - Full multiuser mode   
    12. #   4 - unused   
    13. #   5 - X11   
    14. #   6 - reboot (Do NOT set initdefault to this)   
    15. #    
    16. id:3:initdefault:  12345678910111213141516

    看第16行的代码,这里可以设置默认启动模式,如果设置为 id:3:initdefault:  这代表默认启动为命令行模式。如果设置为 id:5:initdefault:  这代表默认戚东卫图形化界面模式。

    注意:万万不能把默认设为0或者6。因为0代表关机,6代表重启。如果设置为这两个中的其中一个,就会反复地关机和重启。切记!

    说明:#代表单行注释

    1.init初探

         init是Linux系统操作中不可缺少的程序之一。init进程,它是一个由内核启动的用户级进程,然后由它来启动后面的任务,包括多用户环境,网络等。

         内核会在过去曾使用过init的几个地方查找它,它的正确位置(对Linux系统来说)是/sbin/init。如果内核找不到init,它就会试着运行/bin/sh,如果运行失败,系统的启动也会失败。

         根据实际看一下,我们来到/boot下,

     

         可以看见,Grub就在这里,vmlinuz-2.6.32-358.el6.i686这个文件很重要,说通俗一点的话应该是我们的内核,在启动后加载所有驱动(这个有另外的章节再讲,这里不详述)。另外initramfs-2.6.32-358.el6.i686.img是启动init进程的一个镜像文件,包含一个最小的linux系统。

         Linux内核在初始化之后会执行init进程,而init进程会挂载我们的根文件系统,但由于init程序也是在根文件系统上的,所以这就有了悖论。Linux采用两步走的方法来解决这个问题。Linux2.6版以前的方法是:除了内核vmlinuz之外还有一个独立的initrd.img映像文件,其实它就是一个文件系统映像,linux内核在初始化后会mount initrd.img作为一个临时的根文件系统,而init进程就是在initrd.img里的,然后init进程会挂载真正的根文件系统,然后umount initrd.img。但Linux2.6内核的实现方式却不太一样,虽然完成的功能是一样的。Linux2.6采用initramfs。initramfs:init ram filesystem,它是一个cpio格式的内存文件系统,其实这部分也有很多内容可以讲,我们以后再谈。

      通常的步骤是先启动内核,然后内核挂载initrd.img,并执行里面的脚本来进一步挂载各种各样的模块,然后发现真正的root分区,挂载并执行/sbin/init。

      如果没有这个文件,计算机的启动时第一个进程都启动不起来。有他存在1号进程才可以开启。相当于是一个指挥官,他要指挥哪些程序在启动时应当存在,哪些程序不应当存在。/etc/inittab文件是init在启动时读取的文件,也就是指挥官的决策书,这个决策书规定了当前战争的局势,比如和平局势、冷战局势、战争局势。以及该局势下的核心策略。init就是这样,通过inittab这个文件控制了计算机的启动级别,及该级别下启动的进程。

    [root@hao boot]# cat /etc/inittab
    # Default runlevel. The runlevels used are:
    #   0 - halt (Do NOT set initdefault to this)
    #   1 - Single user mode
    #   2 - Multiuser, without NFS (The same as 3, if you do not have networking)
    #   3 - Full multiuser mode
    #   4 - unused
    #   5 - X11
    #   6 - reboot (Do NOT set initdefault to this)
    # 
    id:5:initdefault:

      从这里可以看出,我这里linux的default启动级别是runevel 5(注意,此处不要擅自改为6,如果默认的runlevel是6,那么就会比较麻烦,为什么会这么说,看完下面每个级别具体对应的进程就会明白)。那么具体每个级别都是什么呢?其实每个级别都有若干个进程,只不过每个级别开的进程不同。

      0:几乎所有进程都关闭,停机状态

      1:单用户模式,root账户进行操作

      2:多用户,不能使用net file system,一般很少用

      3:完全多用户,一部分启动,一部分不启动,命令行界面

      4:未使用、未定义的保留模式

      5:图形化,3级别中启动的进程都启动,并且会启动一部分图形界面进程。

      6:停止所有进程,卸载文件系统,重新启动

      这些级别中1、2、4很少用,相对而言0、3、5、6用的会较多。3级别和5级别除了桌面相关的进程外没有什么区别。为了减少资源占用,推荐都用3级别。

      那么这些级别是如何定义的呢,在/etc/rc.d/目录内,有如下目录。

    [root@Hao rc.d]# ll
    total 60
    drwxr-xr-x. 2 root root  4096 Mar 11 13:44 init.d
    -rwxr-xr-x. 1 root root  2617 Feb 22  2013 rc
    drwxr-xr-x. 2 root root  4096 Apr  3 08:36 rc0.d
    drwxr-xr-x. 2 root root  4096 Apr  3 08:36 rc1.d
    drwxr-xr-x. 2 root root  4096 Apr  3 08:36 rc2.d
    drwxr-xr-x. 2 root root  4096 Apr  3 08:36 rc3.d
    drwxr-xr-x. 2 root root  4096 Apr  3 08:36 rc4.d
    drwxr-xr-x. 2 root root  4096 Apr  3 08:36 rc5.d
    drwxr-xr-x. 2 root root  4096 Apr  3 08:36 rc6.d
    -rwxr-xr-x. 1 root root   220 Feb 22  2013 rc.local
    -rwxr-xr-x. 1 root root 19472 Feb 22  2013 rc.sysinit

      在诸如rc3.d和rc5.d目录下有runlevel 3 和runlevel 5中允许和不允许启动的进程,

      再进去目录看一下。

    [root@Hao rc.d]# ls rc3.d/
    K01smartd          K69rpcsvcgssd      S10network           S26acpid
    K02oddjobd         K73winbind         S11auditd            S26haldaemon
    K05wdaemon         K75ntpdate         S11portreserve       S26udev-post
    K10psacct          K75quota_nld       S12rsyslog           S28autofs
    K10saslauthd       K76ypbind          S13cpuspeed          S50bluetooth
    K15htcacheclean    K80kdump           S13irqbalance        S58ntpd
    K15httpd           K84wpa_supplicant  S13rpcbind           S80postfix
    K15svnserve        K87restorecond     S15mdmonitor         S82abrt-ccpp
    K25sshd            K88sssd            S22messagebus        S82abrtd
    K30spice-vdagentd  K89rdisc           S23NetworkManager    S90crond
    K36mysqld          K95firstboot       S24nfslock           S95atd
    K50dnsmasq         K99rngd            S24rpcgssd           S99certmonger
    K50netconsole      S01sysstat         S24rpcidmapd         S99local
    K50snmpd           S02lvm2-monitor    S25blk-availability
    K50snmptrapd       S08ip6tables       S25cups
    K60nfs             S08iptables        S25netfs
    root@Hao rc.d]# ls rc5.d/
    K01smartd        K75ntpdate         S11portreserve       S26udev-post
    K02oddjobd       K75quota_nld       S12rsyslog           S28autofs
    K05wdaemon       K76ypbind          S13cpuspeed          S50bluetooth
    K10psacct        K80kdump           S13irqbalance        S55sshd
    K10saslauthd     K84wpa_supplicant  S13rpcbind           S58ntpd
    K15htcacheclean  K87restorecond     S15mdmonitor         S70spice-vdagentd
    K15httpd         K88sssd            S22messagebus        S80postfix
    K15svnserve      K89rdisc           S23NetworkManager    S82abrt-ccpp
    K36mysqld        K95firstboot       S24nfslock           S82abrtd
    K50dnsmasq       K99rngd            S24rpcgssd           S90crond
    K50netconsole    S01sysstat         S24rpcidmapd         S95atd
    K50snmpd         S02lvm2-monitor    S25blk-availability  S99certmonger
    K50snmptrapd     S08ip6tables       S25cups              S99local
    K60nfs           S08iptables        S25netfs
    K69rpcsvcgssd    S10network         S26acpid
    K73winbind       S11auditd          S26haldaemon

      这些都是链接文件lrwxrwxrwx,链接的路径都是/etc/init.d中的脚本文件。

    [root@Hao rc.d]# ll rc3.d/ | grep ab
    lrwxrwxrwx. 1 root root 19 Feb 10 19:46 S08ip6tables -> ../init.d/ip6tables
    lrwxrwxrwx. 1 root root 18 Feb 10 19:44 S08iptables -> ../init.d/iptables
    lrwxrwxrwx. 1 root root 26 Feb 10 19:51 S25blk-availability -> ../init.d/blk-availability
    lrwxrwxrwx. 1 root root 19 Feb 10 19:46 S82abrt-ccpp -> ../init.d/abrt-ccpp
    lrwxrwxrwx. 1 root root 15 Feb 10 19:46 S82abrtd -> ../init.d/abrtd

      K开头的文件意味着是要关闭进程的文件,S开头的进程则是开启的进程,之后的数字表示启动的顺序,比如S23NetworkManager和S80postfix都要依赖S10network,如果这个顺序错了或者电脑了,那么和网络有关的程序都会启动错误,这个也在管理服务器的过程中也需要定期去看一下。

      对比rc3.d/目录与rc5.d/

    [root@Hao rc.d]# diff  rc3.d/ rc5.d/
    Only in rc3.d/: K30spice-vdagentd
    Only in rc5.d/: S70spice-vdagentd

      发现spice-vdagentd服务在runlevel 3中是KILL的,在runleve5是START的,spice-vdagentd服务是Redhat桌面虚拟组件之一,所以在runlevel3中没必要开,在runleve5中是要开启的,否则Linux将无法提供桌面服务,除此之外,两个运行级别没区别。

      还记得那个之前提到的决策书吗?核心策略对应的会启动的内容都是在这里,比如和平局势(之于runlevel 1)下启用基本的民防系统(之于S01sysstat)就可以了,冷战时期(之于runlevel 3)除了民防以外,还开启了自卫系统(之于S08iptables)、消息传递(S10network)、警报机制(之于S22messagebus)、派遣间谍(之于sshd)、调动步兵部队(之于httpd)等等,如果是战争时期(之于runlevel 5)除了上面种种之外还要启动更多的战争活动,调度更强大的部队。

    2.相关命令

    通过chkconfig –level 3 sshd off在运行级别中的开启或者关闭服务。

    通过chkconfig –list sshd来查看sshd在所有runlevel中的状态。

     

     

    [root@Hao rc.d]# chkconfig --list sshd
    sshd            0:off   1:off   2:on    3:on    4:on    5:on    6:off
    
    [root@Hao rc.d]# chkconfig --level 3 sshd off
    
    [root@Hao rc.d]# chkconfig --list sshd
    sshd            0:off   1:off   2:on    3:off   4:on    5:on    6:off

     

     

    init 6是重启,init 0是关机。

    # who –r 查看当前级别  

    run-level 5  2012-09-02 06:23

    3.附文1.不小心把init 默认的运行级别设为6怎么办

      通过查看 /etc/rc.d/rc6.d可以看到,runlevel 6中只有两个服务是开启的,分别是  S00killall和S01reboot,如果默认的运行级别设为6,那么重启之后,机器会不停重启。

    如果真的意(shou)外(jian)把默认的runlevel运行级别设置成了6,那么需要如下操作更改默认运行级别。在GRUB界面按e

    展开全文
  • system/core/rootdir/ ├── Android.mk ├── etc │ ├── hosts │ ├── init.testmenu │ └── mountd.conf ├── init.environ.rc.in ├── init.rc ├── init.trace.rc ├── init.usb.rc ├── ...
  • Python 中__init__函数以及参数self

    千次阅读 2021-01-29 04:04:36
    Java网站链接:Java全栈工程师 | 以实例驱动学习 | how2j.cn1)class类包含:类的属性:类中所涉及的变量类的方法:类中函数2) __init函数(方法)1、带有两个下划线开头的函数是声明该属性为私有,不能在类地外部被使用...
  • 启动init进程 运行init.rc 启动zygote服务 Zygote fork的第一个进程——SystemServer SystemServer启动系统服务 1. linux启动第一个应用程序init kernel\init\main.c static int __ref kernel_init(void *unused) {...
  • GoLang之init函数

    千次阅读 2022-04-01 14:09:39
    GoLang之init函数
  • init进程详细分析--基于android 10

    千次阅读 2020-03-16 14:20:02
    init进程详细分析 概述 android设备上电,引导程序引导进入boot(通常是uboot),加载initramfs、kernel镜像,启动kernel后,进入用户态程序。第一个用户空间程序是init,PID固定是1.在android系统上,init的代码位于/...
  • Kubernetes 初始化容器InitContainer

    千次阅读 2020-11-30 17:27:42
    Init Container Pod中会有这几种类型的容器: • Infrastructure Container:基础容器 ,维护整个Pod网络空间 • InitContainers:初始化容器,先于业务容器开始执行 • Containers:业务容器 Init 容器...
  • 文章目录概述Bootloader 引导装载和启动 Linux 内核启动 Init 进程(基于 Android9 源码)Init 进程的初始化过程main 函数执行流程解析启动 init.rcinit 文件格式介绍init 进程对信号的处理 概述 在介绍 Init 之前...
  • npm init、npm init -y

    千次阅读 2021-11-12 23:05:26
    npm init npm init -y 命令的作用是: 对项目进行初始化操作,对包进行管理 -y 的含义:yes的意思,在init的时候省去了敲回车的步骤,生成的默认的package.json
  • Android 11 init启动流程

    千次阅读 2022-04-15 14:10:55
    一、Kernel启动init 在kernel/init/main.cpp中 static int __ref kernel_init(void *unused) { ...... if (ramdisk_execute_command) { ret = run_init_process(ramdisk_execute_command); if (!ret)
  • Cloudinit简介

    千次阅读 2021-04-01 20:28:16
    Cloudinit如何判断虚拟机为初始化状态 Cloudinit简介 cloudinit是专为云环境中虚拟机的初始化而开发的工具,它从各种数据源读取相关数据并据此对虚拟机进行配置。常见的数据源包括:云平台的metadata服务、Config...
  • Android init进程

    千次阅读 2022-05-02 16:06:13
    init进程是我们user space中所有进程的父进程,位置在root的/init进程 init进程主要做的事情都在init.c的main函数中 我们kernel相关的log信息都在/proc/kmsg中 init最重要的几个事:创建挂载目录(比如说/proc)、...
  • Cloud-init的认识和应用

    千次阅读 2020-05-12 15:06:59
    Cloud-init是什么 Cloud-init是开源的云初始化程序,能够对新创建弹性云服务器中指定的自定义信息(主机名、密钥和用户数据等)进行初始化配置。通过Cloud-init进行弹性云服务器的初始化配置,将对您使用弹性云...
  • init.rc 启动 shell 脚本 init.rc执行shell脚本 开机执行脚本
  • K8S中Pod的生命周期与init container初始化容器详解
  • pthread_attr_init () 函数详解

    万次阅读 2021-07-12 20:30:35
    调用pthread_attr_init之后,pthread_t结构所包含的内容就是操作系统实现支持的线程所有属性的默认值。 如果要去除对pthread_attr_t结构的初始化,可以调用pthread_attr_destroy函数。如果pthread_attr_init实现时为...
  • java init方法

    千次阅读 2021-06-30 23:33:11
    init和destroy对应,一个加载一个销毁 有时候有一种场景是:初始化一个对象(bean)后立即初始化(加载一些数据) 配置类 @Configuration @ComponentScan("springTest2") public class ConfigTest { @Bean(init...
  • GPIO_init()函数初始化详解

    万次阅读 多人点赞 2021-06-23 10:39:43
    目录 1.GPIO_init()函数初始化示例 1.1 GPIO_InitTypeDef 1.2 GPIO_Init(GPIOB, &GPIO_GPIO_InitStructure) 1.3 GPIO_InitStructure.GPIO_Pin 1.4 GPIO_InitStructure.GPIO_Mode 1.5 GPIO_InitStructure.GPIO_Speed ...
  • 【Java】Spring init-method和@PostConstruct 原理

    万次阅读 多人点赞 2019-07-07 20:21:18
    在bean的xml定义中指定init-method属性。 2)注解配置 在bean的class定义中添加@PostConstruct注解。 例子: xml如下配置: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http:...
  • linux 中module_init()加载顺序

    万次阅读 2018-12-24 16:18:50
    目录 ...kernel-3.18\include\linux\init.h中   Linux内核为不同驱动的加载顺序对应不同的优先级,定义了一些宏: #define pure_initcall(fn) __define_initcall("0",fn,1) ...
  • torch.init.normal_和torch.init.constant_用法

    千次阅读 2021-05-16 20:18:27
    torch.init.normal_:给tensor初始化,一般是给网络中参数weight初始化,初始化参数值符合正态分布。 torch.init.normal_(tensor,mean=,std=) ,mean:均值,std:正态分布的标准差 代码示例: import torch import ...
  • 另外,需要提醒大家注意的一点是,阅读本章需要有一定的 C/C++基础,在这个流程中会涉及到很多重要的知识点,这个系列我们就来一一讲解它们,这一篇我们就来学习init进程,本系列文章都将基于Android8.0源码来分析...
  • 相信大家在很多场合特别是写神经网络的代码的时候都看到过下面的这种代码: ... def __init__(self): super(Net, self).__init__() # 输入图像channel:1;输出channel:6;5x5卷积核 self.conv1 = nn.C
  • python中init是什么

    千次阅读 2021-01-13 16:00:45
    python中init是什么发布时间:2020-09-21 11:32:47来源:亿速云阅读:90作者:Leah这期内容当中小编将会给大家带来有关python中init是什么,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家...
  • torch.nn.init常用函数总结

    千次阅读 2022-01-27 14:45:28
    torch.nn.init中常用的函数:torch.nn.init.uniform_ torch.nn.init.normal_ torch.nn.init.constant_ torch.nn.init.ones_ torch.nn.init.zeros_ torch.nn.init.eye_ torch.nn.init.sparse_
  • 鸿蒙OS开源代码精要解读之——init

    万次阅读 多人点赞 2020-09-25 09:34:26
    鸿蒙OS开源代码精要解读之——init 作者介绍: 中科创达OpenHarmony研究组 说明: 中科创达OpenHarmony研究组第一时间对https://codechina.csdn.net/openharmony上开源的代码进行了详尽的代码研读和学习。为此,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,156,332
精华内容 1,662,532
关键字:

init