{"id":15661095,"url":"https://github.com/wywincl/hotplug","last_synced_at":"2026-03-02T22:02:41.754Z","repository":{"id":30166971,"uuid":"33717306","full_name":"wywincl/hotplug","owner":"wywincl","description":"the hotplug implements of OpenWRT","archived":false,"fork":false,"pushed_at":"2015-04-10T08:28:34.000Z","size":128,"stargazers_count":27,"open_issues_count":0,"forks_count":14,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-23T20:51:34.961Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wywincl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-04-10T08:24:28.000Z","updated_at":"2025-03-29T08:00:35.000Z","dependencies_parsed_at":"2022-07-21T08:49:30.792Z","dependency_job_id":null,"html_url":"https://github.com/wywincl/hotplug","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/wywincl/hotplug","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wywincl%2Fhotplug","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wywincl%2Fhotplug/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wywincl%2Fhotplug/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wywincl%2Fhotplug/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wywincl","download_url":"https://codeload.github.com/wywincl/hotplug/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wywincl%2Fhotplug/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30021792,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-02T21:50:36.781Z","status":"ssl_error","status_checked_at":"2026-03-02T21:50:28.329Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-03T13:25:46.234Z","updated_at":"2026-03-02T22:02:41.723Z","avatar_url":"https://github.com/wywincl.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# OpenWRT Hotplug原理分析\n本次研究基于OpenWRT 14_07 trunk。其他版本有部分差异，请阅读时注意。\n\n## \u003ca name='TOC'\u003e目录表\u003c/a\u003e\n  1. [Hotplug原理](#profiles)\n  1. [Hotplug应用](#situations)\n  1. [参考](#references)\n\n\n## \u003ca name='profiles'\u003eHotplug原理\u003c/a\u003e\n\n**Hotplug**即热插拔，在新版本`OpenWRT`上，`hotplug`，`coldplug`与`watchdog`等被集成到全新的Procd系统中。  \n\n**Procd**是`OpenWRT`下新的预初始化，初始化，热插拔和事件系统。在`openwrt` 中, `procd` 作为 `init` 进程会处理许多事情, 其中就包括 `hotplug`。`procd`本身并不知道如何处理`hotplug`事件，也没有必要知道，因为它只实现机制，而不实现策略。事件的处理是由配置文件决定的，这些配置文件即所谓的`rules`.。老版本下独立的`hotplug2`在`r36987`被移除了。所以下面我们要介绍的就是新版本下`Hotplug`的机制。  \n\n要了解`Hotplug`运行的整个过程，首先得了解`procd`系统的工作流程。才能从全局了解`hotplug`是如何工作的。在这里我们重点介绍与`hotplug`相关的`procd`启动过程。\n\n### Procd启动过程分析\n\n#### preinit()函数\n\n\tvoid\n\tpreinit(void)\n\t{\n\t\tchar *init[] = { \"/bin/sh\", \"/etc/preinit\", NULL };\n\t\tchar *plug[] = { \"/sbin/procd\", \"-h\", \"/etc/hotplug-preinit.json\", NULL };\n\t\n\t\tLOG(\"- preinit -\\n\");\n\t\n\t\tplugd_proc.cb = plugd_proc_cb;\n\t\tplugd_proc.pid = fork();\n\t\tif (!plugd_proc.pid) {\n\t\t\texecvp(plug[0], plug);\n\t\t\tERROR(\"Failed to start plugd\\n\");\n\t\t\texit(-1);\n\t\t}\n\t\tif (plugd_proc.pid \u003c= 0) {\n\t\t\tERROR(\"Failed to start new plugd instance\\n\");\n\t\t\treturn;\n\t\t}\n\t\tuloop_process_add(\u0026plugd_proc);\n\t\n\t\tsetenv(\"PREINIT\", \"1\", 1);\n\t\n\t\tpreinit_proc.cb = spawn_procd;\n\t\tpreinit_proc.pid = fork();\n\t\n\t\tif (!preinit_proc.pid) {\n\t\t\texecvp(init[0], init);\n\t\t\tERROR(\"Failed to start preinit\\n\");\n\t\t\texit(-1);\n\t\t}\n\t\tif (preinit_proc.pid \u003c= 0) {\n\t\t\tERROR(\"Failed to start new preinit instance\\n\");\n\t\t\treturn;\n\t\t}\n\t\tuloop_process_add(\u0026preinit_proc);\n\t\n\t\tDEBUG(4, \"Launched preinit instance, pid=%d\\n\", (int) preinit_proc.pid);\n\t}\n\n1. 创建子进程执行`/etc/preinit`脚本，此时`PREINI`T环境变量被设置为1，主进程同时使用`uloop_process_add()`把`/etc/preinit`子进程加入`uloop`进行监控，当`/etc/preinit`执行结束时回调`plugd_proc_cb()`函数把监控`/etc/preinit`进程对应对象中`pid`属性设置为0，表示`/etc/preinit`已执行完成。\n\n2. 创建子进程执行`/sbin/procd -h  /etc/hotplug-preinit.json`，主进程同时使用`uloop_process_add()`把`/sbin/procd`子进程加入`uloop`进行监控，当`/sbin/procd`进程结束时回调`spawn_procd()`函数。\n\n3. `spawn_procd()`函数繁衍后继真正使用的`/sbin/procd`进程，从`/tmp/debuglevel`读出`debug`级别并设置到环境变量`DBGLVL`中，把`watchdog fd`设置到环境变量`WDTFD`中，最后调用`execvp()`繁衍`/sbin/procd`进程。\n\n#### procd进程\n\n在这里我们主要分析`procd`的五个状态,分别为 `STATE_EARLY`、`STATE_INIT`、`STATE_RUNNING`、`STATE_SHUTDOWN`、`STATE_HALT`，这5个状态将按顺序变化，当前状态保存在全局变量`state`中，可通过`procd_state_next()`函数使用状态发生变化。\n\n\tstatic void state_enter(void)\n\t{\n\t\tchar ubus_cmd[] = \"/sbin/ubusd\";\n\t\n\t\tswitch (state) {\n\t\tcase STATE_EARLY:\n\t\t\tLOG(\"- early -\\n\");\n\t\t\twatchdog_init(0);\n\t\t\thotplug(\"/etc/hotplug.json\");\n\t\t\tprocd_coldplug();\n\t\t\tbreak;\n\t\n\t\tcase STATE_INIT:\n\t\t\t// try to reopen incase the wdt was not available before coldplug\n\t\t\twatchdog_init(0);\n\t\t\tLOG(\"- ubus -\\n\");\n\t\t\tprocd_connect_ubus();\n\t\n\t\t\tLOG(\"- init -\\n\");\n\t\t\tservice_init();\n\t\t\tservice_start_early(\"ubus\", ubus_cmd);\n\t\n\t\t\tprocd_inittab();\n\t\t\tprocd_inittab_run(\"respawn\");\n\t\t\tprocd_inittab_run(\"askconsole\");\n\t\t\tprocd_inittab_run(\"askfirst\");\n\t\t\tprocd_inittab_run(\"sysinit\");\n\t\t\tbreak;\n\t\n\t\tcase STATE_RUNNING:\n\t\t\tLOG(\"- init complete -\\n\");\n\t\t\tbreak;\n\t\n\t\tcase STATE_SHUTDOWN:\n\t\t\tLOG(\"- shutdown -\\n\");\n\t\t\tprocd_inittab_run(\"shutdown\");\n\t\t\tsync();\n\t\t\tbreak;\n\t\n\t\tcase STATE_HALT:\n\t\t\tLOG(\"- reboot -\\n\");\n\t\t\treboot(reboot_event);\n\t\t\tbreak;\n\t\n\t\tdefault:\n\t\t\tERROR(\"Unhandled state %d\\n\", state);\n\t\t\treturn;\n\t\t};\n\t}\n\n* #####STATE_EARLY状态 - init前准备工作\n\t1. 初始化`watchdog`\n\t2. 根据\"`/etc/hotplug.json`\"规则监听`hotplug`\n\t3. `procd_coldplug()`函数处理，把`/dev`挂载到`tmpfs`中，`fork udevtrigger`进程产生冷插拔事件，以便让`hotplug`监听进行处理\n\t4. `udevstrigger`进程处理完成后回调`procd_state_next()`函数把状态从`STATE_EARLY`转变为`STATE_INIT`\n\n* #####STATE_INIT状态 - 初始化工作\n\t1. 连接`ubusd`，此时实际上`ubusd`并不存在，所以`procd_connect_ubus`函数使用了定时器进行重连，而`uloop_run()`需在初始化工作完成后才真正运行。当成功连接上`ubusd`后，将注册`service main_object`对象，`system_object`对象、`watch_event`对象(`procd_connect_ubus()`函数)，\n\t2. 初始化`services`（服务）和`validators`（服务验证器）全局`AVL tree`\n\t3. 把`ubusd`服务加入`services`管理对象中`(service_start_early)`\n\t4. 根据`/etc/inittab`内容把`cmd`、`handler`对应关系加入全局链表`actions`中\n\t5. 顺序加载`respawn、askconsole、askfirst、sysinit`命令\n\t6. `sysinit`命令把`/etc/rc.d/`目录下所有启动脚本执行完成后将回调`rcdone()`函数把状态从`STATE_INIT`转变为`STATE_RUNNING`\n\n* #####STATE_RUNNING状态\n\t1. 进入`STATE_RUNNING`状态后`procd`运行`uloop_run()`主循环 \n\n\n### Hotplug原理图\n\n`Hotplug`原理的整个流程如下所示：\n\n\t-----------------------\n\t|    procd daemon     |\n\t|    (hotplug.json)   |\n\t-----------------------\n\t\t\t  netlink| socket\t\t\tuser space\n    -------------------------------------------------\n\t\t\t\t\t |\t\t\t\t    kernel space\n\t-----------------------\n\t|    (uevent [json])  |\n\t|    kernel           |\n\t-----------------------\n\n主要过程分为以下两个部分：  \n\n\n1.  **内核发出uevent事件**\n\n内核使用`uevent`事件通知用户空间，`uevent`首先在内核中调用`netlink_kernel_create()`函数创建一个`socket`套接字，该函数原型在`netlink.h`中定义。这是一种特殊类型的`socket` ，专门用于内核空间与用户空间的异步通信。  \n`kobject_uevent()`产生`uevent`事件`(/lib/kobject_uevent.c)`，事件的部分信息通过环境变量传递，如`$ACTION, $DEVPATH, $SUBSYSTEM`等，产生的`uevent`先由`netlink_broadcast_filtered()`发出，最后调用`uevent_helper[]`所指定的程序来处理。  \n在`linux`中，`uevent_helper[]`里默认指定`”/sbin/hotplug”`，但可以通过`/sys/kernel/uevent_helper（kernel/ksysfs.c）`或`/proc/kernel/uevent_helper(kernel/sysctl.c)`来修改成指定的程序。\n在新`OpenWRT`中，并不使用`user_helper[]`指定程序来处理`uevent（/sbin/hotplug`不存在，在以前版本中存在），而是通过`PF_NETLINK`套接字来获取来自内核空间的`uevent`。  \n2.  **用户空间监听uevent**\n\n在`proc/plug/hotplug.c`中，创建一个`PF_NETLINK`套接字来监听内核`netlink_broadcast_filtered()`发出的`uevent`。收到`uevent`之后，在根据`/etc/hotplug.json`里的描述，定位到对应的执行函数来处理。  \n通常情况下，`/etc/hotplug.json`会调用`/sbin/hotplug-call`来处理`uevent`，它根据`uevent`的`$SUBSYSTEM`变量来分别调用`/etc/hotplug.d`下不同目录中的脚本。  \n`/sbin/hotplug-call`脚本如下所示，这里面的`$1`表示`hotplug-call`的第一个参数：\n\n\troot@OpenWrt:/sbin# cat hotplug-call \n\t#!/bin/sh\n\t# Copyright (C) 2006-2010 OpenWrt.org\n\t\n\texport HOTPLUG_TYPE=\"$1\"\n\t\n\t. /lib/functions.sh\n\t\n\tPATH=/bin:/sbin:/usr/bin:/usr/sbin\n\tLOGNAME=root\n\tUSER=root\n\texport PATH LOGNAME USER\n\texport DEVICENAME=\"${DEVPATH##*/}\"\n\t\n\t[ \\! -z \"$1\" -a -d /etc/hotplug.d/$1 ] \u0026\u0026 {\n\t        for script in $(ls /etc/hotplug.d/$1/* 2\u003e\u0026-); do (\n\t                [ -f $script ] \u0026\u0026 . $script\n\t        ); done\n\t}\n\n下表是`hotplug.json`的具体内容，重点关注蓝色字段。\n\n    root@OpenWrt:/etc# cat hotplug.json \n\n\t[\n\t        [ \"case\", \"ACTION\", {\n\t                \"add\": [\n\t                        [ \"if\",\n\t                                [ \"and\",\n\t                                        [ \"has\", \"MAJOR\" ],\n\t                                        [ \"has\", \"MINOR\" ],\n\t                                ],\n\t                                [\n\t                                        [ \"if\",\n\t                                                [ \"or\",\n\t                                                        [ \"eq\", \"DEVNAME\",\n\t                                                                [ \"null\", \"full\", \"ptmx\", \"zero\" ],\n\t                                                        ],\n\t                                                        [ \"regex\", \"DEVNAME\",\n\t                                                                [ \"^gpio\", \"^hvc\" ],\n\t                                                        ],\n\t                                                ],\n\t                                                [\n\t                                                        [ \"makedev\", \"/dev/%DEVNAME%\", \"0666\" ],\n\t                                                        [ \"return\" ],\n\t                                                ]\n\t                                        ],\n\t                                        [ \"if\",\n\t                                                [ \"or\",\n\t                                                        [ \"eq\", \"DEVNAME\", \"mapper/control\" ],\n\t                                                        [ \"regex\", \"DEVPATH\", \"^ppp\" ],\n\t                                                ],\n\t                                                [\n\t                                                        [ \"makedev\", \"/dev/%DEVNAME%\", \"0600\" ],\n\t                                                        [ \"return\" ],\n\t                                                ],\n\t                                        ],\n\t                                        [ \"if\",\n\t                                                [ \"has\", \"DEVNAME\" ],\n\t                                                [ \"makedev\", \"/dev/%DEVNAME%\", \"0644\" ],\n\t                                        ],\n\t                                ],\n\t                        ],\n\t                        [ \"if\",\n\t                                [ \"has\", \"FIRMWARE\" ],\n\t                                [\n\t                                        [ \"exec\", \"/sbin/hotplug-call\", \"%SUBSYSTEM%\" ],\n\t                                        [ \"load-firmware\", \"/lib/firmware\" ],\n\t                                        [ \"return\" ]\n\t                                ]\n\t                        ],\n\t                ],\n\t                \"remove\" : [\n\t                        [ \"if\",\n\t                                [ \"and\",\n\t                                        [ \"has\", \"DEVNAME\" ],\n\t                                        [ \"has\", \"MAJOR\" ],\n\t                                        [ \"has\", \"MINOR\" ],\n\t                                ],\n\t                                [ \"rm\", \"/dev/%DEVNAME%\" ]\n\t                        ]\n\t                ]\n\t        } ],\n\t        [ \"if\",\n\t                [ \"eq\", \"SUBSYSTEM\", \"platform\" ],\n\t                [ \"exec\", \"/sbin/hotplug-call\", \"%SUBSYSTEM%\" ]\n\t        ],\n\t        [ \"if\",\n\t                [ \"and\",\n\t                        [ \"has\", \"BUTTON\" ],\n\t                        [ \"eq\", \"SUBSYSTEM\", \"button\" ],\n\t                ],\n\t                [ \"exec\", \"/etc/rc.button/%BUTTON%\" ]\n\t        ],\n\t        [ \"if\",\n\t                [ \"eq\", \"SUBSYSTEM\",\n\t                        [ \"net\", \"input\", \"usb\", \"ieee1394\", \"block\", \"atm\", \"zaptel\", \"tty\", \"button\" ]\n\t                ],\n\t                [ \"exec\", \"/sbin/hotplug-call\", \"%SUBSYSTEM%\" ]\n\t        ],\n\t        [ \"if\",\n\t                [ \"and\",\n\t                        [ \"eq\", \"SUBSYSTEM\", \"usb-serial\" ],\n\t                        [ \"regex\", \"DEVNAME\",\n\t                                [ \"^ttyUSB\", \"^ttyACM\" ]\n\t                        ],\n\t                ],\n\t                [ \"exec\", \"/sbin/hotplug-call\", \"tty\" ]\n\t        ],\n\t]\n\n\n\n**[[⬆]](#TOC)**\n## \u003ca name='situations'\u003eHotplug应用\u003c/a\u003e\n\n### U盘的自动挂载卸载\n`Hotplug`一个常见的实例应用就是U盘或SD卡等外设的自动挂载和卸载功能。所以这里我们主要介绍如何利用`hotplug`实现U盘，移动硬盘等外设自动挂载的方法和原理。本文中的例子还需要根据实际情况作相应适配。  \n\n当然，首先得内核有相应的驱动程序支持才行。当U盘插入后，会产生`uevent`事件，`hotplug`收到这个内核广播事件后，根据`uevent` 事件`json`格式的附带信息内容，在`hotplug.json`中进行定位。事件包含的信息一般为如下所示：\n\n    ACTION(add), DEVPATH(devpath), SUBSYSTEM(block), MAJOR(8), MINOR(1), DEVNAME(devname), DEVTYPE(devtype), SEQNUM(865)\n\n根据上面的信息，就可以在`hotplug.json`中定位到两个条目，如上面`hotplug.json`中蓝色显示字段。第一个条目执行的是`makedev`，该命令会创建设备节点。第二个条目会根据附带信息中的`ACTION, DEVPATH, SUBSYSTEM, DEVNAME, DEVTYPE `等变量，调用命令`exec`去执行`hotplug-call`脚本。\n\n于是 `hotplug-call` 会尝试执行 `/etc/hotplug.d/block/` 目录下的所有可执行脚本。\n\n所以我们可以在这里放置我们的自动挂载/卸载处理脚本。\n例如，编写`/etc/hotplug.d/block/30-usbmount`,填入以下内容实现U盘自动挂载，卸载：\n\n\t#!/bin/sh\n\t \n\t[ \"$SUBSYSTEM\" = block ] || exit0\n\t[ \"$DEVTYPE\" = partition -a\"$ACTION\" = add ] \u0026\u0026 {\n\t    echo\"$DEVICENAME\" | grep 'sd[a-z][1-9]' || exit 0\n\t    test-d /mnt/$DEVICENAME || mkdir /mnt/$DEVICENAME\n\t    mount  -o iocharset=utf8,rw /dev/$DEVICENAME/mnt/$DEVICENAME || \\\n\t        mount-o rw /dev/$i /mnt/$i\n\t}\n\t \n\t[ \"$DEVTYPE\" = partition -a\"$ACTION\" = remove ] \u0026\u0026 {\n\t    echo\"$DEVICENAME\" | grep 'sd[a-z][1-9]' || exit 0\n\t    umount/mnt/$DEVICENAME \u0026\u0026 rmdir /mnt/$DEVICENAME\n\t}\n\n\n### Button按键的检测\n\n在`OpenWRT`中，按键的检测也是通过`Hotplug`机制来实现的。\n\n它首先写了一个内核模块：`gpio_button_hotplug`, 用于监听按键，有中断和 `poll` 两种方式。然后在发出事件的同时, 将记录并计算得出的两次按键时间差也作为 `uevent` 变量发出来。这样在用户空间收到这个 `uevent `事件时就知道该次按键按下了多长时间。\n\n`hotplug.json `中有描述, 如果 `uevent` 中含有` BUTTON` 字符串, 而且` SUBSYSTEM` 为 \"`button`\", 则执行` /etc/rc.button/ `下的 `%BUTTON% `脚本来处理。\n\n细节描述如下：\n当按键时，则触发`button_hotplug_even`t函数（`gpio-button-hotplug.c`）\n\n调用`button_hotplug_create_event`产生`uevent`事件，调用`button_hotplug_fill_event`填充事件(`JSON`格式)，并最终调用`button_hotplug_work`发出`uevent`广播。\n\n上述广播，被守护进程`procd`中的`hotplug_handler (procd/plug/hotplug.c)` 收到，并根据`etc/hotplug.json`中预先定义的`JSON`内容匹配条件，定位到对应的执行函数，具体如下所示，命中了两个条目，所以会依次执行这两个条目队列中的操作函数：\n\n\t[ \"if\",\n\t\t[ \"and\",\n\t\t\t[ \"has\", \"BUTTON\" ],\n\t\t\t[ \"eq\", \"SUBSYSTEM\", \"button\" ],\n\t\t],\n\t\t[ \"exec\", \"/etc/rc.button/%BUTTON%\" ]\n\t],\n\t和\n\t[ \"if\",\n\t\t[ \"eq\", \"SUBSYSTEM\",\n\t\t[ \"net\", \"input\", \"usb\", \"ieee1394\", \"block\", \"atm\", \"zaptel\", \"tty\", \"button\" ]\n\t\t],\n\t\t[ \"exec\", \"/sbin/hotplug-call\", \"%SUBSYSTEM%\" ]\n\t],\n\n在`rc.button`目录下，我们定义了`reset`按钮的执行脚本：\n\n\troot@OpenWrt:/etc/rc.button# cat reset\n\t#!/bin/sh\n\t\n\t[ \"${ACTION}\" = \"released\" ] || exit 0\n\t\n\t. /lib/functions.sh\n\t\n\tlogger \"$BUTTON pressed for $SEEN seconds\"\n\t\n\tif [ \"$SEEN\" -lt 1 ]\n\tthen\n\t        echo \"REBOOT\" \u003e /dev/console\n\t        sync\n\t        reboot\n\telif [ \"$SEEN\" -gt 5 ]\n\tthen\n\t        echo \"FACTORY RESET\" \u003e /dev/console\n\t        jffs2reset -y \u0026\u0026 reboot \u0026\n\tfi\n\n从脚本中我们可以清晰地看出，当按键时间小于1s时，执行`reboot`重启命令，当按键时间超过5s时，执行恢复出厂设置并重启命令。\n\n第二个条目，由于默认情况下没有在`/etc/hotplug.d`目录下创建`button`子目录，因此执行为空。\n\n使用 `export DBGLVL=10; procd -h /etc/hotplug.json` 截获一些打印信息看看:\n\n\troot@OpenWrt:/etc/rc.button# export DBGLVL=10; procd -h /etc/hotplug.json\n\tprocd:hotplug_handler_debug(404): {{\"HOME\":\"\\/\",\"PATH\":\"\\/sbin:\\/bin:\\/usr\\/sbin:\\/usr\\/bin\",\"SUBSYSTEM\":\"button\",\"ACTION\":\"pressed\",\"BUTTON\":\"reset\",\"SEEN\":\"42949450\",\"SEQNUM\":\"331\"}}\n\tprocd: rule_handle_command(355): Command: exec\n\tprocd: rule_handle_command(357):  /etc/rc.button/reset\n\tprocd: rule_handle_command(358): \n\tprocd: rule_handle_command(360): Message:\n\tprocd: rule_handle_command(362):  HOME=/\n\tprocd: rule_handle_command(362):  PATH=/sbin:/bin:/usr/sbin:/usr/bin\n\tprocd: rule_handle_command(362):  SUBSYSTEM=button\n\tprocd: rule_handle_command(362):  ACTION=pressed\n\tprocd: rule_handle_command(362):  BUTTON=reset\n\tprocd: rule_handle_command(362):  SEEN=42949450\n\tprocd: rule_handle_command(362):  SEQNUM=331\n\tprocd: rule_handle_command(363): \n\tprocd: queue_next(281): Launched hotplug exec instance, pid=987\n\tprocd: rule_handle_command(355): Command: exec\n\tprocd: rule_handle_command(357):  /sbin/hotplug-call\n\tprocd: rule_handle_command(357):  button\n\tprocd: rule_handle_command(358): \n\tprocd: rule_handle_command(360): Message:\n\tprocd: rule_handle_command(362):  HOME=/\n\tprocd: rule_handle_command(362):  PATH=/sbin:/bin:/usr/sbin:/usr/bin\n\tprocd: rule_handle_command(362):  SUBSYSTEM=button\n\tprocd: rule_handle_command(362):  ACTION=pressed\n\tprocd: rule_handle_command(362):  BUTTON=reset\n\tprocd: rule_handle_command(362):  SEEN=42949450\n\tprocd: rule_handle_command(362):  SEQNUM=331\n\tprocd: rule_handle_command(363): \n\tprocd: queue_proc_cb(286): Finished hotplug exec instance, pid=987\n\t...\n\n\n### 接口状态检测\n\n当接口状态出现`ifup`或者`ifdown`时，`netifd`守护进程会调用`call_hotplug（）（/interface-event.c）`来处理这个事件，`call_hotplug()`执行`run_cmd（）`,并且设置系统环境变量`$ACTION, $INTERFACE, $DEVICE`, 同时调用`hotplug_cmd_path(=DEFAULT_HOTPLUG_PATH=/sbin/hotplug-call`, 在`netifd.h`中)并传入参数`iface`。下表是上述变量的介绍。\n\n\t---------------------------------------------------------------\n\t变量名称\t\t\t\t|\t\t\t\t说明\n\t---------------------------------------------------------------\n\tACTION                事件，如ifup,ifdown,ifupdate\n\t---------------------------------------------------------------\n\tINTERFACE       \t  发生事件动作的接口名，如（wan, ppp0）\n\t---------------------------------------------------------------\n\tDEVICE                发生事件动作的物理接口名，如（eth0.1或br-lan）\n\t---------------------------------------------------------------\n\n这样用户空间脚本`hotplug-call`就会将`/etc/hotplug.d/iface`目录下的所有脚本执行一遍。\n\n举例说明：\n\n我们在`iface`目录下编写一个脚本名字叫`13-my-action`, 内容如下：\n\n\troot@OpenWrt:/etc/hotplug.d/iface# cat 13-my-action \n\t#!/bin/sh\n\t\n\t[ \"$ACTION\" = ifup ] \u0026\u0026 {\n\t  echo Device:$DEVICE  Action:$ACTION \"13-my-action\" \u003e /dev/console\n\t} \n\n让接口`down`，从下面的`log`中可以看出，`iface`下的自定义脚本被执行了一遍。\n\n\troot@OpenWrt:/etc/hotplug.d/iface# ubus call network.interface.lan up\n\t\n\t[  462.370000] IPv6: ADDRCONF(NETDEV_UP): eth1: link is not ready\n\t[  462.370000] device eth1 entered promiscuous mode\n\t[  462.380000] IPv6: ADDRCONF(NETDEV_UP): br-lan: link is not ready\n\tDevice:br-lan Action:ifup 13-my-action\n\t[  462.980000] eth1: link up (1000Mbps/Full duplex)\n\t[  462.980000] br-lan: port 1(eth1) entered forwarding state\n\t[  462.990000] br-lan: port 1(eth1) entered forwarding state\n\t[  462.990000] IPv6: ADDRCONF(NETDEV_CHANGE): eth1: link becomes ready\n\t[  463.040000] IPv6: ADDRCONF(NETDEV_CHANGE): br-lan: link becomes ready\n\tprocd: Not starting instance igmpproxy::instance1, an error was indicated\n\t[  464.990000] br-lan: port 1(eth1) entered forwarding state\n\n\u003e[备注] 由于守护进程`netifd`在`ubus`中注册了服务，因此我们可以通过`ubus`调用`netifd`提供的服务接口，例如使接口`ifdown`命令为： `ubus call network.interface.lan up/down`。\n\n### 早期Hotplug2\n\n早期的`Hotplug`机制，单独运行守护进程，内核会指定`hotplug2`进程来处理系统内核广播出来的`uevent`事件。原理和上面介绍的大同小异，`hotplug2`采用了与`linux`中的`udev`相同的`rule`编写规则。\n\n\n**[[⬆]](#TOC)**\n## \u003ca name='references'\u003e参考\u003c/a\u003e\n\n`https://openwrt.org/`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwywincl%2Fhotplug","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwywincl%2Fhotplug","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwywincl%2Fhotplug/lists"}