一、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进程的工作,
