linux usb枚举过程分析之守护进程及其唤醒【转】

简介:

转自: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创建并唤醒:
[html]  view plain copy
 
 
  1. khubd_task = kthread_run(hub_thread, NULL, "khubd");  
  2.     if (!IS_ERR(khubd_task))  
  3.         return 0;  
[html]  view plain copy
 
 
  1. static int hub_thread(void *__unused)  
  2. {  
  3.     set_freezable();  
  4.   
  5.     do {  
  6.         hub_events();  
  7.         wait_event_freezable(khubd_wait,  
  8.                 !list_empty(&hub_event_list) ||  
  9.                 kthread_should_stop());  
  10.     } while (!kthread_should_stop() || !list_empty(&hub_event_list));  
  11.   
  12.     pr_debug("%s: khubd exiting\n", usbcore_name);  
  13.     return 0;  
  14. }  
        第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为一个等待队列头,它通过静态方式 定义:
[html]  view plain copy
 
 
  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函数进行唤醒,该函数在多个地方被调用:
[html]  view plain copy
 
 
  1. static void kick_khubd(struct usb_hub *hub)  
  2. {  
  3.     unsigned long   flags;  
  4.   
  5.     spin_lock_irqsave(&hub_event_lock, flags);  
  6.     if (!hub->disconnected && list_empty(&hub->event_list)) {  
  7.         list_add_tail(&hub->event_list, &hub_event_list);  
  8.   
  9.         /* Suppress autosuspend until khubd runs */  
  10.         usb_autopm_get_interface_no_resume(  
  11.                 to_usb_interface(hub->intfdev));  
  12.         wake_up(&khubd_wait);  
  13.     }  
  14.     spin_unlock_irqrestore(&hub_event_lock, flags);  
  15. }  
 第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相关资源:
[html]  view plain copy
 
 
  1. hub->urb = usb_alloc_urb(0, GFP_KERNEL);  
  2. if (!hub->urb) {  
  3.     ret = -ENOMEM;  
  4.     goto fail;  
  5. }  
  6.   
  7. usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq, hub, endpoint->bInterval);  
          urb的回调函数为hub_irq:
[html]  view plain copy
 
 
  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.   
  8.     switch (status) {  
  9.     case -ENOENT:       /* synchronous unlink */  
  10.     case -ECONNRESET:   /* async unlink */  
  11.     case -ESHUTDOWN:    /* hardware going away */  
  12.         return;  
  13.   
  14.     default:        /* presumably an error */  
  15.         /* Cause a hub reset after 10 consecutive errors */  
  16.         dev_dbg (hub->intfdev, "transfer --> %d\n", status);  
  17.         if ((++hub->nerrors 10) || hub->error)  
  18.             goto resubmit;  
  19.         hub->error = status;  
  20.         /* FALL THROUGH */  
  21.   
  22.     /* let khubd handle things */  
  23.     case 0:         /* we got data:  port status changed */  
  24.         bits = 0;  
  25.         for (i = 0; i urb->actual_length; ++i)  
  26.             bits |= ((unsigned long) ((*hub->buffer)[i]))  
  27.                     << (i*8);  
  28.         hub->event_bits[0] = bits;  
  29.         break;  
  30.     }  
  31.   
  32.     hub->nerrors = 0;  
  33.   
  34.     /* Something happened, let khubd figure it out */  
  35.     kick_khubd(hub);  
  36.   
  37. resubmit:  
  38.     if (hub->quiescing)  
  39.         return;  
  40.   
  41.     if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0  
  42.             && status != -ENODEV && status != -EPERM)  
  43.         dev_err (hub->intfdev, "resubmit --> %d\n", status);  
  44. }  
        在hub_irq中可以看到,如果urb被成功提交给控制器,并成功获取port状态,就会调用kick_khubd唤醒守护进程;
对于ohci类型的root hub,它除了和其它hub一样,可以通过usb_submit_urb提交查询hub状态的请求,它还可以通过中断方式去查询,在用usb_add_hcd添加控制器驱动时,会为控制器申请中断:
[html]  view plain copy
 
 
  1. retval = usb_hcd_request_irqs(hcd, irqnum, irqflags);  
  2. if (retval)  
  3.     goto err_request_irq;  
         对于ohci类型的控制器,在中断处理程序ohci_irq中,其中有一项就是用来监控root hub上的port状态的:
[html]  view plain copy
 
 
  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去唤醒守护进程。
















本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sky-heaven/p/4977116.html ,如需转载请自行联系原作者
相关文章
|
20天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
45 1
|
8天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
60 13
|
1天前
|
监控 安全 Linux
启用Linux防火墙日志记录和分析功能
为iptables启用日志记录对于监控进出流量至关重要
|
15天前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
24天前
|
调度 开发者
核心概念解析:进程与线程的对比分析
在操作系统和计算机编程领域,进程和线程是两个基本而核心的概念。它们是程序执行和资源管理的基础,但它们之间存在显著的差异。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
41 4
|
23天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
28天前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
1月前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
85 8
|
1月前
|
网络协议 Linux 虚拟化
如何在 Linux 系统中查看进程的详细信息?
如何在 Linux 系统中查看进程的详细信息?
90 1
|
1月前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?