| VERSIONINGSYSCALLS(9) | Kernel Developer's Manual | VERSIONINGSYSCALLS(9) |
versioningsyscalls —
guide on versioning syscalls
NetBSD has the ability to change the ABI of a syscall whilst retaining backwards compatibility with existing code. This means that existing code keeps working the same way as before, and new code can use new features and/or functionality. In the past this has allowed dev_t to move from 16 bits to 32 bits, ino_t and time_t to move from 32 bits to 64 bits, and adding fields to struct kevent without disturbing existing binaries. To achieve this both kernel and userland changes are required.
In the kernel, a new syscall is added with a new ABI, and the old
syscall is retained and moved to a new location that holds the compatibility
syscalls (src/sys/compat). Kernels can be compiled
with or without backwards compatibility syscalls. See the
COMPAT_XX options in
options(4).
In userland, the original syscall stub is moved into src/lib/libc/compat retaining the same symbol name and ABI. The new stub is added to libc, and in the header file the syscall symbol is made to point to the new name with the new ABI.
This is done via symbol renaming instead of ELF versioned symbols for historical reasons. NetBSD has retained binary compatibility with most syscalls since NetBSD 0.9 with the exception of Scheduler Activation syscalls which are not being emulated because of the cost and safety of doing so.
To avoid confusion, the following words are used to disambiguate which version of the system call is being described.
Additionally, CNUM always represents the
last NetBSD release where the current version of the
system call is the default, multiplied by ten and retaining a leading zero.
For example NetBSD 0.9 has
COMPAT_09 whereas NetBSD
10.0 has COMPAT_100.
This section describes what needs to be modified to add the new
version of the syscall. It assumes the current version of the syscall is
my_syscall(struct
my_struct *ms) and that my_struct will be
versioned. If not versioning a struct, passages that mention
my_struct can be ignored.
The syscall version suffix VNUM indicates
the first release of NetBSD the system call will
appear in. The compat version CNUM is the last
version of NetBSD the old system call was used.
Typically VNUM = CNUM + 1 .
For example if you are versioning
getcontext(2) just after
NetBSD 11 was released, and the original system call
was called
getcontext(),
the system call will become
__getcontext12()
and the compat entry point will become
compat_11_getcontext().
Next time
getcontext(2) needs
versioning, for example just after NetBSD 15 was
released, it will become
__getcontext16()
and the compat entry will become
compat_15___getcontext12().
Please note that the historical practice up to NetBSD 11 has been that the syscall suffix matched the version when the syscall was last used.
To version struct my_struct, first make a copy of my_struct renamed to my_structCNUM in an equivalent header in sys/compat/sys. After that, you can freely modify my_struct as desired.
The stub for the next version of the syscall will be
__my_syscallVNUM(),
and will have entry point
sys___my_syscallVNUM().
sys/kern/syscalls.conf may need to be
modified to contain compat_CNUM in the
compatopts variable.
First, add the next syscall to
sys/kern/syscalls.master keeping
my_syscall() as the name, and set the (optional)
compat field of the declaration to CNUM.
Next, modify the current version of the syscall, and replace the
type field (usually just STD) with
COMPAT_CNUM MODULAR compat_CNUM.
The keyword MODULAR indicates that the
system call can be part of a kernel module. Even if the system call was not
part of a module before, now it will be part of the
COMPAT_CNUM module.
Finally, if applicable, replace the types of the current and old versions of the syscall with the compat type.
Overall, the final diff should look like
- 123 STD { int|sys||my_syscall(struct my_struct *ms); }
+ 123 COMPAT_CNUM MODULAR compat_CNUM { int|sys||my_syscall(struct my_structCNUM *ms); }
...
+ 456 STD { int|sys|VNUM|my_syscall(struct my_struct *ms); }
If the current syscall is rump,
sys/rump/Makefile.rump must contain
CNUM in the RUMP_NBCOMPAT
variable.
If versioning structs, then modify sys/kern/makesyscalls.sh by adding an entry for struct my_structCNUM type to uncompattypes.
The uncompattypes map is used in rump(7) system call table generation, to map from the versioned types to the original names since rump(7) wants to have a non-versioned copy of the system call table.
Then regenerate the syscall tables in the usual way, first by running sys/kern/makesyscalls.sh, then if the system call is rump, doing a build in sys/rump and then running sys/rump/makerumpsyscalls.sh passing it the path to the result of the build you just did as its first argument.
This section covers maintaining compatibility at the kernel level,
by adding an entry point for the current syscall in an appropriate compat
module. For the purposes of this section, we assume the current syscall has
entry point
sys_my_syscall()
and lives inside sys/kern/my_file.c.
The compat version of the current syscall has entry point
compat_CNUM_sys_my_syscall(),
and should be implemented in
sys/compat/common/my_file_CNUM.c with the same
semantics as the current syscall. Often this involves translating the
arguments to the next syscall, and then calling that syscall's entry
point.
sys/compat/common/my_file_CNUM.c must contain an array of struct syscall_package that declares the mapping between syscall number and entry point, terminating in a zero element (see sample diff below).
Additionally,
sys/compat/common/my_file_CNUM.c must contain two
functions,
my_file_CNUM_init()
and
my_file_CNUM_fini()
that are used to initialize/clean up anything related to this syscall. At
the minimum they must make calls to
syscall_establish()
and
syscall_disestablish()
respectively, adding and removing the syscalls. The stubs for these
functions should be located in
sys/compat/common/compat_mod.h.
Overall, sys/compat/common/my_file_CNUM.c must at the minimum contain
static const struct syscall_package my_file_CNUM_syscalls[] = {
{ SYS_compat_CNUM_my_syscall, 0,
(sy_call_t *)compat_CNUM_sys_my_syscall },
{ 0, 0, NULL },
};
int
compat_CNUM_my_syscall(...)
{ /* Compat implementation goes here. */ }
int
my_file_CNUM_init(void)
{ return syscall_establish(NULL, my_file_CNUM_syscalls); }
int
my_file_CNUM_fini(void)
{ return syscall_disestablish(NULL, my_file_CNUM_syscalls); }
Finally,
sys/compat/common/compat_CNUM_mod.c needs to be
modified to have its
compat_CNUM_init()
and
compat_CNUM_fini()
functions call my_file_CNUM_init() and
my_file_CNUM_fini() respectively.
If the current syscall has already been versioned, you might need to modify the old compat syscalls in sys/compat/common to either use the next syscall or the current compat syscall. Note that compat code can be made to depend on compat code for more recent releases.
With the exception of the libraries described below, making the
rest of userland work will just involve recompiling, and perhaps changing a
constant or a #define.
A userland version of any old and current versions of the syscall
must be implemented. For the current syscall with stub
my_syscall(struct my_struct *ms)
in sys/sys/my_header.h, an implementation of
my_syscall() must be written in
lib/libc/compat/sys/compat_my_syscall.c.
Additionally, a call to
__warn_references()
must be added in
lib/libc/compat/sys/compat_my_syscall.c to warn of
any uses of the compat syscall and mention how to use the next version of
the syscall. In almost all cases the instructions on how to use the next
version of the syscall will be “include <sys/my_header.h> to
generate correct reference”.
Overall, lib/libc/compat/sys/compat_my_syscall.c must at the minimum include
#include <sys/compat/my_header.h>
__warn_references(my_syscall,
"warning: reference to compatibility my_syscall();"
" message on how to use the next my_syscall()");
int
my_syscall()
{ /* Compat implementation goes here. */ }
| May 20, 2024 | NetBSD 11.0 |