Cyk*_*ker -4 linux keyboard-event
某些语言具有宽字符。宽字符可以有多个字节。当您在控制台或 X 中键入宽字符时,您实际上是在发送几个字节。单字节字符本身是原子的,从某种意义上说,它要么被传输,要么不被传输,要么被接收,要么不被接收。但对于宽字符而言,情况并非如此。例如,仅传递 3 字节字符的第一个字节会产生垃圾。底层系统如何保证应用程序始终以原子方式接收宽字符?一个好的答案应该解释当用户分别在控制台、X 和 ssh 中键入宽字符时依次发生的事情。故事开始:当用户输入宽字符时,会产生一个中断......
底层系统如何保证应用程序始终以原子方式接收宽字符?
由于堆栈中有多个层,因此这个问题中的术语可能让一些人感到困惑。我的实际意思是:想想你正在编写一个带有编辑框的 GUI 应用程序。当您键入宽字符时,它要么全部显示,要么不显示,从不部分显示。所以底层系统包括应用程序下面的所有内容(在这种情况下,应用程序框架、GUI 库等)。
他们不是。
(文本)应用程序在标准输入上接收字节流。该应用程序是免费阅读尽可能多或尽可能少他们的,因为它喜欢的,确实一个read电话是免费的不是字节的请求数量返回较少。可以随意解释该字节流,请求读取更多字节,或将所有内容视为不透明的二进制数据。如果它想将某些字节解释为一个不同的、离散的块的一部分,它能够这样做,并且它能够继续查找直到它决定它有那个块。除非它是单个字节,否则没有什么能保证它一次全部获得。
这是简短的答案,如果您愿意,现在可以停止阅读:问题的前提是错误的,直到应用程序能够做任何它想做的事情来获得它选择的结果。
下面我们将继续讨论事情可能(或可能不会)解决的一些方式,以及从某人的角度来看它们何时可能是或可能不是“原子”。
对于这个答案的其余部分,我们将忽略网络和套接字流量。我们还将对“角色”的定义采用相当模糊的定义,因为这似乎是起点,并且可能会随着我们的发展而对其进行完善。我们将从最终应用程序的角度考虑问题。最后,我们将简要介绍从键盘硬件开始的(主要是题外的)路径。它仍然会太长。
应用程序read从其输入中请求一些字节。然后它也可以自由地以它喜欢的任何方式解释它接收到的字节,或者让一个库为它这样做。在它需要文本的情况下,它将通过一些字符编码来解释这些字节,这决定了字节序列的含义。希望它与数据源或终端就使用的编码达成一致(但它不必如此!)。对于任何编码,应用程序决定将事物解释为:
wchar_t,它最好知道它已经读取了多少字节,以便在需要时可以要求另一个(因为read可能随时返回奇数个字节,给你一半一个代码单元)。如果该编码是UTF-8,现在这种情况非常有可能,应用程序可以使用任何单个字节进行同步,因为它是一种自同步编码:单字节代码单元的高位为 0,任何部分的高位为 1一个多字节序列。2 字节、3 字节或 4 字节代码单元的第一个字节分别为 110、1110 或 11110,连续字节从 10 开始。读取一个字节告诉您还需要多少,或者您在中间的东西了。
如果必要的话,该应用程序然后可以希望进行第二 read以下字节(一个或多个)。它需要记住它已经读取的部分元素,并在最后将这两部分组合在一起。它甚至可能需要进行第三次或第四次阅读。
然后它可以按照自己的意愿离散地处理它们,可能会为代码点发出一个 32 位的值。将其抽象为一个函数或库以供重用可能是明智的。
所有这些属性还有其他可用的编码,等等。其中一些不被 POSIX 允许作为系统编码,但可能会在应用程序之间达成一致。更有可能的是,如果您今天在类 Unix 系统上使用多字节系统编码,则它是 UTF-8。
我的多字节字符“e?” 可能是两个字节的 UTF-8 (C3 A9) 或三个 (65 CC 81),因为它可能是单个代码点 (U+00E9 LATIN SMALL LETTER E WITH ACUTE) 或两个 (U+0065 LATIN SMALL LETTER E + U+0301 结合急性口音)。“???” 将变得更加复杂。您的应用程序认为什么是原子的?这取决于应用程序,因为实际上到目前为止所有内容都是如此。
Unicode 将这些多码点字符称为“字素簇”。如果您想要原子地是其中之一,那么您执行上述所有操作以读取代码点 - 然后根据您的UAX29 文本分割状态表检查它,并决定是否有可能在同一集群中跟随另一个代码点. 如果是这样,您尝试重新阅读,并继续这样做,直到到达新集群的开始、状态机中的死胡同或输入的结束。
应用程序中的库可以抽象出任何或所有这些,并提供字节流、代码单元、字素簇或其他东西。例如,如果您使用 Swift 进行编程,则会得到字素簇。如果它是在 C 中,你会得到字节,其中“你”包括你所有的库——你可以选择任何你想要的集成行为,只要你使用提供它的库的 API。
其他多字节序列,如箭头键或退格键的控制台转义符(按下Ctrl-V Left并查看您得到什么),也是类似的事情:它们是一个单元,还是不是?你怎么讲?有时你不能,你必须猜测。例如,zsh 的 zkbd 系统要求您按下某些键,以便它可以记住它们以进行键绑定 - 但它只是使用超时来判断一个序列的输入何时完成。稍后,它知道它是否看到了它识别的序列的前缀,并在它采取行动之前查看它是否获得了全部信息(在连接不良的情况下通过 SSH 会发生什么?)。在上下文中,这对于当时正在做的事情来说足够好,但在其他情况下可能不会。这是一个像其他人一样的应用问题。
(类Unix)系统都没有解决这些问题,它只提供 bytes。一个字节在 Unix API 级别是原子的,但基本上没有其他的。其他一切都由应用程序自行执行。
文本表示很难。没有明确、正确、普遍的答案。应用程序能够构建一个适用于自己的应用程序,但系统不会尝试提供构建它的能力以外的任何东西。
就键盘而言,它向系统发送一条消息,系统将其提供给内核键盘驱动程序,该驱动程序将其提供给 X,后者将其提供给终端仿真器,后者将一些编码字节发送到标准输入。这些消息中的每一个都有自己的编码和交换方式,甚至可以从前一层(或合并)分成多个部分 - 例如,XCompose 允许将一个或多个按键的序列转换为单个或多个虚拟输入 - 如果您愿意,您可以考虑如何将所有这些消息“原子化”。
第一条消息很可能是“按键 213 和 71 已按下,大写锁定已关闭,数字锁定已开启”;倒数第二个 X KeyPress 事件(尝试xev);讨论的最后一系列字节。基本上,这些都不是这里的主题,作为系统编程、通用编程或硬件设计之一,但可能适用于其他一些网络站点。整个堆栈太宽泛,无法在任何地方一次全部回答。