3 ms·
The "syscall API" is part of libc too. The read syscall is a trap, you put arguments in the right registers and issue the correct instruction[1] to enter the k
by ajross 1y ago
The "syscall API" is part of libc too. The read syscall is a trap, you put arguments in the right registers and issue the correct instruction[1] to enter the kernel. That's not something that can be expressed in C. The read() function that your C code actually uses is a C function provided by the C library.
[1] "svc 0" on ARM, "int 0x80" on i386, etc...
- Retr0id 1y agosyscalls are an implementation detail of some libc impls on some platforms, but the C spec does not mention syscalls.
- cyphar 1y agoThe libc syscall wrappers are part of the libc API, but on Linux, syscalls are part of the stable ABI and so you can freely do __asm__(...) to write your own version of syscall(2) and it is fully supported. Yeah, __asm__ is probably not in the C spec, but every compiler implements it... For instance, Go directly calls Linux system calls without going through libc (which has lead to lots of workarounds to emulate some glibc-specific behaviour -- swings and roundabouts I guess...). Other operating systems do not provide this kind of compatibility guarantee and instead require you to always go through libc as the syscall ABI is not stable (though ultimately, you can still use __asm__ if you so choose). In any case, file descriptors are definitely not a libc construct on Linux.
- 1718627440 1y agoYes, you can. Then you don't write against the OS, but against the kernel. It sometimes works, because the kernel is a separate project, it sometimes doesn't, you gave an example yourself. > In any case, file descriptors are definitely not a libc construct on Linux. File descriptors come definitely from the kernel, but they do also exist as a concept in libc, and I was referring to them as such. I was saying that I depend on non-portable libc functions, even though I value portability, because the API is just so nice. I did not want to indicate, that I am doing syscalls directly.
- matheusmoreira 1y ago> That's not something that can be expressed in C. I've often made the argument that compilers should add builtins for Linux system calls. Just emit code in the right calling convention and the system call instruction, and return the result. Even high level dynamic languages could have their JIT compilers generate this code. I actually tried to hack a linux_system_call builtin into GCC at some point. Lost that work in a hard drive crash, sadly. The maintainers didn't seem too convinced in the mailing list so I didn't bother rewriting it. > The read() function that your C code actually uses is a C function provided by the C library. These are just magic wrapper functions. The actual Linux system call entry point is language agnostic, specified at the instruction architecture level, and is considered stable. https://www.matheusmoreira.com/articles/linux-system-calls https://www.matheusmoreira.com/articles/linux-system-calls This is different from other systems which force people to use the C library to interface with the kernel. One of the most annoying things in the Linux manuals is they conflate the glibc wrappers with the actual system calls in Linux. The C library does a lot more than just wrap these things, they dynamically choose the best variants and even implement cancellation/interruption mechanisms. Separating the Linux behavior from libc behavior can be difficult, and in my experience requires reading kernel source code.
- 1718627440 1y ago> I've often made the argument that compilers should add builtins for Linux system calls. Just emit code in the right calling convention and the system call instruction, and return the result. Even high level dynamic languages could have their JIT compilers generate this code. You can only do that, when you compile for a specific machine. In general you are compiling for some abstract notion of an OS. JITs always compile for the machine they are running on, so they don't have that problem. There is code, that is compiled directly to your syscalls specific to your machine, so that abstract code can use this. It's called libc for the C language. > One of the most annoying things in the Linux manuals is they conflate the glibc wrappers with the actual system calls in Linux. The C library does a lot more than just wrap these things, they dynamically choose the best variants and even implement cancellation/interruption mechanisms. Separating the Linux behavior from libc behavior can be difficult, and in my experience requires reading kernel source code. In my experience there are often detailed explanation in the notes section. From readv(2): NOTES POSIX.1 allows an implementation to place a limit on the number of items that can be passed in iov. An implementation can advertise its limit by defining IOV_MAX in <limits.h> or at run time via the return value from sysconf(_SC_IOV_MAX). On modern Linux systems, the limit is 1024. Back in Linux 2.0 days, this limit was 16. C library/kernel differences The raw preadv() and pwritev() system calls have call signatures that differ slightly from that of the corresponding GNU C library wrapper functions shown in the SYNOPSIS. The final argument, offset, is un‐ packed by the wrapper functions into two arguments in the system calls: unsigned long pos_l, unsigned long pos These arguments contain, respectively, the low order and high order 32 bits of offset. Historical C library/kernel differences To deal with the fact that IOV_MAX was so low on early versions of Linux, the glibc wrapper functions for readv() and writev() did some extra work if they detected that the underlying kernel system call failed because this limit was exceeded. In the case of readv(), the wrapper function allocated a temporary buffer large enough for all of the items specified by iov, passed that buffer in a call to read(2), copied data from the buffer to the locations specified by the iov_base fields of the elements of iov, and then freed the buffer. The wrapper function for writev() performed the analogous task using a temporary buffer and a call to write(2). The need for this extra effort in the glibc wrapper functions went away with Linux 2.2 and later. However, glibc continued to provide this be‐ havior until version 2.10. Starting with glibc version 2.9, the wrap‐ per functions provide this behavior only if the library detects that the system is running a Linux kernel older than version 2.6.18 (an ar‐ bitrarily selected kernel version). And since glibc 2.20 (which re‐ quires a minimum Linux kernel version of 2.6.32), the glibc wrapper functions always just directly invoke the system calls.