5

In DPMI, interrupt 0x31 services 0x0000 and 0x0100 are capable of simultaneously allocating multiple protected-mode selectors in a single call. When that happens, both services return only the first allocated selector; the others are obtained by adding a fixed increment (repeatedly if necessary), whose value is obtainable from service 0x0003.

All the DPMI hosts I (somewhat superficially) tried this under — Borland’s, Windows 3.11, DOS/4GW and CWSDPMI — return the value 8 from this service, which means that descriptors are allocated in contiguous blocks (as the three least-significant bits do not index selectors, but determine the privilege level and choose between the GDT and the LDT). In principle though, nothing stops a DPMI host from using a larger multiple of 8.

The section of the DPMI specification describing the 0x0003 call (§8.4) does not explain at all why the DPMI host is allowed to do this or how it might be useful for the host to allocate descriptors non-contiguously. What purpose could this freedom serve? What does it afford implementations? Was there a DPMI host implementation that took advantage of it?

(I would prefer an official document or a statement from someone who worked on or was familiar with the development of the DPMI specification, but failing that, speculation may be enough.)

user3840170
  • 23,072
  • 4
  • 91
  • 150
  • 2
    just speculating: A single-tasking DPMI implementation could unify LDT and GDT into one virtual descriptor table and return 4 as "selector increment". Furthermore, it was a common pattern in mode-agnostic code to leave the selector increment variable, and to use 1000h as increment in real mode. As mode-agnostic code wouldn't directly call DPMI services, this doesn't explain why DPMI itself has this funtion, though. – Michael Karcher Aug 05 '21 at 12:49
  • 3
    Perhaps it was a transposition of __AHINCR... – Stephen Kitt Aug 05 '21 at 13:03
  • @StephenKitt Chen’s description of __AHINCR seems to imply the value in protected mode is always 8 anyway, so it doesn’t really provide any justification for this call’s existence. Sure, you need to invoke it if you want to implement your own __AHINCR portable to different DPMI hosts. But it might as well have not existed, with the DPMI contract specifying the increment as always 8. And yet it is there. What is it there for? – user3840170 Aug 05 '21 at 19:14
  • @user3840170 my thought was that Ralph Lipe presumably knew about __AHINCR (which was present in the Windows 2 kernel); I’m not sure what the exact chronology is, but perhaps he also knew how it enabled Windows 2 programs to run without change in protected mode on the skunkworks kernel that would become Windows 3. Since it had proven useful, something similar in DPMI might seem useful too, even with no immediate use. (__AHINCR and the spec for far pointers came before protected mode for Windows applications.) – Stephen Kitt Aug 05 '21 at 20:56
  • 1
    @user3840170 put another way, perhaps adding this to the DPMI spec was hedging bets for the future, with no certainty that it would be useful but with the knowledge that not having something like that would have been a blocker, or at least a significant hurdle, for Windows 3. YAGNI is all very well until you’re stuck because you don’t have it. – Stephen Kitt Aug 05 '21 at 20:59
  • OS/2 1.x had a similar API, DosAllocHuge and DosGetHugeShift (replaced with object allocation in 2.x). – Stephen Kitt Aug 06 '21 at 08:49
  • @StephenKitt When I read the linked documentation, it seems OS/2 1.x provided that API in "DOS mode" (real mode) and "OS/2 mode" (protected mode). So this seems to be an API for mode-angostic code, just like __AHINCR. As DPMI isn't available in real mode (and can not be made available at INT31 due to that "CP/M jump" at INT30/INT31), this DPMI function can not be part of a mode-agnostic API. So I agree with the "hedging bets for the future" theory instead. User code should use __AHINCR or DosGetHugeShift to be real-mode compatible, so not fixing this value to 8 is basically free. – Michael Karcher Aug 06 '21 at 09:28
  • 1
    @Michael yes, I just found it interesting initially to catalog the other places where similar APIs were found. It turns out that DosGetHugeShift did have varying results depending on the version of OS/2, so the API was useful even for protected-mode-only code. – Stephen Kitt Aug 06 '21 at 09:38

1 Answers1

6

I don’t have any insider knowledge on this topic, so this is purely speculation.

I suspect the call exists because there was potential variation in similar mechanisms available at the time, which turned out to be useful at around the same time as the DPMI specification was written (and before the first drafts were published). There’s a decent chance that Ralph Lipe, the original author of the DPMI specification, was aware of memory allocation rules in at least two contemporaneous operating environments, OS/2 and Windows 2.

OS/2 1.x ran in protected mode on 286s and supported allocations larger than 64KiB, in a similar fashion to what ended up in the DPMI specification. This can be seen in the Family API functions DosAllocHuge and DosGetHugeShift; native OS/2 applications also had access to corresponding variables (whose values were constant during any given boot of OS/2), DOSHUGEINCR and DOSHUGESHIFT (see Inside OS/2, section 9.2.2, for details).

Windows 2 didn’t run applications in protected mode, but it did provide a similar “API”, through the __AHINCR and __AHSHIFT variables in the kernel, and compiler writers used that to implement far pointer arithmetic. This famously helped the switch to running applications in protected mode in Windows 3, since correctly-written applications could run as-is in either real or protected mode even if they needed segment arithmetic. (This wasn’t new to Windows 3; OS/2 already allowed writing mode-agnostic programs, using the aforementioned Family API.) See also Why the DOS extender and DPMI were unavailable to DOS programs on 286 standard mode of Windows 3.0.

Significantly in the context of this question, OS/2 1.0 used a shift of 4, not 3 (it interleaved private and shared LDT entries), and OS/2 1.1 used a shift of 5 (it interleaved one private LDT entry for every three shared LDT entries). The Design of OS/2 section 6.2.4.1.2 explains this in more detail. This would have provided two reasons for allowing a non-constant selector gap in the DPMI specification: anyone familiar with 286 protected mode would have been aware that the minimum gap there is 8, not 16, so a DPMI host could conceivably want to use 8; and varying the gap had already been used, between OS/2 1.0 and 1.1.

(I haven’t checked the value returned by OS/2’s DPMI host in later versions of OS/2. OS/2 2.x and later used a flat memory model, so this might not be significant anyway.)

Stephen Kitt
  • 121,835
  • 17
  • 505
  • 462
  • If I recall correctly, Windows 2 did run in protected mode (the /386 version did at least), that’s why the __AHINCR symbol was there (an it’s also the motivation Chen gives for it). – user3840170 Aug 06 '21 at 09:46
  • Windows/386 had a protected-mode kernel, but Windows applications ran in V86 mode on top of it. – Stephen Kitt Aug 06 '21 at 09:50
  • By the way, though: if you’re including references to things like technical manuals for similar APIs of the time, I don’t think it’s pure speculation anymore. This is the kind of informed speculation I’d rather see, if any. – user3840170 Aug 06 '21 at 10:48
  • 1
    Windows/286 ran in real mode. It just added support for the HMA (so 64K extra memory) – Michael Karcher Aug 06 '21 at 21:13