0

I want to write some code for the 16 bit protected mode, specifically a simple operating system with some programs. I know this sounds silly and it probably is, but I'm interested in understanding how to write programs under these constraints.

I'd like to know what kinds of conventions have been employed in the various operating systems working in 16 bit protected mode (e.g. OS/2 and Win 3.1). What ABI did they use? How are far pointers passed around? Were there multiple ABIs for different code models?

To clarify, I know what far pointers are and how they are used on the API level. What I'd like to know is how this works on assembly level. Are segments for far pointers passed on the stack? Are there any special conventions?

fuz
  • 88,405
  • 25
  • 200
  • 352

2 Answers2

2

Most 16-bit protected mode APIs took far pointers as parameters. A far pointer is a 32-bit value containing both a 16-bit offset (low order word) and a 16-bit selector (high order word, selectors refer to segments). It's passed by value by putting it on the stack like any other parameter. Generally these pointers could only refer a region of memory up to 65536 bytes in size, but different far pointers could refer to different regions of memory allowing more than 64K of memory to be used.

For example in the 16-bit Windows API (Win16) the function GetClientRect had the following documented interface:

void GetClientRect(hwnd, lprc)

HWND hwnd;    /* handle of window */
RECT FAR* lprc;   /* address of structure for rectangle   */

The symbol FAR was a macro that expanded to far keyword when using the 16-bit Windows API. Today this API function is documented as taking an LPRECT parameter, which is meant to be read as being "long (far) pointer to RECT". The symbol LPRECT is defined as a typedef of RECT * in the 32-bit and 64-bit Windows APIs. It would be a typedef of RECT far * when using the 16-bit API if that was still supported.

There weren't separate APIs for different memory models (small, medium, compact, large), because of the use of the far keyword on pointers (and the function itself) the API was accessible from all memory models. The compiler would see that it took a far pointer and promote any near (16-bit) pointers as necessary.

Ross Ridge
  • 38,414
  • 7
  • 81
  • 112
  • How were these pointers passed? Is there any ABI documentation you can link me to? I know that people used far pointers (see my question), but I'd like to see some concrete examples so I can get a better understanding. – fuz May 06 '15 at 16:03
  • Far pointers consisted of a 16-bit offset in the lower order word of the 32-bit pointer and a 16-bit selector as the higher order word. I don't know if anyone wrote up any actual ABI documentation, the 16-bit calling conventions predate the term ABI I think, but I've given a 16-bit API example taken from my old Borland C++ 3.1 documentation. – Ross Ridge May 06 '15 at 16:25
  • I actually want to know how this works on machine code level. Because that's what I need to know if I want to write such code for my own system. I know what far pointers are and how they are used on the ABI level. Your answer doesn't answer most of the questions I have (e.g. how is this implemented on the ABI level) and does not really add much to what I already know. Thank you for your effort though. – fuz May 06 '15 at 16:30
  • @FUZxxl I can only answer the question you actually asked. As I said far pointers are 32-bit values. They're passed around, on the stack, like any other 32-bit value. If that was your question you should have just asked that. – Ross Ridge May 06 '15 at 16:44
  • So there isn't any special convention were segments are passed in segment registers or such? – fuz May 06 '15 at 17:40
  • And yes, I did answer this question specifically: “I'd like to know what kinds of conventions have been employed in the various operating systems working in 16 bit protected mode (e.g. OS/2 and Win 3.1). What ABI did they use? How are far pointers passed around? Were there multiple ABIs for different code models?” It helps reading more than just the title of a question. – fuz May 06 '15 at 17:46
  • No, if other values aren't passed in registers, why would far pointers be? (However, if 32-bit values were passed by convention in registers, then far pointers would be passed in those same registers, but I don't think that was ever the case with any protected mode OS API.) Note that since you're writing your own operating system you're free to create your own convention. If you want to pass selectors in segment registers nothing's stopping you. MS-DOS passed segment values in segment registers, but it passed almost everything in registers without following a convention. – Ross Ridge May 06 '15 at 18:02
  • If a convention exists and if that convention is useful, I would like to use it as well. That's why I am asking. So, as there seems to be no such convention, I can happily go and do whatever needs to be done. – fuz May 06 '15 at 18:21
  • There are all sorts of possible conventions, including no convention if you follow MS-DOS's example. You can invent your own or follow one of the existing ones (eg cdecl, pascal, various ones called fastcall), but as far as I know no OS passed selector values in segment registers by convention. – Ross Ridge May 06 '15 at 18:52
1

As far as I remember from old days that AT systems with 80286 architectures were introduced, the memory consisted of 64 KB segments which is the max address that can be addressed using a 16-bit address register (real mode). Near and far jumps could happen within or outside of a segment respectively when coding was done in assembly. When a far jump happens, in assembly, you should first of all give the segment that the jump will happen, then the local address within the segment.

In protected mode, a descriptor table could be used to extend the addressing range of the segments which could increase the amount of memory that could be utilized on a machine.

Multi-tasking and also Terminate and Stay Resident (TSR) programs could be implemented using appropriate BIOS and OS interrupts if the code was implemented to be reentrant.

Dan
  • 126
  • 1
  • 14
  • Sorry, your answer does not answer any of the questions I have. Can you give me some examples about the ABI used and about how far pointers are passed around? – fuz May 06 '15 at 16:32