Thanks for sharing, that’s a really nicely written article.
In case you are wondering, while this would not apply to NetBSD or FreeBSD as is, the interfaces and structures are close enough that it can get you started. If desired, I could write a thing or two on that topic in the future.
I’m not sure about NetBSD, but the process looks very similar to FreeBSD. The main differences are around audit logging, deciding whether the new syscall should be on the Capsicum allow list, and adding 32-bit compat implementations. Aside from that, I think the make invocation to regenerate the important files has a different spelling.
I remain amazed at how much more effort this is to do with Linux than *BSD.
I suspect a big part of it is that if you introduce a syscall in Linux you’re stuck with it being a syscall forever. BSD not having a stable syscall layer means that they can just move stuff out to userspace whenever necessary
I remain amazed at how much more effort this is to do with Linux than *BSD.
The arch-specificity of the syscall tables is indeed unfortunate; aside from that though AFAICT it’s basically a line or two of boilerplate in a couple header files, defining your handler function with a DEFINE_SYSCALL*(), and you’re off to the races…which doesn’t seem too terrible, IMHO, unless there’s something I’m missing? (In fact, in terms of passing parameters it seems simpler, since you end up with regular C function arguments instead of having to cast a void* to a struct pointer and then pulling things out of that.)
In FreeBSD, you don’t have a void*, you have a struct containing all of the arguments. You are not limited to 6 arguments, system calls can have on-stack arguments and they will be copied automatically into that struct. The struct is convenient because most system calls have a sys_ function that is the system call and a kern_ function that implements the underlying behaviour, so the default version just passes the struct through from the wrapper to the implementation, whereas compat implementations construct the struct (this is more annoying to do in Linux, but Linux doesn’t have any other-OS compat layers and so it’s less of an issue).
You don’t have to do anything to extra to expose the prototype in libc, it’s automatically generated, as is any assembly stub necessary to call the system call, so from zero to ‘I can call this from a C program’ is far less effort. I don’t know why Linux doesn’t build a libsyscalls with the userspace wrappers that musl, glibc, bionic, and so on can all use.
Thanks for sharing, that’s a really nicely written article.
I’m not sure about NetBSD, but the process looks very similar to FreeBSD. The main differences are around audit logging, deciding whether the new syscall should be on the Capsicum allow list, and adding 32-bit compat implementations. Aside from that, I think the
make
invocation to regenerate the important files has a different spelling.I remain amazed at how much more effort this is to do with Linux than *BSD.
I suspect a big part of it is that if you introduce a syscall in Linux you’re stuck with it being a syscall forever. BSD not having a stable syscall layer means that they can just move stuff out to userspace whenever necessary
FreeBSD has a stable system call interface. Many updated system call interfaces have one or more COMPAT version for when a newer version has been added. Conversely, if you look in the Linux system call table, you’ll see a bunch of gaps from removed ones (for example, the AFS-related ones).
The arch-specificity of the syscall tables is indeed unfortunate; aside from that though AFAICT it’s basically a line or two of boilerplate in a couple header files, defining your handler function with a
DEFINE_SYSCALL*()
, and you’re off to the races…which doesn’t seem too terrible, IMHO, unless there’s something I’m missing? (In fact, in terms of passing parameters it seems simpler, since you end up with regular C function arguments instead of having to cast avoid*
to a struct pointer and then pulling things out of that.)In FreeBSD, you don’t have a void*, you have a struct containing all of the arguments. You are not limited to 6 arguments, system calls can have on-stack arguments and they will be copied automatically into that struct. The struct is convenient because most system calls have a sys_ function that is the system call and a kern_ function that implements the underlying behaviour, so the default version just passes the struct through from the wrapper to the implementation, whereas compat implementations construct the struct (this is more annoying to do in Linux, but Linux doesn’t have any other-OS compat layers and so it’s less of an issue).
You don’t have to do anything to extra to expose the prototype in libc, it’s automatically generated, as is any assembly stub necessary to call the system call, so from zero to ‘I can call this from a C program’ is far less effort. I don’t know why Linux doesn’t build a libsyscalls with the userspace wrappers that musl, glibc, bionic, and so on can all use.