- 浏览: 960636 次
文章分类
最新评论
-
l67721363:
感谢分享,要是有各个函数性能比较就好了。
SQL优化 数据库优化 -
hanmiao:
此图片来自QQ空间,未经允许不可引用。
Hacking QQ空间
Android uevent进程源码分析
在Android Init进程源码分析中讲到init进程会依次执行被加入到待执行队列action_queue中的Action,在init.rc中我们有这么一段配置:
11 on early-init 12 # Set init and its forked children's oom_adj. 13 write /proc/1/oom_adj -16 14 15 start ueventd 16 17 # create mountpoints 18 mkdir /mnt 0775 root system对于early-init 这个section 在Android Init进程源码分析一文中已经介绍了在解析完init.rc文件后被添加到了action_queue队列
action_for_each_trigger("early-init", action_add_queue_tail);因此Init进程会首先启动ueventd进程,关键字start用于启动一个服务进程,该关键字对应的执行函数为:
KEYWORD(start, COMMAND, 1, do_start)在解析命令行时,被添加到Action的commands链表中:
cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs); cmd->func = kw_func(kw); cmd->nargs = nargs; memcpy(cmd->args, args, sizeof(char*) * nargs); list_add_tail(&act->commands, &cmd->clist);
ueventd进程的执行代码和Init进程的执行代码被编译到了同一个可执行程序Init中了,通过查看Android.mk文件即可以得到证实:
LOCAL_SRC_FILES:= \ builtins.c \ init.c \ devices.c \ property_service.c \ util.c \ parser.c \ logo.c \ keychords.c \ signal_handler.c \ init_parser.c \ ueventd.c \ ueventd_parser.c ifeq ($(strip $(INIT_BOOTCHART)),true) LOCAL_SRC_FILES += bootchart.c LOCAL_CFLAGS += -DBOOTCHART=1 endif ifeq ($(BOARD_HAVE_BLUETOOTH_BCM),true) LOCAL_CFLAGS += \ -DBOARD_HAVE_BLUETOOTH_BCM endif ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT))) LOCAL_CFLAGS += -DALLOW_LOCAL_PROP_OVERRIDE=1 endif LOCAL_MODULE:= init LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT) LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_UNSTRIPPED) LOCAL_STATIC_LIBRARIES := libfs_mgr libcutils libc ifeq ($(HAVE_SELINUX),true) LOCAL_STATIC_LIBRARIES += libselinux LOCAL_C_INCLUDES += external/libselinux/include LOCAL_CFLAGS += -DHAVE_SELINUX endif include $(BUILD_EXECUTABLE) # Make a symlink from /sbin/ueventd to /init SYMLINKS := $(TARGET_ROOT_OUT)/sbin/ueventd $(SYMLINKS): INIT_BINARY := $(LOCAL_MODULE) $(SYMLINKS): $(LOCAL_INSTALLED_MODULE) $(LOCAL_PATH)/Android.mk @echo "Symlink: $@ -> ../$(INIT_BINARY)" @mkdir -p $(dir $@) @rm -rf $@ $(hide) ln -sf ../$(INIT_BINARY) $@ ALL_DEFAULT_INSTALLED_MODULES += $(SYMLINKS)uevent以软链接的方式链接到了Init可执行程序,通过查看手机中的uevent程序可知:
因为ueventd 和 Init 在同一个可执行文件下,因此在启动ueventd进程时,进程入口函数依然是system\core\init\init.c文件中的main函数:
int main(int argc, char **argv) { int fd_count = 0; struct pollfd ufds[4]; char *tmpdev; char* debuggable; char tmp[32]; int property_set_fd_init = 0; int signal_fd_init = 0; int keychord_fd_init = 0; bool is_charger = false; if (!strcmp(basename(argv[0]), "ueventd")) return ueventd_main(argc, argv); ..... }
basename 函数从启动的程序路径下截取应用名称,如果启动的应用为ueventd进程,则跳转到ueventd_main函数作为应用的入口函数:
int ueventd_main(int argc, char **argv) { struct pollfd ufd; int nr; char tmp[32]; /* * init sets the umask to 077 for forked processes. We need to * create files with exact permissions, without modification by * the umask. */ umask(000); /* Prevent fire-and-forget children from becoming zombies. * If we should need to wait() for some children in the future * (as opposed to none right now), double-forking here instead * of ignoring SIGCHLD may be the better solution. */ signal(SIGCHLD, SIG_IGN); open_devnull_stdio(); klog_init(); INFO("starting ueventd\n"); /* Respect hardware passed in through the kernel cmd line. Here we will look * for androidboot.hardware param in kernel cmdline, and save its value in * hardware[]. */ import_kernel_cmdline(0, import_kernel_nv); get_hardware_name(hardware, &revision); //解析ueventd.rc配置文件 ueventd_parse_config_file("/ueventd.rc"); //解析ueventd.xxx.rc 配置文件 snprintf(tmp, sizeof(tmp), "/ueventd.%s.rc", hardware); ueventd_parse_config_file(tmp); //设备节点初始化 device_init(); ufd.events = POLLIN; ufd.fd = get_device_fd(); //进入闭环监控模式 while(1) { ufd.revents = 0; //监控uevent socket的连接,参数为-1表示无限超时,在没有事件发生时函数阻塞监控 nr = poll(&ufd, 1, -1); if (nr <= 0) continue; if (ufd.revents == POLLIN) //设备事件处理 handle_device_fd(); } }该函数前面几个步骤和init进程基本相同,这里就不在详细介绍了,可参考Android Init进程源码分析,在初始化完标准输入输出、log、导入命令行参数后,将解析uevent.rc文件和一个同硬件特定硬件相关的ueventd.XXX.rc文件,ueventd.rc文件内容如下所示:
/dev/null 0666 root root /dev/zero 0666 root root /dev/full 0666 root root /dev/ptmx 0666 root root /dev/tty 0666 root root /dev/random 0666 root root /dev/urandom 0666 root root /dev/ashmem 0666 root root /dev/binder 0666 root root # Anyone can read the logs, but if they're not in the "logs" # group, then they'll only see log entries for their UID. /dev/log/* 0666 root log # the msm hw3d client device node is world writable/readable. /dev/msm_hw3dc 0666 root root # gpu driver for adreno200 is globally accessible /dev/kgsl 0666 root root # these should not be world writable /dev/diag 0660 radio radio /dev/diag_arm9 0660 radio radio /dev/android_adb 0660 adb adb /dev/android_adb_enable 0660 adb adb /dev/ttyMSM0 0600 bluetooth bluetooth /dev/uinput 0660 system bluetooth
在system\core\init\ueventd.c文件中,使用ueventd_parse_config_file函数来对ueventd.rc进行解析,接下来详细分析整个解析过程:
int ueventd_parse_config_file(const char *fn) { char *data; //读取文件内容 data = read_file(fn, 0); if (!data) return -1; //解析整个rc文件内容 parse_config(fn, data); 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 (;;) { //从文件内容中查找token,与init.rc文件类似 int token = next_token(&state); switch (token) { //文件结束 case T_EOF: state.parse_line(&state, 0, 0); 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函数对每一行进行详细解析,该函数实现如下:
static void parse_line_device(struct parse_state* state, int nargs, char **args) { set_device_permission(nargs, args); }
函数直接调用set_device_permission来实现,ueventd.rc文件每一行的书写规则为:
非sysfs 设备文件:
|name| |permission| |user| |group|
/dev/cam0660 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中了,如下:
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都是以宏的形式被定义:
#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
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文件中,在该文件中声明了三个链表:
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()函数来初始化设备文件
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 并绑定到指定地址上:
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,如下所示:
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()函数来出来设备变化事件:
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事件
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); } }
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进程的工作,
相关推荐
UDEV 流程图 uevent 描叙了udev检测到uevent后,超找规则,在按照规则进行操作的过程
Linux 用户空间使用Netlink监听uevent,不是原理介绍,而是实战demo
Linux设备模型浅析之uevent篇.pdf
介绍了Linux设备驱动中的uevent篇
c代码
uevent message listener. If you write something based on uevent you ll need it.
写方便起见,我们在各章节开头把该章所涉及的源码路径全部都列出来了,而在具体分析源码时,则只列出该源码的文件名。 下面就是一个示例: [--]AndroidRuntime.cpp] //这里是源码分析和一些注释。 如有...
USB之android_Vold分析,分析了linux udev与android vold的关系由来,vold的功能、架构,使用netlink的通信过程。kernel的uevent发送,framework层的处理、磁盘的挂载等等。
Linux设备模型浅析之uevent篇.pdf
10.2 android.process.media分析 10.2.1 MSR模块分析 10.2.2 MSS模块分析 10.2.3 android.process.media媒体扫描工作的流程总结 10.3 MediaScanner分析 10.3.1 Java层分析 10.3.2 JNI层分析 10.3.3 PVMediaScanner...
10.2 android.process.media分析 / 465 10.2.1 MSR模块分析 / 466 10.2.2 MSS模块分析 / 467 10.2.3 android.process.media媒体扫描工作的流程总结 / 471 10.3 MediaScanner分析 / 472 10.3.1 Java层分析 / 472 ...
浅析frmware的加载和init通过netlink处理uevent事件的一般流程
Uevent 简介 NetLink 简介 MountService 功能介绍: MountService 同 StroageManager 的交互 MountService 同 Vold 的交互
public boolean onTouchEvent(MotionEvent event) { Toast.makeText(this, "点击了屏幕", Toast.LENGTH_SHORT).show(); return super.onTouchEvent(event); }
UEvent PHP中的Userland事件 这是一个扩展,允许用户在运行时取消关注并通过Zend触发事件,因为美味... 例子 如何 ... 以下代码演示了如何将事件附加到方法或函数的调用: <?php class foo { public static ...
在Android源码树中添加userspace I2C读写工具(i2c-util) 本文使用的开发板是:杭州若格科技有限公司的全志R8。CPU:CPUARM Cortex-A8 更多芯片资料请参见全志官网: http://www.allwinnertech.com/clq/r/R8.html...
该库允许侦听和管理到用户空间Linux内核(自2.6.10版本开始)Netlink消息(即:NETLINK_KOBJECT_UEVENT)。 像一样,您将可以监视,显示和管理连接到系统的设备。如何获取资源 go get github.com/pilebones/go-udev...
cat SC5806.android.aa SC5806.android.ab > r58_android_sc5806_20160811.tar.gz rootroot@rootroot-E400:~/wyb/r58_evb_sc5806$ tar zxvf r58_android_sc5806_20160811.tar.gz tar zxvf SC5806.lichee.tar.gz ...
1.概述 UML中的分区暂时无法挂载。...415 ListenerAction FirstStageMount::UeventCallback(const Uevent& uevent) { 416 // Ignores everything that is not a block device. 417 if (uevent.subsystem != block