Comment by mattgreenrocks
4 years ago
> Long ago, I, like many Unix fans, thought that fork(2) and the fork-exec process spawning model were the greatest thing, and the Windows sucked for only having exec() and _spawn(), the last being a Windows-ism.
I appreciate this quite a bit. Vocal Unix proponents tend to believe that anything Unix does is automatically better than Windows, sometimes without even knowing what the Windows analogue is. Programming in both is necessary to have an informed opinion on this subject.
The one thing I miss most on Unix: the unified model of HANDLEs that enables you to WaitOnMultipleObjects() with almost any system primitive you could want, such as an event with a socket (blocking I/O + a shutdown notification) in one call. On Unix, a flavor of select() tends to be the base primitive for waiting on things to happen, which means you end up writing adapter code for file descriptors to other resources, or need something like eventfd.
Things I don't miss from Windows at all: wchar_t everywhere. :)
WIN32 got a few things very right:
Many other things, Windows got wrong. But the above are far superior to what Unix has to offer.
How are SIDs the right thing?
Superficial silliness like allocating 48 bits to encode integers in [0,18] aside, what problem do structured SIDs actually solve? I’ve been trying to figure that out for the last couple of days and I still don’t get it, possibly because the Windows documentation doesn’t seem to actually say it anywhere.
I completely agree with having UUIDs or something in that vein for user and group IDs and will not dismiss IDs for sessions and such in the same namespace (although haven’t actually seen a use case for those), but structured variable-length SIDs as NT defines them just don’t make sense to me.
While it's true that SIDs have too much structure, that's a lot better than a flat UID namespace that is also distinct from the also flat GID namespace.
The UID/GID namespace is strictly local in POSIX. There's no way to make any two systems agree on UIDs/GIDs other than by making them have the same /etc/passwd and /etc/group content. Sure, you can use LDAP, but still, that's just one domain. Come time to do a merger or acquisition, you can't just set up a trust between two domains and have it work -- you have to do a hard migration.
SIDs don't have that problem.
The 48-bit authority part of SIDs is silly.
And the domain SID prefix of SIDs is annoyingly large (20 bytes!).
However, they are very compressible. For example, ZFS stores them as "FUIDs", which are {interned_domain_sid_id, rid}, and in each dataset ZFS stores the table of interned domain SIDs. I.e., where NTFS needs 24 bytes to store any one domain user/group SID, ZFS uses 8, so a 67% savings.
Of course, MSFT should have applied that sort of compression much more aggressively early on. That would have reduced the sizes of PACs a great deal.
SIDs are a post-DCE evolution of UUIDs. SIDs differ from UUIDs in that they are hierarchical. In the context of the Windows domain model, they're split into a component which identifies the domain, and a "relative" component which identifies the security principal within the domain. Thus you can easily determine the domain authority to which a principal belongs (useful for filtering across trust boundaries), and you can also efficiently translate between SIDs and human-readable names (you don't need to ask every authority).
There is a good paper from Paul Leach which discusses what they learned from using UUIDs in DCE, but I've only ever sighted a paper copy and I don't have access to it anymore...
4 replies →
I’d add an I/O interface to the kernel that was built to be asynchronous from Day 0.
Yes! Every system call should take:
- an event queue handle for completion notification
- an optional event queue handle and timeout for waiting on before returning
- the actual arguments to the system call
Yep like Minix does. NO wonder its the world's most popular OS installed everywhere.
I'd be curious how many of those derive from NT's VMS roots - for instance:
http://lxmi.mi.infn.it/~calcolo/OpenVMS/ssb71/6346/6346p004....
Most of them, as far as I know.
1 reply →
These decisions here are all older than Windows and weren't in reaction to them. It's in reaction to the awful mainframe ways to spawn processes like using JCL.
We've sort of come back to that with kubernetes yaml files to describe how to launch an executable in a specific env and all of the resources it needs. Like it can be traced explicitly, the Borg paper references mainframes and knowingly calls the language that would be replaced by kubernetes's yaml files 'BCL' instead of z/OS's JCL.
Plan9 is a lot older than Kubernetes and has the same namespacing of all processes. So it's not impossible to have a "*nix like" OS that still has mainframe-like separation of concerns to ease deployment.
The distinction I'm making here is between opt-in and opt-out namespacing.
Plan9's vfs namespacing is closer to clone(2) than kubernetes.
4 replies →
Having written server software that had to work in both places, I always loved the simplicity of fork(2) / vfork(2) relative to Windows CreateProcess. Threading models in Win32 were always a pain. Which only got worse with COM (remember apartment threading? rental threading? ugh)
Back in the 90's, processes had smaller memory footprint, and every UNIX my software supported had COW optimizations. So the difference between fork(2) and vfork(2) were not very large in practice. Often, the TCP handshake behind the accept(2) call was of more concern than how long it would take fork(2) to complete. Of course, bandwidth has increased by a factor of 1000 since then, so considerations have changed.
It's how CreatProcess handles commandline argument that infuriates me - not as an argv array but a big string. It's so difficult to work around quoting.
The problem with WaitForMultipleObjects (WFMO) is that it's limited to 64 handles, which basically makes it useless for anything where the number of handles is dynamic as opposed to static. There are ways to get around this limitation by grouping handles into trees, but it's tremendously clunky.
UCS-2 seemed like a good(ish) idea at the time when Unicode's scope didn't include every possible human concept represented in icon form and UTF-8 hadn't yet been spec'd on a napkin by the first adults to bother thinking about the problem.
Even in 1989, it should have been clear that 16 bits were not enough to encode all of the Chinese characters, let alone encoding all the human scripts. Unicode today encodes 92,865 Chinese characters (https://en.wikipedia.org/wiki/CJK_Unified_Ideographs).
The only reason anybody would think of UCS-2 was a good idea was that they did not consult a single Chinese or Japanese scholar on Chinese characters.
Nobody in 1989 expected to encode 92k Chinese characters into Unicode because none of the existing encodings were encoding 92k characters either. The most common encoding for Chinese, GB2312, only has 7k characters.
I recommend reading your own link, specifically the list of sources for the first CJK block to see how many characters were included and where they were sourced from.
Quite true. One of the things Windows got very wrong was UCS-2 and, later, UTF-16. So did JavaScript.
And macOS, and Java, and Qt, and ...
It's almost as if it was universally seen as a good idea at the time. ~
3 replies →
Is there any difference between Windows HANDLE and Linux file descriptor? Aren't they both just indexes into a table of objects managed by the kernel?
HANDLE values are opaque, and generally not reused. Imagine an implementation like this:
where `ptr` might be an index into a table (much like a file descriptor) or maybe a pointer in kernel-land (dangerous sounding!) and `verifier` is some sort of value that can be used by the kernel to validate the `ptr` before "dereferencing" it.
On Unix the semantics of file descriptors are dangerous. EBADF can be a symptom of a very dangerous bug where some thread closed a still-in-use FD then a open gets the same FD and now maybe you get file corruption. This particular type of bug doesn't happen with HANDLEs.
> This particular type of bug doesn't happen with HANDLEs.
This does not match my experience at all. Just like what you said about EBADF, Win32 error code 6 (ERROR_INVALID_HANDLE) is a huge red flag for a race condition where a HANDLE gets re-used and inappropriately called upon in some invalid context, possibly even with security or stability concerns. I used to chase these bugs a lot when I worked on Win32 code bases.
If anything this class of bug is worse in Windows because (1) multi-threaded programs are way more common on Windows and (2) HANDLEs are used for more things than file descriptors.
I guess fd reuse is more likely because they tend to get handed out by the kernel as integers in increasing order. But handle reuse absolutely does happen, and if you have this class of bug in a process with a lot of concurrent handle creation in many threads and in a commonly used program it absolutely will bite as a bug at some point.
1 reply →
Gotcha. But it looks like file descriptors could be made almost as safe by avoiding index reuse. Is there any reason why it is not done? Hashtable too costly costly vs array?
3 replies →
That's not true, unfortunately. Handle values are lifo without any uniquifier.
Isn't HANDLE basically fd?
FD has been gradually turned into HANDLE.