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/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中了,如下:

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

Android系统中,可以使用uevent来检测U盘的挂载地址。具体步骤如下: 1. 在Android系统中,U盘的插入和拔出都会发送uevent消息,可以通过注册一个uevent监听器来接收这些消息。 2. 在uevent消息中,会包含有U盘的一些信息,比如设备名称、挂载路径等。 3. 在接收到U盘插入消息时,可以解析uevent消息中的挂载路径信息,即可获得U盘的挂载地址。 以下是一个使用uevent检测U盘挂载地址的示例代码: ``` import android.os.Handler; import android.os.Looper; import android.os.MessageQueue; import android.util.Log; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; public class UdiskMonitor { private static final String TAG = "UdiskMonitor"; private static final String UDISK_MOUNT_PATH = "/sys/class/android_usb/android0/f_mass_storage/lun/file"; private OnUdiskMountedListener mListener; public void startMonitor(final OnUdiskMountedListener listener) { mListener = listener; // 在主线程中监听uevent消息 new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { // 开始监听uevent消息 startUeventMonitor(); return false; } }); } }); } private void startUeventMonitor() { BufferedReader reader = null; try { // 打开uevent监听文件 reader = new BufferedReader(new FileReader(new File("/proc/net/netlink"))); String line; while ((line = reader.readLine()) != null) { // 解析uevent消息 String[] parts = line.split(" "); if (parts.length >= 6 && parts[0].equals("1")) { String action = parts[1]; String devPath = parts[4]; if (action.equals("add") && devPath.equals(UDISK_MOUNT_PATH)) { // U盘被插入,获取挂载路径并回调监听器 String mountPath = getUdiskMountPath(); if (mountPath != null) { if (mListener != null) { mListener.onUdiskMounted(mountPath); } } } } } } catch (IOException e) { Log.e(TAG, "startUeventMonitor: " + e.getMessage()); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { Log.e(TAG, "startUeventMonitor: " + e.getMessage()); } } } } private String getUdiskMountPath() { String mountPath = null; try { // 读取U盘挂载路径 BufferedReader reader = new BufferedReader(new FileReader(new File(UDISK_MOUNT_PATH))); mountPath = reader.readLine().trim(); reader.close(); } catch (IOException e) { Log.e(TAG, "getUdiskMountPath: " + e.getMessage()); } return mountPath; } public interface OnUdiskMountedListener { void onUdiskMounted(String mountPath); } } ``` 在上述代码中,我们通过监听uevent消息来检测U盘的插入和拔出事件。当接收到U盘插入事件时,我们通过读取U盘的挂载路径来获取U盘的挂载地址,并通过回调监听器来通知外部应用程序。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值