Hacker News new | ask | show | jobs
by kevin_thibedeau 1686 days ago

      int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
Is there a reason why a more idiomatic void * to struct wasn't used for the args?
4 comments

It's a leaky abstraction over the underlying system call mechanism. Because the user stack lives in user space and accessing it from the kernel requires the same dance as any other access to user memory, system call arguments are instead passed in registers where they are immediately available to the kernel with no possibility of faults or TOCTTOU holes.

Here 'unsigned long' is just a convenient stand-in for "generic register-sized argument".

A "void * pointing to struct" requires a copy from user space. If a particular prctl() does need a struct, it can certainly stuff a pointer into one of those 'unsigned long' parameters (in the kernel environment, a pointer can be converted to and from an unsigned long without concern).

> Here 'unsigned long' is just a convenient stand-in for "generic register-sized argument".

That wouldn't be register-sized on x86 would it?

On all Linux architectures, "unsigned long" and "long" are always register-sized. For 32-bit architectures, "long" is 32 bits (the same as "int" which is always 32 bits), and for 64-bit architectures, "long" is 64 bits (the same as "long long" which is always 64 bits). This is different from Windows, in which "long" is always 32 bits, even on 64-bit architectures (and you have to use "long long" if you want 64 bits).

Therefore, Linux-only code (including the Linux kernel itself) commonly uses "long" or "unsigned long" to mean "register-sized" and "large enough to fit a pointer"; portable code normally uses "size_t" or "uintptr_t" for that.

It's not even just Linux - way back in the mid 90s there was an agreement among UNIX vendors to use the "LP64" model for 64 bit architectures, and ILP32 was already the de facto standard for 32 bit UNIX-likes, so "long can hold a pointer" is somewhat of a tradition in the POSIXy world.
Thanks, yeah. Any idea why they still do this instead of switching to `uintptr_t`?
Afaik the kernel is built as C89 and uintptr_t was introduced in C99.
Damn. I'd have they they could just typedef it manually, but interesting, thanks.
The minimum size for unsigned long is 4 bytes
Oh I didn't realize its size varies on x86 and x64... I thought it's always 64-bit under compilers that target Linux! Today I learned...
There is a difference between 'long' and 'long long'. I believe the latter is always 64 bits on Linux.
I'm indeed aware of that one :-)
I think the size can vary by compiler rather than just by architecture.
Yeah it can but in practice systems have a canonical model in their official headers.
Because it is much cleaner to name the arguments rather than to have a void* to what may or may not be the same struct floating somewhere in space.
Because this is the most general signature possible (in general syscalls can have up to 6 arguments because the kernel supports x86-32 which has only 8 general purpose registers of which one stores the syscall number, one the stack pointer which cannot be reused due to signals in case of no sigaltstack, and the remaining 6 are available).

Depending on option, the args can be pointers to structure.

prctl is responsible for setting certain privileged CPU registers on certain platforms. I can imagine some benefit of having the values already loaded into registers for such a brief call instead of having to go out to user-controlled memory (which needs to be accessed carefully within the kernel).

It’s also worth noting that most older Linux system calls do not follow that pattern in general, so I don’t know that I would consider void* idiomatic for system calls until recently.