Single

(待填坑) 详解 OpenWRT 的HotPlug组件

Hotplug原理

Hotplug即热插拔,它的功能就是允许用户在不关闭系统,不切断电源的情况下取出和更换设备,最常见的如USB键盘,鼠标。

Procd是OpenWRT下新的预初始化,初始化,热插拔和事件系统。在openwrt 中, procd 作为 init 进程会处理许多事情, 其中就包括 hotplug。procd本身并不知道如何处理hotplug事件,也没有必要知道,因为它只实现机制,而不实现策略。事件的处理是由配置文件决定的,这些配置文件即所谓的rules.。老版本下独立的hotplug2在r36987被移除了。所以下面我们要介绍的就是新版本下Hotplug的机制。

热插拔与冷插拔入口

要了解Hotplug运行的整个过程,首先得了解procd系统的工作流程。才能从全局了解hotplug是如何工作的。
调用 openWRT的state_enter() 状态机函数,此函数中,在系统early阶段初始 hotplug()、procd_coldplug()函数。注册RPC服务响应程序。源码如下:

static void state_enter(void)
{
	char ubus_cmd[] = "/sbin/ubusd";

	switch (state) {
	case STATE_EARLY:
		LOG("- early -\n");
		watchdog_init(0);
		hotplug("/etc/hotplug.json");	//热插拔函数初始化,hotplug.json文件内容非常关键
		procd_coldplug();				//冷插拔函数初始化
		break;

	case STATE_UBUS:
		// try to reopen incase the wdt was not available before coldplug
		watchdog_init(0);
		set_stdio("console");
		LOG("- ubus -\n");
		procd_connect_ubus();
		service_start_early("ubus", ubus_cmd);
		break;

	case STATE_INIT:
		LOG("- init -\n");
		procd_inittab();
		procd_inittab_run("respawn");
		procd_inittab_run("askconsole");
		procd_inittab_run("askfirst");
		procd_inittab_run("sysinit");

		// switch to syslog log channel
		ulog_open(ULOG_SYSLOG, LOG_DAEMON, "procd");
		break;

	case STATE_RUNNING:
		LOG("- init complete -\n");
		procd_inittab_run("respawnlate");
		procd_inittab_run("askconsolelate");
		break;

	case STATE_SHUTDOWN:
		/* Redirect output to the console for the users' benefit */
		set_console();
		LOG("- shutdown -\n");
		procd_inittab_run("shutdown");
		sync();
		break;

	case STATE_HALT:
		// To prevent killed processes from interrupting the sleep
		signal(SIGCHLD, SIG_IGN);
		LOG("- SIGTERM processes -\n");
		kill(-1, SIGTERM);
		sync();
		sleep(1);
		LOG("- SIGKILL processes -\n");
		kill(-1, SIGKILL);
		sync();
		sleep(1);
#ifndef DISABLE_INIT
		perform_halt();
#else
		exit(EXIT_SUCCESS);
#endif
		break;

	default:
		ERROR("Unhandled state %d\n", state);
		return;
	};
}

一、热插拔源码走读

void hotplug(char *rules)
{
	struct sockaddr_nl nls = {};
	int nlbufsize = 512 * 1024;

	rule_file = strdup(rules);
	nls.nl_family = AF_NETLINK;
	nls.nl_pid = getpid();
	nls.nl_groups = -1;
	// 建立 NETLINK_KOBJECT_UEVENT 连接
	if ((hotplug_fd.fd = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT)) == -1) {
		ERROR("Failed to open hotplug socket: %m\n");
		exit(1);
	}
	if (bind(hotplug_fd.fd, (void *)&nls, sizeof(struct sockaddr_nl))) {
		ERROR("Failed to bind hotplug socket: %m\n");
		exit(1);
	}
	// 建立 监听
	if (setsockopt(hotplug_fd.fd, SOL_SOCKET, SO_RCVBUFFORCE, &nlbufsize, sizeof(nlbufsize)))
		ERROR("Failed to resize receive buffer: %m\n");

	json_script_init(&jctx);
	queue_proc.cb = queue_proc_cb;					//设置回调函数
	uloop_fd_add(&hotplug_fd, ULOOP_READ);			//把 hotplug_fd 添加到 ubus 链表中
}

文件 /etc/hotplug.json

[
        [ "case", "ACTION", {
                "add": [
                        [ "if",
                                [ "and",
                                        [ "has", "MAJOR" ],
                                        [ "has", "MINOR" ],
                                ],
                                [
                                        [ "if",
                                                [ "or",
                                                        [ "eq", "DEVNAME",
                                                                [ "null", "full", "ptmx", "zero" ],
                                                        ],
                                                        [ "regex", "DEVNAME",
                                                                [ "^gpio", "^hvc" ],
                                                        ],
                                                ],
                                                [
                                                        [ "makedev", "/dev/%DEVNAME%", "0666" ],
                                                        [ "return" ],
                                                ]
                                        ],
                                        [ "if",
                                                [ "or",
                                                        [ "eq", "DEVNAME", "mapper/control" ],
                                                        [ "regex", "DEVPATH", "^ppp" ],
                                                ],
                                                [
                                                        [ "makedev", "/dev/%DEVNAME%", "0600" ],
                                                        [ "return" ],
                                                ],
                                        ],
                                        [ "if",
                                                [ "has", "DEVNAME" ],
                                                [ "makedev", "/dev/%DEVNAME%", "0644" ],
                                        ],
                                ],
                        ],
                        [ "if",
                                [ "has", "FIRMWARE" ],
                                [
                                        [ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ],
                                        [ "load-firmware", "/lib/firmware" ],
                                        [ "return" ]
                                ]
                        ],
                ],
                "remove" : [
                        [ "if",
                                [ "and",
                                        [ "has", "DEVNAME" ],
                                        [ "has", "MAJOR" ],
                                        [ "has", "MINOR" ],
                                ],
                                [ "rm", "/dev/%DEVNAME%" ]
                        ]
                ]
        } ],
        [ "if",
                [ "eq", "SUBSYSTEM", "platform" ],
                [ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ]
        ],
        [ "if",
                [ "and",
                        [ "has", "BUTTON" ],
                        [ "eq", "SUBSYSTEM", "button" ],
                ],
                [ "exec", "/etc/rc.button/%BUTTON%" ]
        ],
        [ "if",
                [ "eq", "SUBSYSTEM",
                        [ "net", "input", "usb", "ieee1394", "block", "atm", "zaptel", "tty", "button" ]
                ],
                [ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ]
        ],
        [ "if",
                [ "and",
                        [ "eq", "SUBSYSTEM", "usb-serial" ],
                        [ "regex", "DEVNAME",
                                [ "^ttyUSB", "^ttyACM" ]
                        ],
                ],
                [ "exec", "/sbin/hotplug-call", "tty" ]
        ],
]

热插拔回调函数

static void hotplug_handler(struct uloop_fd *u, unsigned int ev)
{
	int i = 0;
	static char buf[4096];
	int len = recv(u->fd, buf, sizeof(buf) - 1, MSG_DONTWAIT);
	void *index;
	if (len < 1)
		return;

	buf[len] = '\0';

	blob_buf_init(&b, 0);
	index = blobmsg_open_table(&b, NULL);
	while (i < len) {
		int l = strlen(buf + i) + 1;
		char *e = strstr(&buf[i], "=");

		if (e) {
			*e = '\0';
			blobmsg_add_string(&b, &buf[i], &e[1]);
		}
		i += l;
	}
	blobmsg_close_table(&b, index);
	hotplug_handler_debug(b.head);
	json_script_run(&jctx, rule_file, blob_data(b.head));		//执行 /etc/hotplug.json 执行 json 脚本程序。
}

热插拔回调函数中,调用json_script_run()函数,此函数调用如下函数

static void __json_script_run(struct json_call *call, struct json_script_file *file,
			      struct blob_attr *context)
{
	struct json_script_ctx *ctx = call->ctx;

	if (file->seq == call->seq) {
		if (context)
			ctx->handle_error(ctx, "Recursive include", context);

		return;
	}

	file->seq = call->seq;
	while (file) {									// 遍历 json 文件中所有的脚本文件
		json_process_cmd(call, file->data);			// 执行脚本中命令
		file = file->next;
	}
}

调用如下函数,此函数

static int __json_process_cmd(struct json_call *call, struct blob_attr *cur)
{
	struct json_script_ctx *ctx = call->ctx;
	const char *name;
	bool found;
	int ret;

	if (blobmsg_type(cur) != BLOBMSG_TYPE_ARRAY ||
	    blobmsg_type(blobmsg_data(cur)) != BLOBMSG_TYPE_STRING) {
		ctx->handle_error(ctx, "Unexpected element type", cur);
		return -1;
	}

	ret = __json_process_type(call, cur, cmd, ARRAY_SIZE(cmd), &found);
	if (found)
		return ret;

	name = blobmsg_data(blobmsg_data(cur));
	ret = cmd_process_strings(call, cur);
	if (ret)
		return ret;

	ctx->handle_command(ctx, name, blob_data(ctx->buf.head), call->vars);

	return 0;
}

二、冷插拔源码走读

void procd_coldplug(void)
{
	char *argv[] = { "udevtrigger", NULL };
	unsigned int oldumask = umask(0);

	if (!is_container()) {
		umount2("/dev/pts", MNT_DETACH);
		umount2("/dev/", MNT_DETACH);
		mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755,size=512K");
		mkdir("/dev/pts", 0755);
		mount("devpts", "/dev/pts", "devpts", MS_NOEXEC | MS_NOSUID, 0);
	}

	ignore(symlink("/tmp/shm", "/dev/shm"));
	umask(oldumask);
	udevtrigger.cb = udevtrigger_complete;
	udevtrigger.pid = fork();
	if (!udevtrigger.pid) {
		execvp(argv[0], argv);
		ERROR("Failed to start coldplug: %m\n");
		exit(EXIT_FAILURE);
	}

	if (udevtrigger.pid <= 0) {
		ERROR("Failed to start new coldplug instance: %m\n");
		return;
	}

	uloop_process_add(&udevtrigger);

	DEBUG(4, "Launched coldplug instance, pid=%d\n", (int) udevtrigger.pid);
}

参考

https://blog.csdn.net/weixin_38387929/article/details/119461967

暂无评论

发表评论