Win16 Memory Management
Posted by supermatou 4 days ago
Comments
Comment by kalleboo 2 days ago
[0]Made an AppleTalk chat client/server https://github.com/kalleboo/GlobalTalk-Chat
[1]The equivalent to HeapWalker I used was Metroweks ZoneRanger which was bundled with their compiler. It has a nice visualization of how fragmented the memory is https://bitbang.social/@kalleboo/116302075194704555
Comment by JdeBP 2 days ago
The differences were (a) that DOS+Windows was designed so that the same programs could run in both real mode, with overlaying, and 286 protected mode, with segmented virtual memory; and (b) that to really save on RAM DOS+Windows had ideas such as the data segments for DLLs being globally shared across all processes. These added all of the complications mentioned in the headlined article and more besides. It was the operating system, not the processor architecture.
Comment by kalleboo 2 days ago
The 68k didn't come with an MMU like the 286 so MacOS couldn't rely on virtual memory like OS/2 did but at least the flat memory space meant you didn't have to juggle 64k segments
Comment by canucker2016 2 days ago
Not as much of a strait jacket as Windows segmented-memory programming, but compared to Unix, it did feel constricting.
Comment by JdeBP 2 days ago
It did. It was bi-modal. There were at one point switches to the WIN command to tell it whether to come up in real mode or 286 protected mode. In the latter it definitely did use the features of protected mode.
It was the bi-modal nature that was the problem. Essentially, they had to design a whole layer that simulated when in real mode all of the load-on-demand stuff that the processor architecture supplied for free in 286 protected mode, and make it so that the thing would all work either way with no changes to applications.
Comment by skissane 2 days ago
Windows 3.0’s WIN.COM supported:
/R for real mode (8086)
/S for standard mode (16-bit protected mode)
/E for 386 Enhanced Mode (32-bit virtual machine manager (VMM), running Windows in VM1, and DOS apps in VM2+)
Comment by pseudohadamard 1 day ago
Comment by duskwuff 1 day ago
Later in the operating system's lifecycle, applications typically used a single code segment and a custom loader to apply relocations, allowing them to use JSR within that segment.
Comment by userbinator 2 days ago
IMHO one of the best design decisions they made; the Unix dynamic linking model seems absolutely like an absurd workaround in comparison.
Also, no mention of FixDS? https://www.geary.com/fixds.html
Comment by pjmlp 1 day ago
Comment by Joker_vD 2 days ago
What? It's just like static linking! Only, you know, we do it at load time. At least the filenames of the shared objects to load are included into the executable — we could instead just load and search the whole of /usr/lib in unspecified order, you know!
Comment by jdw64 2 days ago
Comment by vjvjvjvjghv 2 days ago
Comment by icedchai 1 day ago
Comment by jchw 2 days ago
Comment by cogman10 2 days ago
I think the knowledge of underlying hardware is useful and good to know.
But also that sort of knowledge got dated pretty quickly in the early computer era. Further, the capabilities of things like optimizing compilers quickly got to a point where they'd outpace most hand written assembly. Today, it's basically just floating point operations where you can still do better than a compiler.
In the early days, you'd have the correct impression that the C compilers spat out utter garbage which was a lot slower than what you could hand craft. As optimization techniques got better and better, the work you did because the compiler was dumb ultimately would have gotten in the way.
Comment by hnthrowaway0315 2 days ago
Comment by FpUser 2 days ago
Ha. kid's stuff. I started with punching machine codes straight into memory
Comment by kev009 2 days ago
Comment by GordonS 2 days ago
I wonder if it's just that kids today (gods that makes me sound old!) are constantly surrounded by entertaining things to do - gaming, TV/films, music, social media.
Comment by jdw64 2 days ago
Comment by trumpdong 2 days ago
Comment by Braini 2 days ago
Comment by hnthrowaway0315 2 days ago
I just hope eventually he loves reading and learns in a more traditional way instead of from laptops and pads.
Comment by GordonS 2 days ago
Comment by the-smug-one 2 days ago
Comment by hnthrowaway0315 2 days ago
Some rules are obvious -- cutoff mobiles and pads completely (he doesn't have access to them so it's for me), sit in the library and study from books (I believe this is even possible for programming topics as I can write on paper). Basically, cutting off everything electronics definitely helps -- even putting my phone in the bag improves productivity significantly.
But the problem is, my son is unruly. If I put him in the library, most likely he runs around and messes things up, which ends up we leave early without doing anything.
Comment by toast0 2 days ago
Some potential ideas to explore. Take what you want, leave what you don't.
a) if you're training for attention span, make sure the target is appropriate and also within reach of your child.
b) have a plan for the visit: when I helped at a school library, classes for kids in your kid's age group would come in, the librarian would read them a story, then the kids would look for a book, check out at the desk and read (or look at the book anyway) quietly until the end of the visit. I think we'd get about 40 minutes for a visit. Most days, at least some of the kids would be getting ansy before it was time to go.
c) Plan around your kid's activity needs. Some kids will do long still antention tasks better after doing some amount of physical activity. Some kids will do these kinds of things better after a meal. Some will do it better in the morning or the afternoon. Many kids will have a harder time if the library visit was a surprise. You know your kid, try to have your library visits when they're likely to work well. If he likes story time, try to visit when there's a story time available.
d) don't expect that you can both go to the library and work independently. You're going to the library with him, and he's going to need you to help him out for much of the time. But you might be able to find him a book together, then find you a book together, then sit down and read for a bit together.
e) if all you can get done is finding a book, no big deal. You can read at home too.
If a lion can figure out how to behave in the library, so can your kid ;) https://www.michelleknudsen.com/library_lion_77788.htm
Comment by unleaded 2 days ago
Comment by bitwize 2 days ago
It's complicated and janky as all get-out, but it made more sense if you were coming from 8080/Z80 development, as this was a scheme to ensure some degree of compatibility with 16-bit 8080 addressing while providing access to much more memory. 8086 was not binary compatible with 8080, but was designed so that 8080 programs could be machine converted to 8086 ones.
In languages like C, this took the form of three different types of pointers: NEAR, FAR, and HUGE. NEAR pointers were 16-bit offsets only, and dereferenced with respect to the current segment (usually in DS). FAR pointers were full segment:offset pairs but pointer arithmetic was only done on the offset which meant objects could be 64K max. HUGE pointers allowed for objects larger than 64k but at a significant performance cost.
Comment by hnlmorg 2 days ago
Even with 32bit systems where you’d want more than 4GB RAM, application software still had 32 bit addresses (and thus 4GB memory limit).
I think it was a lot more common for 8bit systems to allow for 16 bit addressing though.
It’s been a while though. So hopefully I’m not misremembering things.
Comment by barrkel 2 days ago
Comment by senfiaj 2 days ago
Comment by trumpdong 2 days ago
Comment by hnlmorg 2 days ago
When people talked about portable before, they meant code that used an abstraction that was platform agnostic. And that’s how it’s still used today. It’s just we have better abstractions now so our expectations of what is “portable” have gotten stricter.
Eg the P in POSIX (which is nearly 40 years old now) is “portable”. The point of POSIX was to provide common abstractions that one could build against to run on multiple different operating systems. It wasn’t about porting software, it was about preventing people from needing to constantly write platform-specific ports.
Comment by trumpdong 2 days ago
Comment by skissane 2 days ago
This is somewhat outdated information. POSIX.1-2001 added "posix_openpt" to create a pseudoterminal, and most POSIX implementations now support it–at least Linux, macOS, FreeBSD, NetBSD, OpenBSD, Solaris, z/OS, AIX, HP-UX, QNX, Minix and Cygwin do. (Of course, that's only true of current versions, if you go back a decade or more you'll find many of them hadn't implemented it yet.)
Comment by hnlmorg 1 day ago
Saying something “can” be ported doesn’t make it portable. People would port games from the NES to the Master System, to 8 bit Micros but in most cases they were effectively complete rewrites because there wasn’t any common abstractions between those platforms.
Where as tools like POSIX provided abstractions to make code portable.
As I said before, the only reason you think the term has changed over the years is because the abstractions have gotten better and thus people’s expectations for how much effort should be required to port have gotten stricter. But that doesn’t mean the term means something totally new.
And by the way, I have authored portable terminal emulators and $SHELLs. ;)
Comment by andyjohnson0 2 days ago
The 6502 and Z80 could use 16 bit addressing to access up to 64kb of memory. The 6502 had various other addressing systems, including iirc 8 bits, but none of them were wider tha 16 bits.
Comment by skissane 2 days ago
Many 6502 and Z80 systems used bank switching to support more than 64KB of memory. That way you could have 128KB or more physical RAM in a machine with an only 16-bit address bus. MP/M–the multitasking/multiuser version of CP/M–had support for this as a standard feature, since it was hard to fit multiple processes/users into only 64KB; it was ported to the single-user/single-tasking version in CP/M 3.0
Essentially, this was doing virtual memory, not inside the CPU, but in one or more external chips. Actually, back in the 70s and 80s, it wasn't uncommon for a memory management unit (MMU) to be a separate chip (or even PCB) rather than an integrated part of the CPU–for many systems it was an optional add-on if you needed more memory, or wanted to run more advanced operating systems.
Comment by hnlmorg 2 days ago
Though in fairness, I do mostly now just use those systems to teach my kids BASIC
Comment by markus_zhang 2 days ago
Comment by hnlmorg 2 days ago
The computer generates a random number between 1 and 100. And you have to guess. When you’re too high, the computer says “too high” and likewise when you’re too low.
It’s a great starter program because it teaches you strings (the output printed), integers, comparisons, conditionals, and iteration (you keep guessing until you get it right). And the whole thing only take around 20 lines (give or take).
Then the kids plays a few games of that.
And after the novelty of that game wears off, I tell them to customise it however they want. Eg different messages, different ranges to guess from, etc.
It’s the same way I teach Python to primary school / kindergarten kids.
The nice things about this is even if the kids don’t learn and remember the basic primitives, they still get a feel of “proper” coding in the same way that we did when we grew up. And they still get something they can play, even if the game itself is super basic (no pun intended).
I’m not saying this will work with every child, though. All kids are different. But it’s been super successful both at home and in the schools I’ve helped out at.
Comment by markus_zhang 2 days ago
Do you have any blog, or any recommendation of books to read for such topics? Maybe I can find some "Programming for kids" book back from the 90s. I find teaching kids in general very hard, much harder than teaching myself because kids don't have the cognitive capacity as an adult.
Comment by vunderba 2 days ago
https://www.abebooks.com/Absolute-Beginners-Guide-Qbasic-Per...
Also the Usborne series are classics and a lot of them have been made freely available:
Comment by markus_zhang 1 day ago
Maybe I'll just leverage my programming experience and build something using online tutorials and a Dosbian box.
Comment by vunderba 1 day ago
Comment by hnlmorg 1 day ago
It wasn’t something I specifically planned in advance for. Which meant though whole experience was less like homework for them.
As I’m sure you know, sometimes kids just want to experience the stuff their parents do. And when they’re in that kind of mood it’s a lot easier to sit that at a computer than it would be normally.
Comment by markus_zhang 1 day ago
Comment by zabzonk 2 days ago
Comment by JdeBP 2 days ago
Comment by bluedino 2 days ago
Comment by vardump 2 days ago
Comment by cyberax 2 days ago
8-bit microprocessors used 16-bit addresses.
Comment by ack_complete 2 days ago
Similarly, while locking and unlocking memory blocks is no longer generally a concern, most programs still deal with files, and graphics programs still have to call map/unmap functions to access graphics data. All the same tools apply -- helper functions/libraries, RAII, and leak/sanitizer tools to dynamically detect usage errors.
Comment by bananaflag 2 days ago
Comment by icelusxl 2 days ago
Game consoles like NES, SNES and Game Boy had additional hardware built in the cartridge to support memory mapping/bank switching.
For PCs, EMS (memory) provided a similar concept. It reserved a 64 kB window divided in 16 kB pages in the first 1 MB and allowed to map up to 32 MB.
Comment by rvba 2 days ago
For me it is fascinating how today I can learn a foreign language, or how to code by interacting with the LLM.
Comment by Timwi 2 days ago
What all of these languages have in common is that you can write meaningful programs entirely without pointers or manual memory management. In particular, all of these languages handle strings in a natural, high-level way (treating them as a value) and don't require you to allocate and free buffers for them. Perl goes a step further with arrays and hashmaps and employs a full garbage collector.
I have vague memories of trying C for the first time and getting completely lost and bogged down by all the pointers and memory management. My reaction was the same as yours: how does anyone program in this. Why bother with this complexity when you can just use Pascal where you simply don't have to.
Of course, the Pascal compiler was likely written in C or assembly and all the memory management still had to happen even if it was hidden away from me. To some people, this might mean that I “lost” something, but to me, it meant greater freedom as I was able to explore the world of higher-level programming which I found interesting, and not have to bother with the low-level details which I found tedious and even infuriating.
Comment by andrewshadura 1 day ago
Comment by rramadass 2 days ago
Comment by canucker2016 2 days ago
It biased your selection of data structures and algorithms.
Max 64KB array size meant pointers to allocated structs and linked lists were much more popular back then versus 1 large array of structs.
The Win16 HANDLE memory allocation also meant you had to worry about how you handle structs which had pointers to others structs (a FAR ptr may not be a stable value, unless you locked the HANDLE for the duration of the allocation)
Then you had to worry about stuff that no college programming book talked about (ignore the lack of error checking):
char FAR *p;
char FAR *mem = farmalloc(65536);
for (p = &mem[65535]; p >= &mem[0]; p--) {
dostuff(p);
}
Welcome to an infinite loop...Comment by jlokier 2 days ago
char FAR *p;
char FAR *mem = farmalloc(65536);
for (p = &mem[65535]; p >= &mem[0]; p--) {
dostuff(p);
}
Nice one.To be fair to Windows, good C courses should still teach this, but I'm not sure if they do :-)
It's UB to set a pointer to before the first element of an array, or after the last element plus one. So, if it knows the call to farmalloc/malloc returns the start of an object, a modern C compiler on a modern architecture may, in principle, optimise the above to an infinite loop.
I've seen something similar on architectures (long ago) where a zero-bit-pattern pointer was a valid memory address you might actually access. Of course p-1 is not less than p when p is zero.
Comment by canucker2016 2 days ago
The above example would cause an infinite loop on Win16's seg:off far memory model, but compiling on Win32 would not cause an infinite loop.
Problem is that far pointers only affect the offset, not the segment. So decrementing a 0 value offset would just wrap around to 0xFFFF and the segment would stay the same, so you're going from mem[0] to mem[65535] not mem[-1].
Comment by jlokier 2 days ago
Although the code worked on Win32, and works on most modern C compilers, it's not guaranteed to work on modern C compilers, especially with aggressive optimisation turned on.
Comment by canucker2016 1 day ago
I'm explaining why the infinite loop actually occurs for those who haven't encountered the problem.
The problem would happen for an array whose beginning element starts at offset 0 for a particular segment and an iteration stop condition that uses ">= 0th element" that scans down the array. I used a 64K allocated array to ensure that the array base would match offset 0.
Problem would also occur if the end of the array aligns with the segment limit and the iteration end condition was "<= end element" and the scan moves up the array.
For either situation, the array could be < 64KB. One byte would be sufficient.
Comment by markus_zhang 2 days ago
In fact, I’d argue it was more fun than programming Javascript these days.
Comment by JdeBP 2 days ago
Petzold's Programming Windows book, for example, devoted an entire chapter (chapter 7) to memory management, with diagrams and examples. In the 2nd edition (which I just pulled off the shelf to check) that chapter runs to some 40 pages.
Comment by markus_zhang 2 days ago
Comment by rramadass 2 days ago
Win16 programming was an important formative phase in my career. There is a lot of wisdom in old solutions to thorny problems and knowing them often clues you to how one may adapt them to today's problem. For example, when CPU+GPU programming appeared i immediately imagined CPU memory accessed with "near" pointers and GPU memory accessed with "far" pointers with a switch to a pseudo-segment register.
It also conditioned a programmer to learn about various complexities involved and be careful in their programming i.e. it taught you discipline. You understood your compiler, OS and hardware better and how to write code keeping them all in mind. For example, i often say my study of embedded programming started with Win16!
Another bit of cleverness was "Thunking" between 16-bit and 32-bit code. Here is Raymond Chen on how it worked there and Why can’t you thunk between 32-bit and 64-bit Windows? - https://devblogs.microsoft.com/oldnewthing/20081020-00/?p=20...
Comment by boutell 2 days ago
One day I was encouraged to write a Windows Sockets emulation layer for ordinary dial-up shell accounts like those offered by netcom. The idea was to allow the use of the recently released Mosaic browser without an actual internet connection. I figured sure, no problem. I'll use curl or some other tool in the shell account to do the actual fetching of URLs, transfer styles over zmodem, and simulate all the tcp/ip calls in the DLL.
I couldn't even get started. The reason is that I couldn't understand how the different Windows applications could all share memory allocated at runtime in the winsock.dll.
I asked a highly experienced ex Microsoft person, and he just said what are you talking about. There's no API to allocate shared memory.
So I gave up. 6 months later someone else did it.
Around then I realized the truth: Windows 3.1 had no memory protection at all. Specifically all global variables in DLLs were shared by default. The hard part wasn't sharing memory among users of a DLL. If anything, the hard part was having good discipline to avoid sharing it.
Since I'd only used multiuser Unix in school, and I knew Windows supported multitasking (even if only the cooperative kind), I just couldn't wrap my head around the idea that I'm multitasking operating system could exist without memory protection.
Comment by leeter 2 days ago
All of the below is... IIRC
Win16, even in protected mode, in general didn't unless you opted out of the shared VDM. This was to preserve compatibility with how non-protected mode code worked. That said 32bit code or code that specifically marked itself as protected mode got it's own memory space.
> I just couldn't wrap my head around the idea that I'm multitasking operating system could exist without memory protection
NGL... I was shocked when I found out that MacOS before 10... really didn't have much protections at all.
Comment by pjmlp 1 day ago
Now in regards to DLLs it all depended on which memory segments were being used, and the respective code on DllMain in regards to the thread/process attachment code and related handles.
Knowing what to search for quickly gave me this article from back in the day,
https://learn.microsoft.com/en-us/archive/msdn-magazine/2000...
Comment by summa_tech 2 days ago
When I wrote a binary translator, I ended up having to keep a translated return stack to optimize RET opcodes. That put me in exactly the same position as the Win16 kernel with regard to having to patch pointers (in case of Win16, just the segment part) on stack.
Of course I did not have the benefit of my guests calling a lock function, so I ended up having to run a garbage collection operation to determine which pointers are in use & take exceptions on now-invalidated segments. Lots of extra work that Windows didn't need: it's nice to be king :-)
Comment by unleaded 2 days ago
Comment by chiph 2 days ago
This was the magic moment for me, learning Windows 3.0 programming. The idea that my program is no longer master of it's world, but instead is just something that gets loaded and called by Windows.
Comment by atan2 2 days ago
https://news.ycombinator.com/item?id=48424862
I'll just stop posting on HN.
Comment by JdeBP 2 days ago
Comment by elzbardico 2 days ago
A submission to survive most likely needs some initial push from non-organic voting.
It probably helps if you share you submission early with your colleagues and in other sites.
Comment by lloydatkinson 2 days ago
Comment by atan2 2 days ago
Comment by pjmlp 2 days ago
Comment by hnthrowaway0315 2 days ago
Comment by zabzonk 2 days ago
Comment by Aegis_Labs 2 days ago