关闭

Linux那些事儿之我是Hub(25)不说代码说理论

标签: linuxmicrosofthibernatesusewindowssystem
5200人阅读 评论(0) 收藏 举报
分类:

当女作家们越来越多的使用下半身来告诉我什么是文学的时候,当模特们越来越多的使用裸体来告诉我什么是人体艺术的时候,我开始对这个社会困惑了,当行为艺术家们越来越多的使用垃圾堆砌来告诉我什么是波谱的时候,当地下音乐者们越来越多的使用烦躁不安的敲打来告诉我什么是原创的时候,我开始对这个时代迷茫了,当我们系的教授们越来越多的使用旧得不能再旧的教材来告诉我什么是微电子前沿技术的时候,当我们区(开源社区)的兄弟们越来越多的使用复杂得不能再复杂的函数来告诉我什么是编程艺术的时候,我开始对这些电源管理代码晕菜了.

可是没办法,谁叫我不幸生在中国呢,所以我觉得应该抽点时间,找点空闲,抛开代码看看理论.没有一点理论基础,这代码肯定是看不懂的.

电源管理其实发展挺多年了,也算是比较成熟了,主流的技术有两种,APMACPI,APM,Advanced Power Management,高级电源管理,ACPI,Advanced Configuration and Power Interface,高级配置和电源接口,相比之下,APM容易实现,但是,APM属于一个BIOSspec,也就是说需要BIOS的支持,这种情况过去在笔记本电脑中比较普遍,不过自从ACPI横空出世之后,APM就将走向没落了,并被行家认为将在不久的将来从市场中消失,毕竟一种特性依赖于BIOS不是什么好事,这个时代强调的是独立.APM是盖茨他们家和Intel一起于1992年提出的,ACPI则是多了一家小日本的企业,东芝+Intel+Microsoft,这三家于1996年提出了ACPI 1.0,MicrosoftWindows2000也是第一个支持ACPI的操作系统.很显然,ACPI是更为先进的技术,它提供了更为灵活的接口,功能也更为强大,它最有型的地方大概就在于它基本不需要BIOS插手,基本可以通过OS来搞定.它的出现就是为了替代APM,或者说为了克服APM的不足.在如今的Linux发行版里,基本上你可以自己选择使用这两种电源管理的方式,默认应该是ACPI.选择了其中一种就得关掉另一种,不可能说两种方法同时使用,道理很简单,古训有云:一山不能容二虎,除非一公和一母.(顺便友情提醒一下,电源管理只是ACPI的功能中的一部分,除此以外,ACPI还有很多别的功能,干了不少和BIOS抢饭碗的事情.)

比如Red Hat中我们进入setup就可以看到系统服务里面有一个叫做acpid,还有一个叫做apmd,就是与这两种电源管理方式对应的服务进程.打开了一个就不要再打开另一个.

Ok,第一个概念,首先必须知道,Linux中的电源管理作为一个子系统(subsystem),它是一个系统工程,这个工程规模大,波及范围广,技术复杂,建设周期长,材料设备消耗大,施工生产流动性强,受自然和社会环境因素影响大,如果说内存管理相当于我国的南水北调工程,那么电源管理就相当于我国的西电东送工程,这两者都可谓任务重,要求高,而且还被全区(开源社区)人民寄予厚望.谁也不希望劳民伤财之后造出一个像三峡大坝这样的垃圾工程来.别以为我这样说很夸张,我们都知道Linux 2.6内核一个最伟大的变化就是建立了一个新的统一的设备模型,可是你知道当年区领导决定建立这个设备模型的初衷是什么吗?就是为了让电源管理工作变得更加容易.(“The device model was originally intended to make power management tasks easier through the maintenance of a representation of the host system's hardware structure.”)只不过后来失去了党的英明指引,写代码的兄弟们走火入魔,踏上了一条不归路,把这个设备模型做得偏离了最初的方向,然而不曾想做出来的东西意义更大了.不仅解决了电源管理的复杂性,而且把所有的设备管理任务都给集中起来了.

那么我们先抛出几个问题,第一,电源管理的含义是什么?一个字,省电.具体来说,电源管理意味着让世界充满爱的同时,让所有的设备处于尽可能低的耗电状态.如果你计算机中某些部分没有被使用,那么就把它关闭(比如显示器)或者让它进入省电的睡眠模式(比如硬盘).

其次,什么情况设备要挂起呢?比如,合上笔记本,又比如用户自己定义了一个系统电源管理策略(30分钟没有console活动的话就挂起),再比如设备自身有它的电源管理游戏规则(像一个设备五分钟没有活动的话就挂起).于是这就意味着有几种可能,一种是系统级的,即当你合上笔记本的时候,OS负责通知所有的驱动程序,告诉它,需要suspend,即驱动程序提供的suspend函数会被调用,这种情况在江湖上被人叫做system pm,也叫System Sleep Model,另一种情况,不是系统级的,设备级的,就是说我虽然没有合上笔记本,但就单个设备而言,如果我用户希望这个设备处于低耗电的状态,那么驱动程序也应该能够支持,这种情况被道上的兄弟称之为runtime pm,Runtime Power Management Model.换句话说,我不管别的设备死还是活,总之我这个设备自己想睡就睡,睡到自然醒,谁也别烦我.

因此,第三,从设备驱动的角度来说,我们应该如何作出自己应有的贡献呢?众所周知,电源管理最重要的两个概念就是suspendresume,即挂起和恢复.而设备驱动被要求保存好设备的上下文,即在挂起的时候,你作为设备驱动,你得保存好设备的一切状态信息,而在resume的时候,你要能够负责恢复这些信息.所以,你必须申请相关的buffer,把东东存在里面,关键的时候拿出来恢复.总的来说,suspend这个过程就是,上级下命令通知driver,driver保存状态,然后执行命令.notify before save state;save state before power down.resume这个过程则是,power on and restore state.

第四,刚才说了,建立统一设备模型的初衷是为了打造更佳的电源管理模型,这究竟是如何体现的呢?我们说过,2.6的内核,不管你是usb还是pci还是scsi,你最终会有一棵树,会通过父子关系,兄弟关系把所有的设备连接起来,这样做不是为了体现亲情,而是电源管理的基础,因为操作系统需要以一种合理的顺序去唤醒或者催眠一堆的设备,,比如,一个PCI总线上的设备必须在它的父设备睡眠之前先进入睡眠,反过来,又必须在它的父设备醒来以后才能醒来.开源战士范仲淹有一句话把这电源管理机制下的子设备诠释得淋漓尽致:先天下之睡而睡,后天下之醒而醒.

第五,从微观经济学来看,设备挂起意味着什么?意味着没有任何进出口贸易的发生,用英语说这叫,quiesce all I/O,即四个坚持,坚持不进行任何DMA操作,坚持不发送任何IRQ,坚持不读写任何数据,坚持不接受上层驱动发过来的任何请求.

最后提两个术语.STRSTD,这是两种Suspend的状态.STRSuspend to RAM,挂起到内存,STD就是Suspend to Disk,挂起到磁盘.STR就是把系统进入STR前的工作状态数据都存放在内存中去.STR状态下,电源仍然继续为内存和主板芯片组供电,以确保数据不丢失,而其它设备均处于关闭状态,系统的耗电量极低.一旦我们按下Power按钮,系统就被唤醒,马上从内存中读取数据并恢复到STR之前的工作状态,STR的优点是休眠快唤醒也快,因为数据本来就在内存中.STD则是把数据保存在磁盘中,很显然,保存在磁盘中要比保存在内存中慢.不过STD最酷的是因为它写到了磁盘中,所以即使电源完全断了,数据也不会丢失.STD就是我们在Windows里面看到的那个Hibernate,即冬眠,或曰休眠,STR就是我们在Windows里面看到的那个Standby.STRSTD是计算机休眠的两种主要方式.关于STD,多说两句,我想你永远不会忘记,第一次装Linux的时候,有人要你分区的时候分一个swap分区吧?这里就体现了swap分区的一个作用,如果你安装了Suse操作系统,看你的grub里面,一定有一项类似于resume=/dev/sda4,就是断电以后重起了之后,从这个分区里把东西读出来的意思.再补一句,ACPI的状态一共有五种,分别是S1,S2,S3,S4,S5,实际上S4就是STD,STR就是S3,只不过S1,S2,S3差别不大,不过,Linux,S1被叫做Standby,S3被叫做STR.S5就是Shutdown.Linux中说挂起,主要说的就是S1,S3S4.关于S1,S3,S4这三种状态,/sys/power/state文件里可以有所体现,cat /sys/power/state,你会看到”standby”,”mem”,”disk”的字样.

我们来做一个实验.你打开一个网络连接,比如你去某FTP站点下一部武藤兰的A,大小500M的那种,然后正在下的时候(即现在进行时的下载),比如下到100M左右,或者说刚开始下,下了3,5M,这时候你另外开一个终端,执行下面两个命令:

# echo shutdown > /sys/power/disk

# echo disk > /sys/power/state

你的机器将进入传说中的Hibernate状态,你如果觉得不够刺激,可以把电源线都拔掉,然后隔一会儿再按动电源开关,开机,你会发现,你又得重新面对grub,重新选择启动选项,但是再次启动好了之后,那部A片仍然在继续下载.

以上这个实验做的就是STD.当然,你自己做的话失败了别怪我,我早就说过,电源管理这部分是这几年里开源社区最hot的话题之一,问题其实很多,被你遇上了也不必惊讶.不过我需要提醒一下,做之前得确认grub里面kernel那行有那个resume的定义.基本上Suse Linux有这个,Redhat默认好像没有设置这一项.另外,在第一个命令中,你还可以把shutdown换成reboot,这样的话在你执行了第二条命令以后,系统会立刻重起,而不是直接进入power off的状态.重起之后你看到的效果也是一样的,原来什么样,起来之后就还是什么样.

而进行STR的实验就稍微复杂一点,我们这里不多说了,从设备驱动来说,没有必要知道这次挂起是STR还是STD,总之,以上这个STD的实验自从第二个命令执行之后,各个驱动提供的suspend函数就会相继被执行,而之后重起的时候,就会相继调用各驱动提供的resume函数.PM core那边知道如何遍历设备树,所以从usb这边来说,我们甚至不需要做太多递归的事情.

Ok,现在让我们来介绍一下PM Core那边给出的接口.我早就说过,Linux中基本上就是这么几招.首先是把整个内核分成很多个子系统,或者美其名曰Subsystem,然后很多子系统又分为一个核心部分和其他部分,比如我们的usb,就是usbcore和其它部分,usb core这部分负责提供整个usb子系统的初始化,主机控制器的驱动,(当然主机控制器的驱动由于host controller的种类越来越多,host controller driver也便单独构成了一个目录,所以我们有时候说usb core实际上指的是drivers/usb/coredrivers/usb/host目录,但这是相对外围设备而言的,实际上usb core本身是一个模块,而主机控制器的驱动又是单独成为一个模块.)而外围设备的驱动,比如usb-storage,就是直接使用usb core提供出来的接口即可.这种伎俩在Linux内核中比比皆是.而电源管理也是如此,在电源管理子系统中,有一个叫做PM core,它所包含的是一些核心的代码.而其它各大子系统里要加入power management的功能,就必须使用PM core提供的接口函数.因此,现在是时候让我们来看一看了.

首先,既然我们说电源管理是一个系统工程,那么必然是自上而下的一次大规模改革,不仅仅是设备驱动需要支持它,总线驱动也必须提供相应的支持.还记得在讲八大函数的时候,我们贴出来的结构体变量struct bus_type usb_bus_type?其中我们把.suspend赋值为usb_suspend,而把.resume赋值为usb_resume.也就是说这两个函数是各类总线不相同的,每类总线都需要实现自己特定的函数,比如PCI总线,我们也能看到类似的定义,来自drivers/pci/pci-driver.c:

    542 struct bus_type pci_bus_type = {

    543         .name           = "pci",

    544         .match          = pci_bus_match,

    545         .uevent         = pci_uevent,

    546         .probe          = pci_device_probe,

    547         .remove         = pci_device_remove,

    548         .suspend        = pci_device_suspend,

    549         .suspend_late   = pci_device_suspend_late,

    550         .resume_early   = pci_device_resume_early,

    551         .resume         = pci_device_resume,

    552         .shutdown       = pci_device_shutdown,

553         .dev_attrs      = pci_dev_attrs,

它的.suspend就被赋值为pci_device_suspend,.resume被赋值为pci_device_resume.

而设备驱动呢?提供自己的suspend/resume函数,于是最终总线的那个suspend/resume函数就会去调用设备自己的suspend/resume,比如我们回归usb,hub驱动提供了两个函数,hub_suspend/hub_resume,于是usb_suspend/usb_resume最终就会调用hub_suspend/hub_resume,换句话说,总线的suspend/resume是包装,是面子工程,而设备自己的suspend/resume是实质.

所以,接下来我们有两种选择,第一,直接讲hub_suspend/hub_resume,第二,usb_suspend/usb_resume开始讲.很显然,前者相对简单,选择前者意味着我们的故事在讲完这两个函数之后就可以结束.我也轻松你也轻松.而选择后者意味着我们选择了一条更加艰难的道路.选择是一个崭新的开端,选择高耸入云的峭崖便需有路漫漫其修远兮,吾将上下而求索的信念;选择波涌浪滚的大海便需有直挂云帆济沧海的壮志豪情;选择寒风劲厉的荒漠便需有醉卧沙场君莫笑,古来征战几人回的博大胸怀.我很无奈,但是人生是一定要选择的,你不可能什么都抓在手上,如果这样,你什么都会失去!经过谨慎思考,我还是决定铤而走险,去探索一下这个复杂的乱世.

但在真正开始讲代码之前,我还是想强调一下,首先,基本上如果你看完了此前的hub_events和那八大函数,你就算是了解hub驱动了.下面的代码属于usb core中电源管理的部分,对于大多数人来说是可以不用再看的,除非:

1.      你和我一样,很无聊,很孤独,对人生很失望.

2.      或者,你欲投身于开源社区的开发事业,想了解最新的开发进展,你想和Alan Stern,想和Oliver Neukum,想和David Brownell同流合污,Linux内核中usb子系统做出自己的贡献.

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:2433048次
    • 积分:7547
    • 等级:
    • 排名:第3047名
    • 原创:297篇
    • 转载:0篇
    • 译文:0篇
    • 评论:2042条
    博客专栏