有一次,我与一位客户进行故障排除,坚称我们应用程序中的桌面通知不会对他产生影响。标准的东西。通常,答案是设置错误或操作系统干扰。不过,我运行了一个诊断程序,结果非常清楚:通知显示得很好。没有错误,全部为绿色。
在那个箱子里,我什么都扔了,除了厨房的水槽。经过几天与顾客的来来回回(感谢您的耐心,先生),我终于拿到了。他的笔记本电脑显示器和辅助显示器有不同的分辨率和缩放设置。通知本应显示在外部显示器上,但由于存在漏洞,应用程序根据笔记本电脑的屏幕设置呈现通知。日志告诉我们一切都很好,因为我们确实展示了通知。在屏幕边缘之外几厘米处。
当软件遇到物理设备杂乱无章的物理现实时,这样的乱七八糟的事情必然会发生。但归根结底,硬件基本上是可以工作的。网络摄像头大部分都能用。鼠标和键盘大部分都能用。通知主要显示在屏幕的实际存在部分。打印机…。好的,这些是五五折的。
我请格雷格·克劳赫-哈特曼(Greg Kroah-Hartman)告诉我,让计算机外围设备主要按照我们的要求去做是怎么回事。Greg是Linux内核稳定发行版的维护者,也是编写Linux驱动程序书籍的作者。他带我踏上了一段旅程,从一个嵌入鼠标的微型处理器,深入到操作系统的内部。
Wojtek Borowicz:让我们从最基本的开始。我在桌子上滑动鼠标,它会使我显示器上的光标移动。这是怎么发生的呢?
Greg Kroah-Hartman:哦,哇,这是我见过的操作系统面试问题。坦率地说,这不是最基本的。像这样一个简单的任务显示了很多关于当今系统是如何工作的。为了方便起见,我将做一些假设:鼠标是USB鼠标,而不是串行鼠标、蓝牙鼠标、PS/2鼠标或其他鼠标。您的系统正在运行Linux。我不会详细介绍操作系统级别,因为这是我的知识变得模糊的地方。
首先,你的USB鼠标里有一个很小的处理器。它运行的代码非常小且紧凑。它负责两件事:读取鼠标移动和按钮点击的状态,以及当计算机被问及自上次被询问以来是否做了什么不同的事情时,它会做出响应。创建USB协议的主要目的之一是以不到1美元的价格制造鼠标。正因为如此,控制鼠标的处理器可以非常便宜地制造出来,而且处理鼠标所涉及的所有较难的计算都是由操作系统来完成的:在我们的例子中是Linux。
在内核可以向鼠标请求数据之前,它甚至必须知道设备已插入其中。在过去,插入系统的对象必须进行配置,以便系统知道它们是哪种类型的设备,它们插入的位置,以及该设备“说话”的协议类型。USB的目标是试图统一所有这些,并创建一种标准,将常见类型的设备组合在一起,以相同的方式说话,并创建一种方式,让主机系统询问“您是哪种类型的设备?”作为USB规范过程的一部分,定义了大量的常见设备,如鼠标、键盘、磁盘设备、摄像机、脚踏板、电子秤等,这样任何制造商都可以创建使用相同类型协议的设备,并且不必在主机系统上编写自定义代码。一旦为操作系统编写了与一个USB鼠标对话的代码,所有遵循规范的USB鼠标都可以立即工作。这是在设备标准化方面向前迈出的一大步,在过去几十年里,它在使系统更容易使用方面做得比其他任何事情都多。
不管怎样,回到我们的鼠标。主机系统现在知道有鼠标插入了它,所以它会每隔几毫秒左右就会去问鼠标:“你有没有更多的数据给我?”如果是,它会将该数据转换为程序可以使用的标准格式,然后将其公开给用户空间。在鼠标的情况下,数据通常是一个简单的“我在X方向上移动了这么多单位,在Y方向上现在按下(或释放)了这么多单位和按钮N”。
用户空间程序(位于系统内核之外的程序。你使用的每一个应用程序都在用户空间中运行)正在运行,并且已经告诉操作系统“当鼠标向你发送数据时叫醒我”,或者定期询问“你还有更多的鼠标数据要给我吗?”操作系统回复程序,然后程序将数据转换成另一个统一标准,并将其提供给想要在屏幕上表示鼠标指针的程序。在屏幕上绘制鼠标指针是一整套比鼠标数据流水线复杂得多的序列,这是因为不同的硬件协议在某些地方没有标准化。
操作系统最重要的部分之一是它的内核。它管理硬件和软件之间的通信,并将内存分配给系统中运行的其他软件。
所以,当你有不同的计算机外设时,无论是键盘、鼠标、打印机等等,它们也都运行自己的软件吗?
过去10年制造的任何外围设备都没有为其编写运行软件的处理器,这是非常罕见的。请参阅上面的USB鼠标示例。键盘上必须写有代码,以便扫描所有不同的键,以确定当前按下的是什么,并能够在被要求时将数据发送到主机。
打印机是一件非常复杂的东西。20世纪90年代初,我走出大学的第一份工作是编写嵌入到打印机中的软件,用于打印机票和不同类型的包装标签。该软件必须处理描述需要打印哪些文本和条形码以及页面上的位置的数据,以及控制将纸张送入打印机的马达,监控传感器以验证纸张是否存在以及在该时刻它需要位于何处,驱动打印头使其不会错误地烧毁纸张,与处理按钮按下、一天中的时间、不同字体墨盒、永久存储器等的不同芯片进行通信。有一个内部操作系统控制所有这些任务以所需的速度运行,以保持一切顺利进行。现代打印机甚至更加复杂,必须与无线网络通话,处理扫描,以及在打印机本身上编写的不同应用程序。通常,Linux在打印机内部运行是为了让打印机开发人员更容易专注于让打印机正常工作所需做的事情,而不是必须重写“与USB对话”或“与网络对话”等基本内容。
构建自己的设备是否需要编写大量自定义代码,还是所有内容都已经安装到现有操作系统中了?
这完全取决于你想要制造什么类型的设备。现在创建自己的键盘非常简单,这样它就可以“正常工作”,运行开放源码,使用标准的USB键盘协议。这是由于许多常见类型的设备的标准化。
但是,如果您想创建一些以前从未做过的事情,是的,您将必须编写自定义代码,以便操作系统能够控制您的设备并与其对话。
说到操作系统,Linux、Mac和Windows之间编写硬件驱动程序有什么不同?如果我为打印机开发Windows驱动程序,我可以直接将它们转换为Linux和OS X吗?
硬件驱动程序在不同的操作系统之间差别很大。传统上,为Linux编写驱动程序比为其他操作系统编写的代码少大约三分之一,这是因为Linux为您提供了大量的公共代码供您使用。Linux的所有驱动程序都直接包含在Linux的主源码树中,这一事实使我们能够看到多个驱动程序使用的共同功能,并将这些代码合并到驱动程序之外的、由操作系统提供的函数中,从而使驱动程序随着时间的推移变得更简单、更易于编写和维护。
以前从未做过的自定义代码,因为还没有人为它们编写代码。
在某些方面是肯定的,在其他方面是否定的。再拿一只老鼠。用于鼠标的USB协议被称为HID,即人机接口设备(Human Interface Device)的缩写。制造商意识到,一旦制定了该协议并且操作系统支持该协议,他们就可以在其他传输介质上使用相同的通信协议。因此,如果操作系统可以添加对新传输方法的支持,那么它可以立即开始与它已经知道不同传输方法的设备对话。因此,虽然将USB鼠标转换为蓝牙鼠标需要一些流程,但发送到操作系统以描述鼠标如何移动的数据是相同的。
不同的外围设备是否会相互干扰?有没有可能,例如,我的麦克风因为网络摄像头驱动程序而无法工作?或者我的耳机因为Wi-Fi适配器而无法连接?
希望不会。对于现在的大多数硬件协议,设备甚至根本看不到系统中有任何其他设备。他们所能做的就是回答来自主机系统的简单问题:‘你们有什么数据给我吗?’
特定类别的设备或不同类型的自定义设备的驱动程序也不应该能够看到系统中的其他设备,因为它们不能控制这些设备。有些司机为了让设备正常工作而同时控制多个东西的情况有所不同,但这些都是例外,而不是规则。
同样,应用程序是否可能会在个别设备上绊倒?假设您在应用程序中构建了对后退和前进鼠标按键的支持,但有一种型号的鼠标不起作用。您如何调试它?
操作系统的工作是向程序提供所有硬件的统一视图,因此您不必担心不同类型的设备。你需要关注的只是:“鼠标换位置了吗?”但是,当然,硬件就是硬件,硬件设计者可能会故意或无意地搞砸事情并以不同的方式行事,但也有很多例外和方式。
正因为如此,操作系统积累了大量的硬件怪胎表来使事情变得顺畅。但有时,对于更复杂的设备,操作系统无法处理这些差异,因此用户空间库需要介入,以便弄清问题并修复数据。这就是为什么所有程序都会使用公共库来与鼠标等设备对话,这样它们就不必在自己的代码中重复该逻辑。这些库不在操作系统中,而是围绕它创建的低级管道的一部分。执行此操作的鼠标和输入设备的特定库称为libinput。
在计算中,库是以预先编写的代码的形式处理特定任务的工具。工程师使用库来避免每次构建应用程序时都要重新发明轮子。Greg分享了一个libinput的例子:一个用于与鼠标、触摸板和图形平板等输入设备交互的Linux库。
如果您在设备驱动程序中发现错误,如何将修复程序提供给用户?
对于Linux,您需要修复驱动程序并将更改发送给驱动程序所有者和该子系统的开发社区。该更改由开发人员审核并被该子系统的维护人员接受,然后发送到内核维护程序以包含在下一个版本中。当补丁出现在公开版本中时,可以同时将其向后移植到Linux的较旧稳定版本。
像这样的修复一直都在发生。目前,我们平均每天大约有20-40个Linux补丁被重新移植到稳定内核。这与Linux的主要开发周期形成了鲜明对比,Linux的主要开发周期平均每天大约9次更改,为人们提出的新事物添加新的特性和功能。
我们通过usb、usb-c、lightning、hdmi…将设备连接到计算机并相互连接。为什么我们有这么多接口?它们之间有什么不同吗?
在某些方面,它们在硬件级别上有很多不同之处,在其他方面,它们在物理层似乎都以相同的方式工作(它们使用差分信令),以便在两条线路上以非常高的速度传输数据。发送的数据可以是标准格式(如描述鼠标),也可以是较低级别的格式,以模拟另一种类型的设备,使其看起来像是通过较旧类型的连接(即PCI)直接连接到主系统。
为了实现更高的速度、更远的距离、更低的功耗和不同的设计目标,不断创造新的外形规格。与USB-C相比,HDMI适合非常不同的特定需求。创建界面不仅是为了好玩,也是为了解决实际问题。