《Linux设备节点创建》用户空间ueventd创建设备节点规则

    xiaoxiao2026-05-26  4

    一、devfs、udev和sysfs是什么关系?

    linux2.6之前使用devfs设备文件系统,它存在与内核空间;

    linux2.6之后使用udev设备文件系统,它存在与用户空间、但严重依赖与sysfs文件系统。

    二、Android(使用linux2.6以后的设备节点创建策略)设备节点的创建

      在Android中,没有独立的类似于udev或者mdev的用户程序,这个功能集成到了init中做了。代码见:system/core/init/init.c文件,如下:

    int ueventd_main(int argc, char **argv)   {       struct pollfd ufd;       int nr;       char tmp[32];         open_devnull_stdio();       log_init();         INFO("starting ueventd\n");         get_hardware_name(hardware, &revision);     /* /ueventd.rc中以行为单位,除最后sysfs properties外,每一行由四部分组成: 如:/dev/diag      0660   radio             radio    目录          权限   用户ID(uid)     组ID(gid)          # sysfs properties 多一个属性 /sys/devices/virtual/input/input*   enable      0660  root   input     目录  属性    权限   用户ID(uid)     组ID(gid)     */       ueventd_parse_config_file("/ueventd.rc");         snprintf(tmp, sizeof(tmp), "/ueventd.%s.rc", hardware);       ueventd_parse_config_file(tmp);     //初始化uevent,建立socket,执行coldboot,用于检查当前service启动前操作系统已经处理的事件,add这些事件到应用层       device_init();         ufd.events = POLLIN;       ufd.fd = get_device_fd();     //在死循环中处理触发事件       while(1) {           ufd.revents = 0;           nr = poll(&ufd, 1, -1);           if (nr <= 0)               continue;           if (ufd.revents == POLLIN)                  handle_device_fd();       }   }

    int ueventd_parse_config_file(const char *fn)   {       char *data;       data = read_file(fn, 0); //读取文件内容返回给data;     if (!data) return -1;         parse_config(fn, data);   //解析整个rc文件内容      DUMP();     //空函数什么都不做       return 0;   } 

    以上步骤和Init进程解析init.rc文件的步骤相同,不过这里调用的parse_config函数不同,该函数是专门用于解析ueventd.rc文件的,具体解析过程如下:

    static void parse_config(const char *fn, char *s)   {       struct parse_state state;       char *args[UEVENTD_PARSER_MAXARGS];   //最多五个参数       int nargs;       nargs = 0;       state.filename = fn;  //设置解析文件的路径     state.line = 1;       state.ptr = s;     //文件内容     state.nexttoken = 0;        state.parse_line = parse_line_device;  //设置每行解析回调函数      for (;;) {           int token = next_token(&state); //用于获得配置文件中特殊标记,如文件结尾(T_EOF),换行符(T_TEXT),文本(T_NEWLINE) ,从文件内容中查找token,与init.rc文件类似;         switch (token) {           case T_EOF:  //文件结束             state.parse_line(&state, 0, 0); //state.parse_line 调用函数为parse_line_device;                return;           case T_NEWLINE:  //新的一行             if (nargs) {                   state.parse_line(&state, nargs, args);  //调用行解析函数解析每一行                 nargs = 0;               }               break;           case T_TEXT:               if (nargs < UEVENTD_PARSER_MAXARGS) {                   args[nargs++] = state.text;               }               break;           }       }   }

    函数首先查找指定的token,然后对不同的token做不同的处理,对于发现新行时,调用parse_line_device函数对每一行进行详细解析,该函数实现如下:

    int next_token(struct parse_state *state)   {       char *x = state->ptr;   //读数据指针       char *s;   /* #define T_EOF 0 #define T_TEXT 1 #define T_NEWLINE 2 非T_EOF时,直接返回下一个标记 */       if (state->nexttoken) {           int t = state->nexttoken;           state->nexttoken = 0;           return t;       }         for (;;) {           switch (*x) {           case 0:               state->ptr = x;               return T_EOF;             case '\n':               x++;               state->ptr = x;               return T_NEWLINE; //换行符           case ' ':           case '\t':           case '\r':               x++;               continue;  //跳过转义字符 :空格 tab 回车           case '#':               while (*x && (*x != '\n')) x++;  //单行注释               if (*x == '\n') {                   state->ptr = x+1;                   return T_NEWLINE;               } else {                   state->ptr = x;                   return T_EOF;               }           default:               goto text;           }       }     textdone:       state->ptr = x;       *s = 0;       return T_TEXT;   text:       state->text = s = x;   textresume:       for (;;) {           switch (*x) {           case 0:               goto textdone;           case ' ':           case '\t':           case '\r':               x++;               goto textdone;           case '\n':               state->nexttoken = T_NEWLINE;               x++;               goto textdone;           case '"':               x++;               for (;;) {                   switch (*x) {                   case 0:                           /* unterminated quoted thing */                       state->ptr = x;                       return T_EOF;                   case '"':                       x++;                       goto textresume;                   default:                       *s++ = *x++;                   }               }               break;           case '\\':               x++;               switch (*x) {               case 0:                   goto textdone;               case 'n':                   *s++ = '\n';                   break;               case 'r':                   *s++ = '\r';                   break;               case 't':                   *s++ = '\t';                   break;               case '\\':                   *s++ = '\\';                   break;               case '\r':                       /* \ <cr> <lf> -> line continuation */                   if (x[1] != '\n') {                       x++;                       continue;                   }               case '\n':                       /* \ <lf> -> line continuation */                   state->line++;                   x++;                       /* eat any extra whitespace */                   while((*x == ' ') || (*x == '\t')) x++;                   continue;               default:                       /* unknown escape -- just copy */                   *s++ = *x++;               }               continue;           default:               *s++ = *x++;           }       }       return T_EOF;   }

    static void parse_line_device(struct parse_state* state, int nargs, char **args)   {       set_device_permission(nargs, args);  //nargs参数个数 args参数   }

    函数直接调用set_device_permission来实现;

    非sysfs 设备文件:

    |name|  |permission| |user| |group|

    /dev/cam   0660   root       ca

    sysfs 设备文件属性: /sys/devices/virtual/input/input*   enable      0660  root   input

    void set_device_permission(int nargs, char **args)   {       char *name;       char *attr = 0;       mode_t perm;       uid_t uid;       gid_t gid;       int prefix = 0;       char *endptr;       int ret;       char *tmp = 0;         if (nargs == 0)           return;         if (args[0][0] == '#')           return;   /*  |name|  |permission| |user| |group|  */       name = args[0];       if (!strncmp(name,"/sys/", 5) && (nargs == 5)) {           INFO("/sys/ rule %s %s\n",args[0],args[1]);           attr = args[1];           args++;           nargs--;       }       //参数检查       if (nargs != 4) {           ERROR("invalid line ueventd.rc line for '%s'\n", args[0]);           return;       }       /* If path starts with mtd@ lookup the mount number. */       if (!strncmp(name, "mtd@", 4)) {           int n = mtd_name_to_number(name + 4);           if (n >= 0)               asprintf(&tmp, "/dev/mtd/mtd%d", n);           name = tmp;       } else {           int len = strlen(name);           if (name[len - 1] == '*') {               prefix = 1;               name[len - 1] = '\0';           }       }       //权限检查       perm = strtol(args[1], &endptr, 8);       if (!endptr || *endptr != '\0') {           ERROR("invalid mode '%s'\n", args[1]);           free(tmp);           return;       }       //从android_ids数组中查找uid       ret = get_android_id(args[2]);       if (ret < 0) {           ERROR("invalid uid '%s'\n", args[2]);           free(tmp);           return;       }       uid = ret;       //从android_ids数组中查找gid       ret = get_android_id(args[3]);       if (ret < 0) {           ERROR("invalid gid '%s'\n", args[3]);           free(tmp);           return;       }       gid = ret;       //为设备文件添加权限       add_dev_perms(name, attr, perm, uid, gid, prefix);       free(tmp);   }

    首先检查参数的合法性,并根据参数查找uid、gid,对不同的用户和组的uid、gid已经事先配置在数组android_ids中了,如下:

    [cpp] view plain copy static const struct android_id_info android_ids[] = {      { "root",      AID_ROOT, },      { "system",    AID_SYSTEM, },      { "radio",     AID_RADIO, },      { "bluetooth", AID_BLUETOOTH, },      { "graphics",  AID_GRAPHICS, },      { "input",     AID_INPUT, },      { "audio",     AID_AUDIO, },      { "camera",    AID_CAMERA, },      { "log",       AID_LOG, },      { "compass",   AID_COMPASS, },      { "mount",     AID_MOUNT, },      { "wifi",      AID_WIFI, },      { "dhcp",      AID_DHCP, },      { "adb",       AID_ADB, },      { "install",   AID_INSTALL, },      { "media",     AID_MEDIA, },      { "drm",       AID_DRM, },      { "mdnsr",     AID_MDNSR, },      { "nfc",       AID_NFC, },      { "drmrpc",    AID_DRMRPC, },      { "shell",     AID_SHELL, },      { "cache",     AID_CACHE, },      { "diag",      AID_DIAG, },      { "net_bt_admin", AID_NET_BT_ADMIN, },      { "net_bt",    AID_NET_BT, },      { "sdcard_r",  AID_SDCARD_R, },      { "sdcard_rw", AID_SDCARD_RW, },      { "media_rw",  AID_MEDIA_RW, },      { "vpn",       AID_VPN, },      { "keystore",  AID_KEYSTORE, },      { "usb",       AID_USB, },      { "mtp",       AID_MTP, },      { "gps",       AID_GPS, },      { "inet",      AID_INET, },      { "net_raw",   AID_NET_RAW, },      { "net_admin", AID_NET_ADMIN, },      { "net_bw_stats", AID_NET_BW_STATS, },      { "net_bw_acct", AID_NET_BW_ACCT, },      { "misc",      AID_MISC, },      { "nobody",    AID_NOBODY, },  };  

    这些uid、gid都是以宏的形式被定义:

    [cpp] view plain copy #define AID_ROOT             0  /* traditional unix root user */  #define AID_SYSTEM        1000  /* system server */  #define AID_RADIO         1001  /* telephony subsystem, RIL */  #define AID_BLUETOOTH     1002  /* bluetooth subsystem */  

    通过调用get_android_id函数在数组android_ids中查找对应的uid、gid

    [cpp] view plain copy static int get_android_id(const char *id)  {      unsigned int i;      for (i = 0; i < ARRAY_SIZE(android_ids); i++)          if (!strcmp(id, android_ids[i].name))              return android_ids[i].aid;      return 0;  }  

    函数实现比较简单,通过遍历数组,并匹配数组元素的name属性来查找指定name的uid或gid。

    最后通过add_dev_perms函数来设置设备文件的操作权限,该函数定义在system\core\init\devices.c文件中,在该文件中声明了三个链表:

    [cpp] view plain copy static list_declare(sys_perms);  static list_declare(dev_perms);  static list_declare(platform_names);  

    add_dev_perms函数就是将解析得到的设备及设备属性,添加到指定的链表中,

    使用解析得到的内容来创建一个perm_node变量,并根据条件添加到sys_perms或dev_perms链表中。

    int add_dev_perms(const char *name, const char *attr,                    mode_t perm, unsigned int uid, unsigned int gid,                    unsigned short prefix) {      //创建perm_node      struct perm_node *node = calloc(1, sizeof(*node));      if (!node)          return -ENOMEM;        node->dp.name = strdup(name);      if (!node->dp.name)          return -ENOMEM;        if (attr) {          node->dp.attr = strdup(attr);          if (!node->dp.attr)              return -ENOMEM;      }      //设置perm_node的成员属性      node->dp.perm = perm;      node->dp.uid = uid;      node->dp.gid = gid;      node->dp.prefix = prefix;      //根据attr 来选择添加到sys_perms或dev_perms链表中      if (attr)          list_add_tail(&sys_perms, &node->plist);      else          list_add_tail(&dev_perms, &node->plist);      return 0;  } 

    至此ueventd.rc文件的解析工作完成了,uevent进程接下来将调用device_init()函数来初始化设备文件

    [cpp] view plain copy void device_init(void)  {      suseconds_t t0, t1;      struct stat info;      int fd;  #ifdef HAVE_SELINUX      struct selinux_opt seopts[] = {          { SELABEL_OPT_PATH, "/file_contexts" }      };      if (is_selinux_enabled() > 0)          sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);  #endif      /* is 64K enough? udev uses 16MB! */      //创建NETLINK socket,用于监听内核发送过来的uevent消息      device_fd = uevent_open_socket(64*1024, true);      if(device_fd < 0)          return;      //设置socket相关属性      fcntl(device_fd, F_SETFD, FD_CLOEXEC);      fcntl(device_fd, F_SETFL, O_NONBLOCK);      //查看"/dev/.coldboot_done" 文件信息      if (stat(coldboot_done, &info) < 0) {          t0 = get_usecs();          coldboot("/sys/class");          coldboot("/sys/block");          coldboot("/sys/devices");          t1 = get_usecs();          fd = open(coldboot_done, O_WRONLY|O_CREAT, 0000);          close(fd);          log_event_print("coldboot %ld uS\n", ((long) (t1 - t0)));      } else {          log_event_print("skipping coldboot, already done\n");      }  }  

    函数首先调用uevent_open_socket 来创建PF_NETLINK socket 并绑定到指定地址上:

    [cpp] view plain copy int uevent_open_socket(int buf_sz, bool passcred)  {      struct sockaddr_nl addr;      int on = passcred;      int s;            memset(&addr, 0, sizeof(addr));      addr.nl_family = AF_NETLINK;      addr.nl_pid = getpid();      addr.nl_groups = 0xffffffff;      //创建socket      s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);      if(s < 0)          return -1;      //设置该socket属性      setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &buf_sz, sizeof(buf_sz));      setsockopt(s, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));      //绑定该socket      if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {          close(s);          return -1;      }      return s;  }  

    ueventd进程接下来将通过系统调用poll函数来监控该socket,如下所示:

    [cpp] view plain copy ufd.events = POLLIN;  ufd.fd = get_device_fd();    while(1) {      ufd.revents = 0;      nr = poll(&ufd, 1, -1);      if (nr <= 0)          continue;      if (ufd.revents == POLLIN)             handle_device_fd();  }  

    函数get_device_fd()返回创建的socket句柄值,并设置到ufd中,最后ueventd进程进入闭环监控模式,使用poll函数监控ufd,同时将第三个参数设置为-1,表示只有在监控的socket上有事件发生时,该函数才能返回。当热插入某一设备时,Linux内核将通过NETLINKsocket 发送uevent事件,此时poll函数得以返回,并调用handle_device_fd()函数来出来设备变化事件:

    [cpp] view plain copy void handle_device_fd()  {      char msg[UEVENT_MSG_LEN+2];      int n;      //从socket中读取消息内容      while ((n = uevent_kernel_multicast_recv(device_fd, msg, UEVENT_MSG_LEN)) > 0) {          //如果读取的内容长度大于1024,继续读取          if(n >= UEVENT_MSG_LEN)   /* overflow -- discard */              continue;            msg[n] = '\0';          msg[n+1] = '\0';          //将uevent消息解析成uevent类型的事件          struct uevent uevent;          parse_event(msg, &uevent);          //处理uevent事件          handle_device_event(&uevent);          handle_firmware_event(&uevent);      }  }  

    当有设备事件发生时,poll函数返回,并从socket中读取内核发送过来的消息内容,并将该消息解析成uevent事件,同时调用handle_device_event函数和handle_firmware_event函数来分别处理设备事件或firmware事件

    [cpp] view plain copy static void handle_device_event(struct uevent *uevent)  {      //如果是设备添加事件      if (!strcmp(uevent->action,"add"))          fixup_sys_perms(uevent->path);      //块设备事件      if (!strncmp(uevent->subsystem, "block", 5)) {          handle_block_device_event(uevent);      //平台设备事件      } else if (!strncmp(uevent->subsystem, "platform", 8)) {          handle_platform_device_event(uevent);      //通用设备事件      } else {          handle_generic_device_event(uevent);      }  }  

     

    [cpp] view plain copy static void handle_firmware_event(struct uevent *uevent)  {      pid_t pid;      int ret;      if(strcmp(uevent->subsystem, "firmware"))          return;      if(strcmp(uevent->action, "add"))          return;      //创建一个线程来专门执行firmware事件      /* we fork, to avoid making large memory allocations in init proper */      pid = fork();      if (!pid) {          process_firmware_event(uevent);          exit(EXIT_SUCCESS);      }  }  

    具体的处理过程这里不在详细分析,读者有兴趣请自行分析!至此就介绍完了整个ueventd进程的工作,

    转载请注明原文地址: https://ju.6miu.com/read-1310069.html
    最新回复(0)