之前写过一篇关于android5.0 init的介绍,这篇博客是介绍android6.0init,之前有的代码介绍不详细。而且分析 解析init.rc那块代码也没有结合init.rc介绍。

一、 main函数的一些准备工作

下面我们分析下源码:

[cpp] view plain copy

  1. int main(int argc, char** argv) {
  2. if (!strcmp(basename(argv[0]), "ueventd")) {
  3. return ueventd_main(argc, argv);
  4. }
  5. if (!strcmp(basename(argv[0]), "watchdogd")) {
  6. return watchdogd_main(argc, argv);
  7. }

由于ueventd watchdogd是公用代码,所以启动的时候根据文件名来判断是哪个进程,继续分析:

[cpp] view plain copy

  1. // Clear the umask.
  2. umask(0);
  3. add_environment("PATH", _PATH_DEFPATH);//添加环境变量
  4. bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);
  5. // Get the basic filesystem setup we need put together in the initramdisk
  6. // on / and then we'll let the rc file figure out the rest.
  7. if (is_first_stage) {
  8. mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
  9. mkdir("/dev/pts", 0755);
  10. mkdir("/dev/socket", 0755);
  11. mount("devpts", "/dev/pts", "devpts", 0, NULL);
  12. mount("proc", "/proc", "proc", 0, NULL);
  13. mount("sysfs", "/sys", "sysfs", 0, NULL);
  14. }

这块代码主要添加环境变量,以及挂载各种文件系统。

[cpp] view plain copy

  1. open_devnull_stdio();
  2. klog_init();
  3. klog_set_level(KLOG_NOTICE_LEVEL);//log的初始化
  4. NOTICE("init%s started!\n", is_first_stage ? "" : " second stage");
  5. if (!is_first_stage) {
  6. // Indicate that booting is in progress to background fw loaders, etc.
  7. close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));//启动的时候创建一个.booting文件
  8. property_init();//属性初始化
  9. // If arguments are passed both on the command line and in DT,
  10. // properties set in DT always have priority over the command-line ones.
  11. process_kernel_dt();
  12. process_kernel_cmdline();
  13. // Propogate the kernel variables to internal variables
  14. // used by init as well as the current required properties.
  15. export_kernel_boot_props();//设置一些属性
  16. }

这里我们有没有注意到is_first_stage这个变量,我们再来往下看。如果是is_first_stage会再执行execv函数,重新启动init。这个时候参数是"--second-stage"

[cpp] view plain copy

  1. if (is_first_stage) {
  2. if (restorecon("/init") == -1) {
  3. ERROR("restorecon failed: %s\n", strerror(errno));
  4. security_failure();
  5. }
  6. char* path = argv[0];
  7. char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
  8. if (execv(path, args) == -1) {
  9. ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
  10. security_failure();
  11. }
  12. }

这个时候再启动,也就是if_first_stage为false。这个时候参数有"--second-stage"了

[cpp] view plain copy

  1. bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);

我们再看上面函数先是open_devnull_stdio函数,这个函数就是把标准输入,输出,错误输出重定义到空设备上。然后创建一个 .booting文件代表系统在启动,做了一些属性的初始化,以及一些boot相关的系统属性设置获取等。我们先看下open_devnull_stdio代码:

[cpp] view plain copy

  1. void open_devnull_stdio(void)
  2. {
  3. // Try to avoid the mknod() call if we can. Since SELinux makes
  4. // a /dev/null replacement available for free, let's use it.
  5. int fd = open("/sys/fs/selinux/null", O_RDWR);
  6. if (fd == -1) {
  7. // OOPS, /sys/fs/selinux/null isn't available, likely because
  8. // /sys/fs/selinux isn't mounted. Fall back to mknod.
  9. static const char *name = "/dev/__null__";
  10. if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {
  11. fd = open(name, O_RDWR);
  12. unlink(name);
  13. }
  14. if (fd == -1) {
  15. exit(1);
  16. }
  17. }
  18. dup2(fd, 0);
  19. dup2(fd, 1);
  20. dup2(fd, 2);
  21. if (fd > 2) {
  22. close(fd);
  23. }
  24. }

property_init()函数主要是属性的初始化,这个我们在之前分析属性系统的那篇博客分析过了。
我们再来看process_kernel_dt函数

[cpp] view plain copy

  1. static void process_kernel_dt(void)
  2. {
  3. static const char android_dir[] = "/proc/device-tree/firmware/android";
  4. std::string file_name = android::base::StringPrintf("%s/compatible", android_dir);
  5. std::string dt_file;
  6. android::base::ReadFileToString(file_name, &dt_file);
  7. if (!dt_file.compare("android,firmware")) {//compatible文件内容是否是android,firmware
  8. ERROR("firmware/android is not compatible with 'android,firmware'\n");
  9. return;
  10. }
  11. std::unique_ptr<DIR, int(*)(DIR*)>dir(opendir(android_dir), closedir);
  12. if (!dir)
  13. return;
  14. struct dirent *dp;
  15. while ((dp = readdir(dir.get())) != NULL) {//读取目录的每个文件
  16. if (dp->d_type != DT_REG || !strcmp(dp->d_name, "compatible"))
  17. continue;
  18. file_name = android::base::StringPrintf("%s/%s", android_dir, dp->d_name);
  19. android::base::ReadFileToString(file_name, &dt_file);
  20. std::replace(dt_file.begin(), dt_file.end(), ',', '.');
  21. std::string property_name = android::base::StringPrintf("ro.boot.%s", dp->d_name);//每个文件名作为属性名,里面的内容作为属性值
  22. property_set(property_name.c_str(), dt_file.c_str());
  23. }
  24. }

上面这个函数主要是在/proc/device-tree/firmware/Android 这个目录下,先看compatible文件内容是否是android,firmware。然后这个目录下每个文件名作为属性,文件里面的内容作为属性值。这里话就是ro.boot.hareware ro.boot.name这两个属性值。

[plain] view plain copy

  1. root@lte26007:/proc/device-tree/firmware/android # ls
  2. compatible
  3. hardware
  4. name

继续看process_kernel_cmdline函数

[cpp] view plain copy

  1. static void process_kernel_cmdline(void)
  2. {
  3. /* don't expose the raw commandline to nonpriv processes */
  4. chmod("/proc/cmdline", 0440);
  5. /* first pass does the common stuff, and finds if we are in qemu.
  6. * second pass is only necessary for qemu to export all kernel params
  7. * as props.
  8. */
  9. import_kernel_cmdline(false, import_kernel_nv);
  10. if (qemu[0])
  11. import_kernel_cmdline(true, import_kernel_nv);
  12. }

import_kernel_cmdline函数就是读取proc/cmdline中的内容,然后调用import_kernel_nv函数设置系统属性

[cpp] view plain copy

  1. void import_kernel_cmdline(bool in_qemu, std::function<void(char*,bool)> import_kernel_nv)
  2. {
  3. char cmdline[2048];
  4. char *ptr;
  5. int fd;
  6. fd = open("/proc/cmdline", O_RDONLY | O_CLOEXEC);
  7. if (fd >= 0) {
  8. int n = read(fd, cmdline, sizeof(cmdline) - 1);
  9. if (n < 0) n = 0;
  10. /* get rid of trailing newline, it happens */
  11. if (n > 0 && cmdline[n-1] == '\n') n--;
  12. cmdline[n] = 0;
  13. close(fd);
  14. } else {
  15. cmdline[0] = 0;
  16. }
  17. ptr = cmdline;
  18. while (ptr && *ptr) {
  19. char *x = strchr(ptr, ' ');
  20. if (x != 0) *x++ = 0;
  21. import_kernel_nv(ptr, in_qemu);
  22. ptr = x;
  23. }
  24. }

在import_kernel_nv函数中设置系统属性,但是一定要有androidboot这样的关键字眼才会设置ro.boot这样的属性。这块在我们的设备cmdline中没有这样的字眼,也就不会设置这些属性。

[cpp] view plain copy

  1. static void import_kernel_nv(char *name, bool for_emulator)
  2. {
  3. char *value = strchr(name, '=');
  4. int name_len = strlen(name);
  5. if (value == 0) return;
  6. *value++ = 0;
  7. if (name_len == 0) return;
  8. if (for_emulator) {
  9. /* in the emulator, export any kernel option with the
  10. * ro.kernel. prefix */
  11. char buff[PROP_NAME_MAX];
  12. int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );
  13. if (len < (int)sizeof(buff))
  14. property_set( buff, value );
  15. return;
  16. }
  17. if (!strcmp(name,"qemu")) {
  18. strlcpy(qemu, value, sizeof(qemu));
  19. } else if (!strncmp(name, "androidboot.", 12) && name_len > 12) {
  20. const char *boot_prop_name = name + 12;
  21. char prop[PROP_NAME_MAX];
  22. int cnt;
  23. cnt = snprintf(prop, sizeof(prop), "ro.boot.%s", boot_prop_name);
  24. if (cnt < PROP_NAME_MAX)
  25. property_set(prop, value);
  26. }
  27. }

再来看export_kernel_boot_props这个函数,它也就是设置一些属性,设置ro属性根据之前ro.boot这类的属性值,如果没有设置成unknown,像之前我们有ro.boot.hardware, 那我们就可以设置root.hardware这样的属性。

[cpp] view plain copy

  1. static void export_kernel_boot_props() {
  2. struct {
  3. const char *src_prop;
  4. const char *dst_prop;
  5. const char *default_value;
  6. } prop_map[] = {
  7. //{ "ro.boot.serialno",   "ro.serialno",   "", },
  8. { "ro.boot.mode",       "ro.bootmode",   "unknown", },
  9. { "ro.boot.baseband",   "ro.baseband",   "unknown", },
  10. { "ro.boot.bootloader", "ro.bootloader", "unknown", },
  11. { "ro.boot.hardware",   "ro.hardware",   "unknown", },
  12. { "ro.boot.revision",   "ro.revision",   "0", },
  13. };
  14. for (size_t i = 0; i < ARRAY_SIZE(prop_map); i++) {
  15. char value[PROP_VALUE_MAX];
  16. int rc = property_get(prop_map[i].src_prop, value);
  17. property_set(prop_map[i].dst_prop, (rc > 0) ? value : prop_map[i].default_value);
  18. }
  19. }

下面这块都是selinux相关的,我们就不分析了。

[cpp] view plain copy

  1. // Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
  2. selinux_initialize(is_first_stage);
  3. // If we're in the kernel domain, re-exec init to transition to the init domain now
  4. // that the SELinux policy has been loaded.
  5. if (is_first_stage) {
  6. if (restorecon("/init") == -1) {
  7. ERROR("restorecon failed: %s\n", strerror(errno));
  8. security_failure();
  9. }
  10. char* path = argv[0];
  11. char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
  12. if (execv(path, args) == -1) {
  13. ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
  14. security_failure();
  15. }
  16. }
  17. // These directories were necessarily created before initial policy load
  18. // and therefore need their security context restored to the proper value.
  19. // This must happen before /dev is populated by ueventd.
  20. INFO("Running restorecon...\n");
  21. restorecon("/dev");
  22. restorecon("/dev/socket");
  23. restorecon("/dev/__properties__");
  24. restorecon_recursive("/sys");

然后创建了一个epoll的fd

[cpp] view plain copy

  1. epoll_fd = epoll_create1(EPOLL_CLOEXEC);
  2. if (epoll_fd == -1) {
  3. ERROR("epoll_create1 failed: %s\n", strerror(errno));
  4. exit(1);
  5. }

继续分析,signal_handler_init函数主要是当子进程被kill之后,会在父进程接受一个信号。处理这个信号的时候往sockpair一端写数据,而另一端的fd是加入的epoll中。这块我们后面会专门其一节讲解。而property_load_boot_defaults就是解析根目录的default.prop中的属性,然后设置到属性中去。start_prperty_service就是把接受属性的socket的fd加入epoll中,也定义了处理函数,属性之前博客专门分析过了。

[cpp] view plain copy

  1. signal_handler_init();
  2. property_load_boot_defaults();
  3. start_property_service();

看看signal_handler_init函数就是处理子进程kill时的情况。

[cpp] view plain copy

  1. static void SIGCHLD_handler(int) {
  2. if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
  3. ERROR("write(signal_write_fd) failed: %s\n", strerror(errno));
  4. }
  5. }
  6. void signal_handler_init() {
  7. // Create a signalling mechanism for SIGCHLD.
  8. int s[2];
  9. if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
  10. ERROR("socketpair failed: %s\n", strerror(errno));
  11. exit(1);
  12. }
  13. signal_write_fd = s[0];
  14. signal_read_fd = s[1];
  15. // Write to signal_write_fd if we catch SIGCHLD.
  16. struct sigaction act;
  17. memset(&act, 0, sizeof(act));
  18. act.sa_handler = SIGCHLD_handler;
  19. act.sa_flags = SA_NOCLDSTOP;
  20. sigaction(SIGCHLD, &act, 0);
  21. reap_any_outstanding_children();
  22. register_epoll_handler(signal_read_fd, handle_signal);
  23. }

二、解析init.rc

下面我们开始分析解析init.rc并且结合init.rc一起看

init.rc的语言我们可以看这篇博客,主要是init.rc主要有Actions和Service两种,具体看这篇博客http://blog.csdn.net/kc58236582/article/details/52042331

我们通过init_parse_config_file函数来解析init.rc,先把文件数据读取到data中,然后调用parse_config来解析数据。

[cpp] view plain copy

  1. int init_parse_config_file(const char* path) {
  2. INFO("Parsing %s...\n", path);
  3. Timer t;
  4. std::string data;
  5. if (!read_file(path, &data)) {
  6. return -1;
  7. }
  8. data.push_back('\n'); // TODO: fix parse_config.
  9. parse_config(path, data);
  10. dump_parser_state();
  11. NOTICE("(Parsing %s took %.2fs.)\n", path, t.duration());
  12. return 0;
  13. }

我们先来看看dump_parser_state函数,当解析完之后我们可以在这个函数中打印所有的service和action。

[cpp] view plain copy

  1. void dump_parser_state() {
  2. if (false) {
  3. struct listnode* node;
  4. list_for_each(node, &service_list) {
  5. service* svc = node_to_item(node, struct service, slist);
  6. INFO("service %s\n", svc->name);
  7. INFO("  class '%s'\n", svc->classname);
  8. INFO("  exec");
  9. for (int n = 0; n < svc->nargs; n++) {
  10. INFO(" '%s'", svc->args[n]);
  11. }
  12. INFO("\n");
  13. for (socketinfo* si = svc->sockets; si; si = si->next) {
  14. INFO("  socket %s %s 0%o\n", si->name, si->type, si->perm);
  15. }
  16. }
  17. list_for_each(node, &action_list) {
  18. action* act = node_to_item(node, struct action, alist);
  19. INFO("on ");
  20. char name_str[256] = "";
  21. build_triggers_string(name_str, sizeof(name_str), act);
  22. INFO("%s", name_str);
  23. INFO("\n");
  24. struct listnode* node2;
  25. list_for_each(node2, &act->commands) {
  26. command* cmd = node_to_item(node2, struct command, clist);
  27. INFO("  %p", cmd->func);
  28. for (int n = 0; n < cmd->nargs; n++) {
  29. INFO(" %s", cmd->args[n]);
  30. }
  31. INFO("\n");
  32. }
  33. INFO("\n");
  34. }
  35. }
  36. }

好回到正题看parse_config函数,来解析从init.rc文件中获取的数据。

[cpp] view plain copy

  1. static void parse_config(const char *fn, const std::string& data)
  2. {
  3. struct listnode import_list;
  4. struct listnode *node;
  5. char *args[INIT_PARSER_MAXARGS];
  6. int nargs = 0;
  7. parse_state state;
  8. state.filename = fn;
  9. state.line = 0;
  10. state.ptr = strdup(data.c_str());  // TODO: fix this code!
  11. state.nexttoken = 0;
  12. state.parse_line = parse_line_no_op;//这里的函数是空实现
  13. list_init(&import_list);
  14. state.priv = &import_list;
  15. for (;;) {
  16. switch (next_token(&state)) {
  17. case T_EOF:
  18. state.parse_line(&state, 0, 0);
  19. goto parser_done;
  20. case T_NEWLINE:
  21. state.line++;
  22. if (nargs) {
  23. int kw = lookup_keyword(args[0]);
  24. if (kw_is(kw, SECTION)) {
  25. state.parse_line(&state, 0, 0);
  26. parse_new_section(&state, kw, nargs, args);
  27. } else {
  28. state.parse_line(&state, nargs, args);
  29. }
  30. nargs = 0;
  31. }
  32. break;
  33. case T_TEXT:
  34. if (nargs < INIT_PARSER_MAXARGS) {
  35. args[nargs++] = state.text;
  36. }
  37. break;
  38. }
  39. }
  40. parser_done:
  41. list_for_each(node, &import_list) {
  42. struct import *import = node_to_item(node, struct import, list);
  43. int ret;
  44. ret = init_parse_config_file(import->filename);
  45. if (ret)
  46. ERROR("could not import file '%s' from '%s'\n",
  47. import->filename, fn);
  48. }
  49. }

我们先来看看next_token函数,我们来看下这个函数,

[cpp] view plain copy

  1. int next_token(struct parse_state *state)
  2. {
  3. char *x = state->ptr;
  4. char *s;
  5. if (state->nexttoken) {//刚进来为空
  6. int t = state->nexttoken;
  7. state->nexttoken = 0;
  8. return t;
  9. }
  10. for (;;) {
  11. switch (*x) {
  12. case 0:
  13. state->ptr = x;
  14. return T_EOF;
  15. case '\n':
  16. x++;
  17. state->ptr = x;
  18. return T_NEWLINE;
  19. case ' ':
  20. case '\t':
  21. case '\r':
  22. x++;
  23. continue;
  24. case '#':
  25. while (*x && (*x != '\n')) x++;
  26. if (*x == '\n') {
  27. state->ptr = x+1;
  28. return T_NEWLINE;
  29. } else {
  30. state->ptr = x;
  31. return T_EOF;
  32. }
  33. default://刚进来肯定直接是这个
  34. goto text;
  35. }
  36. }
  37. textdone:
  38. state->ptr = x;
  39. *s = 0;
  40. return T_TEXT;
  41. text:
  42. state->text = s = x;//赋值state->text
  43. textresume:
  44. for (;;) {
  45. switch (*x) {
  46. case 0:
  47. goto textdone;
  48. case ' ':
  49. case '\t':
  50. case '\r'://碰到空什么的,直接返回T_TEXT
  51. x++;
  52. goto textdone;
  53. case '\n':
  54. state->nexttoken = T_NEWLINE;//碰到回车换行直接nexttoken是newline
  55. x++;
  56. goto textdone;
  57. case '"':
  58. x++;
  59. for (;;) {
  60. switch (*x) {
  61. case 0:
  62. /* unterminated quoted thing */
  63. state->ptr = x;
  64. return T_EOF;
  65. case '"':
  66. x++;
  67. goto textresume;
  68. default:
  69. *s++ = *x++;
  70. }
  71. }
  72. break;
  73. case '\\':
  74. x++;
  75. switch (*x) {
  76. case 0:
  77. goto textdone;
  78. case 'n':
  79. *s++ = '\n';
  80. break;
  81. case 'r':
  82. *s++ = '\r';
  83. break;
  84. case 't':
  85. *s++ = '\t';
  86. break;
  87. case '\\':
  88. *s++ = '\\';
  89. break;
  90. case '\r':
  91. /* \ <cr> <lf> -> line continuation */
  92. if (x[1] != '\n') {
  93. x++;
  94. continue;
  95. }
  96. case '\n':
  97. /* \ <lf> -> line continuation */
  98. state->line++;
  99. x++;
  100. /* eat any extra whitespace */
  101. while((*x == ' ') || (*x == '\t')) x++;
  102. continue;
  103. default:
  104. /* unknown escape -- just copy */
  105. *s++ = *x++;
  106. }
  107. continue;
  108. default:
  109. *s++ = *x++;//一般的值继续往前走
  110. }
  111. }
  112. return T_EOF;
  113. }

看这个函数的代码,我们只需要知道。当我们普通的进来,没有碰到换行,只有碰到空格的话,返回T_TEXT,并且nextoken为null。

我们再来看T_TEXT的时候只是在数组里面保存了state.text的内容,然后继续下一次。当我们直到碰到/n,回车换行。这个时候返回T_TEXT,但是nexttoken是T_NEWLINE

这样下次,就直接返回T_NEWLINE了,当返回T_NEWLINE直接调用lookup_keyword函数。

[cpp] view plain copy

  1. for (;;) {
  2. switch (next_token(&state)) {
  3. case T_EOF:
  4. state.parse_line(&state, 0, 0);
  5. goto parser_done;
  6. case T_NEWLINE:
  7. state.line++;
  8. if (nargs) {
  9. int kw = lookup_keyword(args[0]);
  10. if (kw_is(kw, SECTION)) {
  11. state.parse_line(&state, 0, 0);
  12. parse_new_section(&state, kw, nargs, args);
  13. } else {
  14. state.parse_line(&state, nargs, args);
  15. }
  16. nargs = 0;
  17. }
  18. break;
  19. case T_TEXT:
  20. if (nargs < INIT_PARSER_MAXARGS) {
  21. args[nargs++] = state.text;
  22. }
  23. break;
  24. }
  25. }

lookup_keyword函数就是看第一个单词返回一个K_**的值而已。

[cpp] view plain copy

  1. static int lookup_keyword(const char *s)
  2. {
  3. switch (*s++) {
  4. case 'b':
  5. if (!strcmp(s, "ootchart_init")) return K_bootchart_init;
  6. break;
  7. case 'c':
  8. if (!strcmp(s, "opy")) return K_copy;
  9. if (!strcmp(s, "lass")) return K_class;
  10. if (!strcmp(s, "lass_start")) return K_class_start;
  11. if (!strcmp(s, "lass_stop")) return K_class_stop;
  12. if (!strcmp(s, "lass_reset")) return K_class_reset;
  13. if (!strcmp(s, "onsole")) return K_console;
  14. if (!strcmp(s, "hown")) return K_chown;
  15. if (!strcmp(s, "hmod")) return K_chmod;
  16. if (!strcmp(s, "ritical")) return K_critical;
  17. break;
  18. case 'd':
  19. if (!strcmp(s, "isabled")) return K_disabled;
  20. if (!strcmp(s, "omainname")) return K_domainname;
  21. break;
  22. case 'e':
  23. if (!strcmp(s, "nable")) return K_enable;
  24. if (!strcmp(s, "xec")) return K_exec;
  25. if (!strcmp(s, "xport")) return K_export;
  26. break;
  27. case 'g':
  28. if (!strcmp(s, "roup")) return K_group;
  29. break;
  30. case 'h':
  31. if (!strcmp(s, "ostname")) return K_hostname;
  32. break;
  33. case 'i':
  34. if (!strcmp(s, "oprio")) return K_ioprio;
  35. if (!strcmp(s, "fup")) return K_ifup;
  36. if (!strcmp(s, "nsmod")) return K_insmod;
  37. if (!strcmp(s, "mport")) return K_import;
  38. if (!strcmp(s, "nstallkey")) return K_installkey;
  39. break;
  40. case 'k':
  41. if (!strcmp(s, "eycodes")) return K_keycodes;
  42. break;
  43. case 'l':
  44. if (!strcmp(s, "oglevel")) return K_loglevel;
  45. if (!strcmp(s, "oad_persist_props")) return K_load_persist_props;
  46. if (!strcmp(s, "oad_system_props")) return K_load_system_props;
  47. break;
  48. case 'm':
  49. if (!strcmp(s, "kdir")) return K_mkdir;
  50. if (!strcmp(s, "ount_all")) return K_mount_all;
  51. if (!strcmp(s, "ount")) return K_mount;
  52. break;

再来看这个宏

[cpp] view plain copy

  1. #define kw_is(kw, type) (keyword_info[kw].flags & (type))

来看看它的定义,首先先说下宏定义##代表后面是连接起来的,#代表就是后面这个变量

[cpp] view plain copy

  1. #define KEYWORD(symbol, flags, nargs, func) \
  2. [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
  3. static struct {
  4. const char *name;
  5. int (*func)(int nargs, char **args);
  6. unsigned char nargs;
  7. unsigned char flags;
  8. } keyword_info[KEYWORD_COUNT] = {
  9. [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
  10. #include "keywords.h"
  11. };

这样我们再来看下keywords.h这个头文件,这里就比较明白是它是解析各个关键词是属于SECTION,COMMAND,OPTION的

[cpp] view plain copy

  1. #ifndef KEYWORD//因为前面定义了KEYWORD
  2. int do_bootchart_init(int nargs, char **args);
  3. ......
  4. #endif
  5. KEYWORD(bootchart_init,        COMMAND, 0, do_bootchart_init)
  6. KEYWORD(chmod,       COMMAND, 2, do_chmod)
  7. KEYWORD(chown,       COMMAND, 2, do_chown)
  8. KEYWORD(class,       OPTION,  0, 0)
  9. ......
  10. KEYWORD(import,      SECTION, 1, 0)
  11. .....
  12. .....
  13. KEYWORD(service,     SECTION, 0, 0)
  14. KEYWORD(writepid,    OPTION,  0, 0)
  15. #ifdef __MAKE_KEYWORD_ENUM__
  16. KEYWORD_COUNT,
  17. };
  18. #undef __MAKE_KEYWORD_ENUM__
  19. #undef KEYWORD
  20. #endif

这样我们就可以通过kw_is(kw, SECTION)来判断是否属于SECTION

我们来看下函数,如果是SECTION,刚开始调用state.parse_line也是空实现

[cpp] view plain copy

  1. if (kw_is(kw, SECTION)) {
  2. state.parse_line(&state, 0, 0);
  3. parse_new_section(&state, kw, nargs, args);
  4. } else {
  5. state.parse_line(&state, nargs, args);
  6. }

再来看看parse_new_section函数

[cpp] view plain copy

  1. static void parse_new_section(struct parse_state *state, int kw,
  2. int nargs, char **args)
  3. {
  4. printf("[ %s %s ]\n", args[0],
  5. nargs > 1 ? args[1] : "");
  6. switch(kw) {
  7. case K_service://如果是service
  8. state->context = parse_service(state, nargs, args);
  9. if (state->context) {
  10. state->parse_line = parse_line_service;
  11. return;
  12. }
  13. break;
  14. case K_on://是on
  15. state->context = parse_action(state, nargs, args);
  16. if (state->context) {
  17. state->parse_line = parse_line_action;
  18. return;
  19. }
  20. break;
  21. case K_import://是import
  22. parse_import(state, nargs, args);
  23. break;
  24. }
  25. state->parse_line = parse_line_no_op;
  26. }

2.1 解析service

我们先来看下如果是service,先调用parse_service函数

[cpp] view plain copy

  1. static void *parse_service(struct parse_state *state, int nargs, char **args)
  2. {
  3. if (nargs < 3) {
  4. parse_error(state, "services must have a name and a program\n");
  5. return 0;
  6. }
  7. if (!valid_name(args[1])) {
  8. parse_error(state, "invalid service name '%s'\n", args[1]);
  9. return 0;
  10. }
  11. service* svc = (service*) service_find_by_name(args[1]);//找service
  12. if (svc) {//如果找到该service,说明重复了
  13. parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);
  14. return 0;
  15. }
  16. nargs -= 2;
  17. svc = (service*) calloc(1, sizeof(*svc) + sizeof(char*) * nargs);//new一个service
  18. if (!svc) {
  19. parse_error(state, "out of memory\n");
  20. return 0;
  21. }
  22. svc->name = strdup(args[1]);//各种初始化
  23. svc->classname = "default";
  24. memcpy(svc->args, args + 2, sizeof(char*) * nargs);
  25. trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
  26. svc->args[nargs] = 0;
  27. svc->nargs = nargs;
  28. list_init(&svc->onrestart.triggers);
  29. cur_trigger->name = "onrestart";
  30. list_add_tail(&svc->onrestart.triggers, &cur_trigger->nlist);
  31. list_init(&svc->onrestart.commands);
  32. list_add_tail(&service_list, &svc->slist);//把service放进service_list
  33. return svc;
  34. }

state->parse_line赋值了parse_line_service函数了。然后我们再出这个函数看看,当你再来一行新的,这个时候不是SECTION,就要调用parse_line_service函数来解析了。

[cpp] view plain copy

  1. case T_NEWLINE:
  2. state.line++;
  3. if (nargs) {
  4. int kw = lookup_keyword(args[0]);
  5. if (kw_is(kw, SECTION)) {
  6. state.parse_line(&state, 0, 0);
  7. parse_new_section(&state, kw, nargs, args);
  8. } else {
  9. state.parse_line(&state, nargs, args);
  10. }
  11. nargs = 0;
  12. }
  13. break;

我们来看下parse_line_service函数:下面就是解析各种参数,然后填充service变量而已。

[cpp] view plain copy

  1. static void parse_line_service(struct parse_state *state, int nargs, char **args)
  2. {
  3. struct service *svc = (service*) state->context;
  4. struct command *cmd;
  5. int i, kw, kw_nargs;
  6. if (nargs == 0) {
  7. return;
  8. }
  9. svc->ioprio_class = IoSchedClass_NONE;
  10. kw = lookup_keyword(args[0]);
  11. switch (kw) {
  12. case K_class:
  13. if (nargs != 2) {
  14. parse_error(state, "class option requires a classname\n");
  15. } else {
  16. svc->classname = args[1];
  17. }
  18. break;
  19. case K_console:
  20. svc->flags |= SVC_CONSOLE;
  21. break;
  22. case K_disabled:

2.2 解析on关键字

下面我们来看下解析on关键字的

[cpp] view plain copy

  1. case K_on:
  2. state->context = parse_action(state, nargs, args);
  3. if (state->context) {
  4. state->parse_line = parse_line_action;
  5. return;
  6. }
  7. break;

先看下parse_action函数

[cpp] view plain copy

  1. static void *parse_action(struct parse_state *state, int nargs, char **args)
  2. {
  3. struct trigger *cur_trigger;
  4. int i;
  5. if (nargs < 2) {
  6. parse_error(state, "actions must have a trigger\n");
  7. return 0;
  8. }
  9. action* act = (action*) calloc(1, sizeof(*act));//新建aciton
  10. list_init(&act->triggers);
  11. for (i = 1; i < nargs; i++) {
  12. if (!(i % 2)) {
  13. if (strcmp(args[i], "&&")) {//有的触发器有几个条件,比如可以两个属性同事满足
  14. struct listnode *node;
  15. struct listnode *node2;
  16. parse_error(state, "& is the only symbol allowed to concatenate actions\n");
  17. list_for_each_safe(node, node2, &act->triggers) {
  18. struct trigger *trigger = node_to_item(node, struct trigger, nlist);
  19. free(trigger);
  20. }
  21. free(act);
  22. return 0;
  23. } else
  24. continue;
  25. }
  26. cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
  27. cur_trigger->name = args[i];
  28. list_add_tail(&act->triggers, &cur_trigger->nlist);
  29. }
  30. list_init(&act->commands);
  31. list_init(&act->qlist);
  32. list_add_tail(&action_list, &act->alist);//把aciton加入action_list中
  33. /* XXX add to hash */
  34. return act;
  35. }

这里新建一个action,然后加入action_list中。主要触发器可以有几个条件。比如满足两个属性要求,然后保存在action的的triggers中。

同样我们再来看看parse_line_action函数,这个函数就是各种命令了。

[cpp] view plain copy

  1. static void parse_line_action(struct parse_state* state, int nargs, char **args)
  2. {
  3. struct action *act = (action*) state->context;
  4. int kw, n;
  5. if (nargs == 0) {
  6. return;
  7. }
  8. kw = lookup_keyword(args[0]);
  9. if (!kw_is(kw, COMMAND)) {
  10. parse_error(state, "invalid command '%s'\n", args[0]);
  11. return;
  12. }
  13. n = kw_nargs(kw);
  14. if (nargs < n) {
  15. parse_error(state, "%s requires %d %s\n", args[0], n - 1,
  16. n > 2 ? "arguments" : "argument");
  17. return;
  18. }
  19. command* cmd = (command*) malloc(sizeof(*cmd) + sizeof(char*) * nargs);
  20. cmd->func = kw_func(kw);
  21. cmd->line = state->line;
  22. cmd->filename = state->filename;
  23. cmd->nargs = nargs;
  24. memcpy(cmd->args, args, sizeof(char*) * nargs);
  25. list_add_tail(&act->commands, &cmd->clist);// 加入到act->commands
  26. }

这里注意是kw_func宏,就是和之前那个宏一样,这里是选择每个命令的处理函数。

2.3 处理import

处理import我们来看下parse_import函数,这个函数很简单就把import的文件名保存在import_list中。

[cpp] view plain copy

  1. static void parse_import(struct parse_state *state, int nargs, char **args)
  2. {
  3. struct listnode *import_list = (listnode*) state->priv;
  4. char conf_file[PATH_MAX];
  5. int ret;
  6. if (nargs != 2) {
  7. ERROR("single argument needed for import\n");
  8. return;
  9. }
  10. ret = expand_props(conf_file, args[1], sizeof(conf_file));
  11. if (ret) {
  12. ERROR("error while handling import on line '%d' in '%s'\n",
  13. state->line, state->filename);
  14. return;
  15. }
  16. struct import* import = (struct import*) calloc(1, sizeof(struct import));
  17. import->filename = strdup(conf_file);
  18. list_add_tail(import_list, &import->list);
  19. INFO("Added '%s' to import list\n", import->filename);
  20. }

最后我们来看下当所有init.rc中的关键字解析完之后,就会遍历import_list,然后调用init_parse_config_file函数再来解析该文件。

[cpp] view plain copy

  1. parser_done:
  2. list_for_each(node, &import_list) {
  3. struct import *import = node_to_item(node, struct import, list);
  4. int ret;
  5. ret = init_parse_config_file(import->filename);
  6. if (ret)
  7. ERROR("could not import file '%s' from '%s'\n",
  8. import->filename, fn);
  9. }

所以一般在init.rc中import的文件,放入action service列表中,会比直接在init.rc中的service和aciton靠后。

三、加入执行队列

在解析init.rc文件后,这节将介绍把Action加入执行队列中。

[cpp] view plain copy

  1. action_for_each_trigger("early-init", action_add_queue_tail);
  2. // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
  3. queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
  4. // ... so that we can start queuing up actions that require stuff from /dev.
  5. queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
  6. queue_builtin_action(keychord_init_action, "keychord_init");
  7. queue_builtin_action(console_init_action, "console_init");
  8. // Trigger all the boot actions to get us started.
  9. action_for_each_trigger("init", action_add_queue_tail);
  10. // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
  11. // wasn't ready immediately after wait_for_coldboot_done
  12. queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
  13. // Don't mount filesystems or start core system services in charger mode.
  14. char bootmode[PROP_VALUE_MAX];
  15. if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
  16. action_for_each_trigger("charger", action_add_queue_tail);
  17. } else {
  18. action_for_each_trigger("late-init", action_add_queue_tail);
  19. }
  20. // Run all property triggers based on current state of the properties.
  21. queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");

我们先来看action_for_each_trigger函数

[cpp] view plain copy

  1. void action_for_each_trigger(const char *trigger,
  2. void (*func)(struct action *act))
  3. {
  4. struct listnode *node, *node2;
  5. struct action *act;
  6. struct trigger *cur_trigger;
  7. list_for_each(node, &action_list) {//遍历每个action
  8. act = node_to_item(node, struct action, alist);
  9. list_for_each(node2, &act->triggers) {//遍历每个action的triggers
  10. cur_trigger = node_to_item(node2, struct trigger, nlist);
  11. if (!strcmp(cur_trigger->name, trigger)) {//是否与传入的trigger名字匹配
  12. func(act);//调用回调函数
  13. }
  14. }
  15. }
  16. }

我们再来看下传入的回调函数action_add_queue_tail,这个函数就是把aciton加入执行列表中。

[cpp] view plain copy

  1. void action_add_queue_tail(struct action *act)
  2. {
  3. if (list_empty(&act->qlist)) {
  4. list_add_tail(&action_queue, &act->qlist);
  5. }
  6. }

1. 这样的话像第一句,就是在所有的aciton中是否有early-init这样的trigger,有的话加入执行列表。

[cpp] view plain copy

  1. action_for_each_trigger("early-init", action_add_queue_tail);

我们看下init.rc中early-init中的内容,设置了init进程的adj,开启ueventd进程等。

[plain] view plain copy

  1. on early-init
  2. # Set init and its forked children's oom_adj.
  3. write /proc/1/oom_score_adj -1000
  4. # Set the security context of /adb_keys if present.
  5. restorecon /adb_keys
  6. start ueventd
  7. #add for amt
  8. mkdir /amt 0775 root system

下面我们再来看下queue_builtin_action函数,这个函数的话就是直接创建一个action,然后新建command,关键是func会调函数设置好。最后把action加入执行队列中。

[plain] view plain copy

  1. void queue_builtin_action(int (*func)(int nargs, char **args), const char *name)
  2. {
  3. action* act = (action*) calloc(1, sizeof(*act));
  4. trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
  5. cur_trigger->name = name;
  6. list_init(&act->triggers);
  7. list_add_tail(&act->triggers, &cur_trigger->nlist);
  8. list_init(&act->commands);
  9. list_init(&act->qlist);
  10. command* cmd = (command*) calloc(1, sizeof(*cmd));
  11. cmd->func = func;
  12. cmd->args[0] = const_cast<char*>(name);
  13. cmd->nargs = 1;
  14. list_add_tail(&act->commands, &cmd->clist);
  15. list_add_tail(&action_list, &act->alist);
  16. action_add_queue_tail(act);
  17. }

2. 因此这里我们看下wait_for_coldboot_done_action函数,这函数就是等待/dev/.coldboot_done文件

[cpp] view plain copy

  1. static int wait_for_coldboot_done_action(int nargs, char **args) {
  2. Timer t;
  3. NOTICE("Waiting for %s...\n", COLDBOOT_DONE);
  4. // Any longer than 1s is an unreasonable length of time to delay booting.
  5. // If you're hitting this timeout, check that you didn't make your
  6. // sepolicy regular expressions too expensive (http://b/19899875).
  7. if (wait_for_file(COLDBOOT_DONE, 1)) {
  8. ERROR("Timed out waiting for %s\n", COLDBOOT_DONE);
  9. }
  10. NOTICE("Waiting for %s took %.2fs.\n", COLDBOOT_DONE, t.duration());

wait_for_file等待/dev/.coldboot_done文件,超时时间设置的是1秒。

[cpp] view plain copy

  1. int wait_for_file(const char *filename, int timeout)
  2. {
  3. struct stat info;
  4. uint64_t timeout_time_ns = gettime_ns() + timeout * UINT64_C(1000000000);
  5. int ret = -1;
  6. while (gettime_ns() < timeout_time_ns && ((ret = stat(filename, &info)) < 0))
  7. usleep(10000);
  8. return ret;
  9. }

3. mix_hwrng_into_linux_rng_action函数从硬件PNG的设备文件/dev/hw_random读取512字节并写到LinuxRNG设备文件dev/urandom中。

4. keychord_init_action初始化组合键监听模块,这个函数调用了keychord_init函数

[cpp] view plain copy

  1. static int keychord_init_action(int nargs, char **args)
  2. {
  3. keychord_init();
  4. return 0;
  5. }
[cpp] view plain copy

  1. void keychord_init() {
  2. service_for_each(add_service_keycodes);
  3. // Nothing to do if no services require keychords.
  4. if (!keychords) {
  5. return;
  6. }
  7. keychord_fd = TEMP_FAILURE_RETRY(open("/dev/keychord", O_RDWR | O_CLOEXEC));
  8. if (keychord_fd == -1) {
  9. ERROR("could not open /dev/keychord: %s\n", strerror(errno));
  10. return;
  11. }
  12. int ret = write(keychord_fd, keychords, keychords_length);
  13. if (ret != keychords_length) {
  14. ERROR("could not configure /dev/keychord %d: %s\n", ret, strerror(errno));
  15. close(keychord_fd);
  16. }
  17. free(keychords);
  18. keychords = nullptr;
  19. register_epoll_handler(keychord_fd, handle_keychord);
  20. }

keychord_init函数先是遍历各个service,然后调用add_service_keycodes函数,在add_service_keycodes函数中,主要看service有没有keycodes这个变量,有的话将新建一个keychord,然后将service的keycodes保存在这个变量中。最后还有一个全局的keychords,所以的数据最后都是可以通过这个全局指针找到。

[cpp] view plain copy

  1. void add_service_keycodes(struct service *svc)
  2. {
  3. struct input_keychord *keychord;
  4. int i, size;
  5. if (svc->keycodes) {
  6. /* add a new keychord to the list */
  7. size = sizeof(*keychord) + svc->nkeycodes * sizeof(keychord->keycodes[0]);
  8. keychords = (input_keychord*) realloc(keychords, keychords_length + size);
  9. if (!keychords) {
  10. ERROR("could not allocate keychords\n");
  11. keychords_length = 0;
  12. keychords_count = 0;
  13. return;
  14. }
  15. keychord = (struct input_keychord *)((char *)keychords + keychords_length);
  16. keychord->version = KEYCHORD_VERSION;
  17. keychord->id = keychords_count + 1;
  18. keychord->count = svc->nkeycodes;
  19. svc->keychord_id = keychord->id;
  20. for (i = 0; i < svc->nkeycodes; i++) {
  21. keychord->keycodes[i] = svc->keycodes[i];
  22. }
  23. keychords_count++;
  24. keychords_length += size;
  25. }
  26. }

然后我们把keychords这个全局变量数据写入/dev/keychord文件中,最后调用register_epoll_handler函数把这个fd注册到epoll中。

[cpp] view plain copy

  1. int ret = write(keychord_fd, keychords, keychords_length);
  2. if (ret != keychords_length) {
  3. ERROR("could not configure /dev/keychord %d: %s\n", ret, strerror(errno));
  4. close(keychord_fd);
  5. }
  6. free(keychords);
  7. keychords = nullptr;
  8. register_epoll_handler(keychord_fd, handle_keychord);

最后在这个fd有数据来的时候,我们读取出来,通过service_find_by_keychord看与哪个service的的keychord匹配,匹配的话就把service启动。但是前提是and_enabled是running。

[cpp] view plain copy

  1. static void handle_keychord() {
  2. struct service *svc;
  3. char adb_enabled[PROP_VALUE_MAX];
  4. int ret;
  5. __u16 id;
  6. // Only handle keychords if adb is enabled.
  7. property_get("init.svc.adbd", adb_enabled);
  8. ret = read(keychord_fd, &id, sizeof(id));
  9. if (ret != sizeof(id)) {
  10. ERROR("could not read keychord id\n");
  11. return;
  12. }
  13. if (!strcmp(adb_enabled, "running")) {
  14. svc = service_find_by_keychord(id);
  15. if (svc) {
  16. INFO("Starting service %s from keychord\n", svc->name);
  17. service_start(svc, NULL);
  18. } else {
  19. ERROR("service for keychord %d not found\n", id);
  20. }
  21. }
  22. }

5. console_init_action是显示A N D R O I D 字样的logo。

[cpp] view plain copy

  1. static int console_init_action(int nargs, char **args)
  2. {
  3. char console[PROP_VALUE_MAX];
  4. if (property_get("ro.boot.console", console) > 0) {
  5. snprintf(console_name, sizeof(console_name), "/dev/%s", console);
  6. }
  7. int fd = open(console_name, O_RDWR | O_CLOEXEC);
  8. if (fd >= 0)
  9. have_console = 1;//是否有控制台
  10. close(fd);
  11. fd = open("/dev/tty0", O_WRONLY | O_CLOEXEC);
  12. if (fd >= 0) {
  13. const char *msg;
  14. msg = "\n"
  15. "\n"
  16. "\n"
  17. "\n"
  18. "\n"
  19. "\n"
  20. "\n"  // console is 40 cols x 30 lines
  21. "\n"
  22. "\n"
  23. "\n"
  24. "\n"
  25. "\n"
  26. "\n"
  27. "\n"
  28. "             A N D R O I D ";
  29. write(fd, msg, strlen(msg));//显示android字样
  30. close(fd);
  31. }
  32. return 0;
  33. }

6. action_for_each_trigger("init", action_add_queue_tail); 触发init触发器, 主要是mount一些设备,还有创建一些目录。

[plain] view plain copy

  1. on init
  2. sysclktz 0
  3. # Backward compatibility.
  4. symlink /system/etc /etc
  5. symlink /sys/kernel/debug /d
  6. # Link /vendor to /system/vendor for devices without a vendor partition.
  7. symlink /system/vendor /vendor
  8. # Create cgroup mount point for cpu accounting
  9. mkdir /acct
  10. mount cgroup none /acct cpuacct
  11. mkdir /acct/uid
  12. # Create cgroup mount point for memory
  13. mount tmpfs none /sys/fs/cgroup mode=0750,uid=0,gid=1000
  14. ......

7. mix_hwrng_into_linux_rng_action也是和RNG相关
8. charger和late-init,根据ro.bootmode来触发charger还是late-init触发器

[cpp] view plain copy

  1. char bootmode[PROP_VALUE_MAX];
  2. if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
  3. action_for_each_trigger("charger", action_add_queue_tail);
  4. } else {
  5. action_for_each_trigger("late-init", action_add_queue_tail);
  6. }

late-init内容如下:

[cpp] view plain copy

  1. on late-init
  2. trigger early-fs
  3. trigger fs
  4. trigger post-fs
  5. # Load properties from /system/ + /factory after fs mount. Place
  6. # this in another action so that the load will be scheduled after the prior
  7. # issued fs triggers have completed.
  8. trigger load_system_props_action//加载系统属性
  9. # Now we can mount /data. File encryption requires keymaster to decrypt
  10. # /data, which in turn can only be loaded when system properties are present
  11. trigger post-fs-data
  12. trigger load_persist_props_action//加载persist属性
  13. # Remove a file to wake up anything waiting for firmware.
  14. trigger firmware_mounts_complete
  15. trigger early-boot
  16. trigger boot//这里面启动main core服务

而on charger就会启动一个charger进程

[plain] view plain copy

  1. on charger
  2. class_start charger
[cpp] view plain copy

  1. service charger /charger
  2. seclabel u:r:healthd:s0
  3. oneshot

9. queue_property_triggers_action就是看现在那些aciton满足条件,把它加入执行列中。

[cpp] view plain copy

  1. static int queue_property_triggers_action(int nargs, char **args)
  2. {
  3. queue_all_property_triggers();
  4. /* enable property triggers */
  5. property_triggers_enabled = 1;
  6. return 0;
  7. }
[cpp] view plain copy

  1. void queue_all_property_triggers()
  2. {
  3. queue_property_triggers(NULL, NULL);
  4. }

最后调用queue_property_triggers,遍历所有的aciton是属性的那种,只要满足条件加入执行队列。

[cpp] view plain copy

  1. void queue_property_triggers(const char *name, const char *value)
  2. {
  3. struct listnode *node, *node2;
  4. struct action *act;
  5. struct trigger *cur_trigger;
  6. bool match;
  7. int name_length;
  8. list_for_each(node, &action_list) {
  9. act = node_to_item(node, struct action, alist);
  10. match = !name;
  11. list_for_each(node2, &act->triggers) {
  12. cur_trigger = node_to_item(node2, struct trigger, nlist);
  13. if (!strncmp(cur_trigger->name, "property:", strlen("property:"))) {
  14. const char *test = cur_trigger->name + strlen("property:");
  15. if (!match) {
  16. name_length = strlen(name);
  17. if (!strncmp(name, test, name_length) &&
  18. test[name_length] == '=' &&
  19. (!strcmp(test + name_length + 1, value) ||
  20. !strcmp(test + name_length + 1, "*"))) {
  21. match = true;
  22. continue;
  23. }
  24. }
  25. const char* equals = strchr(test, '=');
  26. if (equals) {
  27. char prop_name[PROP_NAME_MAX + 1];
  28. char value[PROP_VALUE_MAX];
  29. int length = equals - test;
  30. if (length <= PROP_NAME_MAX) {
  31. int ret;
  32. memcpy(prop_name, test, length);
  33. prop_name[length] = 0;
  34. /* does the property exist, and match the trigger value? */
  35. ret = property_get(prop_name, value);
  36. if (ret > 0 && (!strcmp(equals + 1, value) ||
  37. !strcmp(equals + 1, "*"))) {
  38. continue;
  39. }
  40. }
  41. }
  42. }
  43. match = false;
  44. break;
  45. }
  46. if (match) {
  47. action_add_queue_tail(act);
  48. }
  49. }
  50. }

四、属性系统

属性会在start_property_service函数中,把属性的socket 的fd加入到了epoll中,init主要是检测属性发生改变时,有哪些action满足条件需要触发。以及一些persist属性保存。ctl属性开启 关闭service等。

具体的我们在之前的博客http://blog.csdn.net/kc58236582/article/details/51939322,已经分析的比较详细了,这里就不说了。

五、执行执行队列中的Action

执行命令主要是在main函数中的while循环中调用execute_one_command,因为执行队列会不断变化,所以需要在while循环中不断调用这个函数。

[cpp] view plain copy

  1. while (true) {
  2. if (!waiting_for_exec) {
  3. execute_one_command();
  4. restart_processes();
  5. }
  6. int timeout = -1;
  7. if (process_needs_restart) {
  8. timeout = (process_needs_restart - gettime()) * 1000;
  9. if (timeout < 0)
  10. timeout = 0;
  11. }
  12. if (!action_queue_empty() || cur_action) {
  13. timeout = 0;
  14. }
  15. bootchart_sample(&timeout);
  16. epoll_event ev;
  17. int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
  18. if (nr == -1) {
  19. ERROR("epoll_wait failed: %s\n", strerror(errno));
  20. } else if (nr == 1) {
  21. ((void (*)()) ev.data.ptr)();
  22. }
  23. }

我们来看下这个函数,比较简单先调用action_remove_queue_head函数,然后获取command,最后调用command的func回调函数。

[cpp] view plain copy

  1. void execute_one_command() {
  2. Timer t;
  3. char cmd_str[256] = "";
  4. char name_str[256] = "";
  5. if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {
  6. cur_action = action_remove_queue_head();
  7. cur_command = NULL;
  8. if (!cur_action) {
  9. return;
  10. }
  11. build_triggers_string(name_str, sizeof(name_str), cur_action);
  12. INFO("processing action %p (%s)\n", cur_action, name_str);
  13. cur_command = get_first_command(cur_action);
  14. } else {
  15. cur_command = get_next_command(cur_action, cur_command);
  16. }
  17. if (!cur_command) {
  18. return;
  19. }
  20. int result = cur_command->func(cur_command->nargs, cur_command->args);
  21. if (klog_get_level() >= KLOG_INFO_LEVEL) {
  22. for (int i = 0; i < cur_command->nargs; i++) {
  23. strlcat(cmd_str, cur_command->args[i], sizeof(cmd_str));
  24. if (i < cur_command->nargs - 1) {
  25. strlcat(cmd_str, " ", sizeof(cmd_str));
  26. }
  27. }
  28. char source[256];
  29. if (cur_command->filename) {
  30. snprintf(source, sizeof(source), " (%s:%d)", cur_command->filename, cur_command->line);
  31. } else {
  32. *source = '\0';
  33. }
  34. INFO("Command '%s' action=%s%s returned %d took %.2fs\n",
  35. cmd_str, cur_action ? name_str : "", source, result, t.duration());
  36. }
  37. }

六、kill 进程处理以及再次开启service进程

之前我们在分析signal_handler_init函数的时候没有详细说,现在说下这个函数。

[cpp] view plain copy

  1. void signal_handler_init() {
  2. // Create a signalling mechanism for SIGCHLD.
  3. int s[2];
  4. if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
  5. ERROR("socketpair failed: %s\n", strerror(errno));
  6. exit(1);
  7. }
  8. signal_write_fd = s[0];
  9. signal_read_fd = s[1];
  10. // Write to signal_write_fd if we catch SIGCHLD.
  11. struct sigaction act;
  12. memset(&act, 0, sizeof(act));
  13. act.sa_handler = SIGCHLD_handler;
  14. act.sa_flags = SA_NOCLDSTOP;
  15. sigaction(SIGCHLD, &act, 0);//子进程终结发给父进程的信号
  16. reap_any_outstanding_children();
  17. register_epoll_handler(signal_read_fd, handle_signal);
  18. }

我们先来看下信号的处理函数,SIGCHLD_handler就是往socketpair的一端写入数据

[cpp] view plain copy

  1. static void SIGCHLD_handler(int) {
  2. if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
  3. ERROR("write(signal_write_fd) failed: %s\n", strerror(errno));
  4. }
  5. }

然后sockpair的另一端,注册到epoll中去,我们也来看下处理函数handle_signal,读取了sockpair中的内容后,调用了reap_any_outstanding_children函数,这个函数在signal_handler_init函数里面也调用了。

[cpp] view plain copy

  1. static void handle_signal() {
  2. // Clear outstanding requests.
  3. char buf[32];
  4. read(signal_read_fd, buf, sizeof(buf));
  5. reap_any_outstanding_children();
  6. }

我们来看下reap_any_outstanding_children函数,直接while循环调用了wait_for_one_process函数

[cpp] view plain copy

  1. static void reap_any_outstanding_children() {
  2. while (wait_for_one_process()) {
  3. }
  4. }

我们再来看wait_for_one_process函数,先调用了waitpid方法,pid为-1,代表监听所有的子进程。WNOHANG代表不阻塞。当pid值返回0和-1时return false,直接while循环退出了,否则一直处理一个接着一个进程挂掉的信号。

[cpp] view plain copy

  1. static bool wait_for_one_process() {
  2. int status;
  3. pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));//WNOHANG代表不阻塞
  4. if (pid == 0) {
  5. return false;
  6. } else if (pid == -1) {
  7. ERROR("waitpid failed: %s\n", strerror(errno));
  8. return false;
  9. }
  10. service* svc = service_find_by_pid(pid);//找到service
  11. std::string name;
  12. if (svc) {
  13. name = android::base::StringPrintf("Service '%s' (pid %d)", svc->name, pid);
  14. } else {
  15. name = android::base::StringPrintf("Untracked pid %d", pid);
  16. }
  17. NOTICE("%s %s\n", name.c_str(), DescribeStatus(status).c_str());
  18. if (!svc) {
  19. return true;//没找到service 直接结束处理下个进程信号
  20. }
  21. // TODO: all the code from here down should be a member function on service.
  22. if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {//如果不是oneshot 或者是restart的这种flag
  23. NOTICE("Service '%s' (pid %d) killing any children in process group\n", svc->name, pid);
  24. kill(-pid, SIGKILL);//kill该进程群组所有的进程
  25. }
  26. // Remove any sockets we may have created.去除socket
  27. for (socketinfo* si = svc->sockets; si; si = si->next) {
  28. char tmp[128];
  29. snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
  30. unlink(tmp);
  31. }
  32. if (svc->flags & SVC_EXEC) {
  33. INFO("SVC_EXEC pid %d finished...\n", svc->pid);
  34. waiting_for_exec = false;
  35. list_remove(&svc->slist);
  36. free(svc->name);
  37. free(svc);
  38. return true;
  39. }
  40. svc->pid = 0;
  41. svc->flags &= (~SVC_RUNNING);//去除running的flag
  42. // Oneshot processes go into the disabled state on exit,
  43. // except when manually restarted.
  44. if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
  45. svc->flags |= SVC_DISABLED;//oneshot而且没有restart的flag,附上disabled的flag
  46. }
  47. // Disabled and reset processes do not get restarted automatically.
  48. if (svc->flags & (SVC_DISABLED | SVC_RESET))  {//已经reset或者disabled直接结束
  49. svc->NotifyStateChange("stopped");
  50. return true;
  51. }
  52. time_t now = gettime();
  53. if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) {
  54. if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
  55. if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
  56. ERROR("critical process '%s' exited %d times in %d minutes; "
  57. "rebooting into recovery mode\n", svc->name,
  58. CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
  59. android_reboot(ANDROID_RB_RESTART2, 0, "recovery");
  60. return true;
  61. }
  62. } else {
  63. svc->time_crashed = now;
  64. svc->nr_crashed = 1;
  65. }
  66. }
  67. svc->flags &= (~SVC_RESTART);
  68. svc->flags |= SVC_RESTARTING;// restarting代表重启中
  69. // Execute all onrestart commands for this service.
  70. struct listnode* node;
  71. list_for_each(node, &svc->onrestart.commands) {//有onrestart的command命令的,重启的时候要先调用命令
  72. command* cmd = node_to_item(node, struct command, clist);
  73. cmd->func(cmd->nargs, cmd->args);
  74. }
  75. svc->NotifyStateChange("restarting");
  76. return true;
  77. }

这个函数主要将service的flags赋值,一般的进程被kill 之后最后会被附上SVC_RESTARTING这个flag,而且又onrestart的,先执行其command。对于已经是disabled和reset的service直接结束,对于是oneshot而且没有restart flag的service,直接附上disabled这个flag。

我们先来看看下面servicemanager这个service,当servicemanager重启的时候,会restart healthd等。

[cpp] view plain copy

  1. service servicemanager /system/bin/servicemanager
  2. class core
  3. user system
  4. group system
  5. critical
  6. onrestart restart healthd
  7. onrestart restart zygote
  8. onrestart restart media
  9. onrestart restart surfaceflinger
  10. onrestart restart drm

restart这个命令对应的是do_restart函数,最后调用service_restart函数重启service

[cpp] view plain copy

  1. int do_restart(int nargs, char **args)
  2. {
  3. struct service *svc;
  4. svc = service_find_by_name(args[1]);
  5. if (svc) {
  6. service_restart(svc);
  7. }
  8. return 0;
  9. }

我们再来看看service的NotifyStateChange函数,主要是设置init.svc.(service的name)这个属性为这个service最新的状态。

[cpp] view plain copy

  1. void service::NotifyStateChange(const char* new_state) {
  2. if (!properties_initialized()) {
  3. // If properties aren't available yet, we can't set them.
  4. return;
  5. }
  6. if ((flags & SVC_EXEC) != 0) {
  7. // 'exec' commands don't have properties tracking their state.
  8. return;
  9. }
  10. char prop_name[PROP_NAME_MAX];
  11. if (snprintf(prop_name, sizeof(prop_name), "init.svc.%s", name) >= PROP_NAME_MAX) {
  12. // If the property name would be too long, we can't set it.
  13. ERROR("Property name \"init.svc.%s\" too long; not setting to %s\n", name, new_state);
  14. return;
  15. }
  16. property_set(prop_name, new_state);
  17. }

下面我们需要再结合main函数中在while循环中调用的restart_processes函数

[cpp] view plain copy

  1. static void restart_processes()
  2. {
  3. process_needs_restart = 0;
  4. service_for_each_flags(SVC_RESTARTING,
  5. restart_service_if_needed);
  6. }

结合service_for_each_flags,遍历所有的service,只要service的flags有SVC_RESTARTING的就调用restart_service_if_needed函数

[cpp] view plain copy

  1. void service_for_each_flags(unsigned matchflags,
  2. void (*func)(struct service *svc))
  3. {
  4. struct listnode *node;
  5. struct service *svc;
  6. list_for_each(node, &service_list) {
  7. svc = node_to_item(node, struct service, slist);
  8. if (svc->flags & matchflags) {
  9. func(svc);
  10. }
  11. }
  12. }

在restart_service_if_needed函数中,会去除SVC_RESTARTING的flag,然后调用service_start启动进程。

[cpp] view plain copy

  1. static void restart_service_if_needed(struct service *svc)
  2. {
  3. time_t next_start_time = svc->time_started + 5;
  4. if (next_start_time <= gettime()) {
  5. svc->flags &= (~SVC_RESTARTING);
  6. service_start(svc, NULL);
  7. return;
  8. }
  9. if ((next_start_time < process_needs_restart) ||
  10. (process_needs_restart == 0)) {
  11. process_needs_restart = next_start_time;
  12. }
  13. }

所以普通的进程,使用kill的话,哪怕进程被kill了之后,还会被init进程启动的。我们再来看看service_start函数,先把一些flag清除

[cpp] view plain copy

  1. void service_start(struct service *svc, const char *dynamic_args)
  2. {
  3. // Starting a service removes it from the disabled or reset state and
  4. // immediately takes it out of the restarting state if it was in there.
  5. svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));//清除flag
  6. svc->time_started = 0;
  7. // Running processes require no additional work --- if they're in the
  8. // process of exiting, we've ensured that they will immediately restart
  9. // on exit, unless they are ONESHOT.
  10. if (svc->flags & SVC_RUNNING) {//已经启动的service直接退出
  11. return;
  12. }
  13. bool needs_console = (svc->flags & SVC_CONSOLE);//需要控制台的service
  14. if (needs_console && !have_console) {//是否有控制台,没有直接退出
  15. ERROR("service '%s' requires console\n", svc->name);
  16. svc->flags |= SVC_DISABLED;
  17. return;
  18. }
  19. struct stat s;
  20. if (stat(svc->args[0], &s) != 0) {
  21. ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name);
  22. svc->flags |= SVC_DISABLED;
  23. return;
  24. }
  25. if ((!(svc->flags & SVC_ONESHOT)) && dynamic_args) {
  26. ERROR("service '%s' must be one-shot to use dynamic args, disabling\n",
  27. svc->args[0]);
  28. svc->flags |= SVC_DISABLED;
  29. return;
  30. }

后面是selinux的相关,后面就直接fork进程,处理一些子进程的环境等。

[cpp] view plain copy

  1. char* scon = NULL;
  2. if (is_selinux_enabled() > 0) {
  3. ......
  4. }
  5. NOTICE("Starting service '%s'...\n", svc->name);
  6. pid_t pid = fork();
  7. if (pid == 0) {
  8. ......
  9. _exit(127);
  10. }

最后设置下service的时间,pid,以及flags状态改成running,最后通知(设置属性)改成running了。

[cpp] view plain copy

  1. if (pid < 0) {
  2. ERROR("failed to start '%s'\n", svc->name);
  3. svc->pid = 0;
  4. return;
  5. }
  6. svc->time_started = gettime();
  7. svc->pid = pid;
  8. svc->flags |= SVC_RUNNING;
  9. if ((svc->flags & SVC_EXEC) != 0) {
  10. INFO("SVC_EXEC pid %d (uid %d gid %d+%zu context %s) started; waiting...\n",
  11. svc->pid, svc->uid, svc->gid, svc->nr_supp_gids,
  12. svc->seclabel ? : "default");
  13. waiting_for_exec = true;
  14. }
  15. svc->NotifyStateChange("running");

像普通的进程我们通过kill进程会被init再次启动,那怎样才能kill 这个进程,又不会被init进程启动呢,可以使用stop命令,我们看下do_stop函数。

[cpp] view plain copy

  1. int do_stop(int nargs, char **args)
  2. {
  3. struct service *svc;
  4. svc = service_find_by_name(args[1]);
  5. if (svc) {
  6. service_stop(svc);
  7. }
  8. return 0;
  9. }

调用了service_stop_or_reset只是flag为disabled

[cpp] view plain copy

  1. void service_stop(struct service *svc)
  2. {
  3. service_stop_or_reset(svc, SVC_DISABLED);
  4. }

service_stop_or_reset函数中,最后会kill整个进程组,而且因为把flag改成了disabled,在init中也不会启动进程了。

[cpp] view plain copy

  1. static void service_stop_or_reset(struct service *svc, int how)
  2. {
  3. /* The service is still SVC_RUNNING until its process exits, but if it has
  4. * already exited it shoudn't attempt a restart yet. */
  5. svc->flags &= ~(SVC_RESTARTING | SVC_DISABLED_START);//清相关flag
  6. if ((how != SVC_DISABLED) && (how != SVC_RESET) && (how != SVC_RESTART)) {
  7. /* Hrm, an illegal flag.  Default to SVC_DISABLED */
  8. how = SVC_DISABLED;
  9. }
  10. /* if the service has not yet started, prevent
  11. * it from auto-starting with its class
  12. */
  13. if (how == SVC_RESET) {
  14. svc->flags |= (svc->flags & SVC_RC_DISABLED) ? SVC_DISABLED : SVC_RESET;// 看之前是否有disabled这个flag
  15. } else {
  16. svc->flags |= how;
  17. }
  18. if (svc->pid) {
  19. NOTICE("Service '%s' is being killed...\n", svc->name);
  20. kill(-svc->pid, SIGKILL);//kill 整个进程组
  21. svc->NotifyStateChange("stopping");
  22. } else {
  23. svc->NotifyStateChange("stopped");
  24. }
  25. }

也可以自己调用start命令,最后通过do_start函数启动这个service。

[cpp] view plain copy

  1. int do_start(int nargs, char **args)
  2. {
  3. struct service *svc;
  4. svc = service_find_by_name(args[1]);
  5. if (svc) {
  6. service_start(svc, NULL);
  7. }
  8. return 0;
  9. }

再看下do_restart

[cpp] view plain copy

  1. int do_restart(int nargs, char **args)
  2. {
  3. struct service *svc;
  4. svc = service_find_by_name(args[1]);
  5. if (svc) {
  6. service_restart(svc);
  7. }
  8. return 0;
  9. }

在service_restart函数中,当是running状态,直接把它kill了,然后在init处理进程信号时,会把它的flag变成restarting,之后init进程会重启这个进程。其他的状态就直接启动service了。

[cpp] view plain copy

  1. void service_restart(struct service *svc)
  2. {
  3. if (svc->flags & SVC_RUNNING) {
  4. /* Stop, wait, then start the service. */
  5. service_stop_or_reset(svc, SVC_RESTART);
  6. } else if (!(svc->flags & SVC_RESTARTING)) {
  7. /* Just start the service since it's not running. */
  8. service_start(svc, NULL);
  9. } /* else: Service is restarting anyways. */
  10. }

至于stop start命令只能在init.rc中使用,但是我们可以通过ctl.start ctl.stop ctl.restart来达到这个目的。处理的话,之前在属性系统中已经分析过了。

普通对一个service命令处理只有stop start restart没有reset,而在class_reset class_stop class_start中有,我们来看看这些命令处理。

[cpp] view plain copy

  1. int do_class_stop(int nargs, char **args)
  2. {
  3. service_for_each_class(args[1], service_stop);
  4. return 0;
  5. }
  6. int do_class_reset(int nargs, char **args)
  7. {
  8. service_for_each_class(args[1], service_reset);
  9. return 0;
  10. }

再结合service_for_each_class函数,我们知道class_reset class_stop 只是遍历所有的service,看看其class是否满足,满足就调用service_stop 和 service_reset函数

[cpp] view plain copy

  1. void service_for_each_class(const char *classname,
  2. void (*func)(struct service *svc))
  3. {
  4. struct listnode *node;
  5. struct service *svc;
  6. list_for_each(node, &service_list) {
  7. svc = node_to_item(node, struct service, slist);
  8. if (!strcmp(svc->classname, classname)) {
  9. func(svc);
  10. }
  11. }
  12. }

而我们再看看do_class_start有点不一样,遍历所有的service看看其class是否满足然后调用service_start_if_not_disabled函数

[cpp] view plain copy

  1. int do_class_start(int nargs, char **args)
  2. {
  3. /* Starting a class does not start services
  4. * which are explicitly disabled.  They must
  5. * be started individually.
  6. */
  7. service_for_each_class(args[1], service_start_if_not_disabled);
  8. return 0;
  9. }

看看service_start_if_not_disabled函数只有在flags不等于SVC_DISABLED的时候才会调用service_start函数。

[cpp] view plain copy

  1. static void service_start_if_not_disabled(struct service *svc)
  2. {
  3. if (!(svc->flags & SVC_DISABLED)) {
  4. service_start(svc, NULL);
  5. } else {
  6. svc->flags |= SVC_DISABLED_START;
  7. }
  8. }

这样如果调用class_stop再调用class_start也不能再次启动这些class的service了,只有启动那些之前调用的是reset的service。

class_stop了之后,就只能一个一个service调用start命令了。

Android6.0 init 深入分析的更多相关文章

  1. android6.0 adbd深入分析(二)adb驱动数据的处理、写数据到adb驱动节点

     上篇博客最后讲到在output_thread中.读取了adb驱动的数据后.就调用write_packet(t->fd, t->serial, &p)函数,把数据网socket ...

  2. imx6 Android6.0.1 init.rc解析

    1. 概述 1.1 概述 之前分析过android5的init.rc,不过还是不够仔细,现在来看看android6的,多的就不写了,只写关键点 忘记一些基本概念可以先看看之前的笔记: Android5 ...

  3. 编译可在Nexus5上运行的CyanogenMod13.0 ROM(基于Android6.0)

    编译可在Nexus5上运行的CyanogenMod13.0 ROM (基于Android6.0) 作者:寻禹@阿里聚安全 前言 下文中无特殊说明时CM代表CyanogenMod的缩写. 下文中说的“设 ...

  4. Android6.0之来电转接号码显示修改

    Android6.0之来电转接号码显示修改 手机来电转接界面是在,点开Dialer-搜索框右边的三个点-设置-通话账户(要先插卡)-中国移动或者中国联通--来电转接--第一行,显示始终转接 将所有来电 ...

  5. 高通 NXP NFC(PN547PN548) 移植流程 android6.0

    一.驱动部分 首先向NXP 的 fae要android 6.0 bring up的代码,如:NFC_NCIHALx_AR0F.4.3.0_M_NoSE 结构目录如下: 1. 添加驱动文件 高通平台需使 ...

  6. Android6.0源码下载编译刷入真机

    编译环境是Ubuntu12.04.手机nexus 5,编译安卓6.0.1源码并烧录到真机. 源码用的是科大的镜像:http://mirrors.ustc.edu.cn/aosp-monthly/,下载 ...

  7. android6.0系统Healthd分析及低电量自动关机流程

    系统平台:android6.0概述Healthd是android4.4之后提出来的一种中介模型,该模型向下监听来自底层的电池事件,向上传递电池数据信息给Framework层的BatteryServic ...

  8. Ubuntu虚拟机编译Android6.0总结

    1 前言 昨天使用清华的源下载了android 6.0的源码,校园网可以达到10M的速度,爽!今天一大早就迫不及待地准备编译一个模拟器版本,看看效果,哪知竟然耗费了一整天的时间才搞定...为了避免其他 ...

  9. Android6.0运行时权限管理

    自从Android6.0发布以来,在权限上做出了很大的变动,不再是之前的只要在manifest设置就可以任意获取权限,而是更加的注重用户的隐私和体验,不会再强迫用户因拒绝不该拥有的权限而导致的无法安装 ...

随机推荐

  1. Django REST framework+Vue 打造生鲜超市(九)

    十.购物车.订单管理和支付功能 10.1.添加商品到购物车 (1)trade/serializer.py # trade/serializer.py __author__ = 'derek' from ...

  2. [LeetCode] The Maze 迷宫

    There is a ball in a maze with empty spaces and walls. The ball can go through empty spaces by rolli ...

  3. spark-shell报错:Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/hadoop/fs/FSDataInputStream

    环境: openSUSE42.2 hadoop2.6.0-cdh5.10.0 spark1.6.0-cdh5.10.0 按照网上的spark安装教程安装完之后,启动spark-shell,出现如下报错 ...

  4. sed和awk的使用

  5. 实验吧_NSCTF web200&FALSE(代码审计)

    挺简单的一个代码审计,这里只要倒序解密就行了,这里给一下python版的wp import codecs import base64 strs = 'a1zLbgQsCESEIqRLwuQAyMwLy ...

  6. [USACO 07NOV]Cow Relays

    Description For their physical fitness program, N (2 ≤ N ≤ 1,000,000) cows have decided to run a rel ...

  7. UVA4731:Cellular Network

    根据排序不等式可知,逆序和最小(就是两个向量坐标一个递增一个递减,那么乘起来就最小) 所以排一下序,然后做一下线性dp即可 #include<cstdio> #include<cst ...

  8. CSAPP-程序优化

    代码移动: 如果一个表达式总是得到同样的结果,最好把它移动到循环外面,这样只需要计算一次.编译器有时候可以自动完成,比如说使用 -O1 优化.一个例子: void set_row(double *a, ...

  9. 【HNOI2004】L语言

    题目描述 标点符号的出现晚于文字的出现,所以以前的语言都是没有标点的.现在你要处理的就是一段没有标点的文章. 一段文章T是由若干小写字母构成.一个单词W也是由若干小写字母构成.一个字典D是若干个单词的 ...

  10. hdu 5468(莫比乌斯+搜索)

    hdu 5468 Puzzled Elena   /*快速通道*/ Sample Input 5 1 2 1 3 2 4 2 5 6 2 3 4 5   Sample Output Case #1: ...