147 karma · joined July 15, 2024
The Darwin 0.1 and 0.3 releases contain the old kernel, derived directly from NeXTSTEP, and that's the direct derivative of Mach 2.5. The later XNU appears to be a reconstruction of that kernel with unencumbered code and that's also when IOKit replaced DriverKit.
It is moving the higher-level things into userland that is the harder problem, and the one that has been challenging for microkernels to do well.
So you read a tutorial and it spends about 90% of its time talking about an area that really isn't that interesting compared to other areas. I just don't think that's very compelling at all.
I say this having my own hobby OS which I've ported to four architectures (m68k, amd64, aarch64, riscv). At the moment I'm working on a TCP/IP stack which has been really enjoyable and I've learnt a great deal in doing this already. Other fun areas have been virtual memory and page replacement, designing asychronous I/O facilities, and IPC mechanisms, as well as appropriate synchronisation for each (synchronisation alone is a very deep topic, and there's considerable scope for innovation with techniques like safe memory reclamation.) Others may differ, but in these I find a lot more of interest than x86 minutiae.
Isn't it? There is a very well-developed kernel written in ADA with SPARK and formally verified at that: https://ironclad.nongnu.org
And PASCAL-derived languages were very popular for operating systems in the 80s. To name a few: Apple's LISA OS, DEC's VAXELN, and OBERON. There were others as well that didn't quite make it, like DEC's MICA and Acorn's ARX.
I think the article fell off here. Linux has a profoundly synchronous I/O system. The I/O U-Ring works there by dispatching requests to a kernel-maintained pool of worker threads. There the submitted request are run synchronously.
So the I/O U-Ring is in fact the abstraction, "a feeble attempt to impose your [new] mental model onto an [old] reality", as the author might put it. The actual Linux I/O system is most trivially exposed to userland by the same old "1970s Unix" means of the traditional system calls.
The real change with the I/O U-Ring is that it offers a way to submit work in bulk and finally introduces to Linux a form of I/O that appears to the program to be truly asynchronous.
(And it's not without some fair cause that file I/O is implemented as synchronous code. It's hard to do it asynchronously when it's a complex operation with many steps, where you are interacting with a page cache, not only for file contents but even caching the very metadata that describes how to get from an offset into a file to the block numbers where the data is stored. Windows has a famously profoundly asynchronous I/O system and even there actual file I/O is done just the same with synchronous logic ran in a worker thread, or copied directly from the cache if the requested data is already there.)
1. Signalfd is a mountain of technical debt. It's not like a file at all. It reads entirely different things depending on which process is reading from a single common open file description, and it interacts with epoll in a most bizarre way, in that the process that added the signalfd to the poll set is the one whose signals will notify readiness - even if that process then closed the epoll and signalfd.
2. Signalfd is a mountain of technical debt because they built it over signals. Signals are of two forms: the one is asynchronous, the other synchronous. The one should be replaced with a generic message queue in a technical debt free design. The other is debatable. (Mach and also Fuchsia have an "Exception Port" instead, a message port someone can read from to deal with exceptions incurred by some thread.)
3. Regarding: > epoll can automatically monitor any kind of kernel resource without having to be modified, due to its composable design
Well, so can kqueue - EVFILT_READ/EVFILT_WRITE will work just as well on any file, be it a socket, an eventfd, or even another kqueue (though I don't think anyone who isn't dealing with technical debt should be adding kqueues to kqueues, nor epolls to epolls.) No need to modify kqueue! But in either case, though, you've got to modify something. Whether that's trying to fit signals into a file and getting signalfd, or whether it's adding a signal event filter and getting EVFILT_SIGNAL, something has to be done.
4. FreeBSD's process descriptors predate Linux's pidfds. They are also better and less technically indebted because they are pure capabilities (they were invented as part of the Capsicum object-capability model based security architecture for FreeBSD) while pidfds are not: what you can do with a pidfd is decided on based on the ambient authority of the process trying to, say, signal a pidfd, and not on rights intrinsic to the fact of having a pidfd. In fact, these pidfds are not even like traditional Unix open file descriptions, whose rights are based on the credential of who opened them. This makes privilege-separation architectures impossible with pidfds, but I digress.
5. The author ignored the actual points people argued against epoll with, viz. that 1) epoll's edge triggering behaviour is useless, and 2) that epoll's conflation of file descriptor with open file description is a terribly leaky abstraction:
https://idea.popcount.org/2017-02-20-epoll-is-fundamentally-... https://idea.popcount.org/2017-03-20-epoll-is-fundamentally-...
I write a hobby kernel with a profoundly asynchronous I/O system. Since it's nice to have synchronous I/O in some places (reading in data in response to a page fault, for example, or providing the POSIX read/write functions), I have to turn it into synchronous I/O in some cases.
Asynchronous I/O notifies its completion in a configurable-per-I/O-packet (called "IOP") way. The default is to signal an event object. You can wait on an event object and when it's signalled, you're woken up. The result is that synchronously sending an IOP is hardly any more work than:
iop_send(iop);
wait_for_event(iop->completion_event);
return iop->result;
I would be surprised to hear of any system where you can't do similar. Surely any environment has at least semaphores available, and you can just have the async I/O completion signal that semaphore when it's done?Several kernels for example use type-stable memory, memory that is guaranteed to only hold objects of a particular type, though perhaps only providing that guarantee for as long as you hold an RCU read-lock (this is the case in Linux with SLAB_TYPESAFE_BY_RCU). It is possible in some cases to be able to safely deal with references to objects where the "lifetime" of the referent has ended, but where by dint of it being guaranteed to be the same type of object, you can still do what you want to do.
This comes in handy when you have a problem that commonly appears in kernels where you need to invert a typical lock ordering (a classic case is that the page fault codepath might want to lock, say, VM object then page queue, but the page-replacement codepath will want to lock page-queue then VM object.)
Unfortunately it's hard to think of how the preconditions for these tricks could be formally expressed.
I will wait for x86's doom, but it will take patience. After all, people have been saying it's dying and will be replaced by some RISC alternative for over 30 years now.
TechSpot is referring (without attribution) to an interview the MIT Technology Review did with Hoare, and tacked on a claim that Rust was invented "to prevent issues such as his elevator breaking down" that isn't there in the source material. Elevator firmware is not systems software in any case, and the Rust manual Hoare authored states:
> Rust's goals arise from the context of writing large programs that interact with the internet -- both servers and clients -- and are thus much more concerned with safety and concurrency than older generations of program.
https://github.com/graydon/rust-prehistory/blob/master/doc/r...
The language described there relies on a much larger runtime than Rust today does. It is no systems language. Later it even grew things like the @ garbage-collected pointers.
Those would eventually go away as it turned out that people writing apps don't want to think about memory at all (and are better served by using a real apps language) and Rust instead started embracing its potential as a systems language:
> Although Rust didn't start out that way, one of the most interesting applications of Rust has been very low-level programming, even down to the level of kernels. The interest in this application of Rust was something of a surprise to us, but in hindsight it makes perfect sense. Low-level control over memory management isn't something that most applications software, especially on the server side, wants; most of that software has migrated over to languages like Java, Ruby, and JavaScript that trade control and performance for convenience by making memory management automatically, and dynamically, managed by the runtime. The remaining class of software, most of which is written in C and C++, is software that must manage memory manually in order to achieve some combination of performance, simplicity, and/or the ability to self-host. The prospect of using a new language for this class of software, which includes OS kernels, game engines, and browser engines among others, is what is fueling the growth of the nascent Rust community.
https://pcwalton.github.io/_posts/2013-06-02-removing-garbag...
For a lot of systems software, and I am thinking of system service managers ("init systems") in particular, failing and restarting is not viable. OOM conditions must be recoverable. Rust has recognised this and is trying to correct the course they went down in its early days of ignoring this, being unimportant for an apps language, but vital for a systems language.
One feature they have taken to is throwing panics on OOM conditions:
https://github.com/rust-lang/rust/issues/43596
These "panics" can unwind the stack and be caught with panic::catch_unwind, but with the proviso of only "sometimes". If you take that at face value, then it's not a very useful feature. The implication in the issue linked above however is that this could become a viable way to recover from OOM conditions. And the mechanism proposed there is indistinguishable from exceptions other than that it's insisted that it has nothing to do with exceptions and that you can't rely on it catching panics for you.
What exactly is the difference between a panic and an exception? According to everything I've been able to read about Rust's panic mechanism, it's just that "panics shouldn't be used for normal errors", which is just a prescription on the normative use of the construct. A pretended difference. Can anyone clarify whether there actually is a real difference? Or when "Panic" is said here, does the author mean something that cannot be meaningfully recovered from? That would be a change most inappropriate!
One of the most prominent birthmarks that Rust was stamped with when it was born as an apps (not systems) language was its standard library being designed without regard to the possibility of reacting to out-of-memory conditions by anything other than aborting. Now that it's trying to pivot from solely an apps language to a systems language for some time, there seems to be work underway to correct that deficiency.
C++ for its part is not burdened with that mistake. There is remarkable attention paid in the STL to the possibility of an out-of-memory condition and allowing a complete recovery from the same. This is a useful feature that systems software often requires.
I don't know why you think none were mentioned - to name one, they link a GitHub issue created against the systemd repository by a Googler complaining that systemd is inappropriately using Google's NTP servers, which at the time were not a public service, and kindly asking for systemd to stop using them.
This request was refused and the issue was closed and locked.
Behaviour like this from the systemd maintainers can only appear bizarre, childish, and unreasonable to any unprejudiced observer, putting their character and integrity into question and casting doubt on whether they should be trusted with the maintenance of software so integral to at least a reasonably large minority of modern Linux systems.
They didn't, but I don't know why you're putting a sinister spin on this either way. Of course the licence allows for that. It was the express intention of the authors of the BSD networking stack that it be used far and wide.
They went to considerable effort to extract the networking code into an independent distribution (BSD Net/1) which could be released under the BSD licence independently of the rest of the code (parts of which were encumbered at the time). They wanted it to be used.
It's not clear to me how processor groups inhibit scaling. It's even sensible to constrict the movement of threads willy-nilly between cores in a lot of cases (because of NUMA locality, caches, etc.) And it looks like there's an option to not confine your program to a single processor group, too.