打开程序时,操作系统是否注入自己的机器代码?


32

我正在研究CPU,我知道它如何从内存中读取程序并执行其指令。我还了解到,操作系统将进程中的程序分开,然后在各个程序之间如此快速地切换,以至于您认为它们在同一时间运行,但实际上每个程序都在CPU中单独运行。但是,如果OS也是在CPU中运行的一堆代码,那么它如何管理进程?

我一直在思考,我唯一能想到的解释是:当操作系统将程序从外部存储器加载到RAM时,它会在原始程序指令的中间添加自己的指令,然后执行该程序,可以调用操作系统并执行某些操作。我相信操作系统会添加一条指令到程序中,这将允许CPU在一段时间后返回到操作系统代码。而且,我相信操作系统加载程序时,它会检查是否存在一些禁止的指令(该指令会跳转到内存中的禁止访问地址)并消除该指令。

我在想严厉吗?我不是CS学生,但实际上不是数学学生。如果可能的话,我会想要一本好书,因为如果操作系统也是一堆在CPU中运行的代码,并且无法同时运行,那么我找不到能解释操作系统如何管理进程的人。节目时间。这些书仅说明操作系统可以管理事物,但现在可以管理事物。


7
请参阅:上下文切换操作系统会将上下文切换到应用程序。然后,该应用可以请求将上下文返回给OS的OS服务。当应用程序结束时,上下文切换回操作系统。
Guy Coder 2014年

4
另请参见“ syscall”。
拉斐尔


1
如果评论和答案不能使您的理解或满意回答您的问题,请索取更多信息作为评论,并说明您的想法或迷路之处,或者您需要进一步了解什么。
Guy Coder 2014年

2
我认为中断钩子(中断的钩子),硬件计时器(带有调度处理钩子)和页面调度(对禁止的内存的部分回答)是您需要的主要关键字。操作系统需要与处理器紧密合作,以仅在需要时运行其代码。因此,大多数CPU功能可以用于实际计算,而不是其管理。
Palec 2014年

Answers:


35

不会。操作系统不会把程序的代码弄乱,不会向其中注入新的代码。那将有许多缺点。

  1. 这将非常耗时,因为操作系统将不得不扫描整个可执行文件以进行更改。通常,部分可执行文件仅根据需要加载。而且,插入很昂贵,因为您必须移走很多东西。

  2. 由于无法确定暂停问题,因此无法知道在何处插入“跳回操作系统”指令。例如,如果代码中包含while (true) {i++;},则您肯定需要在该循环中插入一个钩子,但是循环中的条件(true此处为)可能会非常复杂,因此您无法确定循环的时间。另一方面,将钩子插入每个循环的效率非常低:例如,在for (i=0; i<3; i++) {j=j+i;}此过程中跳回到操作系统会大大减慢该过程。并且,出于相同的原因,您无法检测到短循环以使其保持独立。

  3. 由于无法确定暂停问题,因此无法知道代码注入是否改变了程序的含义。例如,假设您在C程序中使用函数指针。注入新代码将移动函数的位置,因此,当您通过指针调用一个代码时,您将跳到错误的位置。如果程序员病得很重,无法使用计算的跳转,那么它们也将失败。

  4. 它会与任何防病毒系统一起玩,因为它也会更改病毒代码并破坏所有校验和。

您可以通过模拟代码并在执行次数超过一定固定次数的任何循环中插入钩子来解决暂停问题。但是,这要求在允许执行之前对整个程序进行极其昂贵的仿真。

实际上,如果您想注入代码,编译器将是很自然的地方。这样,您只需要执行一次,但是由于上面给出的第二个和第三个原因,它仍然不起作用。(而且有人可以编写一个不配合的编译器。)

操作系统从进程中重新获得控制的三种主要方式。

  1. 在协作(或非抢占式)系统中,yield进程可以调用一个函数来将控制权交还给OS。当然,如果这是您唯一的机制,则您只能依靠行为良好的进程,而没有屈服的进程将占用CPU直到终止。

  2. 为避免该问题,使用了定时器中断。CPU允许OS为CPU实现的所有不同类型的中断注册回调。操作系统使用此机制为定时触发的计时器中断注册回调,从而允许其执行自己的代码。

  3. 每当进程尝试从文件中读取文件或以任何其他方式与硬件进行交互时,都会要求操作系统为此工作。当操作系统要求某个进程执行某项操作时,它可以决定暂停该进程并开始运行其他进程。这听起来有点像马基雅维利安(Machiavellian),但这是正确的事情:磁盘I / O速度很慢,因此您最好让进程B在进程A等待旋转的金属块移到正确的位置时运行。网络I / O甚至更慢。键盘输入/输出很方便,因为人不是千兆赫。


5
您能否在最近的2点上继续发展?我对这个问题感到好奇,感觉这里的解释已被跳过。在我看来,问题似乎是“操作系统如何从进程中收回CPU”,您的回答是“操作系统对其进行处理”。但是如何?在您的第一个示例中,出现无限循环:它如何不冻结计算机?
BiAiB 2014年

3
有些操作系统会这样做,大多数操作系统至少会弄乱代码以进行“链接”,因此该程序可以在任何地址加载
Ian Ringrose 2014年

1
@BiAiB这里的关键词是“中断”。CPU不仅可以处理给定的指令流,还可以从单独的源异步中断它-对我们来说最重要的是I / O和时钟中断。由于只有内核空间代码可以处理中断,因此Windows可以确保随时随地从任何正在运行的进程中“窃取”工作。中断处理程序可以执行他们想要的任何代码,包括“将CPU的寄存器存储在某个位置并从此处恢复它们(另一个线程)”。极其简化,但这是上下文切换。
六安

1
加上这个答案;第2点和第3点提到的多任务处理方式称为“抢先式多任务处理”,其名称是指操作系统抢占正在运行的进程的能力。协作多任务在较旧的操作系统上经常使用;在Windows上,至少在Windows 95之前没有引入抢占式多任务处理。我已经读到至少今天使用的一个工业控制系统,该系统仍仅将Windows 3.1用于其实时协作式多任务处理行为。
杰森C

3
@BiAiB实际上,你错了。自i486以来,台式机CPU不会顺序和同步运行代码。但是,即使是较旧的CPU仍具有异步输入-中断。想象一下硬件中断请求(IRQ),就像CPU本身上的一个针脚一样-当它收到时1,CPU停止正在执行的任何操作,然后开始处理中断(这基本上意味着“保留状态并跳转到内存中的地址”)。中断处理本身不是,x86也不是任何代码,实际上是硬连接的。跳转后,它再次执行(任何)x86代码。线程是一种更高的抽象方式。
a安

12

尽管David Richerby的答案是一个很好的答案,但它确实使现代操作系统如何暂停现有程序确实让人眼花乱。对于x86或x86_64体系结构,我的答案应该是准确的,这是台式机和笔记本电脑中唯一常用的体系结构。其他体系结构应具有类似的方法来实现此目的。

操作系统启动时,它会建立一个中断表。该表的每个条目都指向操作系统内部的一些代码。当发生中断(由CPU控制)时,它将查看该表并调用代码。有各种中断,例如被零除,无效代码和某些操作系统定义的中断。

这是用户进程与内核进行对话的方式,例如它是否要对磁盘或操作系统内核控制的其他内容进行读/写操作。操作系统还将设置一个计时器,该计时器在完成时将调用中断,因此将运行的代码从用户程序强制更改为操作系统内核,并且内核可以执行其他操作,例如将其他程序排队等待运行。

从内存中,发生这种情况时,操作系统内核必须保存代码所在的位置,并且当内核完成所需的操作后,它将恢复程序的先前状态。因此,程序甚至不知道它已被中断。

该进程不能更改中断表,原因有两个,第一个原因是该进程在受保护的环境中运行,因此,如果尝试调用某些受保护的汇编代码,则CPU将触发另一个中断。第二个原因是虚拟内存。中断表的位置在实内存中的0x0到0x3FF,但是对于用户进程,该位置通常不被映射,并且尝试读取未映射的内存将触发另一个中断,因此没有受保护的功能和写入实RAM的能力,用户进程无法更改它。


4
中断不是由操作系统定义的,而是由硬件给出的。并且大多数当前体系结构都有特殊的指令来调用操作系统。i386为此使用了(软件生成的)中断,但是在其后继产品上不再采用这种方式。
vonbrand 2014年

2
我知道中断是由cpu定义的,但是内核会设置指针。我可能不好解释。我还认为linux仍然使用int 9与内核进行通讯,但是也许现在有更好的方法。
Programmdude

尽管抢先式调度程序是由计时器中断驱动的概念是正确的,但这是一个相当误导的答案。首先,值得注意的是计时器在硬件中。还需要澄清的是,“保存...还原”过程称为上下文切换,除其他事项外,通常涉及保存所有CPU寄存器(包括指令指针)。而且,进程可以有效地更改中断表,这称为“保护模式”,它也定义了虚拟内存,并且自286开始就存在-指向中断表的指针存储在可写寄存器中。
杰森C

(自8086以来,甚至实模式中断表也已可重定位-未被锁定到内存的第一页。)
Jason C

1
这个答案错过了关键的细节。发生中断时,CPU不会直接切换到内核。相反,它首先保存现有寄存器,然后切换到另一个堆栈,然后才调用内核。从随机程序中使用随机堆栈调用内核是一个相当糟糕的主意。另外,最后一部分是误导性的。您将不会中断“尝试”以读取未映射的内存;这根本不可能。您从虚拟地址读取,并且未映射的内存根本没有虚拟地址。
MSalters 2014年


3

还有就是类似的方法你描述:协同式多任务。OS不会插入指令,但是必须编写每个程序来调用OS功能,这些功能可能会选择运行另一个协作过程。这具有您描述的缺点:一个程序崩溃导致整个系统崩溃。直到3.0(含3.0)的Windows都是这样工作的;3.0在“保护模式”及更高版本中没有。

抢先式多任务处理(近来很常见)依赖于外部中断源。中断会覆盖正常的控制流程,通常会将寄存器保存在某个地方,因此CPU可以执行其他操作,然后透明地恢复程序。当然,操作系统可以更改“中断时在此处恢复”寄存器,因此它可以在不同的进程中恢复。

(某些系统确实在程序加载时以有限的方式重写指令,称为“ thunking”,并且Transmeta处理器会动态地重新编译为自己的指令集)


AFAICR 3.1也是合作的。Win95是抢占式多任务处理的地方。受保护的模式主要带来了地址空间的隔离(这确实提高了稳定性,但出于很大程度上不相关的原因)。
cHao 2014年

Thunking不会将代码重写或注入到应用程序中。修改的加载程序是基于OS的,不是应用程序的产品。诸如使用JIT编译器之类的解释性语言不会修改代码,也不会向代码中注入任何内容。他们将源代码转换为可执行文件。同样,这与向应用程序中注入代码不同。
Dave Gordon

Transmeta将x86可执行代码作为其来源,而不是解释性语言。而且我想到了一种注入代码的情况:在调试器下运行。X86系统通常在断点处使用“ INT 03”覆盖指令,该指令会陷入调试器中。在恢复时,将恢复原始操作码。
pjc50

调试几乎不是任何人运行应用程序的方式。超出了应用程序开发人员的范围。因此,我认为这对OP毫无帮助。
Dave Gordon

3

多任务不需要诸如代码注入之类的东西。在Windows之类的操作系统中,操作系统代码的一部分称为调度程序,它依赖于由硬件计时器触发的硬件中断。操作系统使用它来在不同的程序及其本身之间切换,这在我们的人类感知中似乎都是同时发生的。

基本上,操作系统对硬件计时器进行编程,使其每隔一秒钟关闭一次,也许每秒关闭100次。当计时器关闭时,它会产生硬件中断-一个信号,通知CPU停止其正在执行的操作,将其状态保存在堆栈中,将其模式更改为更具特权的内容并执行将在专门指定的代码中找到的代码放置在内存中。该代码恰好是调度程序的一部分,该调度程序决定下一步应该做什么。它可能是恢复某些其他过程,在这种情况下,它必须执行所谓的“上下文切换”-用其他过程替换其当前状态的整个状态(包括虚拟内存表)。返回流程时,它必须还原该流程的所有上下文,

内存中的“特别指定”位置除了操作系统外,不需要其他任何信息。实现方法各不相同,但要点是CPU将通过执行表查找来响应各种中断。该表的位置位于内存中的特定位置(由CPU的硬件设计确定),该表的内容由操作系统设置(通常在引导时),并且中断的“类型”将确定哪个条目该表中的“中断服务程序”。

这些都不涉及“代码注入”……它是基于与CPU及其支持电路的硬件功能配合使用的操作系统中包含的代码。


2

我认为,与您描述的最接近的真实示例是VMware使用的一种技术,即使用二进制翻译的完全虚拟化。

VMware充当同一硬件上一个或多个同时执行的操作系统之下的一层。

可以使用硬件对大多数正在执行的指令(例如在普通应用程序中)进行虚拟化,但是OS内核本身会利用无法虚拟化的指令,因为如果猜测OS的机器代码未经修改就执行,则会“爆发”。 “由VMware主机控制。例如,客户操作系统将需要在特权最高的保护环中运行,并设置中断表。如果允许这样做,VMware将会失去对硬件的控制。

VMware在执行之前将这些指令重写为OS代码,然后将其替换为跳转到模拟所需效果的VMware代码中。

因此,该技术在某种程度上类似于您所描述的。


2

在许多情况下,操作系统可能会将代码“注入”到程序中。Apple Macintosh系统的基于68000的版本构建了所有段入口点的表(位于静态全局变量IIRC的紧前面)。程序启动时,表中的每个条目均包含一个陷阱指令,后跟段号和段中的偏移量。如果执行了陷阱,则系统将在陷阱指令后查看单词,以查看需要哪些段和偏移量,加载该段(如果尚未加载),将该段的起始地址添加到偏移量中,然后然后将陷阱替换为跳转到该新计算的地址。

在较旧的PC软件上,尽管这在技术上不是由“ OS”完成的,但通常使用陷阱指令而不是协处理器数学指令来构建代码。如果未安装数学协处理器,则陷阱处理程序将对其进行仿真。如果安装了协处理器,则第一次使用陷阱时,处理程序将用协处理器指令替换陷阱指令;以后相同代码的执行将直接使用协处理器指令。


FP方法仍在ARM处理器上使用,与x86 CPU不同的是,ARM处理器确实具有无FP的变体。但是很少见,因为大多数ARM使用都在专用设备中。在这些环境中,通常会知道CPU是否具有FP功能。
MSalters 2014年

在这两种情况下,操作系统都不会将代码注入应用程序中。为了使OS注入代码,它需要软件供应商的许可才能“修改”未获得的应用程序。操作系统请勿注入代码。
Dave Gordon

@DaveGordon被困的指令可以合理地认为是OS向应用程序中注入了代码。
吉尔(Gilles)'所以

@MSalters陷阱指令通常在虚拟机中发生。
吉尔(Gilles)'所以
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.