• Linux设备模型(五) - uevent kernel实现


    1. Uevent的功能

    Uevent是Kobject的一部分,用于在Kobject状态发生改变时,例如增加、移除等,通知用户空间程序。用户空间程序收到这样的事件后,会做相应的处理。

    该机制通常是用来支持热拔插设备的,例如U盘插入后,USB相关的驱动软件会动态创建用于表示该U盘的device结构(相应的也包括其中的kobject),并告知用户空间程序,为该U盘动态的创建/dev/目录下的设备节点,更进一步,可以通知其它的应用程序,将该U盘设备mount到系统中,从而动态的支持该设备。

    2. Uevent在kernel中的位置

    下面图片描述了Uevent模块在内核中的位置:

    由此可知,Uevent的机制是比较简单的,设备模型中任何设备有事件需要上报时,会触发Uevent提供的接口。Uevent模块准备好上报事件的格式后,可以通过两个途径把事件上报到用户空间:一种是通过kmod模块,直接调用用户空间的可执行文件;另一种是通过netlink通信机制,将事件从内核空间传递给用户空间。

    3,数据结构描述

    • kobject_action

    1. /*
    2. * The actions here must match the index to the string array
    3. * in lib/kobject_uevent.c
    4. *
    5. * Do not add new actions here without checking with the driver-core
    6. * maintainers. Action strings are not meant to express subsystem
    7. * or device specific properties. In most cases you want to send a
    8. * kobject_uevent_env(kobj, KOBJ_CHANGE, env) with additional event
    9. * specific variables added to the event environment.
    10. */
    11. enum kobject_action {
    12. KOBJ_ADD,
    13. KOBJ_REMOVE, //Kobject(或上层数据结构)的添加/移除事件
    14. KOBJ_CHANGE, //Kobject(或上层数据结构)的状态或者内容发生改变; 如果设备驱动需要上报的事件不再上面事件的范围内,或者是自定义的事件,可以使用该event,并携带相应的参数。
    15. KOBJ_MOVE, //Kobject(或上层数据结构)更改名称或者更改Parent(意味着在sysfs中更改了目录结构)
    16. KOBJ_ONLINE,
    17. KOBJ_OFFLINE, //Kobject(或上层数据结构)的上线/下线事件,其实是是否使能
    18. KOBJ_BIND,
    19. KOBJ_UNBIND,
    20. };
    • kobj_uevent_env
    1. #define UEVENT_NUM_ENVP 64 /* number of env pointers */
    2. #define UEVENT_BUFFER_SIZE 2048 /* buffer for the variables */
    3. /* environment buffer */
    4. struct kobj_uevent_env {
    5. char *argv[3];
    6. char *envp[UEVENT_NUM_ENVP]; //环境变量的指针数组,envp指向每一个环境变量
    7. int envp_idx; //环境变量的索引
    8. char buf[UEVENT_BUFFER_SIZE]; //存储所有环境变量的buffer
    9. int buflen; //环境变量的buffer的长度
    10. };
    • kset_uevent_ops
    1. struct kset {
    2. struct list_head list;
    3. spinlock_t list_lock;
    4. struct kobject kobj;
    5. const struct kset_uevent_ops *uevent_ops;
    6. ANDROID_KABI_RESERVE(1);
    7. ANDROID_KABI_RESERVE(2);
    8. ANDROID_KABI_RESERVE(3);
    9. ANDROID_KABI_RESERVE(4);
    10. } __randomize_layout;
    11. kset_uevent_ops 是为kset量身订做的一个数据结构,里面包含filter和uevent两个回调函数
    12. * @uevent_ops: the set of uevent operations for this kset. These are
    13. * called whenever a kobject has something happen to it so that the kset
    14. * can add new environment variables, or filter out the uevents if so
    15. * desired.
    16. struct kset_uevent_ops {
    17. int (* const filter)(struct kset *kset, struct kobject *kobj); //当任何Kobject需要上报uevent时,它所属的kset可以通过该接口过滤,阻止不希望上报的event,从而达到从整体上管理的目的
    18. const char *(* const name)(struct kset *kset, struct kobject *kobj); //接口可以返回kset的名称。如果一个kset没有合法的名称,则其下的所有Kobject将不允许上报uvent
    19. int (* const uevent)(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env); //当任何Kobject需要上报uevent时,它所属的kset可以通过该接口统一为这些event添加环境变量
    20. };
    21. eg:
    22. static const struct kset_uevent_ops bus_uevent_ops = {
    23. .filter = bus_uevent_filter,
    24. };
    25. bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
    26. kset = kset_create(name, uevent_ops, parent_kobj);
    27. kset->uevent_ops = uevent_ops;
    28. kset_register(kset);
    29. kobject_uevent(&k->kobj, KOBJ_ADD);

    4,常用API

    4.1 kobject_uevent_env

    以envp为环境变量,上报一个指定action的uevent。环境变量的作用是为执行用户空间程序指定运行环境。

    1. /**
    2. * kobject_uevent_env - send an uevent with environmental data
    3. *
    4. * @kobj: struct kobject that the action is happening to
    5. * @action: action that is happening
    6. * @envp_ext: pointer to environmental data
    7. *
    8. * Returns 0 if kobject_uevent_env() is completed with success or the
    9. * corresponding error when it fails.
    10. */
    11. int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
    12. char *envp_ext[])
    13. {
    14. struct kobj_uevent_env *env;
    15. const char *action_string = kobject_actions[action];
    16. const char *devpath = NULL;
    17. const char *subsystem;
    18. struct kobject *top_kobj;
    19. struct kset *kset;
    20. const struct kset_uevent_ops *uevent_ops;
    21. int i = 0;
    22. int retval = 0;
    23. /*
    24. * Mark "remove" event done regardless of result, for some subsystems
    25. * do not want to re-trigger "remove" event via automatic cleanup.
    26. */
    27. if (action == KOBJ_REMOVE)
    28. kobj->state_remove_uevent_sent = 1;
    29. pr_debug("kobject: '%s' (%p): %s\n",
    30. kobject_name(kobj), kobj, __func__);
    31. /* search the kset we belong to */
    32. top_kobj = kobj;
    33. while (!top_kobj->kset && top_kobj->parent)
    34. top_kobj = top_kobj->parent;
    35. //查找kobj本身或者其parent是否从属于某个kset,如果不是,则报错返回(由此可以说明,如果一个kobject没有加入kset,是不允许上报uevent的)
    36. if (!top_kobj->kset) {
    37. pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
    38. "without kset!\n", kobject_name(kobj), kobj,
    39. __func__);
    40. return -EINVAL;
    41. }
    42. kset = top_kobj->kset;
    43. uevent_ops = kset->uevent_ops;
    44. //查看kobj->uevent_suppress是否设置,如果设置,则忽略所有的uevent上报并返回(注3:由此可知,可以通过Kobject的uevent_suppress标志,管控Kobject的uevent的上报)
    45. /* skip the event, if uevent_suppress is set*/
    46. if (kobj->uevent_suppress) {
    47. pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
    48. "caused the event to drop!\n",
    49. kobject_name(kobj), kobj, __func__);
    50. return 0;
    51. }
    52. //如果所属的kset有uevent_ops->filter函数,则调用该函数,过滤此次上报(注4:这佐证了3.2小节有关filter接口的说明,kset可以通过filter接口过滤不希望上报的event,从而达到整体的管理效果)
    53. /* skip the event, if the filter returns zero. */
    54. if (uevent_ops && uevent_ops->filter)
    55. if (!uevent_ops->filter(kset, kobj)) {
    56. pr_debug("kobject: '%s' (%p): %s: filter function "
    57. "caused the event to drop!\n",
    58. kobject_name(kobj), kobj, __func__);
    59. return 0;
    60. }
    61. //判断所属的kset是否有合法的名称(称作subsystem,和前期的内核版本有区别),否则不允许上报uevent
    62. /* originating subsystem */
    63. if (uevent_ops && uevent_ops->name)
    64. subsystem = uevent_ops->name(kset, kobj);
    65. else
    66. subsystem = kobject_name(&kset->kobj);
    67. if (!subsystem) {
    68. pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
    69. "event to drop!\n", kobject_name(kobj), kobj,
    70. __func__);
    71. return 0;
    72. }
    73. //* 分配一个用于此次上报的、存储环境变量的buffer(结果保存在env指针中),并获得该Kobject在sysfs中路径信息(用户空间软件需要依据该路径信息在sysfs中访问它)
    74. /* environment buffer */
    75. env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
    76. if (!env)
    77. return -ENOMEM;
    78. /* complete object path */
    79. devpath = kobject_get_path(kobj, GFP_KERNEL);
    80. if (!devpath) {
    81. retval = -ENOENT;
    82. goto exit;
    83. }
    84. //调用add_uevent_var接口(下面会介绍),将Action、路径信息、subsystem等信息,添加到env指针中
    85. /* default keys */
    86. retval = add_uevent_var(env, "ACTION=%s", action_string);
    87. if (retval)
    88. goto exit;
    89. retval = add_uevent_var(env, "DEVPATH=%s", devpath);
    90. if (retval)
    91. goto exit;
    92. retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
    93. if (retval)
    94. goto exit;
    95. /* keys passed in from the caller */
    96. if (envp_ext) {
    97. for (i = 0; envp_ext[i]; i++) {
    98. retval = add_uevent_var(env, "%s", envp_ext[i]);
    99. if (retval)
    100. goto exit;
    101. }
    102. }
    103. //如果所属的kset存在uevent_ops->uevent接口,调用该接口,添加kset统一的环境变量到env指针
    104. /* let the kset specific function add its stuff */
    105. if (uevent_ops && uevent_ops->uevent) {
    106. retval = uevent_ops->uevent(kset, kobj, env);
    107. if (retval) {
    108. pr_debug("kobject: '%s' (%p): %s: uevent() returned "
    109. "%d\n", kobject_name(kobj), kobj,
    110. __func__, retval);
    111. goto exit;
    112. }
    113. }
    114. //根据ACTION的类型,设置kobj->state_add_uevent_sent和kobj->state_remove_uevent_sent变量,以记录正确的状态
    115. switch (action) {
    116. case KOBJ_ADD:
    117. /*
    118. * Mark "add" event so we can make sure we deliver "remove"
    119. * event to userspace during automatic cleanup. If
    120. * the object did send an "add" event, "remove" will
    121. * automatically generated by the core, if not already done
    122. * by the caller.
    123. */
    124. kobj->state_add_uevent_sent = 1;
    125. break;
    126. case KOBJ_UNBIND:
    127. zap_modalias_env(env);
    128. break;
    129. default:
    130. break;
    131. }
    132. //调用add_uevent_var接口,添加格式为"SEQNUM=%llu”的序列号
    133. mutex_lock(&uevent_sock_mutex);
    134. /* we will send an event, so request a new sequence number */
    135. retval = add_uevent_var(env, "SEQNUM=%llu", ++uevent_seqnum);
    136. if (retval) {
    137. mutex_unlock(&uevent_sock_mutex);
    138. goto exit;
    139. }
    140. //如果定义了"CONFIG_NET”,则使用netlink发送该uevent
    141. retval = kobject_uevent_net_broadcast(kobj, env, action_string,
    142. devpath);
    143. mutex_unlock(&uevent_sock_mutex);
    144. //以uevent_helper、subsystem以及添加了标准环境变量(HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)的env指针为参数,调用kmod模块提供的call_usermodehelper函数,上报uevent
    145. #ifdef CONFIG_UEVENT_HELPER
    146. /* call uevent_helper, usually only enabled during early boot */
    147. if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
    148. struct subprocess_info *info;
    149. retval = add_uevent_var(env, "HOME=/");
    150. if (retval)
    151. goto exit;
    152. retval = add_uevent_var(env,
    153. "PATH=/sbin:/bin:/usr/sbin:/usr/bin");
    154. if (retval)
    155. goto exit;
    156. retval = init_uevent_argv(env, subsystem);
    157. if (retval)
    158. goto exit;
    159. retval = -ENOMEM;
    160. info = call_usermodehelper_setup(env->argv[0], env->argv,
    161. env->envp, GFP_KERNEL,
    162. NULL, cleanup_uevent_env, env);
    163. if (info) {
    164. retval = call_usermodehelper_exec(info, UMH_NO_WAIT);
    165. env = NULL; /* freed by cleanup_uevent_env */
    166. }
    167. }
    168. #endif
    169. exit:
    170. kfree(devpath);
    171. kfree(env);
    172. return retval;
    173. }
    174. EXPORT_SYMBOL_GPL(kobject_uevent_env);

    Android在源码目录:system/extras/tests/uevents/中,可以监听底层UEvent事件上报的程序,该程序没有自动编译到系统中,需要单独编译。

    编译完成后,可以编译成system/bin/uevents的可执行程序,可以通过adb shell,输入uenvets可以查看上报事件:

    change@/devices/platform/soc/soc:mmi,charger/power_supply/mmi_battery ACTION=change DEVPATH=/devices/platform/soc/soc:mmi,charger/power_supply/mmi_battery SUBSYSTEM=power_supply POWER_SUPPLY_NAME=mmi_battery POWER_SUPPLY_TYPE=Mains POWER_SUPPLY_STATUS=Full POWER_SUPPLY_HEALTH=Good POWER_SUPPLY_TEMP=250 POWER_SUPPLY_CAPACITY=100 POWER_SUPPLY_CYCLE_COUNT=2 POWER_SUPPLY_CHARGE_FULL=4015000 POWER_SUPPLY_CHARGE_FULL_DESIGN=4015000 POWER_SUPPLY_VOLTAGE_NOW=4379000 POWER_SUPPLY_CURRENT_NOW=-704000 POWER_SUPPLY_CHARGE_COUNTER=4015000 SEQNUM=54900

    change@/devices/platform/soc/ae00000.qcom,mdss_mdp/backlight/panel0-backlight ACTION=change DEVPATH=/devices/platform/soc/ae00000.qcom,mdss_mdp/backlight/panel0-backlight SUBSYSTEM=backlight SOURCE=sysfs SEQNUM=54903

    主动向uevent 节点写入add也会导致内核生成并重新发送当前注册设备的uevent消息:

    console 1

    /sys/bus/platform/devices/goodix_ts.0 # echo add > uevent

    console 2

    lynkco:/system/bin # uevents

    add@/devices/platform/goodix_ts.0 ACTION=add DEVPATH=/devices/platform/goodix_ts.0 SUBSYSTEM=platform SYNTH_UUID=0 DRIVER=goodix_ts MODALIAS=platform:goodix_ts SEQNUM=55327

    4.2 kobject_uevent

    和kobject_uevent_env功能一样,只是没有指定任何的环境变量。

    1. /**
    2. * kobject_uevent - notify userspace by sending an uevent
    3. *
    4. * @kobj: struct kobject that the action is happening to
    5. * @action: action that is happening
    6. *
    7. * Returns 0 if kobject_uevent() is completed with success or the
    8. * corresponding error when it fails.
    9. */
    10. int kobject_uevent(struct kobject *kobj, enum kobject_action action)
    11. {
    12. return kobject_uevent_env(kobj, action, NULL);
    13. }
    14. EXPORT_SYMBOL_GPL(kobject_uevent);

    4.3 add_uevent_var

    1. /**
    2. * add_uevent_var - add key value string to the environment buffer
    3. * @env: environment buffer structure
    4. * @format: printf format for the key=value pair
    5. *
    6. * Returns 0 if environment variable was added successfully or -ENOMEM
    7. * if no space was available.
    8. */
    9. int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...)
    10. {
    11. va_list args;
    12. int len;
    13. //环境变量的个数不能超过最大值
    14. if (env->envp_idx >= ARRAY_SIZE(env->envp)) {
    15. WARN(1, KERN_ERR "add_uevent_var: too many keys\n");
    16. return -ENOMEM;
    17. }
    18. //把环境变量格式化输出到 env->buf
    19. va_start(args, format);
    20. len = vsnprintf(&env->buf[env->buflen],
    21. sizeof(env->buf) - env->buflen,
    22. format, args);
    23. va_end(args);
    24. //检查buffer size
    25. if (len >= (sizeof(env->buf) - env->buflen)) {
    26. WARN(1, KERN_ERR "add_uevent_var: buffer size too small\n");
    27. return -ENOMEM;
    28. }
    29. //env->envp[env->envp_idx++]指向此次添加的环境变量,通过envp_idx能直接取出key=value pair
    30. env->envp[env->envp_idx++] = &env->buf[env->buflen];
    31. //增加buflen计数,两个key=value中间有空格
    32. env->buflen += len + 1;
    33. return 0;
    34. }
    35. EXPORT_SYMBOL_GPL(add_uevent_var);

    4.4 kobject_action_type

    将enum kobject_action类型的Action,转换为字符串。

    5,API使用示例

    向user space发送自定义的uevent事件。

    1. 1
    2. env = kzalloc(sizeof(*env), GFP_KERNEL_ACCOUNT);
    3. if (!env)
    4. return;
    5. add_uevent_var(env, "CREATED=%llu", created);
    6. add_uevent_var(env, "COUNT=%llu", active);
    7. if (type == KVM_EVENT_CREATE_VM) {
    8. add_uevent_var(env, "EVENT=create");
    9. kvm->userspace_pid = task_pid_nr(current);
    10. } else if (type == KVM_EVENT_DESTROY_VM) {
    11. add_uevent_var(env, "EVENT=destroy");
    12. }
    13. add_uevent_var(env, "PID=%d", kvm->userspace_pid);
    14. if (kvm->debugfs_dentry) {
    15. char *tmp, *p = kmalloc(PATH_MAX, GFP_KERNEL_ACCOUNT);
    16. if (p) {
    17. tmp = dentry_path_raw(kvm->debugfs_dentry, p, PATH_MAX);
    18. if (!IS_ERR(tmp))
    19. add_uevent_var(env, "STATS_PATH=%s", tmp);
    20. kfree(p);
    21. }
    22. }
    23. /* no need for checks, since we are adding at most only 5 keys */
    24. env->envp[env->envp_idx++] = NULL;
    25. kobject_uevent_env(&kvm_dev.this_device->kobj, KOBJ_CHANGE, env->envp);
    26. kfree(env);
    27. 2
    28. char *envp[4] = { "FC_EVENT=nvmediscovery", hostaddr, tgtaddr, NULL };
    29. kobject_uevent_env(&fc_udev_device->kobj, KOBJ_CHANGE, envp);

    参考链接:

    Linux设备模型(3)_Uevent

  • 相关阅读:
    【logrotate】linux定时文件切割(解决openresty单个日志文件过大问题)
    EfficientPhys
    Jenkinsfile语法
    热门Java开发工具IDEA入门指南——IntelliJ IDEA概述(上)
    Servlet执行流程&&Servlet 生命周期
    javascript将html中的dom元素转图片
    C++中的注释作用
    【深度学习】05-02-自注意力机制多种变形-李宏毅老师21&22深度学习课程笔记
    python-(6-4-3)爬虫---re解析案例
    MCS:离散随机变量——Hyper Geometric分布
  • 原文地址:https://blog.csdn.net/u011456016/article/details/136309461