精华内容
下载资源
问答
  • 不过有些用户不喜欢在开机时停留在显示启动选项的过程中,因此,在这样的情况下,我们可以参考下面的两种方法来解决这个问题。 方法一:无视OneKey Ghost,修改启动项时间 1、右击打开系统属性——高级系统设置,...

    大多数用户在使用OneKey Ghost安装电脑系统后,会在开机启动项中多出一个OneKey Ghost选项,也就是一键Ghost。不过有些用户不喜欢在开机时停留在显示启动选项的过程中,因此,在这样的情况下,我们可以参考下面的两种方法来解决这个问题。

    方法一:无视OneKey Ghost,修改启动项时间

    1、右击打开系统属性——高级系统设置,在高级选项卡中,点击启动和故障修复下的“设置”按钮;


    2、选择默认操作系统,并修改“显示操作系统列表的时间”,改成一个很小的时间,或者直接不勾选,然后点击确定;


    方法二:删除OneKey Ghost选项

    1、按Win+R键打开运行,输入cmd并回车

    2、在打开的命令提示符中输入bcdedit,然后回车,可以看到OneKey Ghost;

    注:如果使用bcdedit命令没有权限,显示为拒绝服务。可以在系统中找到cmd.exe文件,文件在 C:\Windows\System32 这个目录下,右键“以管理员身份运行“即可。

    3、然后输入bcdedit /delete {标识符} /f,再点击回车,提示操作成功完成即可,其中的标识符可以从上一行得到。


    如果在电脑开机后发现多了OneKey Ghost启动选项,那么可以参考上面的方法修改启动项显示时间或者直接删除该项来解决。


    原文地址:http://www.jb51.net/diannaojichu/452521.html

    展开全文
  • 前阶段在项目中涉及到了Android系统定制任务,Android系统定制前提要知道Android系统是如何启动的。 本文参考了一些书籍的若干章节,比如《Android进阶解密-第2章-Android系统启动》、《深入理解Android虚拟机-第8/9...

    声明

    • 前阶段在项目中涉及到了Android系统定制任务,Android系统定制前提要知道Android系统是如何启动的。
    • 本文参考了一些书籍的若干章节,比如《Android进阶解密-第2章-Android系统启动》、《深入理解Android虚拟机-第8/9/10章-init进程详解/Dalvik VM的进程系统/Dalvik VM运作流程详解》、《深入理解Android系统-第6/7/8章-init启动进程详解/Zygote进程详解/System进程详解》等
    • 本文使用的代码是LineageOS的cm-14.1,对应Android 7.1.2,可以参考我的另一篇博客:如何下载Nexus5的LineageOS14.1(cm-14.1)系统源码并编译、刷机
    • 很多代码注释待详细写

    1 init进程

        init进程在Linux系统中是内核启动后启动的1号进程,init进程在Android系统中依然是内核初始化完成后首先启动的1号进程。init进程主要作用是:

    1. 创建、挂载启动所需的文件目录,包括:tmpfs、devpts、proc、sys、selinuxfs;
    2. 生成设备驱动节点(设备热插拔、udev、uevent,可参考书籍《Android框架揭秘》3.4节)
    3. 解析、处理init.rc等脚本文件中的命令;
    4. 创建Zygote和属性服务;
    5. 使用while(true)循环创建子进程;

        其源码位置在:

    vim  ~/LineageOS/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);
        }
    
        // 清理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.
        //创建和挂载启动所需的用户空间文件目录
        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.
        open_devnull_stdio();
        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.
            // 检测/dev/.booting文件是否可读写和创建
            close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
            //初始化属性服务
            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.
        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) {
            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");
        //创建epoll句柄
        epoll_fd = epoll_create1(EPOLL_CLOEXEC);
        if (epoll_fd == -1) {
            ERROR("epoll_create1 failed: %s\n", strerror(errno));
            exit(1);
        }
        //设置子进程信号处理函数,如果子进程(Zygote进程)异常退出,init进程会调用该函数中设定的信号处理函数进行处理,防止init的子进程成为僵尸进程。
        //系统会在子进程暂停/终止时发出SIGCHLD信号通知父进程init,signal_handler_init()函数用来设置init进程接收SIGCHLD信号后的处理动作。
        signal_handler_init();
        //导入默认的环境变量
        property_load_boot_defaults();
        export_oem_lock_status();
        //启动属性服务
        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>());
        //解析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(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.
        //在charger模式下略过mount文件系统的工作
        std::string bootmode = property_get("ro.bootmode");
        if (bootmode == "charger" || charging_mode_booting() ||
                strcmp(battchg_pause, BOARD_CHARGING_CMDLINE_VALUE) == 0) {
            am.QueueEventTrigger("charger");
        } else if (strncmp(bootmode.c_str(), "ffbm", 4) == 0) {
            NOTICE("Booting into ffbm mode\n");
            am.QueueEventTrigger("ffbm");
        } 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;
    }
    

    2 Android Init Language脚本文件语法

        Android源码中会有很多的rc脚本文件用来对系统进行初始化配置,一般存放在:

    ~/LineageOS/system/core/rootdir
    及
    ~/LineageOS/device/lge/hammerhead
    

        完整的init.rc脚本包含四个状态类别:

    • Actions-动作
    • Commands-命令
    • Services-服务
    • Options-选项

    2.1 通用语法规则

    • 注释以"#"开头;
    • 关键字和参数以空格隔开,每个语句以行为单位;
    • C语言风格的反斜杠转义字符("")可以用来为参数添加空格;
    • 为了防止字符串中的空格把其分隔成多个部分,我们需要对其使用双引号;
    • Actions和Services暗示着一个新语句(section)的开始,这两个关键字后面跟着的commands或者options都属于这个新语句(section);
    • Actions和Services有唯一的名字,如果出现和已有动作或服务重名的,将会被当做错误忽略掉;

        init.rc文件是以块(section)为单位组织的,一个section可以包含多行。section分成两大类:一类称为“Action(行为)”,另一类称为“Service(服务)”。

        “行为”块以关键字“on”开始,表示一堆命令的集合,“服务”块以“service”开始,表示启动某个进程的方式和参数。

        “块”以关键字“on”或“service”开始,直到下一个“on”或“service”结束,中间所有行都属于这个“块”(空行或者注释都不具备分割作用)。下面是一部分init.rc文件示例:

    on early-init
        # Set init and its forked children's oom_adj.
        write /proc/1/oom_score_adj -1000
    
        # Disable sysrq from keyboard
        write /proc/sys/kernel/sysrq 0
    
        # Set the security context of /adb_keys if present.
        restorecon /adb_keys
    
        # Shouldn't be necessary, but sdcard won't start without it. http://b/22568628.
        mkdir /mnt 0775 root system
    
        # Set the security context of /postinstall if present.
        restorecon /postinstall
    
        start ueventd
    
    on property:sys.boot_from_charger_mode=1
        class_stop charger
        trigger late-init
    on nonencrypted
        class_start main
        class_start late_start
    service installd /system/bin/installd
        class main
        socket installd stream 600 system system
    on load_persist_props_action
        load_persist_props
        start logd
        start logd-reinit
    
    service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
        class main
        socket zygote stream 660 root system
        onrestart write /sys/android_power/request_state wake
        onrestart write /sys/power/state on
        onrestart restart audioserver
        onrestart restart cameraserver
        onrestart restart media
        onrestart restart netd
        writepid /dev/cpuset/foreground/tasks
    

    2.2 Actions

        Action是在某种条件下触发一系列的命令,通常有一个trigger。如:

    on <trigger>
       <command>
       <command>
    

        Actionde 的关键字“on”后面的字符串为“trigger”(触发器),如上面代码中的“early-init”,trigger后面的内容是命令列表command,每一行都是一条命令,常见的command参见2.4节。

        Action其实就是一系列的Command集合。每个Action都有一个trigger,它决定本Action的执行时机。当一个符合Action触发条件的事件发生了,此Action会加入到执行队列的末尾(除非它已经在队列里了)。每一个Action都将依次从队列中取出,此Action的每个Command都将依次执行。

    2.2.1 trigger

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

    1. boot是Init执行后第一个触发的Trigger(在/init.conf装载之后);
    2. =:这种形式的Trigger会在属性"name"设置为指定的“value”时被触发;
    3. device-added- :这种形式的Trigger会在一个设备文件增加时触发;
    4. device-removed- :这种形式的Trigger会在一个设备文件移除时触发;
    5. service-exited-:这种形式的Trigger会在一个指定的Service退出时触发;

    2.2.2 command

        Commands是Action的命令列表中的命令,或者是Service的选项onrestart的参数命令。

    1. exec [ [ [ ]* ] ] – [ ]*:fork和启动一个程序,在程序完成启动之前,Init进程将会阻塞。command跟在“–”之后,在这之前我们可以指定像安全上下文、用户名和组名这样的参数信息;seclabel默认指定为“-”。
    2. export :设置全局环境变量为(所有在这命令执行之后运行的进程都将继承该环境变量)。
    3. ifup :启动网络接口。
    4. import :引入一个配置文件,扩展当前配置。
    5. hostname :设置主机名。
    6. chdir :设置进程当前的工作目录。
    7. chmod :设置文件或目录的访问权限。
    8. chown :设置文件或目录的所有者和组。
    9. chroot :设置进程的根目录。
    10. class_start :启动所有指定服务名称下的未运行服务。
    11. class_stop :停止所有指定服务名称下的已运行的服务。
    12. class_reset :停止服务,后续可以通过class_start启动。
    13. domainname :设置域名。
    14. insmod :安装一个驱动模块。
    15. mkdir [mode] [owner] [group]:新创建一个目录 ,可以指定访问权限、拥有者和组。如果没有指定,默认的访问权限是755,属于root用户和root组。
    16. mount [ ]*:在指定目录下挂载一个设备。可以是以mtd@name的格式指定的一个mtd块设备。包括“ro”、“rw”、“remount”、“noatime”等。
    17. restorecon :重新存储指定的文件到一个有file_contexts配置的安全上下文。不用指定目录,它们会被Init进程自动创建。
    18. setcon :设置当前进程的安全上下文为指定的串。主要用在early-init中去设置Init的安全上下文。
    19. setenforce 0|1:设置SELinux系统级的enforcing状态。0代表permissive,1代表enforcing。
    20. setkey:目前未使用。
    21. setprop :设置系统属性为值。
    22. setrlimit :设置的rlimit值。
    23. setsebool :设置SELinux的boolean型属性的值为。的值可以是“1|true|on"或者”0|false|off“。
    24. start :启动指定服务(如果此服务还未运行)。
    25. stop :停止指定服务(如果此服务还在运行中)。
    26. restart :重启指定名称的服务,先stop,再start。
    27. symlink :创建一个符号连接。
    28. sysclktz <mins_west_of_gmt>:设置系统的时钟基准(0代表格林尼治平均时(GMT)为准)。
    29. trigger 触发一个事件。用于将一个Action和另一个Action连在一起执行。
    30. wait []:等待指定路径的文件被创建出来,创建完成就停止等待,或者等到超时时间到达。如果未指定超时时间,缺省时间是5秒。
    31. write [<string]*:打开指定的文件,并写入一个或多个字符串。

    2.3 Service

        Service是指的服务,如:

    service <name> <pathname> [ <argument> ]*
       <option>
       <option>
    

        service后的的字符串为服务名称,service下面的行称为option(选项),常见的option参见2.5节

        无论是“Action”块还是“Service”块,它们的执行顺序都与.rc文件中的声明顺序无关,是否执行及执行顺序要由init进程在运行时决定

    2.3.1 opinion

        Options是Service的修饰词,它们决定一个Service何时以及如何运行,主要包括下面选项:

    1. critical:表示这是一个关键的服务。如果该Service 4分钟内重新启动超过4次,系统将自动重启并进入recovery模式。
    2. disabled:表示服务不能通过start className(main/core等)启动。它必须以命令“start service_name”的形式显示指定名称启动。
    3. setenv :在Service启动时将环境变量“name”设置为“value”。
    4. socket [ []]:创建一个名为/dev/socket/的套接字,并把文件描述符传递给要启动的进程。的值是“dgram”或者是“stream”,User和group的值默认为0。
    5. user :在启动这个Service前设置Service的用户名,默认是root。如果进程没有相应的权限,将不能使用该命令。如果进程有root权限,可以在程序中设置你想要的uid。
    6. group []*:在启动这个Service前设置Service的服务。除了第一个组名,剩下的组名同城用于设置进程的附加组(通过setgroups())。默认是root。
    7. oneshot:Service退出后不再重启。
    8. class :给Service指定一个名字。所有同名字的服务可以同时启动和停止。如果不通过class选项指定一个名字,则默认是“default”。
    9. onrestart:当Service重启时,执行一条命令。
    10. writepid <file…>:将当前进程的进程号写到<file…>文件中。

    2.4 语法总结

        通过这些语法规则,可以去源码目录的~/LineageOS/system/core/rootdir
        .rc文件遵循的这些语法功能其实主要还是给人看的,方便程序员通过.rc文件来控制Android系统启动初始化;对于机器来说,它会对*.rc文件进行解析,换另外的一个视角去读取*.rc文件。

    3 init.rc中Service/Action解析

        在源码 ~/LineageOS/system/core/init/init.cpp中对init.rc进行解析的代码如下:

        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>());
        //解析init.rc配置文件
        parser.ParseConfig("/init.rc");
    
    

        在源码 ~/LineageOS/system/core/init/init_parser.cpp中,所调用的ParseConfig函数定义为:

    bool Parser::ParseConfig(const std::string& path) {
        //判断传入path为目录的话执行:ParseConfigDir(path)
        if (is_dir(path.c_str())) {
            return ParseConfigDir(path);
        }
        //传入的为/init.rc,所以执行:ParseConfigFile(path)
        return ParseConfigFile(path);
    }
    

        在源码 ~/LineageOS/system/core/init/init_parser.cpp中,所调用的ParseConfigFile函数定义为:

    bool Parser::ParseConfigFile(const std::string& path) {
        INFO("Parsing file %s...\n", path.c_str());
        //计时用
        Timer t;
        //将./init.rc文件内容逐个字符读入一个string类型的对象data中;
        std::string data;
        if (!read_file(path.c_str(), &data)) {
            return false;
        }
        //添加字符'\n'到字符串一定用push_back函数,切不可直接用"+"
        data.push_back('\n'); // TODO: fix parse_config.
        //调用ParseData函数对
        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();
        //记录下解析./init.rc所用时间
        NOTICE("(Parsing %s took %.2fs.)\n", path.c_str(), t.duration());
        return true;
    }
    

        在源码 ~/LineageOS/system/core/init/init_parser.cpp中,所调用的ParseData函数定义为:

    void Parser::ParseData(const std::string& filename, const std::string& data) {
        //TODO: Use a parser with const input and remove this copy
        //将data内容副本存入data_copy中,并在结尾添加'\0';
        std::vector<char> data_copy(data.begin(), data.end());
        data_copy.push_back('\0');
        //初始化parse_state结构体,用于对已经字符串化的init.rc进行封装,其声明在parser.h中
        parse_state state;
        state.filename = filename.c_str();
        state.line = 0;
        state.ptr = &data_copy[0];
        state.nexttoken = 0;
        //SectionParser类声明在init_parser.h中
        SectionParser* section_parser = nullptr;
        std::vector<std::string> args;
    
        for (;;) {
            //next_token函数定义在parser.cpp中,用于解析state
            switch (next_token(&state)) {
             //next_token函数逐字符解析到state的最结尾'\0'时会返回T_EOF,结束解析;
            case T_EOF:
                if (section_parser) {
                    section_parser->EndSection();
                }
                return;
            //next_token函数逐字符解析到state的'\n'字符时会返回T_NEWLINE;
            case T_NEWLINE:
                state.line++;
                if (args.empty()) {
                    break;
                }
                if (section_parsers_.count(args[0])) {
                    if (section_parser) {
                        section_parser->EndSection();
                    }
                    //根据此行首词args[0]是关键字service/on,此行的解析要创建Ation或Service新对象
                    section_parser = section_parsers_[args[0]].get();
                    std::string ret_err;
                    //ParseSection函数主要用来搭建Service/Action的架子
                    if (!section_parser->ParseSection(args, &ret_err)) {
                        parse_error(&state, "%s\n", ret_err.c_str());
                        section_parser = nullptr;
                    }
                    //根据此行首词args[0]不是关键字service/on,此行的解析用来填充上面创建的Ation或Service对象的子项
                } else if (section_parser) {
                    std::string ret_err;
                    //ParseLineSection函数主要用来解析Service/Action子项
                    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.emplace_back(state.text);
                break;
            }
        }
    }
    

        解析过程总体来讲就是根据参数创建出Service/Action对象,然后根据选项域的内容填充Service/Action对象,最后将Service/Action对象加入到vector类型的Services/Action链表中。

        解析service时,会用到两个函数,一个是ServiceParser::ParseSection函数主要用来搭建service的架子。另一个是ServiceParser::ParseLineSection用于解析子项。

        解析Action时,会用到两个函数,一个是ActionParser::ParseSection函数主要用来搭建service的架子。另一个是ActionParser::ParseLineSection用于解析子项。

    提示:关于init进程解析init.rc的这部分的代码,从Android4.4-Android7.1是一直在变,以前使用纯C实现,现在完全是C++代码,但是原理是不变的!

    4 init进程控制Action

        待补充…

    5 init进程控制Service

        以init启动zygote为例,init.rc中利用import命令加载了init.zygote32.rc(因为我的是Nexus5手机),截取init.rc中一段代码:

    import /init.environ.rc
    import /init.usb.rc
    import /init.${ro.hardware}.rc
    import /init.usb.configfs.rc
    import /init.${ro.zygote}.rc
    ...省略n行...
    

        查看源码中:~/LineageOS/system/core/rootdir/init.zygote32.rc文件内容为:

    service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
        class main
        socket zygote stream 660 root system
        onrestart write /sys/android_power/request_state wake
        onrestart write /sys/power/state on
        onrestart restart audioserver
        onrestart restart cameraserver
        onrestart restart media
        onrestart restart netd
        writepid /dev/cpuset/foreground/tasks
    

        根据2.3.1节中对于opinion的介绍可知,此服务所在的class为main。在init.rc中可查看main是何时启动的呢?

    ...省略n行...
    on nonencrypted
        # A/B update verifier that marks a successful boot.
        exec - root cache -- /system/bin/update_verifier nonencrypted
        class_start main
        class_start late_start
    ...省略n行...
    

        其中class_start main的含义就是启动所有classname 为 main 的Service,此时zygote服务也就被启动了。class_start命令对应的实际执行函数为do_class_start,查看其源代码在~/LineageOS/system/core/init/builtins.cpp中:

    static int do_class_start(const std::vector<std::string>& args) {
        //ForEachServiceInClass将遍历Service链表,查找到classname为main的服务(zygote),执行StartIfNotDisabled函数
        ServiceManager::GetInstance().
            ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });
    
        std::string prop_name = StringPrintf("class_start:%s", args[1].c_str());
        if (prop_name.length() < PROP_NAME_MAX) {
            ActionManager::GetInstance().QueueEventTrigger(prop_name);
        }
        return 0;
    }
    

        StartIfNotDisabled函数代码在源码 ~/LineageOS/system/core/init/service.cpp中:

    bool Service::StartIfNotDisabled() {
        if (!(flags_ & SVC_DISABLED)) {//检测相关service的opinion中是否设置了disabled选项,若没有设置则直接运行Start函数;
            return Start();
        } else {
            flags_ |= SVC_DISABLED_START;
        }
        return true;
    }
    

        在此文件中查看Start函数:

    bool Service::Start() {
        // Starting a service removes it from the disabled or reset state and
        // immediately takes it out of the restarting state if it was in there.
        flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
        time_started_ = 0;
    
        // Running processes require no additional work --- if they're in the
        // process of exiting, we've ensured that they will immediately restart
        // on exit, unless they are ONESHOT.
        //如果Service已经运行了,则不启动;
        if (flags_ & SVC_RUNNING) {
            return false;
        }
    
        bool needs_console = (flags_ & SVC_CONSOLE);
        if (needs_console && !have_console) {
            ERROR("service '%s' requires console\n", name_.c_str());
            flags_ |= SVC_DISABLED;
            return false;
        }
        //判断所要启动的Service对应的可执行文件是否存在,否则不启动;
        struct stat sb;
        if (stat(args_[0].c_str(), &sb) == -1) {
            ERROR("cannot find '%s' (%s), disabling '%s'\n",
                  args_[0].c_str(), strerror(errno), name_.c_str());
            flags_ |= SVC_DISABLED;
            return false;
        }
    
        std::string scon;
        if (!seclabel_.empty()) {
            scon = seclabel_;
        } else {
            char* mycon = nullptr;
            char* fcon = nullptr;
    
            INFO("computing context for service '%s'\n", args_[0].c_str());
            int rc = getcon(&mycon);
            if (rc < 0) {
                ERROR("could not get context while starting '%s'\n", name_.c_str());
                return false;
            }
    
            rc = getfilecon(args_[0].c_str(), &fcon);
            if (rc < 0) {
                ERROR("could not get context while starting '%s'\n", name_.c_str());
                free(mycon);
                return false;
            }
    
            char* ret_scon = nullptr;
            rc = security_compute_create(mycon, fcon, string_to_security_class("process"),
                                         &ret_scon);
            if (rc == 0) {
                scon = ret_scon;
                free(ret_scon);
            }
            if (rc == 0 && scon == mycon) {
                ERROR("Service %s does not have a SELinux domain defined.\n", name_.c_str());
                free(mycon);
                free(fcon);
                return false;
            }
            free(mycon);
            free(fcon);
            if (rc < 0) {
                ERROR("could not get context while starting '%s'\n", name_.c_str());
                return false;
            }
        }
    
        NOTICE("Starting service '%s'...\n", name_.c_str());
        //创建子进程
        pid_t pid = fork();
        if (pid == 0) {
            umask(077);
    
            for (const auto& ei : envvars_) {
                add_environment(ei.name.c_str(), ei.value.c_str());
            }
    
            for (const auto& si : sockets_) {
                int socket_type = ((si.type == "stream" ? SOCK_STREAM :
                                    (si.type == "dgram" ? SOCK_DGRAM :
                                     SOCK_SEQPACKET)));
                const char* socketcon =
                    !si.socketcon.empty() ? si.socketcon.c_str() : scon.c_str();
                //后面介绍create_socket函数的左右
                int s = create_socket(si.name.c_str(), socket_type, si.perm,
                                      si.uid, si.gid, socketcon);
                if (s >= 0) {
                    //后面介绍PublishSocket函数的作用
                    PublishSocket(si.name, s);
                }
            }
    
            std::string pid_str = StringPrintf("%d", getpid());
            for (const auto& file : writepid_files_) {
                if (!WriteStringToFile(pid_str, file)) {
                    ERROR("couldn't write %s to %s: %s\n",
                          pid_str.c_str(), file.c_str(), strerror(errno));
                }
            }
    
            if (ioprio_class_ != IoSchedClass_NONE) {
                if (android_set_ioprio(getpid(), ioprio_class_, ioprio_pri_)) {
                    ERROR("Failed to set pid %d ioprio = %d,%d: %s\n",
                          getpid(), ioprio_class_, ioprio_pri_, strerror(errno));
                }
            }
    
            if (needs_console) {
                setsid();
                OpenConsole();
            } else {
                ZapStdio();
            }
    
            setpgid(0, getpid());
    
            // As requested, set our gid, supplemental gids, and uid.
            if (gid_) {
                if (setgid(gid_) != 0) {
                    ERROR("setgid failed: %s\n", strerror(errno));
                    _exit(127);
                }
            }
            if (!supp_gids_.empty()) {
                if (setgroups(supp_gids_.size(), &supp_gids_[0]) != 0) {
                    ERROR("setgroups failed: %s\n", strerror(errno));
                    _exit(127);
                }
            }
            if (uid_) {
                if (setuid(uid_) != 0) {
                    ERROR("setuid failed: %s\n", strerror(errno));
                    _exit(127);
                }
            }
            if (!seclabel_.empty()) {
                if (setexeccon(seclabel_.c_str()) < 0) {
                    ERROR("cannot setexeccon('%s'): %s\n",
                          seclabel_.c_str(), strerror(errno));
                    _exit(127);
                }
            }
    
            std::vector<std::string> expanded_args;
            std::vector<char*> strs;
            expanded_args.resize(args_.size());
            strs.push_back(const_cast<char*>(args_[0].c_str()));
            for (std::size_t i = 1; i < args_.size(); ++i) {
                if (!expand_props(args_[i], &expanded_args[i])) {
                    ERROR("%s: cannot expand '%s'\n", args_[0].c_str(), args_[i].c_str());
                    _exit(127);
                }
                strs.push_back(const_cast<char*>(expanded_args[i].c_str()));
            }
            strs.push_back(nullptr);
            //调用execve函数,Service子进程启动,即启动/system/bin/app_process;这里可以跳到下篇博客的第3节,启动Zygote。
            if (execve(strs[0], (char**) &strs[0], (char**) ENV) < 0) {
                ERROR("cannot execve('%s'): %s\n", strs[0], strerror(errno));
            }
    
            _exit(127);
        }
    
        if (pid < 0) {
            ERROR("failed to start '%s'\n", name_.c_str());
            pid_ = 0;
            return false;
        }
    
        time_started_ = gettime();
        pid_ = pid;
        flags_ |= SVC_RUNNING;
    
        if ((flags_ & SVC_EXEC) != 0) {
            INFO("SVC_EXEC pid %d (uid %d gid %d+%zu context %s) started; waiting...\n",
                 pid_, uid_, gid_, supp_gids_.size(),
                 !seclabel_.empty() ? seclabel_.c_str() : "default");
        }
    
        NotifyStateChange("running");
        return true;
    }
    

        create_socket函数所在源码位置~/LineageOS/system/core/init/util.cpp:

    /*
     * create_socket - creates a Unix domain socket in ANDROID_SOCKET_DIR
     * ("/dev/socket") as dictated in init.rc. This socket is inherited by the
     * daemon. We communicate the file descriptor's value via the environment
     * variable ANDROID_SOCKET_ENV_PREFIX<name> ("ANDROID_SOCKET_foo").
     */
    int create_socket(const char *name, int type, mode_t perm, uid_t uid,
                      gid_t gid, const char *socketcon)
    {
        struct sockaddr_un addr;
        int fd, ret, savederrno;
        char *filecon;
    
        if (socketcon) {
            if (setsockcreatecon(socketcon) == -1) {
                ERROR("setsockcreatecon(\"%s\") failed: %s\n", socketcon, strerror(errno));
                return -1;
            }
        }
        //调用socket函数创建一个socket,使用文件描述符fd来描述此socket;
        fd = socket(PF_UNIX, type, 0);
        if (fd < 0) {
            ERROR("Failed to open socket '%s': %s\n", name, strerror(errno));
            return -1;
        }
    
        if (socketcon)
            setsockcreatecon(NULL);
        //为socket创建一个类型为AF_UNIX的socket地址addr;
        memset(&addr, 0 , sizeof(addr));
        addr.sun_family = AF_UNIX;
        snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s",
                 name);
    
        ret = unlink(addr.sun_path);
        if (ret != 0 && errno != ENOENT) {
            ERROR("Failed to unlink old socket '%s': %s\n", name, strerror(errno));
            goto out_close;
        }
    
        filecon = NULL;
        if (sehandle) {
            ret = selabel_lookup(sehandle, &filecon, addr.sun_path, S_IFSOCK);
            if (ret == 0)
                setfscreatecon(filecon);
        }
    
        ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr));
        savederrno = errno;
    
        setfscreatecon(NULL);
        freecon(filecon);
    
        if (ret) {
            ERROR("Failed to bind socket '%s': %s\n", name, strerror(savederrno));
            goto out_unlink;
        }
        //设置设备文件/dev/socket/zygote的用户id、用户组id、用户权限;
        ret = lchown(addr.sun_path, uid, gid);
        if (ret) {
            ERROR("Failed to lchown socket '%s': %s\n", addr.sun_path, strerror(errno));
            goto out_unlink;
        }
        ret = fchmodat(AT_FDCWD, addr.sun_path, perm, AT_SYMLINK_NOFOLLOW);
        if (ret) {
            ERROR("Failed to fchmodat socket '%s': %s\n", addr.sun_path, strerror(errno));
            goto out_unlink;
        }
    
        INFO("Created socket '%s' with mode '%o', user '%d', group '%d'\n",
             addr.sun_path, perm, uid, gid);
    
        return fd;
    
    out_unlink:
        unlink(addr.sun_path);
    out_close:
        close(fd);
        return -1;
    }
    
    

        PublishSocket函数所在源码位置~/LineageOS/system/core/init/service.cpp:

    //参数fd是指向create_socket函数创建的socket的文件描述符;
    void Service::PublishSocket(const std::string& name, int fd) const {
        //将宏ANDROID_SOCKET_ENV_PREFIX和参数name描述的字符串连接起来,保存在字符串key中;
        std::string key = StringPrintf(ANDROID_SOCKET_ENV_PREFIX "%s", name.c_str());
        std::string val = StringPrintf("%d", fd);
        add_environment(key.c_str(), val.c_str());
    
        /* make sure we don't close-on-exec */
        fcntl(fd, F_SETFD, 0);
    }
    
    

    6 属性服务

        init进程启动时会启动Android系统的属性服务,并在内存中开辟一块空间来存储这些属性。相关代码在系统源码:

        ...省略n行...
        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));
            //初始化属性服务的配置
            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();
        }
        ...省略n行...
        //启动属性服务
        start_property_service();
    

    6.1 init.rc中就用到的属性

        在分析init.rc时就发现在该脚本开头就有用到属性ro.hardware、ro.zygote:

    import /init.environ.rc
    import /init.usb.rc
    import /init.${ro.hardware}.rc
    import /init.usb.configfs.rc
    import /init.${ro.zygote}.rc
    

    其实在init.cpp中可发现,init进程启动了属性服务后,随即就进行了init.rc脚本文件的解析。

    6.2 利用内核属性设置应用层配置文件

        内核变量可以由bootloader传入,其中init.cpp中process_kernel_cmdline函数就是用于获取传入内核变量的值,其源码为:

    static void process_kernel_cmdline() {
        // Don't expose the raw commandline to unprivileged processes.
        chmod("/proc/cmdline", 0440);
    
        // The first pass does the common stuff, and finds if we are in qemu.
        // The second pass is only necessary for qemu to export all kernel params
        // as properties.
        import_kernel_cmdline(false, import_kernel_nv);
        if (qemu[0]) import_kernel_cmdline(true, import_kernel_nv);
    }
    

        init.cpp随后调用了export_kernel_boot_props函数,其就是利用内核属性来设置应用层配置文件的属性,其源码为:

    static void export_kernel_boot_props() {
        //初始化该结构体类型的数组prop_map[],并赋初始值为unknown;
        struct {
            const char *src_prop;
            const char *dst_prop;
            const char *default_value;
        } prop_map[] = {
    #ifndef IGNORE_RO_BOOT_SERIALNO
            { "ro.boot.serialno",   "ro.serialno",   "", },
    #endif
            { "ro.boot.mode",       "ro.bootmode",   "unknown", },
            { "ro.boot.baseband",   "ro.baseband",   "unknown", },
            { "ro.boot.bootloader", "ro.bootloader", "unknown", },
            { "ro.boot.hardware",   "ro.hardware",   "unknown", },
    #ifndef IGNORE_RO_BOOT_REVISION
            { "ro.boot.revision",   "ro.revision",   "0", },
    #endif
        };
        //通过property_set来获取proc_map中src_prop的属性值,并赋给相应的dst_prop;
        for (size_t i = 0; i < ARRAY_SIZE(prop_map); i++) {
            std::string value = property_get(prop_map[i].src_prop);
            property_set(prop_map[i].dst_prop, (!value.empty()) ? value.c_str() : prop_map[i].default_value);
        }
    }
    
    

        其中,property_get、property_set函数定义在源码~/LineageOS/system/core/init/property_service.cpp中:

    std::string property_get(const char* name) {
        char value[PROP_VALUE_MAX] = {0};
        __system_property_get(name, value);
        return value;
    }
    

        property_get函数最终调用的是bionic库函数中的__system_property_get函数,在源码目录 ~/LineageOS/bionic/libc/bionic/system_properties.cpp中:

    int __system_property_get(const char *name, char *value)
    {
        const prop_info *pi = __system_property_find(name);
    
        if (pi != 0) {
            return __system_property_read(pi, 0, value);
        } else {
            value[0] = 0;
            return 0;
        }
    }
    

        在源码文件~/LineageOS/bionic/libc/include/sys/_system_properties.hz中定义了几个宏:

    #define PROP_PATH_RAMDISK_DEFAULT  "/default.prop"
    #define PROP_PATH_SYSTEM_BUILD     "/system/build.prop"
    #define PROP_PATH_VENDOR_BUILD     "/vendor/build.prop"
    #define PROP_PATH_LOCAL_OVERRIDE   "/data/local.prop"
    #define PROP_PATH_FACTORY          "/factory/factory.prop"
    

        这些宏在~/LineageOS/system/core/init/property_service.cpp的load_properties_from_file函数中用得到,意思是从这些文件中获取属性值。

    6.3 初始化属性服务

        初始化属性服务的配置函数property_init,定义在 ~/LineageOS/system/core/init/property_service.cpp中:

    void property_init() {
        //初始化属性内存区域
        if (__system_property_area_init()) {
            ERROR("Failed to initialize property area\n");
            exit(1);
        }
    }
    

        启动属性服务的函数start_property_service,定义在~/LineageOS/system/core/init/property_service.cpp中:

    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);
        }
        //对property_set_fd进行监听,此socket就成为server,即属性服务;参数8代表的意思是该属性服务最多可以同时为8个用户设置属性的需求提供服务;
        listen(property_set_fd, 8);
        //将property_set_fd放入epoll中,利用epoll来监听property_set_fd,当property_set_fd接收到数据时会调用handle_property_set_fd函数对其处理;
        register_epoll_handler(property_set_fd, handle_property_set_fd);
    }
    

        在linux新的内核中,epoll用来替换select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为内核中的select实现是数组数据结构轮询来处理的,轮询的fd数目越多,自然耗时越多。而epoll用于保存事件的数据类型为红黑树,查找速度快。

    6.4 处理客户端请求

        属性服务接收到客户端的请求后会调用handle_property_set_fd函数进行处理,定义在~/LineageOS/system/core/init/property_service.cpp中:

    static void handle_property_set_fd()
    {
        prop_msg msg;
        int s;
        int r;
        struct ucred cr;
        struct sockaddr_un addr;
        socklen_t addr_size = sizeof(addr);
        socklen_t cr_size = sizeof(cr);
        char * source_ctx = NULL;
        struct pollfd ufds[1];
        const int timeout_ms = 2 * 1000;  /* Default 2 sec timeout for caller to send property. */
        int nr;
    
        if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
            return;
        }
    
        /* Check socket options here */
        if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
            close(s);
            ERROR("Unable to receive socket options\n");
            return;
        }
    
        ufds[0].fd = s;
        ufds[0].events = POLLIN;
        ufds[0].revents = 0;
        nr = TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms));
        if (nr == 0) {
            ERROR("sys_prop: timeout waiting for uid=%d to send property message.\n", cr.uid);
            close(s);
            return;
        } else if (nr < 0) {
            ERROR("sys_prop: error waiting for uid=%d to send property message: %s\n", cr.uid, strerror(errno));
            close(s);
            return;
        }
    
        r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));
        if(r != sizeof(prop_msg)) {
            ERROR("sys_prop: mis-match msg size received: %d expected: %zu: %s\n",
                  r, sizeof(prop_msg), strerror(errno));
            close(s);
            return;
        }
    
        switch(msg.cmd) {
        case PROP_MSG_SETPROP:
            msg.name[PROP_NAME_MAX-1] = 0;
            msg.value[PROP_VALUE_MAX-1] = 0;
    
            if (!is_legal_property_name(msg.name, strlen(msg.name))) {
                ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
                close(s);
                return;
            }
    
            getpeercon(s, &source_ctx);
    
            if(memcmp(msg.name,"ctl.",4) == 0) {
                // Keep the old close-socket-early behavior when handling
                // ctl.* properties.
                close(s);
                if (check_control_mac_perms(msg.value, source_ctx, &cr)) {
                    handle_control_message((char*) msg.name + 4, (char*) msg.value);
                } else {
                    ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
                            msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
                }
            } else {
                if (check_mac_perms(msg.name, source_ctx, &cr)) {
                    property_set((char*) msg.name, (char*) msg.value);
                } else {
                    ERROR("sys_prop: permission denied uid:%d  name:%s\n",
                          cr.uid, msg.name);
                }
    
                // Note: bionic's property client code assumes that the
                // property server will not close the socket until *AFTER*
                // the property is written to memory.
                close(s);
            }
            freecon(source_ctx);
            break;
    
        default:
            close(s);
            break;
        }
    }
    

    7 总结

        init进程的工作内容很多,主要的就三部分:

    1. 创建并挂载启动所需的文件系统及目录;
    2. 初始化、启动属性服务;
    3. 解析init.rc脚本文件并按照脚本文件中的指令启动各系统服务,如Zygote、Linux层守护进程、ServiceManager等;

    Enjoy it!!

        下一篇分析:cm-14.1 Android系统启动过程分析(二)-Zygote进程启动过程

    展开全文
  • Windows 7 属性/右键没有共享选项

    千次阅读 2017-07-26 17:35:23
    我遇到的情况是命令行【msconfig】把相关的服务禁用了,导致右键的共享选项没有了。 :把下面选中的两项设置为自动启动,基本就可以解决问题了。

    我遇到的情况是命令行【msconfig】把相关的服务禁用了,导致右键的共享选项没有了。

    :把下面选中的两项设置为自动启动,基本就可以解决问题了。



    展开全文
  • 进入华硕BIOS中,按F7进入设置,点击启动后下拉,在【启动选项属性】一栏中有多个启动选项 #可以设置。 但是下拉选项中不显示P3 60G。 尽管可以下面的【启动设备选择】手动点击该启动项,但是每次开机都手动点击太...

    前言

    我的电脑有两块硬盘,Windows系统安装在固态硬盘中,只有[P3 60G]这个引导项能正常进入Windows系统,但是没法把他设置成【启动选项#1】

    问题

    进入华硕BIOS中,按F7进入设置,点击启动后下拉,在【启动选项属性】一栏中有多个启动选项 #可以设置。

    但是下拉选项中不显示P3 60G。

    尽管可以下面的【启动设备选择】手动点击该启动项,但是每次开机都手动点击太麻烦。

    解决

    可以通过改变 Hard Drive BBS Priorities,切换简中意思就是【硬盘BBS属性】 来让这个P3 60G可以被改成启动选项#1

    在"启动选项 #"的下面,点击硬盘bbs属性,设置第一个硬盘为 固态硬盘

    再次返回到【启动选项属性】,就可以修改[启动选项 #1] 为[P3 60G]了。

    最后按F10保存退出即可。

    展开全文
  • 项目属性包含两个选项卡,分别是 Info (信息)选项卡和 Build Settings 选项卡。 Info 选项卡主要有三个分组:  Deployment ( |dɪˈplɔɪmənt| noun 部署 ) Target (部署对象):用来定义项目所有...
  • VS项目属性中MFC的使用选项

    千次阅读 2016-03-31 14:58:49
    在VS的IDE中,打开项目属性对话框,里面有一个设置项---MFC的使用,如下图:   有三种选择: 1、使用标准 Windows库:即不适用MFC; 2、在静态库中使用MFC:程序编译时把用到的MFC DLL 的二进制...
  • 项目属性设置包含两个选项卡,分别是info(信息)选项卡和Build Settings选项卡。info选项卡主要有三个分组:、 Deployment Target(部署对象):用来定义项目所有对象的最低OS版本; Configuration(配置):项目所有对象...
  • 当运行安装程序时,可使用命令行选项更改安装程序用于安装应用程序的某些参数,例如安装程序属性的显示设置和默认值。 安装程序和 Windows 安装程序使用属性来控制应用程序安装过程。安装程序属性的默认值在 Windows...
  • 修改XP,WIN7启动引导项属性

    千次阅读 2010-12-13 12:44:00
    修改引导设置,例如XP系统下安装了WINPE,修改是非常简单,右键我的电脑---属性----高级----启动和故障恢复----设置,在这里可以对默认操作系统选择,等待时间等做修改,想修改显示文本,点击编辑,找到对应的显示...
  • 需借助工具软媒魔方,下载地址:http://mofang.ruanmei.com/,下载绿色版,免安装,装在U盘里四处秀技能...设置大师,或者直接在绿色版的文件夹里找winmaster.exe,双击启动即可。 2、选择顶部右侧“右键菜单”。 ...
  • 依次打开组件服务-计算机-我的电脑会出现红色向下箭头,右键找不到属性选项。解决办法如下:在“服务”里找到这三个服务,都启动就行了Distributed Transaction CoordinatorRemote Procedure Call (RPC)Security ...
  • Dubbo之启动时检查(check属性)。

    万次阅读 2018-03-07 17:29:16
    dubbo.registry.check=false,前面两个都是指订阅成功,但提供者列表是否为空是否报错,如果注册订阅失败时,也允许启动,需使用此选项,将在后台定时重试。  引用缺省是延迟初始化的,只有引用被注入到其他Bean...
  • selenium 定制启动 chrome 的选项

    万次阅读 多人点赞 2016-07-07 14:22:59
    定制启动选项 我们最常用的是三个功能 添加chrome启动参数 修改chrome设置 添加扩展应用 下面以 python 为例一一说明,其他语言可以参考 selenium 源码 添加 chrome 启动参数 # 启动时...
  • 解决方法: 1.控制面板---文件夹选项---查看----取消
  • 使用 selenium 时,我们可能需要对 chrome 做一些特殊的设置,以完成我们期望的浏览器行为,比如阻止图片加载,阻止JavaScript...chromeoptions 是一个方便控制 chrome 启动属性的类。通过 selenium 的源码,可以看到
  • 解决属性没有共享选项的问题: http://www.xitongcheng.com/jiaocheng/win7_article_22671.html 解决在局域网中看不到另外一台机器的问题。 https://jingyan.baidu.com/article/fec7a1e53efe621190b4e7ae.html 1、...
  • 文件属性及资源文件的使用

    千次阅读 2015-02-07 13:22:41
    一、C#文件属性1、什么是文件属性文件属性可以用来指示项目系统对应文件执行的操作。具体的操作见下文。Visual Basic和Visual C#的文件都具有4个属性:“FileName”、“BuildAcition”、“CustomTool”、”...
  • 打开Visual Studio 2005 的工具>选项>设备工具时提示:“加载此属性页时出错!”处理方法:1、在“Visual Studio 2005 命令提示”中运行:devenv /ResetSkipPkgs2、重启vs2005,打开vs 2005的工具中的选项,就OK了。...
  • 最近重新装了一个Win8。1的系统,不知怎么的,一开机就弹出由于启动计算机时出现了页面...两种方法都需要打开系统的高级设置:右击我的电脑选择属性—>选择高级系统设置—>在高级选项中,找到虚拟内存的这一项,点击更
  • 本文转载连接:https://blog.csdn.net/taoerit/article/details/77062549 网上下载了vs项目,我用vs2010打开,发现提示 解决方法: 项目->属性,如图所示,改成v100 ...
  • hbm2ddl.auto 属性 create、create-drop、validation、update选项解释1、先来看看hibernate官方给出的解释 选项 解释 create Database dropping will be generated followed by database creation. create-drop ...
  • 使用项目属性

    千次阅读 2014-11-21 09:37:58
    在 IDE 中,所有编译器选项、链接器选项、调试器设置和自定义生成步骤都显示为“属性”。 可以使用项目的属性页查看和修改项目的属性。 你可以将项目属性独立应用于生成配置(调试或发布)和目标平台(Win32、x64 ...
  • 第一种方法运行里输入"gpedit.msc",单击[确定]即可启动组策略编辑器.然后依次展开[用户配置]-[管理模板]-[控制面板]-[显示],可以根据自己的需要点隐藏桌面选项卡,双击,改成已禁用,应该你的电脑是已启用,所以造成...
  • java -D 配置系统属性

    万次阅读 2017-02-13 19:06:39
    我们都知道在启动tomcat或直接执行java命令的时候可以通过参数-XX等来配置虚拟机的大小,同样,也应该留意到java -Dkey=value的参数。那么这个参数是什么作用呢?使用案例其实,在不知不觉中我们已经在使用-D的参数...
  • VB控件属性大全

    万次阅读 多人点赞 2018-06-14 08:54:57
    1.01、窗体(FORM)的常用属性... 21.02、标签控件(Lable)的常用属性... 71.03、文本框控件(TextBox)的常用属性... 91.04、框架控件(Frame)的常用属性... 131.05、命令按钮控件(CommandButton)的常用属性... 151.06、...
  • 有时候电脑中毒需要在安全模式下杀毒,想使用Windows XP默认的还原系统也得在安全模式下进行....或者右键点击我的电脑>>属性(Win键+Break) 进入高级选项卡,设置启动和故障恢复,点击编辑. 在Boot.ini文件中加入以下内容:
  • Java Swing 2D系统属性参数详解

    千次阅读 2011-10-07 10:32:56
    Java Swing 2D系统属性参数详解 -Dsun.java2d.opengl=true // 如果硬件加速已经被enable,可以通过这个选项来提高Swing GUI 速度,默认值为false -Dsun.java2d.trace=[log[,time
  • 树莓派4B不插HDMI线无法启动,以及分辨率没有1920*1080选项解决方法1.打开/boot目录下的config.txt2.修改配置3.重启电脑 最近发现树莓派突然出现了问题,VNC总是莫名其妙连不上,检查后发现是树莓派没有正常启动,多...
  • visual studio(VS2015)路径和...1.常规选项: a)输出目录 输出目录就是.exe,.ilk,*.pdb文件所在目录:(SolutionDir)(SolutionDir)(Configuration)\ 其中: (SolutionDir)为解决方案所在目录,D:\MYProjects\Tes

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 331,748
精华内容 132,699
关键字:

启动选项属性