Safer/better equivalent to ioctl?
Safer/better equivalent to ioctl?
So, I know that Linux (or Unix in general) has the ioctl(2) system call for generic device driver communication to avoid implementing millions of system calls for every kind of device driver in existence. I've never been a fan of ioctl myself; I feel like there should be a better way of approaching this. My idea might be to have an ahcictl, nvmectl, etc., for each kind of disk interface; ethernetctl/ethctl, wifictl for networking; and so on, and then maybe provide a C++ interface that uses function overloading to call the underlying interfaces. But I'm not really sure, so thought I might see what you guys think about just a generally better design other than ioctl().
Re: Safer/better equivalent to ioctl?
Alternatively, pseudo file systems. ioctl()s are about sending data to the kernel or retrieving data from the kernel. Both of these can be done with a pseudo-file. That gets around the issue of allocating tons of system call numbers for tons of niche interfaces. It also gets around some ridiculous ioctl()s, like the ones for setting IP addresses. You allocate some socket and use ioctl() on it. But there is never a need for any socket, you just need it as basis for the ioctl(). So maybe "echo 192.168.0.1/24>/net/if/eth0/ipv4/addr" is simpler than using that weirdness, or using "netlink", which Linux came up with. Don't get me wrong, netlink is great at giving you info you could not get otherwise, but for ad-hoc initialization like above IP address setting it is a bit overkill.
Carpe diem!
Re: Safer/better equivalent to ioctl?
That's... Actually a really good idea. So for NVMe controllers for example (following the FHS, you might have something like: /dev/nvme/nvme0/regs/cap/cmbs, which would access the CMBS bit of the CAP register. (Yeah, I know, devfs isn't commonly used like that -- that's more for /sys -- but it would make sense because /dev is for devices and device information.) Or, for the RTL 8139 you might have /dev/net/rtl0/mac for the MAC address.
Re: Safer/better equivalent to ioctl?
I am not sure if pseudo file systems really solve all use cases for ioctls. Pseudo FSes are great for non-performance critical stuff, but ioctls are also used for stuff like zero-copy I/O, and multiplexing that over write() is probably not a good idea. In particular, interpreting part of the written message as a pointer and dereferencing that in the kernel is probably a very bad idea, both for security reasons (e.g., you could trick a program to write to such a file to inject shellcode or extract data) and also because it restricts the implementation (e.g., it will break when used through a pipe to a different program).
You could always replace ioctls by native syscalls but then you need an efficient mechanism to dispatch these calls (and a simple lookup table won't work anymore). You could take an approach inspired by object-oriented programming. For example, you could group ioctls into "interfaces", have a syscall to request an interface handle for a pair of (fd, interface name), and then dispatch the ioctls as "syscalls" through the interface handle (i.e., on the kernel side, each interface would have its own syscall table).
You could always replace ioctls by native syscalls but then you need an efficient mechanism to dispatch these calls (and a simple lookup table won't work anymore). You could take an approach inspired by object-oriented programming. For example, you could group ioctls into "interfaces", have a syscall to request an interface handle for a pair of (fd, interface name), and then dispatch the ioctls as "syscalls" through the interface handle (i.e., on the kernel side, each interface would have its own syscall table).
managarm: Microkernel-based OS capable of running a Wayland desktop (Discord: https://discord.gg/7WB6Ur3). My OS-dev projects: [mlibc: Portable C library for managarm, qword, Linux, Sigma, ...] [LAI: AML interpreter] [xbstrap: Build system for OS distributions].
Re: Safer/better equivalent to ioctl?
I'm confused. Are you saying that the syscall handler for instance would require something like an interface ID and then the syscall number, followed by arguments? Something like what UEFI does with its protocol handles?Korona wrote:I am not sure if pseudo file systems really solve all use cases for ioctls. Pseudo FSes are great for non-performance critical stuff, but ioctls are also used for stuff like zero-copy I/O, and multiplexing that over write() is probably not a good idea. In particular, interpreting part of the written message as a pointer and dereferencing that in the kernel is probably a very bad idea, both for security reasons (e.g., you could trick a program to write to such a file to inject shellcode or extract data) and also because it restricts the implementation (e.g., it will break when used through a pipe to a different program).
You could always replace ioctls by native syscalls but then you need an efficient mechanism to dispatch these calls (and a simple lookup table won't work anymore). You could take an approach inspired by object-oriented programming. For example, you could group ioctls into "interfaces", have a syscall to request an interface handle for a pair of (fd, interface name), and then dispatch the ioctls as "syscalls" through the interface handle (i.e., on the kernel side, each interface would have its own syscall table).
Re: Safer/better equivalent to ioctl?
Yes (that would be one possibility).
managarm: Microkernel-based OS capable of running a Wayland desktop (Discord: https://discord.gg/7WB6Ur3). My OS-dev projects: [mlibc: Portable C library for managarm, qword, Linux, Sigma, ...] [LAI: AML interpreter] [xbstrap: Build system for OS distributions].
Re: Safer/better equivalent to ioctl?
You know, I hadn't though of that. That sounds like a good idea as well, particularly for those ioctl()s that actually do use the FD, e.g. TCSETATTR. I've been searching for some way to make terminal attributes into a pseudo-file that applications can still find reliably. The problem is that in general, finding the file name for a file descriptor can be pretty hard. Even if you have /proc/self/fd in your OS, readlink() is a messy interface. The code I came up with to read an arbitrary link requires malloc() in a loop. And the kernel definitely knows the terminal attached to the file descriptor directly. So this all seemed a bit pointless. Also, suddenly your applications all need to know about the different types of terminal device in your OS. And you have more dependencies on the VFS being configured in a particular way.Korona wrote:You could always replace ioctls by native syscalls but then you need an efficient mechanism to dispatch these calls (and a simple lookup table won't work anymore). You could take an approach inspired by object-oriented programming. For example, you could group ioctls into "interfaces", have a syscall to request an interface handle for a pair of (fd, interface name), and then dispatch the ioctls as "syscalls" through the interface handle (i.e., on the kernel side, each interface would have its own syscall table).
So having these interfaces and request them directly on the FD seems like a good idea for those types of ioctl()s. Also sound data. The old OSS would work by letting you open /dev/dsp and writing your sound samples to the device. Problem 1: What if you have multiple sound cards? OK, make /dev/dsp0, /dev/dsp1, ... and /dev/dsp is a symlink to your default device. Problem 2: How to configure the card? ioctl(). *sigh*. And then ALSA came along and said "ioctl() makes you sigh? Well, here's loads more of them!" And now there are ioctl()s for everything. You don't even write your samples to the device, you transfer them by ioctl(). So that seems like a thing I could quarantine away with those interfaces.
Carpe diem!
Re: Safer/better equivalent to ioctl?
For sound especially, I was thinking of something like /dev/dspx/samples, which you'd write to. Every time you did the driver would update any metadata it needs to pass to the sound controller about the samples being processed -- things like position at any rate. Then you'd have somethign like /dev/dspx/sampleinfo, which would include general info about the samples being written, e.g.: sample rate, number of channels, interleaving status and such. Then you'd have /dev/dspx/cmd, which would allow you to tell the driver when to actually begin processing the samples. That would make it easier to write streams in bulk.
- AndrewAPrice
- Member
- Posts: 2300
- Joined: Mon Jun 05, 2006 11:00 pm
- Location: USA (and Australia)
Re: Safer/better equivalent to ioctl?
Do you want to keep the Unix philosophy of making everything a file?
My OS is Perception.
Re: Safer/better equivalent to ioctl?
Not really. I think that descriptors for disks, files, etc., should be separate and there shouldn't be a universal "handle" for everything (that makes type checking kinda difficult). I also feel like devices/sockets/etc. shouldn't be treated as a part of the file system because they really, really are independent entities with pretty much nothing in common with each other other than being able to read from and write to them.
- AndrewAPrice
- Member
- Posts: 2300
- Joined: Mon Jun 05, 2006 11:00 pm
- Location: USA (and Australia)
Re: Safer/better equivalent to ioctl?
I went a different route in my OS and made a system where programs could implement and discover named services. The names are fully qualified, e.g. "perception.devices.StorageDevice".
Anyone who wants to build a storage device driver can implement and register the StorageDevice interface, and my VFS will be monitoring for when a new StorageDevice service is registered.
My graphics driver implements "perception.devices.GraphicsDevice", my window manager implements "perception.WindowManager", etc.
I came up with my own interface descriptor language, that lists methods and the request/response types, and it generates a C++ stub for calling and implementing the methods.
See: https://wiki.osdev.org/Remote_Procedure_Call
Inspiration: https://grpc.io/ https://thrift.apache.org/
What my IDL looks like: https://github.com/AndrewAPrice/Percept ... n/permebuf
Anyone who wants to build a storage device driver can implement and register the StorageDevice interface, and my VFS will be monitoring for when a new StorageDevice service is registered.
My graphics driver implements "perception.devices.GraphicsDevice", my window manager implements "perception.WindowManager", etc.
I came up with my own interface descriptor language, that lists methods and the request/response types, and it generates a C++ stub for calling and implementing the methods.
See: https://wiki.osdev.org/Remote_Procedure_Call
Inspiration: https://grpc.io/ https://thrift.apache.org/
What my IDL looks like: https://github.com/AndrewAPrice/Percept ... n/permebuf
My OS is Perception.
Re: Safer/better equivalent to ioctl?
If you do that, you lose the device-independent I/O that is relatively common in Unix. For instance, it's very common to have programs that can do I/O to either a terminal or a disk file without having to be aware of any differences between the two, and if you make those completely separate, then every program has to be specifically aware of every possible device type, which I would say is a big step backwards. It also makes security a lot more complex if you have separate primitives for each device because each has to have its own separate security layer (which already makes security way more complicated than it needs to be on conventional Unices with their multiple non-file-based primitives), whereas in an OS with a purely file-oriented architecture you basically only have to have one relatively simple security layer for the entire system. In addition, file-oriented architecture with per-process namespaces makes containerization and multiple instances of system services a lot easier.Ethin wrote:Not really. I think that descriptors for disks, files, etc., should be separate and there shouldn't be a universal "handle" for everything (that makes type checking kinda difficult). I also feel like devices/sockets/etc. shouldn't be treated as a part of the file system because they really, really are independent entities with pretty much nothing in common with each other other than being able to read from and write to them.
UX/RT, the OS I'm working on, will basically do the opposite of what you're proposing and take "everything is a file" as far as it can be taken (anonymous memory will be replaced with files in a tmpfs, process/thread creation will be done through procfs, and even most filesystem APIs themselves other than reads/writes will go through an RPC connection over a file descriptor). Any service that an OS implements can be built on top of receive/send or read/write primitives (in a typical microkernel OS everything actually is), so why overcomplicate things by having more primitives than you need? It's always possible to provide interface libraries on top of the file-based interfaces for services where it isn't convenient to use the file-based interface directly (UX/RT will do this for most of its APIs).
Developer of UX/RT, a QNX/Plan 9-like OS
- AndrewAPrice
- Member
- Posts: 2300
- Joined: Mon Jun 05, 2006 11:00 pm
- Location: USA (and Australia)
Re: Safer/better equivalent to ioctl?
You get piping (piping one program's stdout to another's stdin or a file, which should be the job of the shell), but I can't really think of a concrete example where two totally different classes of devices would pipe ioctls with the other understanding it?andrew_w wrote:If you do that, you lose the device-independent I/O that is relatively common in Unix. For instance, it's very common to have programs that can do I/O to either a terminal or a disk file without having to be aware of any differences between the two, and if you make those completely separate, then every program has to be specifically aware of every possible device type, which I would say is a big step backwards. It also makes security a lot more complex if you have separate primitives for each device because each has to have its own separate security layer (which already makes security way more complicated than it needs to be on conventional Unices with their multiple non-file-based primitives), whereas in an OS with a purely file-oriented architecture you basically only have to have one relatively simple security layer for the entire system. In addition, file-oriented architecture with per-process namespaces makes containerization and multiple instances of system services a lot easier.
Does every program need to touch every device, other than your device manager that can scan and load drivers?andrew_w wrote:then every program has to be specifically aware of every possible device type
If you use an IDL to define an interface per device class, and you had a legitimate need to pretend your joystick is an Ethernet card, you can create a wrapper program that implements a virtual Ethernet card and transforms the command to joystick commands.
So as a concrete example, let's say you had a program that distorted your voice and you wanted to pipe your microphone to it, then pipe the outputs to your speaker, you could do:
Code: Select all
voice_transformer < /device/microphone > /devices/speaker
Code: Select all
print_microphone --depth=24 --rate=96 | voice_transformer | play_stdin --depth=24 --rate=96
Last edited by AndrewAPrice on Mon Mar 22, 2021 10:56 am, edited 1 time in total.
My OS is Perception.
Re: Safer/better equivalent to ioctl?
In my OS, I am going to make an object manager. Drivers will get registered in the object manager. They can register functions for different tasks. For example, a driver would be registered. It would then make I/O control handlers in the object dispatch table. An app would grab the table, read the message numbers that correspond to a specific control (as my OS will be microkernel based) and then send a message with the proper parameters. I don't plan on making a strict Unix clone, rather something kind of Unix like.
Re: Safer/better equivalent to ioctl?
My view is that ioctl is a completely useless interface, and by not implementing it you can stop device driver manufacturers and kernel gurus from modifying their devices or the kernel with undocumented APIs. If a function is important to support, you provide API functions for it, and if it simply is something fancy a hw manufacturer wants to push, then they can either configure it with environmental variables or do some automatic configuration. Of course, unless the OS team thinks it is something useful that more than one device supports, and then provide a new API for it.