요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
처음부터 확장 가능한 ABI 설계
adding-syscalls.rst:54-103System call ABI는 사실상 영구 지원 대상이다. eventfd2, dup3, inotify_init1, pipe2, renameat2처럼 처음 interface의 확장 공간이 부족해 후속 syscall이 생긴 역사를 반복하지 않으려면 최초 제안 때 future extension을 공개적으로 논의해야 한다.
Argument가 적은 syscall은 flags 인자를 두고 알려지지 않은 bit가 하나라도 켜져 있으면 -EINVAL을 반환한다. 현재 flag가 없더라도 flags == 0을 확인해야 new userspace가 old kernel에서 새 의미가 적용됐다고 오해하지 않는다.
if (flags & ~(THING_FLAG1 | THING_FLAG2 | THING_FLAG3))
return -EINVAL;
Argument가 많다면 pointer로 전달하는 struct에 size field를 넣는다. 뒤에 추가할 field는 0일 때 이전 동작과 같아야 한다. New userspace가 old kernel을 호출하면 old kernel은 자신이 아는 struct size 뒤의 byte가 모두 0인지 확인하고, old userspace가 new kernel을 호출하면 kernel이 작은 struct를 0으로 확장한다.
struct xyzzy_params {
u32 size;
u32 param_1;
u64 param_2;
u64 param_3;
};
Object handle, path, offset와 권한
adding-syscalls.rst:106-173- Kernel object를 userspace에서 참조한다면 새로운 정수 handle 체계를 만들지 말고 file descriptor를 사용한다.
- 새 fd를 반환한다면 syscall flags에 close-on-exec 기능을 포함해 생성과 fcntl(FD_CLOEXEC) 사이의 multi-thread race를 없앤다. Architecture마다 값이 다른 O_CLOEXEC 숫자를 그대로 재사용하지 않는다.
- 반환 fd에서 poll family가 무엇을 의미하는지 정의한다. Readable·writable 상태는 kernel object event를 userspace에 알리는 표준 방식이다.
- Path를 받는다면 dfd와 AT_EMPTY_PATH를 지원할 수 있는 *at 형태를 검토한다. xyzzyat(AT_FDCWD, path)는 path 기반 호출, xyzzyat(fd, "", AT_EMPTY_PATH)는 이미 열린 fd 기반 호출이 된다.
- File offset 인자는 32비트 architecture에서도 64비트를 지원하도록 loff_t를 사용한다.
- Privileged 동작은 관련 Linux capability로 보호하되 지나치게 넓은 CAP_SYS_ADMIN 사용을 피한다.
- 다른 process를 조작한다면 ptrace_may_access()로 동일 권한 또는 필요한 capability를 확인한다.
- 일부 32비트 architecture에서는 명시적 64-bit scalar가 argument 1·3·5처럼 홀수 번호에 있을 때 연속 register pair에 배치하기 쉽다.
Review 가능한 patch series 구성
adding-syscalls.rst:176-193새 syscall 제안은 최소 네 부분을 서로 다른 commit으로 나눈다. Core implementation과 prototype·generic numbering·Kconfig·fallback stub, 한 architecture의 wire-up, tools/testing/selftests 아래 userspace 사용 예제, draft man page다.
Kernel API를 새로 만드는 제안은 linux-api@vger.kernel.org를 반드시 Cc한다. Man page는 별도 repository 대상이더라도 cover letter에 plain text draft를 함께 제공할 수 있다.
Generic syscall 구현과 Linux 6.11 이후 table
adding-syscalls.rst:196-295Entry point를 sys_xyzzy 함수로 직접 선언하지 않고 argument 수에 맞는 SYSCALL_DEFINEn(xyzzy, type, name, ...) macro로 만든다. 이 macro는 tracing과 tooling에 필요한 syscall metadata도 생성한다.
- include/linux/syscalls.h에 asmlinkage long sys_xyzzy(...) prototype을 추가한다.
- Optional 기능이면 보통 init/Kconfig에 CONFIG option을 만들고 help text와 필요 시 EXPERT dependency를 둔다.
- Makefile은 obj-$(CONFIG_XYZZY_SYSCALL) 형태로 source build를 option에 연결한다.
- Option을 끈 configuration도 build되는지 확인한다.
- kernel/sys_ni.c에 COND_SYSCALL(xyzzy)를 넣어 미지원 configuration에서 -ENOSYS fallback을 제공한다.
전통적인 generic table 방식은 include/uapi/asm-generic/unistd.h에 __NR_xyzzy와 __SYSCALL entry를 추가하고 __NR_syscalls를 갱신한다. 같은 merge window의 다른 syscall과 번호가 겹치면 review 중 번호가 바뀔 수 있다.
Linux 6.11부터 arc, arm64, csky, hexagon, loongarch, nios2, openrisc, riscv는 asm-generic/unistd.h 대신 scripts/syscall.tbl의 common entry를 공유한다. Architecture 전용 ABI를 새로 만들면 arch/*/kernel/Makefile.syscalls의 syscall_abis_32·64도 갱신한다.
468 common xyzzy sys_xyzzy
x86 syscall table 연결
adding-syscalls.rst:297-312일반 x86 syscall은 arch/x86/entry/syscalls/syscall_64.tbl에 x86_64와 x32가 공유하는 common entry를, syscall_32.tbl에 i386 entry를 추가한다. Number는 각 table의 namespace에서 배정되며 merge 충돌에 따라 바뀔 수 있다.
/* syscall_64.tbl */
333 common xyzzy sys_xyzzy
/* syscall_32.tbl */
380 i386 xyzzy sys_xyzzy
32비트 userspace compatibility entry
adding-syscalls.rst:315-468대부분의 syscall은 32비트 process가 64비트 kernel에서 native 구현을 그대로 호출할 수 있다. Compat wrapper가 필요한 첫 경우는 kernel이 user memory 안의 pointer, pointer가 든 struct, time_t·off_t·long처럼 word size에 따라 달라지는 정수 또는 그런 field가 든 struct를 해석할 때다.
두 번째 경우는 syscall argument 자체가 loff_t나 __u64처럼 32비트 architecture에서도 명시적 64비트 scalar일 때다. 32비트 호출 규약이 값을 두 개의 32-bit word로 나누므로 wrapper가 다시 조립한다. 반대로 loff_t __user *처럼 명시적 64-bit type을 가리키는 pointer는 pointee layout이 같으므로 이것만으로 compat syscall이 필요하지 않다.
- COMPAT_SYSCALL_DEFINEn으로 compat_sys_xyzzy entry를 만든다.
- include/linux/compat.h에 asmlinkage prototype을 둔다.
- Layout이 다른 struct는 compat_uptr_t, compat_long_t 등을 사용한 compat struct를 정의한다.
- Generic old table은 __SYSCALL 대신 __SC_COMP(native, compat)를 사용한다.
- 6.11 이후 common table architecture는 scripts/syscall.tbl의 compat column에 compat_sys_xyzzy를 추가한다.
- arm64 AArch32용 entry는 arch/arm64/tools/syscall_32.tbl에 추가한다.
- x86 i386 table은 __ia32_compat_sys_xyzzy를 지정한다. x32가 ILP32 pointer layout을 쓰면 별도 __x32_compat entry를, pointer가 없고 native layout과 같으면 64-bit entry를 재사용한다.
일반 위치로 돌아오지 않는 syscall
adding-syscalls.rst:471-519일반 syscall은 다음 userspace instruction으로 돌아가고 stack, 대부분 register와 virtual address space를 유지한다. rt_sigreturn은 복귀 위치를 바꾸고 fork·clone은 address-space 관계를 만들며 execve는 memory image와 경우에 따라 execution architecture까지 바꾼다.
이런 syscall은 kernel stack에 추가 register를 저장·복원하고 복귀 방식을 완전히 제어하는 architecture assembly stub이 필요할 수 있다. x86_64는 entry_64.S의 stub_xyzzy, 32-bit compat는 entry_64_compat.S의 stub32_xyzzy를 table에서 가리킨다. UML은 실제 x86 entry assembly를 build하지 않으므로 sys_call_table_64.c에서 stub을 native implementation에 mapping할 수 있다.
Audit, selftest와 man page
adding-syscalls.rst:521-572Open·exec·socket multiplexor처럼 audit subsystem이 특별 분류하는 syscall과 유사하다면 architecture별 audit classification도 갱신한다. 기존 syscall과 유사한 기능이면 이름을 kernel 전체에서 검색해 seccomp, tracing, audit 같은 특별 처리 누락이 없는지 확인한다.
tools/testing/selftests 아래에 userspace demonstration과 regression test를 둔다. 아직 libc wrapper가 없으므로 syscall()로 호출하고 새 UAPI struct header가 필요하면 install header 경로도 준비한다. x86_64 -m64, i386 -m32, x32 -mx32를 포함해 지원 ABI에서 실행한다.
모든 새 syscall에는 완전한 man page가 필요하다. Groff source가 이상적이고 plain text draft도 가능하다. linux-man@vger.kernel.org를 Cc하고 cover letter에는 reviewer가 바로 읽을 수 있는 rendered text를 넣는다.
Kernel 내부에서 sys_*를 직접 호출하지 않는다
adding-syscalls.rst:575-603sys_xyzzy와 compat_sys_xyzzy는 syscall table을 통한 userspace 진입점이다. Kernel 내부에서 같은 기능이 필요하면 ksys_xyzzy 같은 helper에 실제 동작을 두고 native stub, compat stub과 kernel caller가 helper를 공유한다.
x86_64는 pt_regs를 syscall wrapper에서 해석하는 별도 calling convention을 사용하므로 일반 C call로 sys_*를 부르면 argument 전달이 맞지 않는다. User pointer와 kernel pointer의 접근 규칙도 다르다. 예외는 arch/ 안의 architecture override와 compat wrapper처럼 entry glue를 구현하는 code에 한정된다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. _addsyscalls:
Adding a New System Call
========================
This document describes what's involved in adding a new system call to the
Linux kernel, over and above the normal submission advice in
:ref:`Documentation/process/submitting-patches.rst <submittingpatches>`.
System Call Alternatives
------------------------
The first thing to consider when adding a new system call is whether one of
the alternatives might be suitable instead. Although system calls are the
most traditional and most obvious interaction points between userspace and the
kernel, there are other possibilities -- choose what fits best for your
interface.
- If the operations involved can be made to look like a filesystem-like
object, it may make more sense to create a new filesystem or device. This
also makes it easier to encapsulate the new functionality in a kernel module
rather than requiring it to be built into the main kernel.
- If the new functionality involves operations where the kernel notifies
userspace that something has happened, then returning a new file
descriptor for the relevant object allows userspace to use
``poll``/``select``/``epoll`` to receive that notification.
- However, operations that don't map to
:manpage:`read(2)`/:manpage:`write(2)`-like operations
have to be implemented as :manpage:`ioctl(2)` requests, which can lead
to a somewhat opaque API.
- If you're just exposing runtime system information, a new node in sysfs
(see ``Documentation/filesystems/sysfs.rst``) or the ``/proc`` filesystem may
be more appropriate. However, access to these mechanisms requires that the
relevant filesystem is mounted, which might not always be the case (e.g.
in a namespaced/sandboxed/chrooted environment). Avoid adding any API to
debugfs, as this is not considered a 'production' interface to userspace.
- If the operation is specific to a particular file or file descriptor, then
an additional :manpage:`fcntl(2)` command option may be more appropriate. However,
:manpage:`fcntl(2)` is a multiplexing system call that hides a lot of complexity, so
this option is best for when the new function is closely analogous to
existing :manpage:`fcntl(2)` functionality, or the new functionality is very simple
(for example, getting/setting a simple flag related to a file descriptor).
- If the operation is specific to a particular task or process, then an
additional :manpage:`prctl(2)` command option may be more appropriate. As
with :manpage:`fcntl(2)`, this system call is a complicated multiplexor so
is best reserved for near-analogs of existing ``prctl()`` commands or
getting/setting a simple flag related to a process.
Designing the API: Planning for Extension
-----------------------------------------
A new system call forms part of the API of the kernel, and has to be supported
indefinitely. As such, it's a very good idea to explicitly discuss the
interface on the kernel mailing list, and it's important to plan for future
extensions of the interface.
(The syscall table is littered with historical examples where this wasn't done,
together with the corresponding follow-up system calls --
``eventfd``/``eventfd2``, ``dup2``/``dup3``, ``inotify_init``/``inotify_init1``,
``pipe``/``pipe2``, ``renameat``/``renameat2`` -- so
learn from the history of the kernel and plan for extensions from the start.)
For simpler system calls that only take a couple of arguments, the preferred
way to allow for future extensibility is to include a flags argument to the
system call. To make sure that userspace programs can safely use flags
between kernel versions, check whether the flags value holds any unknown
flags, and reject the system call (with ``EINVAL``) if it does::
if (flags & ~(THING_FLAG1 | THING_FLAG2 | THING_FLAG3))
return -EINVAL;
(If no flags values are used yet, check that the flags argument is zero.)
For more sophisticated system calls that involve a larger number of arguments,
it's preferred to encapsulate the majority of the arguments into a structure
that is passed in by pointer. Such a structure can cope with future extension
by including a size argument in the structure::
struct xyzzy_params {
u32 size; /* userspace sets p->size = sizeof(struct xyzzy_params) */
u32 param_1;
u64 param_2;
u64 param_3;
};
As long as any subsequently added field, say ``param_4``, is designed so that a
zero value gives the previous behaviour, then this allows both directions of
version mismatch:
- To cope with a later userspace program calling an older kernel, the kernel
code should check that any memory beyond the size of the structure that it
expects is zero (effectively checking that ``param_4 == 0``).
- To cope with an older userspace program calling a newer kernel, the kernel
code can zero-extend a smaller instance of the structure (effectively
setting ``param_4 = 0``).
See :manpage:`perf_event_open(2)` and the ``perf_copy_attr()`` function (in
``kernel/events/core.c``) for an example of this approach.
Designing the API: Other Considerations
---------------------------------------
If your new system call allows userspace to refer to a kernel object, it
should use a file descriptor as the handle for that object -- don't invent a
new type of userspace object handle when the kernel already has mechanisms and
well-defined semantics for using file descriptors.
If your new :manpage:`xyzzy(2)` system call does return a new file descriptor,
then the flags argument should include a value that is equivalent to setting
``O_CLOEXEC`` on the new FD. This makes it possible for userspace to close
the timing window between ``xyzzy()`` and calling
``fcntl(fd, F_SETFD, FD_CLOEXEC)``, where an unexpected ``fork()`` and
``execve()`` in another thread could leak a descriptor to
the exec'ed program. (However, resist the temptation to re-use the actual value
of the ``O_CLOEXEC`` constant, as it is architecture-specific and is part of a
numbering space of ``O_*`` flags that is fairly full.)
If your system call returns a new file descriptor, you should also consider
what it means to use the :manpage:`poll(2)` family of system calls on that file
descriptor. Making a file descriptor ready for reading or writing is the
normal way for the kernel to indicate to userspace that an event has
occurred on the corresponding kernel object.
If your new :manpage:`xyzzy(2)` system call involves a filename argument::
int sys_xyzzy(const char __user *path, ..., unsigned int flags);
you should also consider whether an :manpage:`xyzzyat(2)` version is more appropriate::
int sys_xyzzyat(int dfd, const char __user *path, ..., unsigned int flags);
This allows more flexibility for how userspace specifies the file in question;
in particular it allows userspace to request the functionality for an
already-opened file descriptor using the ``AT_EMPTY_PATH`` flag, effectively
giving an :manpage:`fxyzzy(3)` operation for free::
- xyzzyat(AT_FDCWD, path, ..., 0) is equivalent to xyzzy(path,...)
- xyzzyat(fd, "", ..., AT_EMPTY_PATH) is equivalent to fxyzzy(fd, ...)
(For more details on the rationale of the \*at() calls, see the
:manpage:`openat(2)` man page; for an example of AT_EMPTY_PATH, see the
:manpage:`fstatat(2)` man page.)
If your new :manpage:`xyzzy(2)` system call involves a parameter describing an
offset within a file, make its type ``loff_t`` so that 64-bit offsets can be
supported even on 32-bit architectures.
If your new :manpage:`xyzzy(2)` system call involves privileged functionality,
it needs to be governed by the appropriate Linux capability bit (checked with
a call to ``capable()``), as described in the :manpage:`capabilities(7)` man
page. Choose an existing capability bit that governs related functionality,
but try to avoid combining lots of only vaguely related functions together
under the same bit, as this goes against capabilities' purpose of splitting
the power of root. In particular, avoid adding new uses of the already
overly-general ``CAP_SYS_ADMIN`` capability.
If your new :manpage:`xyzzy(2)` system call manipulates a process other than
the calling process, it should be restricted (using a call to
``ptrace_may_access()``) so that only a calling process with the same
permissions as the target process, or with the necessary capabilities, can
manipulate the target process.
Finally, be aware that some non-x86 architectures have an easier time if
system call parameters that are explicitly 64-bit fall on odd-numbered
arguments (i.e. parameter 1, 3, 5), to allow use of contiguous pairs of 32-bit
registers. (This concern does not apply if the arguments are part of a
structure that's passed in by pointer.)
Proposing the API
-----------------
To make new system calls easy to review, it's best to divide up the patchset
into separate chunks. These should include at least the following items as
distinct commits (each of which is described further below):
- The core implementation of the system call, together with prototypes,
generic numbering, Kconfig changes and fallback stub implementation.
- Wiring up of the new system call for one particular architecture, usually
x86 (including all of x86_64, x86_32 and x32).
- A demonstration of the use of the new system call in userspace via a
selftest in ``tools/testing/selftests/``.
- A draft man-page for the new system call, either as plain text in the
cover letter, or as a patch to the (separate) man-pages repository.
New system call proposals, like any change to the kernel's API, should always
be cc'ed to linux-api@vger.kernel.org.
Generic System Call Implementation
----------------------------------
The main entry point for your new :manpage:`xyzzy(2)` system call will be called
``sys_xyzzy()``, but you add this entry point with the appropriate
``SYSCALL_DEFINEn()`` macro rather than explicitly. The 'n' indicates the
number of arguments to the system call, and the macro takes the system call name
followed by the (type, name) pairs for the parameters as arguments. Using
this macro allows metadata about the new system call to be made available for
other tools.
The new entry point also needs a corresponding function prototype, in
``include/linux/syscalls.h``, marked as asmlinkage to match the way that system
calls are invoked::
asmlinkage long sys_xyzzy(...);
Some architectures (e.g. x86) have their own architecture-specific syscall
tables, but several other architectures share a generic syscall table. Add your
new system call to the generic list by adding an entry to the list in
``include/uapi/asm-generic/unistd.h``::
#define __NR_xyzzy 292
__SYSCALL(__NR_xyzzy, sys_xyzzy)
Also update the __NR_syscalls count to reflect the additional system call, and
note that if multiple new system calls are added in the same merge window,
your new syscall number may get adjusted to resolve conflicts.
The file ``kernel/sys_ni.c`` provides a fallback stub implementation of each
system call, returning ``-ENOSYS``. Add your new system call here too::
COND_SYSCALL(xyzzy);
Your new kernel functionality, and the system call that controls it, should
normally be optional, so add a ``CONFIG`` option (typically to
``init/Kconfig``) for it. As usual for new ``CONFIG`` options:
- Include a description of the new functionality and system call controlled
by the option.
- Make the option depend on EXPERT if it should be hidden from normal users.
- Make any new source files implementing the function dependent on the CONFIG
option in the Makefile (e.g. ``obj-$(CONFIG_XYZZY_SYSCALL) += xyzzy.o``).
- Double check that the kernel still builds with the new CONFIG option turned
off.
To summarize, you need a commit that includes:
- ``CONFIG`` option for the new function, normally in ``init/Kconfig``
- ``SYSCALL_DEFINEn(xyzzy, ...)`` for the entry point
- corresponding prototype in ``include/linux/syscalls.h``
- generic table entry in ``include/uapi/asm-generic/unistd.h``
- fallback stub in ``kernel/sys_ni.c``
.. _syscall_generic_6_11:
Since 6.11
~~~~~~~~~~
Starting with kernel version 6.11, general system call implementation for the
following architectures no longer requires modifications to
``include/uapi/asm-generic/unistd.h``:
- arc
- arm64
- csky
- hexagon
- loongarch
- nios2
- openrisc
- riscv
Instead, you need to update ``scripts/syscall.tbl`` and, if applicable, adjust
``arch/*/kernel/Makefile.syscalls``.
As ``scripts/syscall.tbl`` serves as a common syscall table across multiple
architectures, a new entry is required in this table::
468 common xyzzy sys_xyzzy
Note that adding an entry to ``scripts/syscall.tbl`` with the "common" ABI
also affects all architectures that share this table. For more limited or
architecture-specific changes, consider using an architecture-specific ABI or
defining a new one.
If a new ABI, say ``xyz``, is introduced, the corresponding updates should be
made to ``arch/*/kernel/Makefile.syscalls`` as well::
syscall_abis_{32,64} += xyz (...)
To summarize, you need a commit that includes:
- ``CONFIG`` option for the new function, normally in ``init/Kconfig``
- ``SYSCALL_DEFINEn(xyzzy, ...)`` for the entry point
- corresponding prototype in ``include/linux/syscalls.h``
- new entry in ``scripts/syscall.tbl``
- (if needed) Makefile updates in ``arch/*/kernel/Makefile.syscalls``
- fallback stub in ``kernel/sys_ni.c``
x86 System Call Implementation
------------------------------
To wire up your new system call for x86 platforms, you need to update the
master syscall tables. Assuming your new system call isn't special in some
way (see below), this involves a "common" entry (for x86_64 and x32) in
arch/x86/entry/syscalls/syscall_64.tbl::
333 common xyzzy sys_xyzzy
and an "i386" entry in ``arch/x86/entry/syscalls/syscall_32.tbl``::
380 i386 xyzzy sys_xyzzy
Again, these numbers are liable to be changed if there are conflicts in the
relevant merge window.
Compatibility System Calls (Generic)
------------------------------------
For most system calls the same 64-bit implementation can be invoked even when
the userspace program is itself 32-bit; even if the system call's parameters
include an explicit pointer, this is handled transparently.
However, there are a couple of situations where a compatibility layer is
needed to cope with size differences between 32-bit and 64-bit.
The first is if the 64-bit kernel also supports 32-bit userspace programs, and
so needs to parse areas of (``__user``) memory that could hold either 32-bit or
64-bit values. In particular, this is needed whenever a system call argument
is:
- a pointer to a pointer
- a pointer to a struct containing a pointer (e.g. ``struct iovec __user *``)
- a pointer to a varying sized integral type (``time_t``, ``off_t``,
``long``, ...)
- a pointer to a struct containing a varying sized integral type.
The second situation that requires a compatibility layer is if one of the
system call's arguments has a type that is explicitly 64-bit even on a 32-bit
architecture, for example ``loff_t`` or ``__u64``. In this case, a value that
arrives at a 64-bit kernel from a 32-bit application will be split into two
32-bit values, which then need to be re-assembled in the compatibility layer.
(Note that a system call argument that's a pointer to an explicit 64-bit type
does **not** need a compatibility layer; for example, :manpage:`splice(2)`'s arguments of
type ``loff_t __user *`` do not trigger the need for a ``compat_`` system call.)
The compatibility version of the system call is called ``compat_sys_xyzzy()``,
and is added with the ``COMPAT_SYSCALL_DEFINEn()`` macro, analogously to
SYSCALL_DEFINEn. This version of the implementation runs as part of a 64-bit
kernel, but expects to receive 32-bit parameter values and does whatever is
needed to deal with them. (Typically, the ``compat_sys_`` version converts the
values to 64-bit versions and either calls on to the ``sys_`` version, or both of
them call a common inner implementation function.)
The compat entry point also needs a corresponding function prototype, in
``include/linux/compat.h``, marked as asmlinkage to match the way that system
calls are invoked::
asmlinkage long compat_sys_xyzzy(...);
If the system call involves a structure that is laid out differently on 32-bit
and 64-bit systems, say ``struct xyzzy_args``, then the include/linux/compat.h
header file should also include a compat version of the structure (``struct
compat_xyzzy_args``) where each variable-size field has the appropriate
``compat_`` type that corresponds to the type in ``struct xyzzy_args``. The
``compat_sys_xyzzy()`` routine can then use this ``compat_`` structure to
parse the arguments from a 32-bit invocation.
For example, if there are fields::
struct xyzzy_args {
const char __user *ptr;
__kernel_long_t varying_val;
u64 fixed_val;
/* ... */
};
in struct xyzzy_args, then struct compat_xyzzy_args would have::
struct compat_xyzzy_args {
compat_uptr_t ptr;
compat_long_t varying_val;
u64 fixed_val;
/* ... */
};
The generic system call list also needs adjusting to allow for the compat
version; the entry in ``include/uapi/asm-generic/unistd.h`` should use
``__SC_COMP`` rather than ``__SYSCALL``::
#define __NR_xyzzy 292
__SC_COMP(__NR_xyzzy, sys_xyzzy, compat_sys_xyzzy)
To summarize, you need:
- a ``COMPAT_SYSCALL_DEFINEn(xyzzy, ...)`` for the compat entry point
- corresponding prototype in ``include/linux/compat.h``
- (if needed) 32-bit mapping struct in ``include/linux/compat.h``
- instance of ``__SC_COMP`` not ``__SYSCALL`` in
``include/uapi/asm-generic/unistd.h``
Since 6.11
~~~~~~~~~~
This applies to all the architectures listed in :ref:`Since 6.11<syscall_generic_6_11>`
under "Generic System Call Implementation", except arm64. See
:ref:`Compatibility System Calls (arm64)<compat_arm64>` for more information.
You need to extend the entry in ``scripts/syscall.tbl`` with an extra column
to indicate that a 32-bit userspace program running on a 64-bit kernel should
hit the compat entry point::
468 common xyzzy sys_xyzzy compat_sys_xyzzy
To summarize, you need:
- ``COMPAT_SYSCALL_DEFINEn(xyzzy, ...)`` for the compat entry point
- corresponding prototype in ``include/linux/compat.h``
- modification of the entry in ``scripts/syscall.tbl`` to include an extra
"compat" column
- (if needed) 32-bit mapping struct in ``include/linux/compat.h``
.. _compat_arm64:
Compatibility System Calls (arm64)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
On arm64, there is a dedicated syscall table for compatibility system calls
targeting 32-bit (AArch32) userspace: ``arch/arm64/tools/syscall_32.tbl``.
You need to add an additional line to this table specifying the compat
entry point::
468 common xyzzy sys_xyzzy compat_sys_xyzzy
Compatibility System Calls (x86)
--------------------------------
To wire up the x86 architecture of a system call with a compatibility version,
the entries in the syscall tables need to be adjusted.
First, the entry in ``arch/x86/entry/syscalls/syscall_32.tbl`` gets an extra
column to indicate that a 32-bit userspace program running on a 64-bit kernel
should hit the compat entry point::
380 i386 xyzzy sys_xyzzy __ia32_compat_sys_xyzzy
Second, you need to figure out what should happen for the x32 ABI version of
the new system call. There's a choice here: the layout of the arguments
should either match the 64-bit version or the 32-bit version.
If there's a pointer-to-a-pointer involved, the decision is easy: x32 is
ILP32, so the layout should match the 32-bit version, and the entry in
``arch/x86/entry/syscalls/syscall_64.tbl`` is split so that x32 programs hit
the compatibility wrapper::
333 64 xyzzy sys_xyzzy
...
555 x32 xyzzy __x32_compat_sys_xyzzy
If no pointers are involved, then it is preferable to re-use the 64-bit system
call for the x32 ABI (and consequently the entry in
arch/x86/entry/syscalls/syscall_64.tbl is unchanged).
In either case, you should check that the types involved in your argument
layout do indeed map exactly from x32 (-mx32) to either the 32-bit (-m32) or
64-bit (-m64) equivalents.
System Calls Returning Elsewhere
--------------------------------
For most system calls, once the system call is complete the user program
continues exactly where it left off -- at the next instruction, with the
stack the same and most of the registers the same as before the system call,
and with the same virtual memory space.
However, a few system calls do things differently. They might return to a
different location (``rt_sigreturn``) or change the memory space
(``fork``/``vfork``/``clone``) or even architecture (``execve``/``execveat``)
of the program.
To allow for this, the kernel implementation of the system call may need to
save and restore additional registers to the kernel stack, allowing complete
control of where and how execution continues after the system call.
This is arch-specific, but typically involves defining assembly entry points
that save/restore additional registers and invoke the real system call entry
point.
For x86_64, this is implemented as a ``stub_xyzzy`` entry point in
``arch/x86/entry/entry_64.S``, and the entry in the syscall table
(``arch/x86/entry/syscalls/syscall_64.tbl``) is adjusted to match::
333 common xyzzy stub_xyzzy
The equivalent for 32-bit programs running on a 64-bit kernel is normally
called ``stub32_xyzzy`` and implemented in ``arch/x86/entry/entry_64_compat.S``,
with the corresponding syscall table adjustment in
``arch/x86/entry/syscalls/syscall_32.tbl``::
380 i386 xyzzy sys_xyzzy stub32_xyzzy
If the system call needs a compatibility layer (as in the previous section)
then the ``stub32_`` version needs to call on to the ``compat_sys_`` version
of the system call rather than the native 64-bit version. Also, if the x32 ABI
implementation is not common with the x86_64 version, then its syscall
table will also need to invoke a stub that calls on to the ``compat_sys_``
version.
For completeness, it's also nice to set up a mapping so that user-mode Linux
still works -- its syscall table will reference stub_xyzzy, but the UML build
doesn't include ``arch/x86/entry/entry_64.S`` implementation (because UML
simulates registers etc). Fixing this is as simple as adding a #define to
``arch/x86/um/sys_call_table_64.c``::
#define stub_xyzzy sys_xyzzy
Other Details
-------------
Most of the kernel treats system calls in a generic way, but there is the
occasional exception that may need updating for your particular system call.
The audit subsystem is one such special case; it includes (arch-specific)
functions that classify some special types of system call -- specifically
file open (``open``/``openat``), program execution (``execve``/``exeveat``) or
socket multiplexor (``socketcall``) operations. If your new system call is
analogous to one of these, then the audit system should be updated.
More generally, if there is an existing system call that is analogous to your
new system call, it's worth doing a kernel-wide grep for the existing system
call to check there are no other special cases.
Testing
-------
A new system call should obviously be tested; it is also useful to provide
reviewers with a demonstration of how user space programs will use the system
call. A good way to combine these aims is to include a simple self-test
program in a new directory under ``tools/testing/selftests/``.
For a new system call, there will obviously be no libc wrapper function and so
the test will need to invoke it using ``syscall()``; also, if the system call
involves a new userspace-visible structure, the corresponding header will need
to be installed to compile the test.
Make sure the selftest runs successfully on all supported architectures. For
example, check that it works when compiled as an x86_64 (-m64), x86_32 (-m32)
and x32 (-mx32) ABI program.
For more extensive and thorough testing of new functionality, you should also
consider adding tests to the Linux Test Project, or to the xfstests project
for filesystem-related changes.
- https://linux-test-project.github.io/
- git://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git
Man Page
--------
All new system calls should come with a complete man page, ideally using groff
markup, but plain text will do. If groff is used, it's helpful to include a
pre-rendered ASCII version of the man page in the cover email for the
patchset, for the convenience of reviewers.
The man page should be cc'ed to linux-man@vger.kernel.org
For more details, see https://www.kernel.org/doc/man-pages/patches.html
Do not call System Calls in the Kernel
--------------------------------------
System calls are, as stated above, interaction points between userspace and
the kernel. Therefore, system call functions such as ``sys_xyzzy()`` or
``compat_sys_xyzzy()`` should only be called from userspace via the syscall
table, but not from elsewhere in the kernel. If the syscall functionality is
useful to be used within the kernel, needs to be shared between an old and a
new syscall, or needs to be shared between a syscall and its compatibility
variant, it should be implemented by means of a "helper" function (such as
``ksys_xyzzy()``). This kernel function may then be called within the
syscall stub (``sys_xyzzy()``), the compatibility syscall stub
(``compat_sys_xyzzy()``), and/or other kernel code.
At least on 64-bit x86, it will be a hard requirement from v4.17 onwards to not
call system call functions in the kernel. It uses a different calling
convention for system calls where ``struct pt_regs`` is decoded on-the-fly in a
syscall wrapper which then hands processing over to the actual syscall function.
This means that only those parameters which are actually needed for a specific
syscall are passed on during syscall entry, instead of filling in six CPU
registers with random user space content all the time (which may cause serious
trouble down the call chain).
Moreover, rules on how data may be accessed may differ between kernel data and
user data. This is another reason why calling ``sys_xyzzy()`` is generally a
bad idea.
Exceptions to this rule are only allowed in architecture-specific overrides,
architecture-specific compatibility wrappers, or other code in arch/.
References and Sources
----------------------
- LWN article from Michael Kerrisk on use of flags argument in system calls:
https://lwn.net/Articles/585415/
- LWN article from Michael Kerrisk on how to handle unknown flags in a system
call: https://lwn.net/Articles/588444/
- LWN article from Jake Edge describing constraints on 64-bit system call
arguments: https://lwn.net/Articles/311630/
- Pair of LWN articles from David Drysdale that describe the system call
implementation paths in detail for v3.14:
- https://lwn.net/Articles/604287/
- https://lwn.net/Articles/604515/
- Architecture-specific requirements for system calls are discussed in the
:manpage:`syscall(2)` man-page:
http://man7.org/linux/man-pages/man2/syscall.2.html#NOTES
- Collated emails from Linus Torvalds discussing the problems with ``ioctl()``:
https://yarchive.net/comp/linux/ioctl.html
- "How to not invent kernel interfaces", Arnd Bergmann,
https://www.ukuug.org/events/linux2007/2007/papers/Bergmann.pdf
- LWN article from Michael Kerrisk on avoiding new uses of CAP_SYS_ADMIN:
https://lwn.net/Articles/486306/
- Recommendation from Andrew Morton that all related information for a new
system call should come in the same email thread:
https://lore.kernel.org/r/20140724144747.3041b208832bbdf9fbce5d96@linux-foundation.org
- Recommendation from Michael Kerrisk that a new system call should come with
a man page: https://lore.kernel.org/r/CAKgNAkgMA39AfoSoA5Pe1r9N+ZzfYQNvNPvcRN7tOvRb8+v06Q@mail.gmail.com
- Suggestion from Thomas Gleixner that x86 wire-up should be in a separate
commit: https://lore.kernel.org/r/alpine.DEB.2.11.1411191249560.3909@nanos
- Suggestion from Greg Kroah-Hartman that it's good for new system calls to
come with a man-page & selftest: https://lore.kernel.org/r/20140320025530.GA25469@kroah.com
- Discussion from Michael Kerrisk of new system call vs. :manpage:`prctl(2)` extension:
https://lore.kernel.org/r/CAHO5Pa3F2MjfTtfNxa8LbnkeeU8=YJ+9tDqxZpw7Gz59E-4AUg@mail.gmail.com
- Suggestion from Ingo Molnar that system calls that involve multiple
arguments should encapsulate those arguments in a struct, which includes a
size field for future extensibility: https://lore.kernel.org/r/20150730083831.GA22182@gmail.com
- Numbering oddities arising from (re-)use of O_* numbering space flags:
- commit 75069f2b5bfb ("vfs: renumber FMODE_NONOTIFY and add to uniqueness
check")
- commit 12ed2e36c98a ("fanotify: FMODE_NONOTIFY and __O_SYNC in sparc
conflict")
- commit bb458c644a59 ("Safer ABI for O_TMPFILE")
- Discussion from Matthew Wilcox about restrictions on 64-bit arguments:
https://lore.kernel.org/r/20081212152929.GM26095@parisc-linux.org
- Recommendation from Greg Kroah-Hartman that unknown flags should be
policed: https://lore.kernel.org/r/20140717193330.GB4703@kroah.com
- Recommendation from Linus Torvalds that x32 system calls should prefer
compatibility with 64-bit versions rather than 32-bit versions:
https://lore.kernel.org/r/CA+55aFxfmwfB7jbbrXxa=K7VBYPfAvmu3XOkGrLbB1UFjX1+Ew@mail.gmail.com
- Patch series revising system call table infrastructure to use
scripts/syscall.tbl across multiple architectures:
https://lore.kernel.org/lkml/20240704143611.2979589-1-arnd@kernel.org
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
새 system call을 추가하기 전에
1-52이 문서는 Documentation/process/submitting-patches.rst의 일반적인 제출 지침에 더해 Linux kernel에 새 system call을 추가할 때 필요한 사항을 설명한다.
가장 먼저 검토할 것은 system call이 아닌 다른 interface가 더 적합한지다. System call은 userspace와 kernel이 상호 작용하는 가장 전통적이고 분명한 지점이지만 유일한 방법은 아니므로 interface 성격에 가장 잘 맞는 방식을 선택해야 한다.
Filesystem 또는 device
관련 operation을 filesystem-like object로 표현할 수 있다면 새 filesystem이나 device를 만드는 편이 더 적절할 수 있다. 이 방식은 기능을 main kernel에 built-in으로 넣지 않고 kernel module로 캡슐화하기도 쉽다.
Kernel이 어떤 사건의 발생을 userspace에 알리는 기능이라면 관련 object의 새 file descriptor를 반환하게 만들 수 있다. 그러면 userspace는 poll/select/epoll로 notification을 받을 수 있다. 반면 read(2)나 write(2) 형태로 대응되지 않는 operation은 ioctl(2) request로 구현해야 하므로 API가 다소 불투명해질 수 있다.
sysfs, /proc, fcntl(2), prctl(2)
Runtime system information을 노출하기만 한다면 Documentation/filesystems/sysfs.rst가 설명하는 sysfs의 새 node나 /proc filesystem이 더 적절할 수 있다. 다만 이 mechanism에 접근하려면 해당 filesystem이 mount되어 있어야 하며 namespaced, sandboxed, chrooted environment에서는 그렇지 않을 수 있다. Debugfs는 production userspace interface로 간주되지 않으므로 API를 추가해서는 안 된다.
특정 file이나 file descriptor에만 적용되는 operation이면 fcntl(2) command option을 추가하는 방안이 더 적절할 수 있다. 그러나 fcntl(2)은 많은 복잡성을 감추는 multiplexing system call이므로 기존 fcntl(2) 기능과 매우 유사하거나 file descriptor 관련 단순 flag를 얻고 설정하는 정도의 기능에 가장 알맞다.
특정 task나 process에 적용되는 operation이면 prctl(2) command option을 추가할 수 있다. prctl(2) 역시 복잡한 multiplexor이므로 기존 prctl() command와 거의 같은 기능이나 process 관련 단순 flag의 get/set에 한정하는 편이 좋다.
확장 가능한 API 설계
54-103새 system call은 kernel API의 일부가 되며 사실상 무기한 지원해야 한다. 따라서 interface를 kernel mailing list에서 명시적으로 논의하고 처음부터 향후 확장을 계획하는 것이 중요하다.
Syscall table에는 확장을 미리 고려하지 않아 후속 system call이 생긴 역사적 사례가 많다. eventfd/eventfd2, dup2/dup3, inotify_init/inotify_init1, pipe/pipe2, renameat/renameat2가 그 예다. Kernel의 역사를 보고 처음부터 확장 지점을 설계해야 한다.
Argument가 적으면 flags를 둔다
Argument가 두어 개뿐인 단순한 system call은 향후 확장을 위해 flags argument를 두는 방식이 선호된다. 서로 다른 kernel version에서 userspace program이 flag를 안전하게 쓰게 하려면 알려지지 않은 bit가 있는지 검사하고, 있으면 EINVAL로 system call을 거부한다.
if (flags & ~(THING_FLAG1 | THING_FLAG2 | THING_FLAG3))
return -EINVAL;
아직 정의된 flag가 하나도 없다면 flags argument가 0인지 검사한다.
Argument가 많으면 size field가 있는 struct를 쓴다
Argument가 많은 복잡한 system call은 대부분의 argument를 structure에 넣고 pointer로 전달하는 방식이 선호된다. Structure 안에 size argument를 두면 미래의 field 추가를 처리할 수 있다.
struct xyzzy_params {
u32 size; /* userspace sets p->size = sizeof(struct xyzzy_params) */
u32 param_1;
u64 param_2;
u64 param_3;
};
나중에 추가하는 param_4 같은 field의 0 값이 과거 동작을 뜻하도록 설계하면 양방향 version mismatch를 모두 처리할 수 있다.
- 새 userspace program이 오래된 kernel을 호출할 때: kernel이 알고 있는 structure 크기 너머의 memory가 모두 0인지 검사한다. 사실상 param_4 == 0을 확인하는 셈이다.
- 오래된 userspace program이 새 kernel을 호출할 때: kernel은 작은 structure instance를 0으로 확장해 사실상 param_4 = 0으로 만든다.
이 pattern의 실제 예는 perf_event_open(2)과 kernel/events/core.c의 perf_copy_attr()에서 볼 수 있다.
File descriptor, pathname, capability와 argument 배치
106-173새 system call이 userspace에서 kernel object를 가리키게 한다면 그 object handle로 file descriptor를 사용해야 한다. Kernel에 file descriptor를 위한 mechanism과 잘 정의된 semantics가 이미 있으므로 새로운 userspace object handle type을 만들지 않는다.
새 FD와 close-on-exec
새 xyzzy(2)가 file descriptor를 반환한다면 flags argument에는 새 FD에 O_CLOEXEC를 설정한 것과 같은 값을 포함해야 한다. 그래야 xyzzy() 반환 뒤 fcntl(fd, F_SETFD, FD_CLOEXEC)를 호출하기 전의 timing window를 없앨 수 있다. 이 window에서 다른 thread가 예기치 않게 fork()와 execve()를 수행하면 descriptor가 exec된 program으로 새어 나갈 수 있다.
실제 O_CLOEXEC 상수 값을 그대로 재사용하려는 유혹은 피한다. 이 값은 architecture-specific이고 이미 상당히 가득 찬 O_* flag numbering space의 일부다.
새 FD를 반환한다면 그 descriptor에 poll(2) 계열 system call을 적용했을 때의 의미도 설계해야 한다. FD를 read-ready 또는 write-ready로 만드는 것은 해당 kernel object에서 event가 발생했음을 kernel이 userspace에 알리는 일반적인 방법이다.
Pathname API는 *at 형태를 검토한다
xyzzy(2)가 filename argument를 받는다면 xyzzyat(2) 형태가 더 적절한지 검토한다.
int sys_xyzzy(const char __user *path, ..., unsigned int flags);
int sys_xyzzyat(int dfd, const char __user *path, ..., unsigned int flags);
xyzzyat(2)는 userspace가 대상 file을 지정하는 방법을 더 유연하게 만든다. 특히 AT_EMPTY_PATH를 사용하면 이미 open한 file descriptor에 기능을 적용할 수 있어 사실상 fxyzzy(3) operation까지 별도 system call 없이 얻는다.
xyzzyat(AT_FDCWD, path, ..., 0) == xyzzy(path, ...)
xyzzyat(fd, "", ..., AT_EMPTY_PATH) == fxyzzy(fd, ...)
*at() call의 근거는 openat(2), AT_EMPTY_PATH 예는 fstatat(2) man page에서 확인할 수 있다.
Offset, privilege와 target process
File 내부 offset을 나타내는 parameter는 32-bit architecture에서도 64-bit offset을 지원하도록 loff_t를 사용한다.
Privileged functionality는 capabilities(7)에 설명된 적절한 Linux capability bit로 통제하고 capable()로 검사한다. 관련 기능을 다스리는 기존 capability bit를 선택하되, root 권한을 나누려는 capability의 목적을 해치지 않도록 느슨하게 관련된 기능을 하나의 bit에 지나치게 묶지 않는다. 특히 이미 지나치게 일반적인 CAP_SYS_ADMIN의 새 사용은 피한다.
호출 process가 아닌 다른 process를 조작하는 system call은 ptrace_may_access()로 제한한다. Target process와 같은 permission을 가진 caller 또는 필요한 capability가 있는 caller만 target을 조작할 수 있어야 한다.
일부 non-x86 architecture는 명시적인 64-bit parameter가 홀수 번째 argument, 즉 1·3·5번에 있을 때 연속된 32-bit register 두 개를 사용하기 쉽다. Argument가 pointer로 전달되는 structure 안에 있다면 이 고려 사항은 적용되지 않는다.
API proposal과 patchset 구성
176-193새 system call을 쉽게 review할 수 있도록 patchset을 독립된 묶음으로 나누는 것이 좋다. 최소한 다음 항목은 서로 다른 commit이어야 한다.
- System call core implementation, prototype, generic numbering, Kconfig 변경, fallback stub implementation
- 특정 architecture의 wiring. 보통 x86이며 x86_64, x86_32, x32를 모두 포함한다.
- tools/testing/selftests/ 아래 selftest로 작성한 userspace 사용 예
- Cover letter에 plain text로 넣거나 별도 man-pages repository patch로 제출하는 새 system call의 draft man page
Kernel API의 다른 변경과 마찬가지로 새 system call proposal은 항상 linux-api@vger.kernel.org를 CC해야 한다.
Core API와 architecture wiring, userspace 검증, 문서를 분리하면 각 책임과 review 범위가 분명해진다.
Generic system call implementation
196-249새 xyzzy(2)의 주 entry point 이름은 sys_xyzzy()지만 function을 직접 선언해 정의하지 않고 적절한 SYSCALL_DEFINEn() macro로 추가한다. n은 system call argument 수다. Macro에는 system call 이름 뒤에 각 parameter의 (type, name) pair를 전달한다. 이 macro를 쓰면 새 system call의 metadata를 다른 tool이 이용할 수 있다.
System call 호출 방식과 맞추기 위해 include/linux/syscalls.h에 asmlinkage로 표시한 대응 prototype도 필요하다.
asmlinkage long sys_xyzzy(...);
x86 같은 architecture는 자체 syscall table을 가지지만 여러 architecture는 generic table을 공유한다. Generic 목록에 추가할 때는 include/uapi/asm-generic/unistd.h에 entry를 넣는다.
#define __NR_xyzzy 292
__SYSCALL(__NR_xyzzy, sys_xyzzy)
추가된 system call을 반영해 __NR_syscalls count도 갱신한다. 같은 merge window에 새 system call이 여러 개 들어오면 conflict를 해결하기 위해 syscall number가 바뀔 수 있다.
kernel/sys_ni.c는 각 system call이 구현되지 않았을 때 -ENOSYS를 반환하는 fallback stub를 제공한다. 새 system call도 여기에 추가한다.
COND_SYSCALL(xyzzy);
새 kernel 기능과 이를 제어하는 system call은 보통 optional이어야 하므로 CONFIG option을 추가한다. 일반적으로 init/Kconfig에 넣는다. 새 기능과 system call을 설명하고, 일반 사용자에게 숨겨야 한다면 EXPERT에 depend하게 한다. 새 source file은 Makefile에서 CONFIG option에 종속시킨다.
obj-$(CONFIG_XYZZY_SYSCALL) += xyzzy.o
새 CONFIG option을 끈 상태에서도 kernel이 build되는지 반드시 다시 확인한다.
Generic core commit의 필수 항목
- 보통 init/Kconfig에 두는 새 기능의 CONFIG option
- Entry point를 정의하는 SYSCALL_DEFINEn(xyzzy, ...)
- include/linux/syscalls.h의 대응 prototype
- include/uapi/asm-generic/unistd.h의 generic table entry
- kernel/sys_ni.c의 fallback stub
Kernel 6.11 이후 공통 syscall table
251-294Kernel 6.11부터 arc, arm64, csky, hexagon, loongarch, nios2, openrisc, riscv의 일반 system call implementation은 include/uapi/asm-generic/unistd.h를 수정하지 않는다.
대신 scripts/syscall.tbl을 갱신하고 필요한 경우 arch/*/kernel/Makefile.syscalls를 조정한다. scripts/syscall.tbl은 여러 architecture가 공유하는 common syscall table이므로 다음과 같은 새 entry가 필요하다.
468 common xyzzy sys_xyzzy
common ABI entry를 scripts/syscall.tbl에 추가하면 table을 공유하는 모든 architecture에 영향을 준다. 더 제한적이거나 architecture-specific한 변경이면 architecture-specific ABI를 쓰거나 새 ABI를 정의한다.
xyz라는 새 ABI를 도입했다면 arch/*/kernel/Makefile.syscalls에도 대응 update를 넣는다.
syscall_abis_{32,64} += xyz (...)
6.11 이후 generic core commit의 필수 항목
- 보통 init/Kconfig에 두는 CONFIG option
- SYSCALL_DEFINEn(xyzzy, ...) entry point
- include/linux/syscalls.h의 대응 prototype
- scripts/syscall.tbl의 새 entry
- 필요하면 arch/*/kernel/Makefile.syscalls의 Makefile update
- kernel/sys_ni.c의 fallback stub
x86 system call wiring
297-312새 system call을 x86 platform에 연결하려면 master syscall table을 갱신한다. 특별한 system call이 아니라면 arch/x86/entry/syscalls/syscall_64.tbl에 x86_64와 x32용 common entry를 넣는다.
333 common xyzzy sys_xyzzy
arch/x86/entry/syscalls/syscall_32.tbl에는 i386 entry를 넣는다.
380 i386 xyzzy sys_xyzzy
여기 적은 number도 해당 merge window의 conflict에 따라 바뀔 수 있다.
Generic compatibility system call
315-400System call 대부분은 userspace program이 32-bit여도 같은 64-bit implementation을 호출할 수 있다. Argument에 명시적인 pointer가 있어도 보통 투명하게 처리된다. 그러나 32-bit와 64-bit의 크기 차이를 다루는 compatibility layer가 필요한 경우가 있다.
Compat layer가 필요한 첫 번째 경우
64-bit kernel이 32-bit userspace program도 지원하면서 32-bit 또는 64-bit 값이 들어 있을 수 있는 __user memory를 parse해야 하는 경우다. 다음 형태의 system call argument가 해당한다.
- Pointer를 가리키는 pointer
- Pointer를 포함한 struct를 가리키는 pointer. 예: struct iovec __user *
- 크기가 달라지는 integral type을 가리키는 pointer. 예: time_t, off_t, long
- 크기가 달라지는 integral type을 포함한 struct를 가리키는 pointer
Compat layer가 필요한 두 번째 경우
loff_t나 __u64처럼 32-bit architecture에서도 명시적으로 64-bit인 argument가 있는 경우다. 32-bit application에서 64-bit kernel로 들어오는 값은 32-bit 값 두 개로 분할되므로 compatibility layer에서 다시 조립해야 한다.
명시적인 64-bit type을 가리키는 pointer argument 자체는 compatibility layer가 필요하지 않다. 예를 들어 splice(2)의 loff_t __user * argument 때문에 compat_ system call이 필요해지지는 않는다.
compat_sys_xyzzy() 구현
Compatibility version 이름은 compat_sys_xyzzy()이며 SYSCALL_DEFINEn과 같은 방식의 COMPAT_SYSCALL_DEFINEn() macro로 추가한다. 이 implementation은 64-bit kernel의 일부로 실행되지만 32-bit parameter value가 들어온다고 가정하고 필요한 변환을 수행한다.
일반적으로 compat_sys_ version이 값을 64-bit 형태로 변환한 뒤 sys_ version을 호출하거나, native와 compat entry가 공통 inner implementation function을 호출한다.
include/linux/compat.h에는 system call 호출 방식과 맞도록 asmlinkage로 표시한 compat entry prototype도 넣는다.
asmlinkage long compat_sys_xyzzy(...);
System call이 32-bit와 64-bit에서 layout이 다른 struct xyzzy_args를 사용한다면 include/linux/compat.h에 struct compat_xyzzy_args도 정의한다. 원래 struct에서 크기가 달라지는 각 field를 대응 compat_ type으로 바꾼다. compat_sys_xyzzy()는 이 compat structure로 32-bit invocation의 argument를 parse한다.
struct xyzzy_args {
const char __user *ptr;
__kernel_long_t varying_val;
u64 fixed_val;
/* ... */
};
struct compat_xyzzy_args {
compat_uptr_t ptr;
compat_long_t varying_val;
u64 fixed_val;
/* ... */
};
Generic syscall list도 compat version을 가리키도록 바꿔야 한다. include/uapi/asm-generic/unistd.h에서 __SYSCALL 대신 __SC_COMP를 쓴다.
#define __NR_xyzzy 292
__SC_COMP(__NR_xyzzy, sys_xyzzy, compat_sys_xyzzy)
Generic compat 구현의 필수 항목
- Compat entry point의 COMPAT_SYSCALL_DEFINEn(xyzzy, ...)
- include/linux/compat.h의 대응 prototype
- 필요한 경우 include/linux/compat.h의 32-bit mapping struct
- include/uapi/asm-generic/unistd.h에서 __SYSCALL 대신 __SC_COMP 사용
Compat wrapper가 pointer와 가변 폭 field를 native representation으로 변환한 뒤 공통 구현으로 넘긴다.
6.11 이후 common table과 arm64 compat
402-435이 절의 6.11 이후 방식은 Generic System Call Implementation의 6.11 이후 목록에 있는 architecture 중 arm64를 제외한 모든 architecture에 적용된다.
32-bit userspace program이 64-bit kernel에서 실행될 때 compat entry point로 들어가도록 scripts/syscall.tbl entry에 열을 하나 더 추가한다.
468 common xyzzy sys_xyzzy compat_sys_xyzzy
- Compat entry point의 COMPAT_SYSCALL_DEFINEn(xyzzy, ...)
- include/linux/compat.h의 대응 prototype
- compat 열이 추가된 scripts/syscall.tbl entry
- 필요한 경우 include/linux/compat.h의 32-bit mapping struct
arm64에는 32-bit AArch32 userspace를 위한 compatibility system call 전용 table인 arch/arm64/tools/syscall_32.tbl이 있다. Compat entry point를 지정하는 line을 이 table에 추가한다.
468 common xyzzy sys_xyzzy compat_sys_xyzzy
x86 compatibility wiring과 x32 ABI
437-468Compat version이 있는 system call을 x86에 연결하려면 syscall table entry를 조정한다. 먼저 arch/x86/entry/syscalls/syscall_32.tbl에 열을 하나 더 두어 64-bit kernel에서 실행되는 32-bit userspace program이 compat entry로 들어가게 한다.
380 i386 xyzzy sys_xyzzy __ia32_compat_sys_xyzzy
다음으로 새 system call의 x32 ABI가 어떤 argument layout을 사용할지 결정한다. 64-bit version 또는 32-bit version 중 하나와 일치해야 한다.
Pointer-to-pointer가 있다면 결정은 명확하다. x32는 ILP32이므로 layout은 32-bit version과 같아야 한다. arch/x86/entry/syscalls/syscall_64.tbl entry를 나눠 x32 program이 compatibility wrapper로 들어가게 한다.
333 64 xyzzy sys_xyzzy
...
555 x32 xyzzy __x32_compat_sys_xyzzy
Pointer가 없다면 x32 ABI에서 64-bit system call을 재사용하는 편이 좋다. 이 경우 arch/x86/entry/syscalls/syscall_64.tbl entry는 바꾸지 않는다.
어느 경우든 argument layout에 쓰인 type이 x32(-mx32)에서 선택한 32-bit(-m32) 또는 64-bit(-m64) equivalent와 정확히 mapping되는지 확인해야 한다.
다른 실행 위치로 복귀하는 system call
471-519System call 대부분은 완료 뒤 user program이 중단한 바로 다음 instruction에서 계속 실행된다. Stack과 register 대부분은 호출 전과 같고 virtual memory space도 같다.
그러나 rt_sigreturn은 다른 위치로 복귀할 수 있고, fork/vfork/clone은 memory space를 바꾸며, execve/execveat는 program의 architecture까지 바꿀 수 있다.
이런 동작을 허용하려면 system call kernel implementation이 추가 register를 kernel stack에 save하고 restore해야 할 수 있다. 그래야 system call 뒤 execution을 어디서 어떤 상태로 계속할지 완전히 제어할 수 있다.
구현은 architecture-specific이지만 보통 추가 register를 save/restore하고 실제 system call entry point를 호출하는 assembly entry point를 정의한다.
x86_64에서는 arch/x86/entry/entry_64.S에 stub_xyzzy entry point를 구현하고 arch/x86/entry/syscalls/syscall_64.tbl을 그 entry에 맞춘다.
333 common xyzzy stub_xyzzy
64-bit kernel에서 실행되는 32-bit program용 equivalent는 보통 stub32_xyzzy라고 하며 arch/x86/entry/entry_64_compat.S에 구현한다. arch/x86/entry/syscalls/syscall_32.tbl도 대응되게 조정한다.
380 i386 xyzzy sys_xyzzy stub32_xyzzy
앞 절처럼 compatibility layer가 필요하면 stub32_ version은 native 64-bit version이 아니라 compat_sys_ version을 호출해야 한다. x32 ABI implementation이 x86_64 version과 공통이 아니라면 x32 syscall table도 compat_sys_ version을 부르는 stub를 호출해야 한다.
User-mode Linux도 계속 동작하도록 mapping을 추가하는 것이 좋다. UML syscall table은 stub_xyzzy를 참조하지만 register 등을 simulation하므로 UML build에는 arch/x86/entry/entry_64.S implementation이 들어가지 않는다. arch/x86/um/sys_call_table_64.c에 다음 define을 추가하면 해결된다.
#define stub_xyzzy sys_xyzzy
특수 처리, test와 man page
521-573Kernel 전체의 특수 처리 확인
Kernel 대부분은 system call을 generic하게 다루지만 특정 system call에 맞춰 갱신해야 하는 예외가 가끔 있다.
Audit subsystem이 한 예다. Architecture-specific function으로 file open(open/openat), program execution(execve/exeveat), socket multiplexor(socketcall) 같은 특수 system call type을 분류한다. 새 system call이 이들 중 하나와 유사하다면 audit system도 갱신해야 한다.
일반적으로 새 system call과 비슷한 기존 system call이 있다면 kernel 전체에서 그 이름을 grep해 다른 special case가 없는지 확인할 가치가 있다.
Selftest와 외부 test suite
새 system call은 당연히 test해야 하며 reviewer에게 userspace program의 사용법을 보여 주는 것도 유용하다. 두 목적을 함께 달성하는 좋은 방법은 tools/testing/selftests/ 아래 새 directory에 단순한 self-test program을 넣는 것이다.
새 system call에는 libc wrapper function이 없으므로 test는 syscall()로 직접 호출해야 한다. Userspace-visible structure를 새로 만들었다면 test를 compile할 수 있도록 대응 header도 install해야 한다.
Selftest가 지원하는 모든 architecture에서 성공하는지 확인한다. 예를 들어 x86_64(-m64), x86_32(-m32), x32(-mx32) ABI program으로 각각 compile해 동작을 검사한다.
새 기능을 더 넓고 철저하게 test하려면 Linux Test Project에 test를 추가하거나 filesystem 변경이면 xfstests project에 test를 추가하는 방안도 검토한다.
- Linux Test Project
https://linux-test-project.github.io/ - xfstests repository
git://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git
Man page
모든 새 system call에는 완전한 man page가 따라와야 한다. Groff markup이 이상적이지만 plain text도 가능하다. Groff를 썼다면 reviewer 편의를 위해 patchset cover email에 미리 render한 ASCII version도 넣는 것이 좋다.
Man page는 linux-man@vger.kernel.org를 CC해야 한다. 자세한 제출 방법은 kernel.org의 man-pages patch 안내를 참고한다.
Kernel 내부에서 syscall entry를 직접 호출하지 않는다
575-603System call은 userspace와 kernel 사이의 상호 작용 지점이다. 따라서 sys_xyzzy()나 compat_sys_xyzzy() 같은 system call function은 userspace가 syscall table을 통해서만 호출해야 하며 kernel의 다른 code가 직접 호출해서는 안 된다.
System call 기능을 kernel 내부에서도 사용해야 하거나 old syscall과 new syscall이 공유해야 하거나 native syscall과 compatibility variant가 공유해야 한다면 ksys_xyzzy() 같은 helper function으로 구현한다. 그러면 syscall stub인 sys_xyzzy(), compatibility syscall stub인 compat_sys_xyzzy(), 다른 kernel code가 helper를 호출할 수 있다.
적어도 64-bit x86에서는 v4.17부터 kernel 내부에서 system call function을 호출하지 않는 것이 엄격한 요구 사항이다. 이 architecture는 system call에 다른 calling convention을 쓴다. Syscall wrapper가 struct pt_regs를 즉시 decode한 다음 실제 syscall function으로 처리를 넘긴다.
그 결과 syscall entry에서 CPU register 여섯 개를 매번 임의의 userspace content로 채우는 대신 특정 syscall에 실제로 필요한 parameter만 전달한다. 임의의 userspace 값이 call chain 아래로 흘러가면 심각한 문제를 만들 수 있다.
또한 kernel data와 user data는 접근 규칙이 다를 수 있다. 이것도 sys_xyzzy()를 직접 호출하는 것이 일반적으로 나쁜 이유다.
이 규칙의 예외는 architecture-specific override, architecture-specific compatibility wrapper 또는 arch/ 아래의 다른 code에서만 허용된다.
참고 자료와 근거
606-661System call의 flags argument 사용과 unknown flag 처리에 관한 Michael Kerrisk의 LWN 글, 64-bit system call argument 제약을 설명한 Jake Edge의 LWN 글, Linux v3.14의 system call implementation path를 자세히 설명한 David Drysdale의 LWN 글 두 편이 참고 자료로 제시된다.
Architecture-specific system call 요구 사항은 syscall(2) man page에서 확인할 수 있다. Linus Torvalds가 ioctl()의 문제를 논의한 email 모음과 Arnd Bergmann의 “How to not invent kernel interfaces”도 interface 설계 배경을 제공한다.
Michael Kerrisk의 CAP_SYS_ADMIN 새 사용을 피하는 방법, 새 system call의 관련 정보를 같은 email thread에 모두 담으라는 Andrew Morton의 권고, 새 system call에 man page를 붙이라는 Michael Kerrisk의 권고가 이어진다.
x86 wiring을 별도 commit으로 만들라는 Thomas Gleixner의 제안, 새 system call에 man page와 selftest를 함께 제공하라는 Greg Kroah-Hartman의 제안, 새 system call과 prctl(2) extension 선택을 논의한 Michael Kerrisk의 글도 참고할 수 있다.
Argument가 많은 system call은 future extension을 위한 size field가 있는 struct에 argument를 넣으라는 Ingo Molnar의 제안도 제시된다.
O_* numbering space flag의 재사용으로 생긴 numbering 문제는 commit 75069f2b5bfb("vfs: renumber FMODE_NONOTIFY and add to uniqueness check"), 12ed2e36c98a("fanotify: FMODE_NONOTIFY and __O_SYNC in sparc conflict"), bb458c644a59("Safer ABI for O_TMPFILE")에서 확인할 수 있다.
Matthew Wilcox의 64-bit argument 제약 논의, unknown flag를 엄격히 검사하라는 Greg Kroah-Hartman의 권고, x32 system call은 32-bit보다 64-bit version과의 호환을 선호해야 한다는 Linus Torvalds의 권고도 포함된다. 마지막 자료는 여러 architecture에서 scripts/syscall.tbl을 사용하도록 syscall table infrastructure를 개정한 patch series다.
- Flags argument in system calls
https://lwn.net/Articles/585415/ - Handling unknown system call flags
https://lwn.net/Articles/588444/ - 64-bit system call argument constraints
https://lwn.net/Articles/311630/ - System call implementation paths, part 1
https://lwn.net/Articles/604287/ - System call implementation paths, part 2
https://lwn.net/Articles/604515/ - syscall(2) architecture notes
http://man7.org/linux/man-pages/man2/syscall.2.html#NOTES - Linus Torvalds on ioctl()
https://yarchive.net/comp/linux/ioctl.html - How to not invent kernel interfaces
https://www.ukuug.org/events/linux2007/2007/papers/Bergmann.pdf - Avoiding new CAP_SYS_ADMIN uses
https://lwn.net/Articles/486306/ - Keep syscall proposal information in one thread
https://lore.kernel.org/r/20140724144747.3041b208832bbdf9fbce5d96@linux-foundation.org - New syscall man-page recommendation
https://lore.kernel.org/r/CAKgNAkgMA39AfoSoA5Pe1r9N+ZzfYQNvNPvcRN7tOvRb8+v06Q@mail.gmail.com - Separate x86 wire-up commit
https://lore.kernel.org/r/alpine.DEB.2.11.1411191249560.3909@nanos - Man page and selftest recommendation
https://lore.kernel.org/r/20140320025530.GA25469@kroah.com - New system call versus prctl(2)
https://lore.kernel.org/r/CAHO5Pa3F2MjfTtfNxa8LbnkeeU8=YJ+9tDqxZpw7Gz59E-4AUg@mail.gmail.com - Struct size field for extensibility
https://lore.kernel.org/r/20150730083831.GA22182@gmail.com - 64-bit argument restrictions
https://lore.kernel.org/r/20081212152929.GM26095@parisc-linux.org - Reject unknown flags
https://lore.kernel.org/r/20140717193330.GB4703@kroah.com - x32 compatibility preference
https://lore.kernel.org/r/CA+55aFxfmwfB7jbbrXxa=K7VBYPfAvmu3XOkGrLbB1UFjX1+Ew@mail.gmail.com - scripts/syscall.tbl infrastructure series
https://lore.kernel.org/lkml/20240704143611.2979589-1-arnd@kernel.org
System call보다 적합한 interface가 있는가
adding-syscalls.rst:4-51System call은 userspace와 kernel 사이의 전통적인 진입점이지만 새 기능마다 syscall number를 배정하는 것이 정답은 아니다. 먼저 object model, 접근 방식, notification과 namespace 조건을 보고 기존 interface가 더 자연스러운지 판단한다.