为什么 MS-DOS“不可重入”并且 DOS 中断不能调用另一个中断?

Why is MS-DOS “not reentrant” and a DOS interrupt can't call another one?

这个问题纯粹是学术性的,因为现在没有人使用MS-DOS,但我还是想知道为什么。

在一些书籍和文章中,他们说如果在另一个中断期间调用 DOS 中断,可能会导致死锁。这就是 MS-DOS 不可重入的原因。 例如RESIDENT PROGRAMS,另外一本书,如下所述:

A  interrupt occurs
B  interrupt handling
C DOS command starts
D new interrupt occurs
E interrupt handling
F DOS COMMAND starts
G DOS command finished
H interrupt finished
I return to the original interrupt handling
J return to original DOS command

It says, when I is finished, going to J, which is trying to return to the point the first DOS command was interrupted, but as all DOS variables and stack are changed by F and G, when you try to go back to the original interrupt (B), you actually go back to the second interrupt (E), and that causes the deadlock.

但就我而言,中断就像调用一样。保存当前CS:IP,查看vector,找到中断处理程序,执行,return到中断发生的地方。完全像 call。 死锁是怎么可能的,这是没有意义的。

所以我的问题是究竟是什么导致了死锁?一个具体的例子将不胜感激。

主要有两个原因:

DOS 内核不是为可重入设计的

当代码应该是可重入的时,在设计时必须考虑到这一点,并且不能使用某些设计模式,例如用于临时数据的静态缓冲区。避免这些设计模式不是 DOS 作者的优先考虑,因此代码通常不是可重入的。

对于其他功能,以可重入的方式实现它们真的很难甚至不可能。例如,以一个将字符输出到屏幕的函数为例。这是通过首先推进光标然后将字符绘制到帧缓冲区中来完成的。假设中断发生在光标前进之后但字符绘制之前。然后,会发生以下情况:

  1. 外部调用:光标从位置 1 前进到位置 2
  2. 中断!
  3. 内部调用:光标从位置2前进到位置3
  4. 内部调用:字符绘制在位置 3
  5. 中断结束
  6. 外部调用:字符绘制在位置 3

所以不是两个字符,而是只画了一个,中间有一个空白。

虽然在某些情况下可以通过关闭中断来避免此类问题,但实际上一直这样做并不是好的设计,因为这会增加中断延迟。此外,对于像文件系统这样更复杂的子系统,可能需要截然不同的设计才能做到这一点。

可重入性的另一个问题(与 multi-threading 安全性相反)是您不能真正使用临界区。当您是中断处理程序并尝试进入临界区但无法进入时,您将无法等待临界区空闲,因为持有它的代码在中断处理程序完成之前不会继续执行。所以这是一个难题,很难正确处理这种情况。

DOS内核有自己的堆栈

DOS 应用程序往往具有非常小的堆栈。与此同时,DOS 多年来不断发展壮大,可能需要大量堆栈 space 才能执行其功能。为了解决这个问题,DOS 设计者从 DOS 2 开始添加了 DOS-internal 堆栈。每当调用 DOS 中断时,中断处理程序首先切换到 DOS 堆栈,然后执行用户调用的功能。如果在 DOS 内部尝试此操作,堆栈切换将破坏外部 DOS 调用的调用堆栈。

幸运的是 DOS 阻止您这样做:有一个“在 DOS 中”标志可以跟踪 DOS 调用是否是 运行。如果调用 运行,堆栈切换将中止,您的 DOS 调用将失败。

顺便说一下,在编写 pop-up TSR 时这是一个大问题,关于这个主题的书籍用很长的章节来讲述当调用 TSR 时 DOS 调用你能做什么和不能做什么,以及如何解决这些问题。