hotplug启动过程之call_usermodehelper分析

本文探讨了Linux系统中Hotplug进程的工作原理及其与内核的交互方式。Hotplug进程负责处理USB设备的热插拔事件,通过Usermode-helper API由内核启动。文章详细解释了Usermode-helper API如何实现内核与用户空间应用程序之间的通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

主题引子:

系统启动后,每次插入或拔出usb设备,应用层会通过进程hotplug来对usb分区做相应的mount或umount操作,且该hotplug进程并非守护进程,执行完操作后即退出,不在会ps中显示出来。 开始以为hotplug进程是被应用层消息框架所触发,即消息总控层收到内核驱动响应事件,然后通过消息机制发送消息启动hotplug进程。为了验证该论断,在消息机制总控层代码中添加打印进行调试,发现一直都没有相应打印,难道hotplug启动过程跟应用层消息机制没有关系,那hotplug又是如何被内核驱动层触发的呢?

由于hotplug可执行二进制文件放在bin目录,在sbin目录也有一个link链接,可以通过bin/hotplug或sbin/hotplug来启动,于是尝试在全局grep搜索bin/hotplug,果然在kernel中找到sbin/hotplug及其对应宏CONFIG_UEVENT_HELPER_PATH,继续搜索宏CONFIG_UEVENT_HELPER_PATH,很快定位到文件kobject_uevent.c中,做进一步分析,找到关键函数call_usermodehelper(),当时还并没有引发自己的重视,仅仅当做一陌生函数去baidu了解下功能,这才发现改函数的厉害所在,该问题的关键所在,就是它被驱动触发并从内核中启动了用户层的hotplug进程。

若在项目中不想使用hotplug,可以在内核配置文件中进行配置,或者在hotplug应用程序代码中注掉相应代码即可。

内核中配置路径:makemenuconfig    Device Drivers   --->  General Driver Options   --->  hotplug

使用usermode-helper API的实现从内核调用或启动用户空间应用程序:

调用特定的内核函数(系统调用)是 GNU/Linux 中软件开发的原本就有的组成部分。但如果方向反过来呢,内核空间调用用户空间?确实有一些有这种特性的应用程序需要每天使用。例如,当内核找到一个设备,这时需要加载某个模块,进程如何处理?动态模块加载在内核通过 usermode-helper 进程进行。

usermode-helper API 中的一些核心函数来实现内核启动用户进程,核心函数提供了最大程度的控制,其中 helper 函数在单个调用中完成了大部分工作。管道相关调用(call_usermodehelper_stdinpipe 和 helper 函数 call_usermodehelper_pipe)创建了一个相联管道供helper 使用。具体地说,创建了管道(内核中的文件结构)。用户空间应用程序对管道可读,内核对管道可写。

最直接的 usermode-helper API 应用程序是从内核空间加载内核模块。request_module 函数封装了 usermode-helper API 的功能并提供了简单的接口。在一个常用的模块中,内核指定一个设备或所需服务并调用 request_module 来加载模块。通过使用 usermode-helper API,模块通过 modprobe 加载到内核(应用程序通过 request_module 在用户空间被调用)。与模块加载类似的应用程序是设备热插拔(在运行时添加或删除设备)。该特性是通过使用 usermode-helper API,调用用户空间的 /sbin/hotplug 工具实现的。

call_usermodehelper()函数是简化了的API函数,执行了核心API函数setup和exec等。

usermode-helperAPI 是内核中重要的部分,这是由于其广泛多样的用途(从内核模块加载、设备热插拔到 udev 事件发布)。尽管 API 实际的应用程序非常重要,但内核也是很重要的一部分,是非常有用的 Linux 工具。


了解更多:

http://blog.sina.com.cn/s/blog_7cd02bea0100pn06.html

int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, 473 char *envp_ext[]) 474 { 475 struct kobj_uevent_env *env; 476 const char *action_string = kobject_actions[action]; 477 const char *devpath = NULL; 478 const char *subsystem; 479 struct kobject *top_kobj; 480 struct kset *kset; 481 const struct kset_uevent_ops *uevent_ops; 482 int i = 0; 483 int retval = 0; 484 485 /* 486 * Mark "remove" event done regardless of result, for some subsystems 487 * do not want to re-trigger "remove" event via automatic cleanup. 488 */ 489 if (action == KOBJ_REMOVE) 490 kobj->state_remove_uevent_sent = 1; 491 492 pr_debug("kobject: '%s' (%p): %s\n", 493 kobject_name(kobj), kobj, __func__); 494 495 /* search the kset we belong to */ 496 top_kobj = kobj; 497 while (!top_kobj->kset && top_kobj->parent) 498 top_kobj = top_kobj->parent; 499 500 if (!top_kobj->kset) { 501 pr_debug("kobject: '%s' (%p): %s: attempted to send uevent " 502 "without kset!\n", kobject_name(kobj), kobj, 503 __func__); 504 return -EINVAL; 505 } 506 507 kset = top_kobj->kset; 508 uevent_ops = kset->uevent_ops; 509 510 /* skip the event, if uevent_suppress is set*/ 511 if (kobj->uevent_suppress) { 512 pr_debug("kobject: '%s' (%p): %s: uevent_suppress " 513 "caused the event to drop!\n", 514 kobject_name(kobj), kobj, __func__); 515 return 0; 516 } 517 /* skip the event, if the filter returns zero. */ 518 if (uevent_ops && uevent_ops->filter) 519 if (!uevent_ops->filter(kobj)) { 520 pr_debug("kobject: '%s' (%p): %s: filter function " 521 "caused the event to drop!\n", 522 kobject_name(kobj), kobj, __func__); 523 return 0; 524 } 525 526 /* originating subsystem */ 527 if (uevent_ops && uevent_ops->name) 528 subsystem = uevent_ops->name(kobj); 529 else 530 subsystem = kobject_name(&kset->kobj); 531 if (!subsystem) { 532 pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the " 533 "event to drop!\n", kobject_name(kobj), kobj, 534 __func__); 535 return 0; 536 } 537 538 /* environment buffer */ 539 env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL); 540 if (!env) 541 return -ENOMEM; 542 543 /* complete object path */ 544 devpath = kobject_get_path(kobj, GFP_KERNEL); 545 if (!devpath) { 546 retval = -ENOENT; 547 goto exit; 548 } 549 550 /* default keys */ 551 retval = add_uevent_var(env, "ACTION=%s", action_string); 552 if (retval) 553 goto exit; 554 retval = add_uevent_var(env, "DEVPATH=%s", devpath); 555 if (retval) 556 goto exit; 557 retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem); 558 if (retval) 559 goto exit; 560 561 /* keys passed in from the caller */ 562 if (envp_ext) { 563 for (i = 0; envp_ext[i]; i++) { 564 retval = add_uevent_var(env, "%s", envp_ext[i]); 565 if (retval) 566 goto exit; 567 } 568 } 569 570 /* let the kset specific function add its stuff */ 571 if (uevent_ops && uevent_ops->uevent) { 572 retval = uevent_ops->uevent(kobj, env); 573 if (retval) { 574 pr_debug("kobject: '%s' (%p): %s: uevent() returned " 575 "%d\n", kobject_name(kobj), kobj, 576 __func__, retval); 577 goto exit; 578 } 579 } 580 581 switch (action) { 582 case KOBJ_ADD: 583 /* 584 * Mark "add" event so we can make sure we deliver "remove" 585 * event to userspace during automatic cleanup. If 586 * the object did send an "add" event, "remove" will 587 * automatically generated by the core, if not already done 588 * by the caller. 589 */ 590 kobj->state_add_uevent_sent = 1; 591 break; 592 593 case KOBJ_UNBIND: 594 zap_modalias_env(env); 595 break; 596 597 default: 598 break; 599 } 600 601 mutex_lock(&uevent_sock_mutex); 602 /* we will send an event, so request a new sequence number */ 603 retval = add_uevent_var(env, "SEQNUM=%llu", ++uevent_seqnum); 604 if (retval) { 605 mutex_unlock(&uevent_sock_mutex); 606 goto exit; 607 } 608 retval = kobject_uevent_net_broadcast(kobj, env, action_string, 609 devpath); 610 mutex_unlock(&uevent_sock_mutex); 611 612 #ifdef CONFIG_UEVENT_HELPER 613 /* call uevent_helper, usually only enabled during early boot */ 614 if (uevent_helper[0] && !kobj_usermode_filter(kobj)) { 615 struct subprocess_info *info; 616 617 retval = add_uevent_var(env, "HOME=/"); 618 if (retval) 619 goto exit; 620 retval = add_uevent_var(env, 621 "PATH=/sbin:/bin:/usr/sbin:/usr/bin"); 622 if (retval) 623 goto exit; 624 retval = init_uevent_argv(env, subsystem); 625 if (retval) 626 goto exit; 627 628 retval = -ENOMEM; 629 info = call_usermodehelper_setup(env->argv[0], env->argv, 630 env->envp, GFP_KERNEL, 631 NULL, cleanup_uevent_env, env); 632 if (info) { 633 retval = call_usermodehelper_exec(info, UMH_NO_WAIT); 634 env = NULL; /* freed by cleanup_uevent_env */ 635 } 636 } 637 #endif 638 639 exit: 640 kfree(devpath); 641 kfree(env); 642 return retval; 643 }
最新发布
07-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值