6 ms·
These branches of computing history are really interesting. > Domain/OS uses a single-level storage mechanism, whereby a program gains access to an object by m
by gelstudios 6y ago
These branches of computing history are really interesting.
> Domain/OS uses a single-level storage mechanism, whereby a program gains access to an object by mapping object pages directly into the process's address space.
It sounds similar in that respect to IBM i, and seems like an evolutionary branch that died off. What ever happened to this paradigm?
- wmf 6y agoThere was also KeyKOS and EROS. One problem with such systems is that data corruption can live forever if you're not careful (of course the same can happen with mmap which is our watered-down version of single-level storage).
- skissane 6y ago> It sounds similar in that respect to IBM i, and seems like an evolutionary branch that died off Even on IBM i it is in decline. Originally everything ran in the single-level store address space, but then they introduced additional non-single-level address spaces (teraspaces). And one of the major things that teraspaces are used for, is to run PASE, which is IBM i's AIX binary compatibility subsystem. And IBM appears to have a preference to ship new stuff in PASE. The single-level store environment is still used by "classic" apps (such as RPG and COBOL), but newer stuff – especially anything written in newer languages such as Java, Python, etc – runs outside of the single-level store in a PASE teraspace.
- anyfoo 6y agoBut is that just to accommodate the newer, more mainstream stuff, or because it's actually technically better?
- skissane 6y agoI think it is mainly about making it easier to port code from more mainstream platforms (AIX/Unix/Linux), which reduces engineering costs. Porting open source code is a low cost way to get new functionality and features, and makes the environment seem more familiar and modern to newcomers who are familiar with Linux – and commercial Unixes such as AIX are pretty close to Linux. When detractors call it a "legacy" platform, their sales team can now respond "it's not legacy, it runs node.js!" But one thing I think it demonstrates is a problem with non-mainstream operating system architectures. Even if a non-mainstream operating system architecture is technically superior, sooner or later you want to port software to it from a mainstream operating system, which means you need a compatibility layer implementing a more mainstream operating system architecture. And before you know it most of the code is running in the compatibility layer, because that's where all the new applications are coming from and there is no way you can keep up with that pace yourself. And then you have to ask what is the point of the innovative non-mainstream architecture if so much of the software you run doesn't actually use it. So eventually it leads you to moving off the non-mainstream architecture and on to a more mainstream one. Is IBM i technically superior? It is a weird mixture of (a) advanced concepts like single-level store, an object-oriented operating system and bytecode virtual machine (b) legacy crud like EBCDIC, RPG, block mode terminals, 10 character limit on object names and a single-level filesystem (c) a severe lack of extensibility and openness in which a lot of OS concepts (e.g object types) are closed up so only IBM engineering can extend them (or possibly ISVs who pay big $$$$ for NDA manuals) (d) the completely different worlds of POSIX/AIX/Java grafted on the side, and increasingly taking over the rest. I grant that (a) could be said to be technically superior, but (b) and (c) clearly are not.
- anyfoo 6y agoBut that's entirely my point, yeah. I don't know if a single-level store address space is better, but if the reason for its decline on IBM i is merely that mainstream software doesn't mesh well with it, I feel like it doesn't tell me much about the paradigm itself. By the way, I'd argue about whether all of b) is technically inferior or not. Object name limits certainly are, but I got to really know data entry with block mode terminals long, long after its hey day (I've certainly come across it back then, but I was rarely a user). I feel that it can be enormously efficient for data entry and maintenance tasks. Many a person who had to move from intensive use of a block mode data entry terminal to performing the same tasks with a web app got quite annoyed at the clumsiness of it all. The web was not created for "business apps" but for hypertext document retrieval, the other uses got bolted on and it still very much shows. It's sad, because proper terminal emulation well used to be a ubiquitous feature of the Internet, before browsers took over almost entirely.
- skissane 6y ago> but I got to really know data entry with block mode terminals long, long after its hey day (I've certainly come across it back then, but I was rarely a user) I don't think block mode terminals are necessarily inferior. I see some big problems with 5250 though. The biggest is EBCDIC. Another big problem is character-at-a-time interfaces let you build things like text editors (vim and emacs), spreadsheets (like Lotus 123), etc. Sure you can build a text editor for a block mode terminal (SEU on IBM i, XEDIT on z/VM, ISPF EDIT on z/OS) but there are just certain features and interaction styles that vim and emacs support that block mode terminals can't do as nicely (example: interactive search). Lotus 123 was actually ported to 3270 (to run under MVS and VM/CMS), I've never used it (I would love it if someone could find a copy so I could!) but from what I've heard it was pretty clunky compared to the MS-DOS / PC version. Sometimes I think that block mode terminals could have exposed some kind of byte code to enable running some interactivity in the client. Actually real 3270s and 5250s generally had some kind of CPU in them (like an 8080) so I can't see why they couldn't have done that. And of course terminal emulators could do that. Then you could have these more flexible interaction styles that character mode terminals support even in a block mode terminal.
- kragen 6y agoWhy did single-level stores die off? It's an interesting question, and I'm not sure I know the answer. That's also how Multics worked, but I think what happened was it turned out that Unix was better. It's not wholly coincidental, or intentional, that Unix didn't have mmap. The PDP-11 and the PDP-7 didn't have paging hardware, so early Unix couldn't implement mmap at all. And it was common to access files bigger than the virtual address space, and doing that with mmap requires you to sequentially map, then unmap, different parts of the file—basically what you have to do with read() and write(). So, early Unix couldn't implement mmap because it was designed to run on cheap hardware. Also, though, if a program is reading from a file by memory-mapping it, you can't replace the file with a pipe unless you change the program. (If you lseek() on a pipe, it croaks with an ESPIPE, now called "Illegal seek".) Unix got enormously better composability and scriptability than other contemporary OSes by virtue of pipes, to the point where the Unix group ported their pipe-based toolkit of "software tools" to other operating systems in the mid-1970s in order to have a more comfortable working environment. Then, of course, the world started to revolve around TCP, which gives you a byte-stream between two machines, like a magtape, not a random-access collection of pages like a disk. (There are lots of networked applications that really prefer a remote-disk model; Acrobat Reader and Microsoft Access come to mind. But that wasn't where TCP/IP was in the 01980s and early 01990s, Sun's WebNFS aside.) Another problem is that, when a program is mutating a shared mutable resource like a disk sector, there are times when the resource is in an inconsistent state. Usually, we think of this as a problem for concurrent access, and the solution is to keep any other thread from observing the inconsistent state, for example with a mutex. But it's also a problem for failure recovery: if your program crashes before restoring consistency, now you have data corruption to recover from. In Unix, the mutable shared resource was usually the filesystem, so this was mostly only a problem if the kernel crashed, perhaps due to a power failure; ordinary user programs mostly created new files, so if they crashed during execution, the worst that could happen was that their output file would be incomplete. Then the user could delete it and try again. So, even though Unix wasn't a fault-tolerant OS like Tandem's Guardian, it did tend to limit the impact of faults. (The occasional exceptions to this rule, such as Berkeley mbox files, were a continuous source of new bugs.) The easiest way to handle this kind of problem is with atomic transactions, so that if a program crashes halfway through an update, the old state remains the current state, and there is no data corruption problem to worry about. As I understand the situation, this is how IMS and DB2 have handled this problem since the 01960s and 01970s, respectively, and of course today we build lots of applications on top of transaction systems like Postgres, Kafka, ZODB, Git, MariaDB, and especially SQLite. But none of those systems existed in the 01980s, except for IMS, DB2, and Postgres, and none of those ran on Domain/OS. I don't have any experience with Domain/OS but I imagine that this was a source of bugs for Domain/OS applications as well. There's another, arguably distinct, fault-related problem that pops up in current use with mmap(). If you try to read() from a file, copying data into your address space, this may succeed or fail, or it may succeed partially, for example if you hit the end of the file. All of these conditions arise at the readily identifiable point in your program where it invokes read(), and so you can look at the code to see if you forgot to handle one of them at that point. Moreover, you can be sure that neither of those two problems will arise later while you're using the data you've read, possibly while you have some other shared mutable resource in a temporarily inconsistent state. By contrast, with mmap(), such a failure can arise any time you access the mapped memory, in most cases. For example, someone else may have truncated the file since you mapped it, as in http://canonical.org/~kragen/sw/dev3/mmapcrash.c http://canonical.org/~kragen/sw/dev3/mmapcrash.c, where as soon as the array index strays onto the now-nonexistent page, the program dies with a bus error. This makes it more difficult to write programs that handle failures correctly. Relatedly, there's a performance issue: although memory-mapping a page and then reading it means the kernel doesn't have to copy its contents into your address space, which often increases performance, it does still have to read the page from disk. But it has much less information about your access patterns than when you're using read() and lseek(). This sometimes reduces performance, because prefetching pages before userland requests them makes a big performance difference—in the 01980s, we're talking about 30000 microseconds to wait for the disk, versus 1 microsecond to handle a page fault or 2 microseconds to handle a small read(), if the data is prefetched. It doesn't take a whole lot of extra prefetch failures to make mmap() slower, potentially by orders of magnitude. With modern NVDIMMs and NVMe Flash, and especially new memory architectures like 3D XPoint, the performance advantages of memory-mapping might become much more important again. If it takes 300 ns to call and return from read() or write(), plus 700 ns to copy 4096 bytes into or out of userspace, then spending 100 ns to read a random cache line from 3D XPoint memory (is that about how long it takes?) might be greatly preferable to spending 1000 ns to read a page of data from it through the syscall interface. But this was not a possibility in the Apollo years. One final minor issue with the Multics segment-mapping approach, at least when realized with paging hardware instead of segmentation hardware, is slack space at the ends of files. If the fundamental fixed-size units of a file consists of are not a multiple of the page size, such as a byte of text, then there will be times when the file's natural size is not a whole number of pages. So, for example, in CP/M files consist of 128-byte "sectors", thus saving 7 precious bits per directory entry. Your application program needs some kind of application-specific logic to tell whether the last page of the file has unused space in it, and, if so, how much. So in CP/M, for example, some applications would place a ^Z after the last legitimate byte of a text file, and others would fill the rest of the sector with up to 127 ^Z characters. As you can imagine, this kind of thing is fertile ground not only for application bugs (you can't reliably store ^Z in a text file, and never as the last byte) but also subtle application incompatibilities. If you want to write a Unix "cat" program for CP/M, it needs to have an opinion about which of these conventions to use, and also what to do if it finds a ^Z that isn't in the last sector. Again, I never used Domain/OS, so I don't know how it handled text files or other files that commonly had a non-page-aligned EOF. The Apollo engineers were brilliant and produced a stunning system that was much better than Unix in many ways. So maybe they had a good solution to this problem, like a universally-used text-file-handling library that didn't use a brain-dead encoding like the CP/M one. I'm just saying it's a problem that crops up in userspace with the single-level store approach (on paging hardware), while the Unix approach relegates it to the filesystem driver.
- gumby 6y agoThis was how Multics was designed too. It was one of the important features left out when Unix was written, because it was very hard to do on a PDP-7.