为什么有Linux内核策略可以永不中断用户空间?


38

我开始在Linux Kernel Mailing列表上的礼节中考虑此问题。作为世界上最著名,可以说是最成功,最重要的自由软件项目,Linux内核受到了广泛的关注。该项目的创始人和负责人Linus Torvalds在这里显然不需要介绍。

莱纳斯(Linus)在LKML上有时会引起争议。他自己承认,这些火焰经常与破坏用户空间有关。这使我想到了我的问题。

关于中断用户空间为何如此糟糕,我可以有一些历史观点吗?据我了解,破坏用户空间将需要在应用程序级别进行修复,但这是否不好,如果它可以改善内核代码呢?

据我了解,Linus声明的政策是不破坏用户空间胜过其他一切,包括代码质量。为什么这如此重要,这种政策的利弊是什么?

(显然,一贯实施的这种政策存在一些弊端,因为莱纳斯偶尔就其主题在LKML上与他的高级副手之间存在“分歧”。据我所知,他总是在此事上有所作为。)


1
您在导言中拼错了Linus的名字。
伊斯梅尔·米格尔

不是我确定,但是我忘了投票,现在投了赞成票。
Ismael Miguel

Answers:


38

原因不是历史原因,而是实际原因。在Linux内核之上运行的程序很多。如果内核接口破坏了这些程序,那么每个人都需要升级那些程序。

现在的确是,大多数程序实际上并不直接依赖于内核接口(系统调用),而是仅依赖于C标准库的接口(围绕系统调用的C 包装器)。哦,但是哪个标准库?Glibc?uClibC?Dietlibc?仿生?肌肉?等等

但是,还有许多程序可以实现特定于操作系统的服务,并且依赖于标准库未公开的内核接口。(在Linux上,其中许多是通过/proc和提供的/sys。)

然后是静态编译的二进制文件。如果内核升级中断了其中之一,则唯一的解决方案是重新编译它们。如果您有资料来源:Linux确实也支持专有软件。

即使有可用的资源,收集所有资源也可能很痛苦。尤其是在升级内核以修复硬件错误时。人们通常需要独立于系统的其余部分来升级内核,因为他们需要硬件支持。用莱纳斯·托瓦尔兹(Linus Torvalds)话说

完全不能接受破坏用户程序。(…)我们知道人们使用旧的二进制文件已经有很多年了,发布新版本并不意味着您可以将其丢弃。您可以信任我们。

还解释说,使其成为一个强有力的规则的一个原因是避免依赖地狱,在这种情况下,您不仅必须升级另一个程序才能使某些新内核工作,而且还必须升级另一个程序,另一个程序和另一个程序。 ,因为一切都取决于一切的特定版本。

这是有点确定有一个明确的单向依赖。很难过,但有时不可避免。(…)不行的是要有两个依赖关系。如果用户空间的HAL代码依赖于新内核,那没关系,尽管我怀疑用户希望它不是“本周内核”,而是“最近几个月的内核”。

但是,如果您有两个依赖关系,那么您就搞砸了。这意味着您必须逐步升级,而这是不可接受的。这对用户来说是可怕的,但更重要的是,对开发人员来说,这是可怕的,因为这意味着您不能说“发生了一个错误”,而是要进行尝试,例如使用等分或类似方法将其缩小。

在用户空间中,通常通过保留不同的库版本来解决这些相互依赖性。但是您只能运行一个内核,因此它必须支持人们可能想要使用的所有内容。

正式地

[已声明稳定的系统调用]的向后兼容性将至少保证2年。

但实际上

预期大多数接口(如syscalls)将永远不会改变并且始终可用。

确实更经常变化的是接口,这些接口仅打算由与硬件相关的程序使用/sys。(/proc另一方面,自从引入以来,它/sys一直保留给非硬件相关的服务,因此几乎永远不会以不兼容的方式中断。)

综上所述,

破坏用户空间将需要在应用程序级别进行修复

这很不好,因为只有一个内核,人们希望独立于其系统的其余部分进行升级,但是其中有许多应用程序具有相互依赖的复杂性。保持内核稳定更容易,因为它可以使成千上万的应用程序在数百万种不同的设置下保持最新。


1
谢谢你的回答。那么,被声明为稳定的接口是POSIX系统调用的超集吗?我对历史的问题是这种做法是如何演变的。大概Linux内核的原始版本至少在最初就没有担心用户空间中断。
Faheem Mitha 2015年

3
@FaheemMitha是的,自1991年以来,他们就这样做了。我认为Linus的方法没有发展,一直都是“普通应用程序的接口不变,与内核紧密相关的软件接口很少变化”。
吉尔(Gilles)'所以

24

在任何相互依赖的系统中,基本上都有两种选择。抽象和集成。(我故意不使用技术术语)。使用Abstraction,您的意思是,当您调用API时,尽管该API背后的代码可能会更改,但结果始终是相同的。例如,当我们打电话时,我们fs.open()不在乎它是网络驱动器,SSD还是硬盘驱动器,我们总是会得到一个打开的文件描述符,我们可以用它来处理。通过“集成”,目标是提供一种“最好”的做事方式,即使方式发生变化。例如,对于网络共享而言,打开文件可能与对磁盘上文件而言不同。这两种方式在现代Linux桌面中都得到了广泛的使用。

从开发人员的角度来看,这是一个“适用于任何版本”或“适用于特定版本”的问题。OpenGL是一个很好的例子。大多数游戏都设置为可使用特定版本的OpenGL。是否从源代码编译都没有关系。如果该游戏是使用OpenGL 1.1编写的,并且您试图使其在3.x上运行,那么您将不会过得很开心。在频谱的另一端,无论如何,某些呼叫都有望正常工作。例如,我想打电话给fs.open()我,不要在乎我使用的内核版本。我只想要一个文件描述符。

每种方式都有好处。集成以向后兼容为代价提供了“较新的”功能。虽然抽象提供了对“较新”调用的稳定性。尽管重要的是要注意这是优先事项,而不是可能性。

从公共角度来看,没有非常好的理由,抽象在复杂系统中总是更好。例如,假设是否fs.open()根据内核版本而有所不同。然后,一个简单的文件系统交互库将需要维护数百种不同的“打开文件”方法(或可能的块)。当新的内核版本问世时,您将无法“升级”,您将不得不测试所使用的每个软件。内核6.2.2(伪)可能会破坏您的文本编辑器。

对于某些实际示例,OSX往往不关心破坏用户空间。他们的目标是更频繁地“整合”而不是“抽象”。在每次主要的OS更新中,事情都会中断。这并不是说一种方法比另一种更好。这是一个选择和设计的决定。

最重要的是,Linux生态系统中充斥着很棒的开源项目,人们或团队在空闲时间从事该项目,或者因为该工具非常有用。考虑到这一点,第二个开始变得不再有趣,而是开始成为PIA,那些开发人员将前往其他地方。

例如,我向提交了补丁BuildNotify.py。不是因为我无私,而是因为我使用了该工具,并且想要一个功能。这很容易,所以这里有一个补丁。如果它很复杂或麻烦,我不会使用BuildNotify.py,我会发现其他东西。如果每次出现内核更新时,我的文本编辑器都崩溃了,那么我将使用其他操作系统。我对社区的贡献(无论多么小)将不会继续存在或不存在,依此类推。

因此,设计决定是对抽象的系统调用进行的,因此当我这样做时fs.open(),它就起作用了。这意味着要保持fs.open长期fs.open2()流行。

从历史上看,这通常是POSIX系统的目标。“这是一组调用和期望的返回值,您可以找出中间值。” 再次出于便携性原因。为什么Linus选择使用这种方法是他大脑的内在原因,所以您必须让他确切地知道为什么。但是,如果是我,我会选择抽象而不是复杂系统上的集成。


1
用户空间的API,即“ syscall” API,定义明确(尤其是POSIX子集)且稳定,因为删除其中的任何部分都会破坏人们可能已经安装的软件。它没有稳定的驱动程序 API。
pjc50

4
@FaheemMitha,相反。内核开发人员可以在需要时随意破坏驱动程序API,只要他们在下一个版本之前修复所有内核驱动程序即可。它破坏了用户空间API,甚至进行了可能破坏用户空间的非API事情,从而使Linus产生了史诗般的反应。
2015年

4
例如,如果有人决定在某些情况下通过从ioctl()返回不同的错误代码来更改它:lkml.org/lkml/2012/12/23/75(包含对负责任的开发人员的宣誓和人身攻击)。该补丁被拒绝了,因为它会破坏PulseAudio,从而破坏GNOME系统上的所有音频。
pjc50

1
@FaheemMitha,基本上是def add(a,b); 返回a + b; 结束--- def加(a,b); c = a + b; 返回c; 结束--- def add(a,b); c = a + b +10; 返回c-10; end-所有add的“相同”实现。让他如此沮丧的是人们确实对add(a,b)进行定义时;返回(a + b)* -1; 本质上,更改内核的“内部”事物的工作方式是可以的。不能更改返回给已定义的“公共” API调用的内容。有两种API调用:“私有”和“公共”。他认为,公开的API调用在没有充分理由的情况下绝不能更改。
coteyr

3
非代码示例;您去商店,购买了87辛烷值汽油。您,作为消费者,无需“关心”气体的来源或处理方式。您只是在乎自己的气。如果气体经过了不同的精炼过程,则无需担心。确保精炼过程可以改变。甚至还有不同的石油来源。但是,您关心的是获得87辛烷值汽油。因此,他的职位是改变来源,改变炼油厂,改变任何东西,只要在泵处排出的是87辛烷值汽油。所有“幕后”内容都无关紧要。只要有87辛烷值气体。
coteyr

8

这是设计决策和选择。Linus希望能够向用户空间开发人员保证,除非在极为罕见和例外(例如,与安全相关的情况)下,内核中的更改不会破坏他们的应用程序。

优点是用户空间开发人员不会因为任意和反复无常的原因而不会突然在新内核上破坏代码。

缺点是内核必须永久保留旧代码和旧syscall等(或至少要早于其使用日期)。


感谢您的答复。您是否知道此决定如何演变的历史?我知道项目的观点有所不同。例如,Mercurial项目没有固定的API,并且可以破坏依赖于此的代码。
Faheem Mitha 2015年

不,对不起,我不记得它是怎么发生的。您可以向Linus或LKML发送电子邮件并询问他。
cas 2015年

2
Mercurial不是操作系统。操作系统的全部目的是使其他软件能够在其之上运行,并且破坏该其他软件非常不受欢迎。相比之下,Windows保持了很长时间的向后兼容性。16位Windows代码直到最近才被淘汰。
pjc50

@ pjc50这是真的,Mercurial是不是一个操作系统,但无论如何,还有就是其他的软件,即使只是脚本,依赖于它。并且可能会被更改破坏。
Faheem Mitha 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.