11 ms·
The simple, everything is a file, model of Plan9 is what makes the namespaces API as clear and as general as it is. All the objects export the same file API. Ev
by afr0ck 3y ago
The simple, everything is a file, model of Plan9 is what makes the namespaces API as clear and as general as it is. All the objects export the same file API. Every interaction with the OS objects is done through file open, create, read, write, etc. But it has it's drawbacks.
First, it's not always easy to map every object operation into either an open read or write. With time, we should have seen a lot of ugly interfaces resulting from this limitation.
Second, hardware progress, the web, etc., introduced a lot of heterogeneity and complexity. People could no more keep up with simple general designs. And to squeeze every bit of performance, everyone was doing things different based on the hardware and the workloads. They use whatever makes their software, drivers, and OS objects work as fast as they could.
And this is how we ended with the extreme fragmentation and heterogeneity we have in Linux, which explains the complex and less general implementation of its namespaces and its other features.
Edit: fix typos
- zokier 3y ago> First, it's not always easy to map every object operation into either an open read or write. Heck, even for basic files on disk, and even more so for sockets, the traditional open/read/write/close is starting to feel not so great. There is reason why iouring is hailed as the second coming, and it solves just part of the problems; stuff like fsync apocalypse comes to mind. And ioctls are imho completely disgusting hack. Ultimately IO is intrinsically complex topic, and trying to paper over that complexity with simple interfaces is disingenuous and falls flat on edge cases.
- xiphias2 3y agoio_uring is a more modern API to file access. Actually I think it would be great if everything was a file and the communication with the kernel was only with io_uring.
- rcxdude 3y agoit's a more modern API to system calls in general, though most of the focus is on IO.
- codedokode 3y ago> it would be great if everything was a file This is a bad design. Process is not a file because you cannot send signals to a file, or cannot debug a file. Network socket is not a file because you cannot get file's peer address. Shared memory is not a file. And so on.
- deleted 3y ago[deleted]
- c00lio 3y agoWhy would (pseudocode, nonexisting but possible example) write(SIGKILL, "/proc/12345/signals") not be possible? For the other direction, there is signalfd in Linux. And of course you can get a peer address from /dev/tcp (https://andreafortuna.org/2021/03/06/some-useful-tips-about-dev-tcp/ https://andreafortuna.org/2021/03/06/some-useful-tips-about-... ). Yes, /dev/tcp is not an OS primitive but a bash builtin, but there isn't really a reason you cannot do this in the OS. Shared memory can be a file, you just have to mmap() it. mmap is always left out of the open/close/read/write-enumeration of the traditional file API, but I think it is actually extremely useful and should be the fifth alongside those. Debugging is hard to imagine, yes. One could look at /proc/12345/mem, write breakpoints in there, but I'm not sure about how to do the more exotic things.
- samus 3y agoThis means layering additional protocols on top of the file APIs. Of course you can do that, but eventually there will be a similar explosion such as on top of `ioctl`, and it's doubtful whether the resulting interfaces will be any easier to use than the existing ones.
- c00lio 3y agoOn the other hand, layering tons of stuff on unsuitable interfaces has been all the rage in the last 25 years. Just think of everything-over-http, stuff-it-in-xml/json, program-it-in-yaml and similar industry trends. ;)
- kps 3y ago> io_uring is a more modern API to file access. With a remarkable similarity to certain mainframe systems from half a century ago.
- mananaysiempre 3y ago> Ultimately IO is [an] intrinsically complex topic, and trying to paper over that complexity with simple interfaces is disingenuous and falls flat on edge cases. I’m not against pointing out the edge cases in the Plan 9 file model[1,2], but the thing is, I haven’t seen complex I/O interfaces that aren’t a horror show, either. Granted, I haven’t seen that many of those at all, but I’ve had a thorough look at the ones in OS/2, Win32, and NT, and none of them seem particularly inspiring. I’d very much like to see some nice alternatives, to be clear! The reference to io_uring also doesn’t seem all that strong of an argument, honestly. I’d like to say there are three layers to the idea of “traditional” “Unix” “files” as an OS (not storage) interface: - System and user resources you have access to are identified by unforgeable references (called “fds”; the merits of allowing userspace to control their naming as opposed to having the kernel assign the names are debatable). You can feed bytes into these, (ask to) get bytes out of them, and perhaps have a out-of-band call to e.g. transmit one of the other references you hold to a peer. You can of course also delete a reference. So far this is just dynamically typed object-capabilities by another name. It’s going to require higher-level protocols on top, but so does basically everything else on this level of generality. Plan 9 mostly (if not completely) eliminated ioctls here by using separate control files instead. (The part where the OS merges the payloads of write calls into a byte stream then cuts it back up into reads is more opinionated, but I don’t think even Bell Labs systems ever adhered to that principle strictly[1].) - You obtain (most of) these references by navigating a stringy hierarchical namespace. There are additional calls to do so and to modify that namespace. This is less of an obviously correct least common denominator, and as history shows the consensus is less strong here as well. Mountpoints, symlinks, namespaces per TFA, even the *at() calls all change how this part functions. On the other hand, I don’t think anybody longs for version numbers or nesting limits (or even drive letters) of other systems that have used naming approaches similar enough for a comparison. - You access these services through synchronous system calls write(), read(), ioctl(), close(), open(), etc. This is the part that is changed by the introduction by io_uring... But I don’t feel it’s all that important for the conceptual model, unlike the preceding points. [1] Cutting a bytestream into packets is as always a tedious slog of buffering so some of the protocol implementations use write() / read() boundaries thus actually (depend on being able to) use the API in a datagram-like fashion (which 9P enables IIUC). [2] Auth is based on a /proc/self-like hack wherein the kernel-side implementation of the “file” inspects the opening process through kernel-side knowledge you can’t access nor proxy from userspace.
- akira2501 3y ago> First, it's not always easy to map every object operation into either an open read or write. It doesn't seem like it. Linux has a habit of multiplexing alternate functions through a single handle with additional and somewhat scary methods like ioctl. Plan9 manages this with servers, directories, and more than one path available for a single resource depending on what you're trying to access. This is far more sane. > With time, we should have seen a lot of ugly interfaces resulting from this limitation. They don't seem any more complicated than they need to be. Compare implementing a fuse server vs implementing a plan9 server. Yet I don't see where all of this complication adds anything or enables implementation of technologies that couldn't be implemented on plan9 with a few additional paths. > People could no more keep up with simple general designs. And to squeeze every bit of performance, These are contrary goals, and I'm not sure what you mean people can't "keep up" with "general designs." What is there to "keep up" with? And in exchange for that performance we got one of the most insane /class/ of unfixable CPU bugs ever imagined. > And this is how we ended with the extreme fragmentation and heterogeneity we have in Linux, And yet.. many of these systems are now being unified into generalized file descriptor based systems, that have wacky open methods, but boil down to allowing simpler interfaces through read(2) and write(2).
- jabl 3y agoFor an example, most high performance devices expose functionality as memory locations mapped into the CPU address space. In many cases it's necessary to allow userspace direct access to (part of) the memory the device exposes, such as with GPU's, RDMA NIC's and so forth. Not sure you could get high performance with a read/write stream based interface, as conceptually elegant such an interface is.
- creshal 3y agoDevices have been doing that since forever, so Plan9 has an mmap equivalent with segment/segattach, where you use file I/O (only) to define a memory mapping and attach it to the current process. Everything from that point on is regular memory I/O.
- SubjectToChange 3y ago
- mpweiher 3y agoStorage Combinators manage to unify a lot of this with a somewhat nicer, REST-based and object-oriented interface (they came out of something I called in-Process REST). Byte-oriented interface are much easier to fit on top of that than the other way around. Almost more importantly, they don't claim to be universal. Instead, you can still send messages where that makes sense. The approaches are much simpler and powerful when combined than when each tries to be everything. Yes, you are allowed to say "synergies". With the corresponding object streams (that can be specialised to byte-streams as well), we have something I like to call Plan A from Userspace. https://2019.splashcon.org/details/splash-2019-Onward-papers/7/Storage-Combinators https://2019.splashcon.org/details/splash-2019-Onward-papers... https://objective.st https://objective.st
- arghwhat 3y agoIn plan9 this is dealt with through a similar workaround as we use in Unix: ctl files with a per-file protocol. Linux uses a mix of this (sys and proc files) and ioctls (which are also per-file protocols) for many things. There is no real practical difference in capability between this and dedicated syscalls. For Linux, the distinction is usually just whether the functionality is "global" or isolated to a certain area like devices or drivers.
- jerf 3y agoI wrote a while ago about how Go is more UNIX than UNIX, which in context really meant is more Plan 9 than Plan 9: https://www.jerf.org/iri/post/2931/ https://www.jerf.org/iri/post/2931/ The idea there is that the particular way interfaces work in Go is possibly the way that Plan 9 should have worked. In reality, trying to fit everything into a file is still non-functional, because not everything is a file. But if you instead have a hierarchy of interfaces, starting at the very bottom with "this is a stream", working up to "this is a stream you can close", and so on and so forth up through "this is a seekable, sparse, appendable chunk of bytes that can have ACLs set and has the following ioctls", you can get what you're looking for out of common interfaces, while at the same time not having to run all around the system putting "do nothing" methods on things just to conform to interfaces. ("Do nothing" methods are a valid tool for a bit of fitting into an interface, and actually quite important, but only when the methods are themselves something that can be fulfilled by a do-nothing implementation. "Set this ACL" shouldn't be satisfied by a do-nothing method.) For many things even a file is overkill; what you care about is that you can stream bytes in or out once you have it, not whether you can change the ownership of the thing you are working with. And with ioctls you see cases where files aren't anywhere near good enough. (Note this is not advocacy for Go as a language you might want to program in; it's more a suggestion for a Plan 10 by drawing on Go as a particular combination of features. It would take non-trivial work to figure out how to turn this into an OS feature, but it's not inconceivable amounts of work IMHO.)
- hdidhdish 3y agoso golang is a poor man's erlang?
- jerf 3y agoAre you asking for six pages of text? I'm probably the worst person in the world to say that to. That said, I have no idea how you get "poor man's Erlang" out of this specific post. As a practical matter, Erlang is a standard dynamically-typed language in this matter; as a theoretical matter it has some limited support for protocols as used by things like gen_server but I don't think I ever saw a single use outside of the standard library, and I'm about 90% sure there's no implicit satisfaction of them; you must declare what you are implementing. It is irrelevant to my point as Python or Perl. We already know what this sort of dynamically-typed interface looks like in those systems, namely, "a lot less nice in practice than in theory but still useful enough most of the time". Nice for writing scripts, sufficient for reasonably-sized programs, not a sufficient foundation for an OS with Plan 9's level of aspirations.
- SkeuomorphicBee 3y ago> First, it's not always easy to map every object operation into either an open read or write. Now a days people are mapping everything to JSON, and it works. The Plan9 directory structure was basically the JSON of the 80s, a hierarchical data structure were the hierarchy is easily navigable in a standard way, and the leafs can then hold any non-standard stuff you may need.
- taeric 3y agoMapping to JSON, "works." And usually not in an interop kind of way.
- compscigirl 3y agoIsn’t everything in Unix/Linux already a file? Isn’t that the power of Unix? There’s even a Wikipedia article about it: https://en.m.wikipedia.org/wiki/Everything_is_a_file#:~:text=Everything%20is%20a%20file%20is,through%20the%20filesystem%20name%20space https://en.m.wikipedia.org/wiki/Everything_is_a_file#:~:text....
- crabbone 3y agoWait until you realize you need asynchronous I/O operations, and then you also need to manage caching on I/O operations, and you also need to distinguish appends from over-writes, and that you also need to care about who allocates memory for the buffer being written to / read from... And then comes concurrency and exceptions... UNIX "design" anticipated none of the above. And it's not like these things were somehow unknown at the time. The "designers" thought they are making something remarkable by cutting corners and making a "simple" (but really a half-baked) OS. It's such a shame that UNIX became the vector for the spread of the Internet and eventually infected virtually every computer system on Earth. It's even a greater disappointment that its "design" decisions are still revered as the holy Bible in academia and in the industry.
- miga 3y agoIt was not Unix that become platform for Internet, but Posix. Linux is not derived from Unix, but supports Unix (Posix) APIs. As does modern MS Windows, and Mac OS X. This also means that Google could experiment with OSes like Haiku or Fuchsia that support Posix, but are built in a different way.
- bwanab 3y agoWhat's an existing example of a non-'half-backed' OS? I personally liked VMS for concurrency, but for dealing with code and coding there was no contest that Unix was far better. I think the reality is they're all half-baked since none can satisfy every need.
- crabbone 3y agoI used "half-baked" in the context of UNIX history, where the legend goes that UNIX was the stop-gap solution for AT&T failing to come up with a better designed system on schedule. Unfortunately, today, UNIX is by and large all we have. Other things are either some kind of UNIX but with a twist, or very underdeveloped. Looking into the future, I'm very enthusiastic about OS-as-a-library approach, but I don't think we have a solid contender there yet.
- 3y ago
- numbsafari 3y ago> it's not always easy to map every object operation into either an open read or write A lot of what defines REST works well here. The "Uniform Interface" creates a simple API that can scale to a surprising amount of functionality. Not saying it doesn't have it's downsides, but you can accomplish a lot with minimal increase in the surface area of that "uniform interface".
- AceJohnny2 3y ago> And this is how we ended with the extreme fragmentation and heterogeneity we have in Linux Tangentially, I've long wondered why the Linux "API" is such a mess, in particular why are there multiple tracing frameworks? Multiple security frameworks? Which to choose?? It turns out, this is a consequence of Linux's stand-alone development, along with their (good) "never break userspace" mantra. The two together mean they can never deprecate an API. Contrast this with the *BSD's development, where the kernel is developed alongside the libc and (a core set of) user-space applications. This allows them to evolve and deprecate their APIs, because they can update the clients. (From a runtime perspective, all is not lost in Linux, as I suppose they can move an API to a module or allow "users" (distros etc) to disable it at compile-time.)