转自:http://blog.csdn.net/xuelin273/article/details/38646765

usb热插拔,即usb设备可以实现即插即用,像U盘一样,插到电脑里就可以用,不用时可以直接拔除,这个动作不会影响USB设备使用性能。

在linx 系统中,usb热插拔由两部分方面共同实现,即内核空间和用户空间,内核由一个守护进程实现,用户空间由udev 程序实现。在内核空间里,有一个专门用于监控usb hub的状态的守护进程,守护进程通过等待队列实现,等待队列平时处理休眠状态,当usb hub上状态变化时(即有usb设备从usb hub上插入或拔出)时,便会去唤醒等待队列,然后去实现usb设备枚举,枚举成功后,向linux系统注册usb设备,并通过kobject_event通知用户空间,有设备插入或拔出,用户空间里有一个专门用于实现动态加载设备节点的程序udev,当它收到内核通知后,能够动态创建usb设备节点,这样便实现了usb的热插拔。

本文主要从usb设备枚举最基本的几个方面进行讲解,即usb守护进程、守护进程如何唤醒、被谁唤醒。

一.守护进程

        在linux系统中,usb是一个相对比较复杂的子系统,所以usb子系统的初始化过程 也相对复杂,涉及了多个方面:usb总线初始化、usb文件系统初始化、usb hub初始化、usb设备驱动注册等。其中,在usb hub初始化usb_hub_init过程中,它除了向系统注册usb hub驱动处,还创建了一个用于监控usb root hub状态的守护进程hub_thread.
        hub_thread由kthread_run创建并唤醒:
  1. khubd_task = kthread_run(hub_thread, NULL, "khubd");
  2. if (!IS_ERR(khubd_task))
  3. return 0;
  1. static int hub_thread(void *__unused)
  2. {
  3. set_freezable();
  4. do {
  5. hub_events();
  6. wait_event_freezable(khubd_wait,
  7. !list_empty(&hub_event_list) ||
  8. kthread_should_stop());
  9. } while (!kthread_should_stop() || !list_empty(&hub_event_list));
  10. pr_debug("%s: khubd exiting\n", usbcore_name);
  11. return 0;
  12. }

第3行的set_freezable作用是清除当前线程标志flags中的PF_NOFREEZE位,表示当前线程能进入挂起或休眠状态。

        接下来就是一个do...while的死循环,循环结束的条件是当前线程收到stop请求并且hub_event_list列表里为空,守护进程由kthread_run创建并唤醒,当调用kthread_stop时,就会停止该线程;只有收到kthread_stop且hub_event_list列表为空时才会跳出do...while循环。
        在do...while循环中只有两行代码,hub_events为usb系统中最核心部分,这里只要hub_event_list里非空就会运行USB的枚举过程,直到hub_event_list为空,则跳出hub_events,通过wait_event_freezable进入休眠,其中khubd_wait为一个等待队列头,它通过静态方式 定义:
  1. static DECLARE_WAIT_QUEUE_HEAD(khubd_wait);
       wait_event_freezable中第二个参数为等待队列进入休眠条件condition,只有第二个参数为假时才能进入休眠,即hub_event_list列表为空且没有收到stop请求,这里hub_event_list肯定为空,所以只要没有收到kthread_stop请求,就能进入休眠,休眠被唤醒后检测condition时否已经满足条件,即condition为真,hub_event_list不为空或收到stop请求,如果是收到stop请求,则直接退出do...while循环,如果是因为hub_event_list非空,则运行hub_events,实现usb枚举。

二 .守护进程唤醒

         当运行完hub_events后,usb线程就会通过wait_event_freezable进入休眠状态,直到被信号中断或条件满足被唤醒,usb的守护进程由kick_khubd函数进行唤醒,该函数在多个地方被调用:
  1. static void kick_khubd(struct usb_hub *hub)
  2. {
  3. unsigned long   flags;
  4. spin_lock_irqsave(&hub_event_lock, flags);
  5. if (!hub->disconnected && list_empty(&hub->event_list)) {
  6. list_add_tail(&hub->event_list, &hub_event_list);
  7. /* Suppress autosuspend until khubd runs */
  8. usb_autopm_get_interface_no_resume(
  9. to_usb_interface(hub->intfdev));
  10. wake_up(&khubd_wait);
  11. }
  12. spin_unlock_irqrestore(&hub_event_lock, flags);
  13. }
 第6行,只有在hub连接并且hub中的event_list为初始状态;

第7行,将hub的event_list添加到hub_event_list列表,用于满足守护进程被唤醒时的条件。

 第10行,自动挂载计数加1,停止hub在进行枚举的时候进入suspend态。
         第12行,通过wake_up唤醒已经休眠的usb守护进程。
         有多个地方可以调用kick_khubd来唤醒usb守护进程:

          hub有两个端口,即control endpoint和interrupt endpoint,其中interrupt endpoint主要用于查询hub 上的port状态变化。在添加控制器驱动时会创建一个root hub,而当root hub的port上检测到有hub插入时,也会创建一个usb hub,在为root hub或普通hub进行配置时,会为它申请的interrupt endpoint申请urb相关资源:
  1. hub->urb = usb_alloc_urb(0, GFP_KERNEL);
  2. if (!hub->urb) {
  3. ret = -ENOMEM;
  4. goto fail;
  5. }
  6. usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq, hub, endpoint->bInterval);

urb的回调函数为hub_irq:

  1. static void hub_irq(struct urb *urb)
  2. {
  3. struct usb_hub *hub = urb->context;
  4. int status = urb->status;
  5. unsigned i;
  6. unsigned long bits;
  7. switch (status) {
  8. case -ENOENT:       /* synchronous unlink */
  9. case -ECONNRESET:   /* async unlink */
  10. case -ESHUTDOWN:    /* hardware going away */
  11. return;
  12. default:        /* presumably an error */
  13. /* Cause a hub reset after 10 consecutive errors */
  14. dev_dbg (hub->intfdev, "transfer --> %d\n", status);
  15. if ((++hub->nerrors < 10) || hub->error)
  16. goto resubmit;
  17. hub->error = status;
  18. /* FALL THROUGH */
  19. /* let khubd handle things */
  20. case 0:         /* we got data:  port status changed */
  21. bits = 0;
  22. for (i = 0; i < urb->actual_length; ++i)
  23. bits |= ((unsigned long) ((*hub->buffer)[i]))
  24. << (i*8);
  25. hub->event_bits[0] = bits;
  26. break;
  27. }
  28. hub->nerrors = 0;
  29. /* Something happened, let khubd figure it out */
  30. kick_khubd(hub);
  31. resubmit:
  32. if (hub->quiescing)
  33. return;
  34. if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0
  35. && status != -ENODEV && status != -EPERM)
  36. dev_err (hub->intfdev, "resubmit --> %d\n", status);
  37. }

在hub_irq中可以看到,如果urb被成功提交给控制器,并成功获取port状态,就会调用kick_khubd唤醒守护进程;

对于ohci类型的root hub,它除了和其它hub一样,可以通过usb_submit_urb提交查询hub状态的请求,它还可以通过中断方式去查询,在用usb_add_hcd添加控制器驱动时,会为控制器申请中断:
  1. retval = usb_hcd_request_irqs(hcd, irqnum, irqflags);
  2. if (retval)
  3. goto err_request_irq;
         对于ohci类型的控制器,在中断处理程序ohci_irq中,其中有一项就是用来监控root hub上的port状态的:
  1. if (ints & OHCI_INTR_RHSC) {
  2. ohci_vdbg(ohci, "rhsc\n");
  3. ohci->next_statechange = jiffies + STATECHANGE_DELAY;
  4. ohci_writel(ohci, OHCI_INTR_RD | OHCI_INTR_RHSC, ®s->intrstatus);
  5. ohci_writel(ohci, OHCI_INTR_RHSC, ®s->intrdisable);
  6. usb_hcd_poll_rh_status(hcd);
  7. }

当port上状态发生变化时,就会调用usb_hcd_poll_rh_status去查询usb root hub port状态,并调用hub中interrupt urb的回调函数hub_irq,最终去唤醒usb守护进程。

        对于root hub,当它使用usb_submit_urb去提交查询hub状态请求时,它是通过启动一个定时器rh_timer去定时查询root hub状态的,如果成功获取hub状态,并通过hub_irq返回,则在hub_irq最后会再次自动调用usb_submit_urb去获取hub状态。如果不能获取或不能成功通过hub_irq返回,则不会再去调用usb_submit_urb;
         对于root hub可以通过主动去调用usb_submit_urb来查询port状态或通过控制器的中断来处理,除此之外,在向系统添加控制器驱动时,会自动创建并注册一个root hub,驱动将会对这个root hub匹配hub驱动并对它进行配置,配置完后它会主动的调用kick_khubd去唤醒守护进程(hub_activate)。
        如果在枚举的时候发现插入的是一个usb hub,则也会为这个hub匹配其驱动,如果成功匹配驱动,便会对hub进行配置,配置成功后也会去调用kick_khubd去唤醒守护进程。

linux usb枚举过程分析之守护进程及其唤醒【转】的更多相关文章

  1. Linux中的两种守护进程stand alone和xinetd

    Linux中的两种守护进程stand alone和xinetd --http://www.cnblogs.com/itech/archive/2010/12/27/1914846.html#top 一 ...

  2. Windows 和 Linux 上Redis的安装守护进程配置

    # Windows 和 Linux 上Redis的安装守护进程配置 Redis 简介 ​ Redis是目前最常用的非关系型数据库(NOSql)之一,常以Key-Value的形式存储.Redis读写速度 ...

  3. ASP.NET Core Linux下为 dotnet 创建守护进程(必备知识)

    前言 在上篇文章中介绍了如何在 Docker 容器中部署我们的 asp.net core 应用程序,本篇主要是怎么样为我们在 Linux 或者 macOs 中部署的 dotnet 程序创建一个守护进程 ...

  4. ASP.ENT Core Linux 下 为 donet创建守护进程(转载)

    原文地址:http://www.cnblogs.com/savorboard/p/dotnetcore-supervisor.html 前言 在上篇文章中介绍了如何在 Docker 容器中部署我们的 ...

  5. [Linux] PHP程序员玩转Linux系列-使用supervisor实现守护进程

    1.PHP程序员玩转Linux系列-怎么安装使用CentOS 2.PHP程序员玩转Linux系列-lnmp环境的搭建 3.PHP程序员玩转Linux系列-搭建FTP代码开发环境 4.PHP程序员玩转L ...

  6. linux下编写简单的守护进程

    搭建linux服务器的时候,需要写一个简单的守护进程来监控服务的运行情况,shell脚本如下: #!/bin/sh function daemon() { while true do server=` ...

  7. Linux网络编程学习(四) -----守护进程的建立(第三章)

    本文介绍一个例程daemon_init() #include <sys/types.h> #include <signal.h> #include <unistd.h&g ...

  8. Linux下性能监控、守护进程与计划任务管理

    目录 一:监视系统进程(ps .top) 二:查看网络连接信息 (netstat) 三:文件进程.端口关联(lsof) 四:计划任务管理(at .crontab) at crontab 一:监视系统进 ...

  9. Linux学习笔记(9)-守护进程

    明天学这个!! ---------------------------------------------------------- 守护进程(Daemon)是运行在后台的一种特殊进程.它独立于控制终 ...

随机推荐

  1. 手写vue双向绑定数据

    来一张原理图: 实现思路: (1)绑定data 种的数据,为每个数据添加指令.通过Object,defineProperty() 来通知属性是否更改 (2) 找到每个DOM节点的指令.绑定事件.并绑定 ...

  2. 【Leetcode】535. Encode and Decode TinyURL

    Question: TinyURL is a URL shortening service where you enter a URL such as https://leetcode.com/pro ...

  3. mysql 清空数据

    清空数据有2 个命令 -- 清空全部数据,不写日志,不可恢复,速度极快 truncate table 表名; -- 清空全部数据,写日志,数据可恢复,速度慢 delete from 表名 业务需求:清 ...

  4. thinkphp5 速查表

    本速查表里的类都是think为命名空间的,实例化时省去了  use.用的时候注意. 本速查表里会有四种方法的调用: 公有方法 $class = new Class();  $class->foo ...

  5. Aqua Data Studio 数据库开发工具

    Aqua Data Studio是一款完整IDE的数据库开发工具,它提供3种主要功能:数据查询与管理工具.比对数据工具与源控制和文件系统的整合工具.帮助你创建,编辑和执行 SQL 的管理工具脚本编写, ...

  6. 3.23日PSP

    工作 类型 日期 开始时间 结束时间 中断时间 净时间 搭hadoop环境(已终止) 技能 3.23 00:00 00:50 0min 50min 看构建之法 学习 3.23 9:30 10:00 3 ...

  7. KSOA单据保护表中Clientid解析为mac和ip

    SELECT DISTINCT a.*,ISNULL(c.client_net_address,'') AS client_net_address FROM ( SELECT * ,),,) ) ), ...

  8. 使用AutoMapper实现Dto和Model的自由转换(上)

    在实际的软件开发项目中,我们的“业务逻辑”常常需要我们对同样的数据进行各种变换.例如,一个Web应用通过前端收集用户的输入成为Dto,然后将Dto转换成领域模型并持久化到数据库中.另一方面,当用户请求 ...

  9. 【BZOJ1025】[SCOI2009]游戏(动态规划)

    [BZOJ1025][SCOI2009]游戏(动态规划) 题面 BZOJ 洛谷 题解 显然就是一个个的置换,那么所谓的行数就是所有循环的大小的\(lcm+1\). 问题等价于把\(n\)拆分成若干个数 ...

  10. HGOI20181030 模拟题解

    problem:给定一个序列,问你能不能通过一次交换把他弄成有序 sol: 对于0%的数据,满足数列是一个排列,然后我就打了这档分(自己瞎造的!) 对于100%的数据,显然我们先对数列进行排序然后上下 ...