2

This question is an extension of why do we need to pass buffers to system calls in order to have information returned? Why can't system calls allocate the buffer internally? since memory allocation is one of many motivations to pass callbacks to syscalls.

I would like to to pass an allocate callback to a syscall variant of the original that requires a buffer and since the original syscall would likely know the size of the buffer to allocate before writing to the buffer.

Many file system APIs (not just posix/*nix, but Windows/NT does this too) always ask that you pass a x byte path buffer (x would be 256 or 1024 or 4096 or MAX_PATH). You still have to pass x into the function to indicate buffer size and the function may fail if it needs more space than the buffer allows.

Is there a restriction where a syscall cannot call user space functions? Is it possible, but complicated since the syscall must retain the calling userspace context and the callback userspace context? Are all syscalls supposed to be atomic from a thread switching perspective and calling userspace code from within a syscall would break that atomicity?

2
  • The Kernel has no idea what memory allocation scheme the user program is using. So it cannot pass back a syscall-allocated buffer which the process can: free as if it came from malloc; or choose to re-use for the next syscall; or allocate on-stack; or have already defined as static memory at compile-time. And then we have mmap to deal with too. Sep 14, 2022 at 8:31
  • What are you trying to achieve? Do you want to rebuild the kernel with a modified syscall? Also, the subject of this question is "Why do syscalls not accept userspace callback functions", but currently there are no syscalls that accept callback functions, so how it's kind of a moot point. Maybe if you provide more details about your goal it would be easier to help with your specific need.
    – aviro
    Sep 14, 2022 at 11:12

1 Answer 1

3

System calls run kernel code. The kernel doesn't trust user mode code, so if a system call takes a callback, it can't execute that callback in kernel mode. It must execute the callback in user mode.

The system call can't know for sure what the callback will do. The callback may make system calls of its own. For example, a callback to allocate memory may want to call a system call such as sbrk or mmap to give the process more memory. So the kernel needs to maintain an extra execution context for the pending system call, in addition to the one for any nested system call. The kernel can have multiple execution contexts per process: that's what kernel threads are for. But now these contexts would need to have additional structure, since some contexts are nested in others.

This additional complication can be avoided by having the system call pass all the intermediate information to the process. Let the process execute whatever callback code it wants, and then invoke a continuation system call when it's ready. This way the kernel keeps a simple execution model (system call gets invoked, system call runs, system call returns), and the process gets to run callbacks however it wants.

Well, there you have it: that's how it works now. A process can make a system call to query the size of the needed buffer, and then another system call (possibly the same system call number with different parameters) to obtain the data.

There is an inherent difficulty that the data may change during the execution of the callback. System calls don't have to be atomic, but they have to present a consistent view of the system at a given instant. For example, a process may try to read data from a file, and the file may grow while the callback runs. Since the kernel can't trust that the callback will complete in a given amount of time — or indeed that it will ever complete — the kernel cannot block updates to the file while the callback is running. So the user land code has to cope with this difficulty in any case: when the post-callback code runs, the desired data might not fit in the buffer anymore.

2
  • But signal handlers are callbacks made by the kernel, aren't they? The kernel has to thunk back to user space to make the call, and thunk back to kernel space after the handler returns. So, there is overhead involved, but it's doable. I wish I could install callbacks from open() and close(), to help me track down file descriptor leaks in a large program.
    – WaltK
    Jan 22, 2023 at 18:40
  • 1
    @WaltK The kernel doesn't need to pause something while the handler is running, unlike most useful callbacks. There are plenty of ways to monitor open/close calls and they don't require kernel callbacks: debugger breakpoints, strace (or dtrace or whatever equivalent your Unix variant offers), periodic inspection with lsof, valgrind... Jan 22, 2023 at 22:49

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .