Idea: expose Linux raw syscall interface in std::os::linux

I think it may be useful to expose raw syscall interface for Linux. Right now to use syscall functionality your either have to go via libc wrappers, which in turn forces us to use "broken" errno (it's quite funny how syscalls essentially use Result-like interface, but we still have to go via errno), or use Nightly. API can be as simple as 7 functions and maybe a set of convenience constants.

Why we may need it? For example for experiments with new syscalls (e.g. io_uring) or for writing lightweight crates and libraries. Eventually it may be used for implementing libc-free std.

What do you think?

UPD: The relevant PR got rejected, read this message if you interested in alternative approaches.

17 Likes

I'd like to see this as well, to common up the underlying interface. (I'd like to see it in core, ideally, not std, because it enables writing applications directly to the syscall interface.)

One major reason to do this: doing it in a crate requires nightly to invoke inline assembly, but exposing this interface in core could occur in stable Rust (because core and std can use nightly features in their implementation). Having the minimal wrappers available would enable crates to supply safer interfaces atop that, such as wrappers for each syscall, or standardized error handling.

(I'd love to standardize the error handling as well, since every syscall has a standard interface of -1 to -4095 for error codes, but that would require some bikeshedding on what the resulting error types look like.)

6 Likes

I am not sure about this. Right now core is completely OS-agnostic and I don't think we should break it with syscalls. I think we better work in direction of incrementally making std libc-independent (or introduce a separate target like it was proposed here).

4 Likes

I can understand that, but that would then make this unusable for some common use cases.

While I'd certainly like to work towards making std libc-independent, that doesn't help when trying to build without the rest of std. I would love to have a modular std, as envisioned by some of the std-aware cargo proposals. But I'd like to have a usable solution for stable syscalls before that point.

What would go horribly wrong if an OS-specific core::os existed, just like std::os? What would that break?

Could it be a third crate? e.g stdlinux crate that depends on core?

Rust already ships private crates, so I presume it could ship such crate not marked as private.

2 Likes

I mean, it could be (nit: should be a more general name), but should it be? Is there some reason it shouldn't be in core?

2 Likes

I thought the point of core was to only expose bare-metal-friendly std functionality which doesn't rely on the presence of an underlying OS/runtime.

From this perspective, exposing syscalls in core would be philosophically strange.

On the other hand, it could be argued that core::arch is an existing example of functionality in core which relies on the presence of specific system resources to function properly.

In an ideal world, there would be core + various optional modules with runtime requirements (including Linux syscalls), and using std would just be a convenience shorthand for using core with all the bells and whistles that can be normally expected of the target platform...

9 Likes

core::arch is indeed very similar. So is core::ffi.

The description of core says "It links to no upstream libraries, no system libraries, and no libc.".

Raw Linux syscalls don't require linking to a library, and if you're building for a Linux target, having a core::os module doesn't seem drastically different than core::arch. core::os would only invoke some assembly. If appropriately inlined, ideally syscalls will compile down to "make sure the arguments end up in the appropriate registers, then invoke a syscall instruction".

To the best of my knowledge, while people expect core to compile and run without a memory allocator, and to be possible to compile and run without an OS, that doesn't mean a compiled build of core is expected to be OS-independent. As far as I know, there's no degree to which people expect core from x86_64-unknown-linux-gnu to work on x86_64-pc-windows-gnu, for instance; you'd need a separate build of core for that target. So I don't see any obvious issue with having a core::os module that contains additional target-specific items, which doesn't exist on other targets.

In any case, I think this is getting a little off-topic from the original point.

I'd very much like to see syscall functions introduced into Rust's standard library. I'd be happy to help.

3 Likes

Another thing to consider is that syscall is a variadic function: in addition to the syscall number it also takes between 0 and 7 other arguments. The standard library is allowed to use nightly features so I don't think it would be a problem necessarily, but it might be worth trying to avoid.

The proposal in this thread is to have functions syscall0 through syscall6, not a variadic function.

4 Likes

The thread mentions the API of the ::syscall crate: it provides seven functions of different arity (syscall0, ..., syscall6), and a macro to cast the arguments to usize and dispatch to the right function.

Not sure if it's relevant but there was some discussion of exposing some arm assembly instructions through the stdlib, things like wait for interrupt wfi, that you can't do without assembly. The two other choices are nightly + inline assembly, or separate files + linking + accepting the cost of calling a function.

Sounds like the better solution here is to fix the libc error handling implementation, which is incidentally easier because it’s a crate and not std.

Putting this in core sounds very wrong, since I understand core to be a minimal subset of standard.

Both of these proposals seem to impose permanent long-term compatibility complications in response to a temporary problem (assembly not available on stable). Seems like it might be better to accelerate inline assembly, which is probably holding other crates back, than to start moving more stuff into std/core as a workaround to use it.

2 Likes

According to the link in newpavlov's post, the errno handling isn't a bug in rust's libc bindings. It's claiming that POSIX errno semantics are inherently undesirable.

That seems like a misinterpretation of the goals to me. I think it'd be particularly interesting to support static linking of pure Rust executables on Linux (i.e. without musl).

That said I don't think this belongs in core. But it might make sense in std, and serve as a foundation for supporting static linking of pure Rust executables with at least a subset of std functionality available without the need for musl.

Static linking is fine, but I don’t see why this couldn’t be done as a separate no_std crate if the nightly features were stabilized. You can still statically link I’d you use third-party crates.

I like the idea of including this in core. It seems more useful there than in std where you already have higher level OS abstractions (and can fallback to libc::syscall if you really need to). The constants for Linux syscall numbers however seems like a much better fit for an external library.

I'd be willing to believe that there are applications targeting exotic OS's that would need to make syscalls, but otherwise wouldn't need any fancy assembly. With this proposal they'd be able to run on a stable compiler, which otherwise wouldn't be possible because inline assembly has been stuck in nightly only for years now and doesn't look to be changing any time soon. Further even if they run on nightly, it isn't like there'd be a ton of variability in the implementation of a syscall() function, I have an almost identical one to the proposal here in my own code.

1 Like

I've published a Rust PR with the cnages: https://github.com/rust-lang/rust/pull/63745

Why can't you use a #![no_std] crate from crates.io that exposes this ?

See my reply in the PR thread.