8 ms·
Opening /proc/self/fd/1 is not the same as dup(1)
- larsnystrom 3y agoI can't be the only one who dreams of a radically different successor to Linux when I read stuff like this. Like, why do I need to care about file descriptors? Why is everything a file, except its not because it can be a socket, or a pipe, or a block device, or something else I haven't heard about? Why does the OS even care what my current working directory is? How are we ever going to get rid of C when the whole OS interface is defined in C? Even with the simplest program, running in a single thread (or is it pthread?), I can't control the flow of execution because suddently SIGNALS happen and who knows what'll happen then. Like, you could use select(2), but you should use pselect(2) to deal with signals, except select(2) should not be used because it can't deal with PIDs larger than 1024, so use poll(2), or maybe epoll(7), or ppoll(2) if you want to deal with signals, but what happens when you use epoll(7) and have to deal with signals? And did you notice all those numbers next to the syscall names? Wait, what do you mean syscall, is it not a function? And of course, to create a new process, you use a syscall called fork(), because processes and cutlery are intrinsically linked. Honestly, the OS situation is a mess and I just want to not think about it, but not having an OS is not really an option.
- jmprspret 3y agoEver looked into 9front?
- moody__ 3y agoOut of curiosity I decided to test these assumptions on 9front and things work as the author would have expected: cpu% ./dup -proc > out; cat out; echo 1d cpu% ./dup -dup > out; cat out; echo 1d The slightly modified code can be found here: http://okturing.com/src/18632/body http://okturing.com/src/18632/body This seems to be another case of Linux attempting to emulate Plan 9 design and not quite hitting the mark. In general these operating system interfaces are much more consistent and sane within the Plan 9 environment at (seemingly) every turn. I think a lot of folks see the implementation quirks of Linux and decide its time to toss the baby out with the bath water, but studying Plan 9 really shows how nice things could have been.
- thriftwy 3y ago> he whole OS interface is defined in C What do you think about io_uring? I believe it moves away from OS interface as (C) function calls.
- Pesthuf 3y agoCareful now, implying the inventors of gets() may not be flawless beings of divine intellect whose creation has outshone whatever the second coming of Christ may end up becoming will get you a slow and painful death.
- bregma 3y agoYou could serialize everything through epoll(): use signalfd() to redirect signals into epoll(), use eventfd() for IPC through epoll(), etc. The kernel programming API is that everything is operated on through a file descriptor, not that everything is a file. You misunderstand. You can program in another language other than C and avoid using GNU libc, or Musl libc, or any other libc, so avoid using the C API to talk to the kernel. Other languages like Rust and Go provide their own runtimes and avoid using the C runtime for syscalls. Syscalls are written in assembly language, or at least syscall(2) itself is because the kernel API is just marshalling and a context switch. You misunderstand. Oh, and the fork(2) function on Linux is implemented by a libc using the clone syscall(2). The (2) is the chapter in the manual providing the documentation. You misunderstand.
- crabbone 3y agoWhile I think that most of your reply is pretending to not notice the real problem and making lame excuses, I specifically want to point out this: > You can program in another language other than C and avoid using GNU libc Linux is a set of various system interfaces. Many of which are exposed through libc functions. If you deliberately exclude libc and program something parallel to it instead, then you aren't really using Linux. You've created a hybrid system. This only reinforces OP's complaint about not being able to program in a different language, because, in other words, this means that to C language programmer Linux is available fully, and to other languages the functionality is not completely available. Take, for example, async I/O, which is implemented in libc around other system primitives, s.a. threads. If you don't use libc, you don't have async I/O, and by extension, you don't have Linux, since Linux is supposed to have that.
- thecodedmessage 3y agoThis is a surprising take. Linux as a project is the kernel. Unlike most OSes, the most commonly used libc is maintained separately. As a result, Linux the operating system is the one where it’s the most common and supported to avoid the “standard” libc because the kernel itself has a stable interface.
- rrdharan 3y agohttps://en.m.wikipedia.org/wiki/The_UNIX-HATERS_Handbook https://en.m.wikipedia.org/wiki/The_UNIX-HATERS_Handbook
- tsimionescu 3y agoYou're mixing up many entirely different topics in this rant, so it's hard to unpack. That we use the term "file descriptors" for pointers from userspace to any kernel object, even those that are not files, is unfortunate, but ultimately just a naming quirk. Windows has a better name, "Handle", but the concept is exactly the same. The OS includes the file system, and file systems include a notion of paths, and relative paths are really useful. So, the OS helps you by automatically resolving relative paths to your current directory, instead of forcing every application to manually keep track of this. Linux is perhaps the only popular OS whose interface is not defined in C. All syscalls are clearly documented at the assembler level in Linux, and kept backwards compatible. All other popular OSs (Windows, MacOS, FreeBSD) have a C lib you have to dynamically link if you expect compatibility. Even if signals weren't a thing, you'd still have to worry about processor interrupts. There is no such thing as a purely single-threaded program on any gpCPU released in the last 30+ years. The variety of calls in Linux to handle various kinds of events is unfortunate. Windows has a slightly cleaner interface, though even there it's not ideal. Hopefully io_uring will subsume all of the current use cases. The numbers after the syscalls are related to the man pages where they are documented. Not all that relevant. Sycalls are not functions, they are specific APIs that the kernel provides to userspace, defined at the assembler level (you put this value in this register/stack and jump to this address/invoke this CPU interrupt). It is up to your language to wrap syscalls into functions, which may have an entirely different calling convention. A kernel can't provide APIs as language-specific functions, as Python's calling convention is vastly different from Haskell's. Fork() has many meanings that are not related to cutlery, used in CS in other places. Fork() is also an extraordinarily terrible interface for process creation for reasons which have nothing to do with its name. I would be happy if one day Linux gets rid of this insanity and adds a CreateProcess syscall that doesn't have to pretend to copy the entire address space of the current process.
- oasisaimlessly 3y ago> I would be happy if one day Linux gets rid of this insanity and adds a CreateProcess syscall that doesn't have to pretend to copy the entire address space of the current process. fork() is going to exist forever, but posix_spawn() already exists: https://linux.die.net/man/3/posix_spawn https://linux.die.net/man/3/posix_spawn
- hnlmorg 3y agoAny sufficiently complex software ecosystem eventually ends up amassing a heap of ugliness due to assumptions made at the time that are no longer correct nor are easy to change. Web development is a great example of this: the modern browser is not that far removed from an operating system and how many quirks do they need to cater for these days plus how many footguns do web developers need to consider? Also some of your specific concerns are impossible to resolve. Take SIGNALS for example. They're ostensibly just callback functions / events. It's very easy to do event-driven programming if all of your events are being raised by the same language runtime as the code you're writing your application in, but how do you raise an event that crosses application boundaries and where your application code would be written in a different language to the event bus (the kernel in this instance)? You ultimately end up with some kind of IPC ugliness and the best solution in the 70s was SIGNAL. Given its now core to the OS, stripping out SIGNALs from Linux would be as easy as stripping HTML from websites. There are plenty of radically different successors to Linux though. But they all have their own rough edges too. Ultimately these things are complicated and you always end up making compromises somewhere.
- stefan_ 3y agofork() is truly the epitome of terrible design. Yet all the OS books praise it as some genius - tells you how little research has progressed...
- yau8edq12i 3y agoHave you read an "OS book" written recently?
- stefan_ 3y agoWe shall open the Tanenbaum, now in its fifth edition circa 2023, no doubt still taught to students all over the world: > Processes are created in Linux in an especially simple manner. The fork system call creates an exact copy of the original process. > The Linux sequence of clone plus exec is yet more orthogonal, since even more fine-grained building blocks are available. As a general rule, having a small number of orthogonal elements that can be combined in many ways leads to a small, simple, and elegant system. Yup, still the same bullshit. It is not elegant, it is not fine-grained, it is not simple, it is certainly not small.
- yau8edq12i 3y agoThat book is decades old. New editions rarely change this type of fluff.
- zokier 3y agoFuchsia exists.
- deleted 3y ago[deleted]
- Joker_vD 3y ago> Unix processes have file descriptors which point to file descriptions (`struct file` in Linux). Also known as file handles referencing file objects (on Windows). Unix terminology is unnecessarily confusing in this place IMHO. On the subject of the article itself: why was this change introduced? To give kernel support for reopen(3) since dup(3) already exists?
- AshamedCaptain 3y agoI propose to reintroduce the term FCB (File Control Block ) to help clear things up :)
- zare_st 3y agoUnix terminology is older. FD is a kernel-user interface based on integer map. HANDLE is a typedef'd pointer. Apples vs oranges.
- tsimionescu 3y agoDoesn't the Linux behavior make more sense? It basically guarantees that open() always returns a new file description. Having open() return an existing file description for some special files seems like a recipe for exploits. Dup() already exists for this purpose.
- DSMan195276 3y agoI think it has some minor tradeoffs, like the article mentions not all of the underlying files can be opened a second time so that makes them basically unusable in this form. If the `open()` call did a dup instead then you could read/write to it in the same way as the process can regardless of the type of underlying file.
- hawski 3y agoIdeally I would like to see both behaviors available, making the current way the preferred one. But AFAIK it would be hard, because /dev/fd/* are just symlinks and this would need a very special case.
- tsimionescu 3y agoBut what would be the use case of re-opening your own files in this way, that dup() doesn't cover? I mean, it's cute that it's in principle possible, but what does it actually do that can't be achieved more cleanly in other ways?
- nrdvana 3y agoUsually to make a program do something the author didn't think of, by passing it a filename to one of its descriptors where the author only gave you a configuration of a path. For example, starting a service under a monitoring system which gives it a pipe to a logger on fd3, then telling it to write a log file of /dev/fd/3
- kazinator 3y agoIt supports the wacky semantics of the freopen function, which is probably why glibc implements freopen by opening /proc/self/fd/<n>. Specifically, the case of freopen when the path argument is null, which was introduced in C99. freopen does all the open-time actions such as that the "w" mode truncates the file. So that could be why. If we dup the descriptor, we have to call ftruncate to implement the "w" flag. I think if we just open the /proc item, O_TRUNC will do it for us. The freopen function is wacky in the first place, and obviously the approach has the flaw that (as we learn from the submitted article) that sockets cannot be opened this way. Obviously, /proc/.../fd was not designed for freopen. /proc/<other-than-self>/fd is very useful for implementing, e.g. process substitution in Bash: $ diff -u <(this command) <(that command) This calls diff with /proc/<pid>/fd/<n> /proc/<pid>/fd/<m> which refer to pipes set up by the shell. The diff program thinks it's just opening files. /proc/self/fd/ exists because there is a /proc/self symlink to /proc/<self-pid>/ not necessarily because it's useful for a process to open its own descriptors this way. Other /proc/self things are useful, like /proc/self/exe to find out where your executable is located.
- kazinator 3y agoObscure fact #37: Glibc's implementation of freopen opens /proc/self/fd/<n>, when the pathname is null, e.g.: freopen(NULL, "w", stdout);