← Documents Documentation/process/adding-syscalls.rst GitHub 원문 ↗

Linux 6.18.37 · UAPI 설계

Linux kernel에 새 system call 추가하기

새 syscall이 필요한지 판단하는 단계부터 확장 가능한 ABI, generic·x86 table, 32비트 compat, selftest와 man page까지 구현 경로를 설명합니다.

Source pathDocumentation/process/adding-syscalls.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

System call보다 적합한 interface가 있는가

adding-syscalls.rst:4-51

System call은 userspace와 kernel 사이의 전통적인 진입점이지만 새 기능마다 syscall number를 배정하는 것이 정답은 아니다. 먼저 object model, 접근 방식, notification과 namespace 조건을 보고 기존 interface가 더 자연스러운지 판단한다.

조건검토할 interface주의점
Filesystem object처럼 표현 가능새 filesystem 또는 device와 file descriptorread·write로 표현하기 어려우면 ioctl이 늘어나 API가 불투명해질 수 있다.
Kernel이 event를 알림fd를 반환하고 poll·select·epoll 지원기존 event loop와 결합할 수 있다.
Runtime system 정보 노출sysfs 또는 /procNamespace, sandbox, chroot에서 filesystem이 mount되지 않았을 수 있다. debugfs는 production ABI가 아니다.
특정 file·fd의 단순 동작fcntl command복잡한 multiplexor이므로 기존 fcntl 동작과 유사하거나 단순 flag에 한정한다.
특정 task·process의 단순 속성prctl command기존 prctl과 유사한 기능 또는 단순 process flag에 적합하다.

처음부터 확장 가능한 ABI 설계

adding-syscalls.rst:54-103

System 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-295

Entry 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-572

Open·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-603

sys_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 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1
2 .. _addsyscalls:
3
4 Adding a New System Call
5 ========================
6
7 This document describes what's involved in adding a new system call to the
8 Linux kernel, over and above the normal submission advice in
9 :ref:`Documentation/process/submitting-patches.rst <submittingpatches>`.
10
11
12 System Call Alternatives
13 ------------------------
14
15 The first thing to consider when adding a new system call is whether one of
16 the alternatives might be suitable instead. Although system calls are the
17 most traditional and most obvious interaction points between userspace and the
18 kernel, there are other possibilities -- choose what fits best for your
19 interface.
20
21 - If the operations involved can be made to look like a filesystem-like
22 object, it may make more sense to create a new filesystem or device. This
23 also makes it easier to encapsulate the new functionality in a kernel module
24 rather than requiring it to be built into the main kernel.
25
26 - If the new functionality involves operations where the kernel notifies
27 userspace that something has happened, then returning a new file
28 descriptor for the relevant object allows userspace to use
29 ``poll``/``select``/``epoll`` to receive that notification.
30 - However, operations that don't map to
31 :manpage:`read(2)`/:manpage:`write(2)`-like operations
32 have to be implemented as :manpage:`ioctl(2)` requests, which can lead
33 to a somewhat opaque API.
34
35 - If you're just exposing runtime system information, a new node in sysfs
36 (see ``Documentation/filesystems/sysfs.rst``) or the ``/proc`` filesystem may
37 be more appropriate. However, access to these mechanisms requires that the
38 relevant filesystem is mounted, which might not always be the case (e.g.
39 in a namespaced/sandboxed/chrooted environment). Avoid adding any API to
40 debugfs, as this is not considered a 'production' interface to userspace.
41 - If the operation is specific to a particular file or file descriptor, then
42 an additional :manpage:`fcntl(2)` command option may be more appropriate. However,
43 :manpage:`fcntl(2)` is a multiplexing system call that hides a lot of complexity, so
44 this option is best for when the new function is closely analogous to
45 existing :manpage:`fcntl(2)` functionality, or the new functionality is very simple
46 (for example, getting/setting a simple flag related to a file descriptor).
47 - If the operation is specific to a particular task or process, then an
48 additional :manpage:`prctl(2)` command option may be more appropriate. As
49 with :manpage:`fcntl(2)`, this system call is a complicated multiplexor so
50 is best reserved for near-analogs of existing ``prctl()`` commands or
51 getting/setting a simple flag related to a process.
52
53
54 Designing the API: Planning for Extension
55 -----------------------------------------
56
57 A new system call forms part of the API of the kernel, and has to be supported
58 indefinitely. As such, it's a very good idea to explicitly discuss the
59 interface on the kernel mailing list, and it's important to plan for future
60 extensions of the interface.
61
62 (The syscall table is littered with historical examples where this wasn't done,
63 together with the corresponding follow-up system calls --
64 ``eventfd``/``eventfd2``, ``dup2``/``dup3``, ``inotify_init``/``inotify_init1``,
65 ``pipe``/``pipe2``, ``renameat``/``renameat2`` -- so
66 learn from the history of the kernel and plan for extensions from the start.)
67
68 For simpler system calls that only take a couple of arguments, the preferred
69 way to allow for future extensibility is to include a flags argument to the
70 system call. To make sure that userspace programs can safely use flags
71 between kernel versions, check whether the flags value holds any unknown
72 flags, and reject the system call (with ``EINVAL``) if it does::
73
74 if (flags & ~(THING_FLAG1 | THING_FLAG2 | THING_FLAG3))
75 return -EINVAL;
76
77 (If no flags values are used yet, check that the flags argument is zero.)
78
79 For more sophisticated system calls that involve a larger number of arguments,
80 it's preferred to encapsulate the majority of the arguments into a structure
81 that is passed in by pointer. Such a structure can cope with future extension
82 by including a size argument in the structure::
83
84 struct xyzzy_params {
85 u32 size; /* userspace sets p->size = sizeof(struct xyzzy_params) */
86 u32 param_1;
87 u64 param_2;
88 u64 param_3;
89 };
90
91 As long as any subsequently added field, say ``param_4``, is designed so that a
92 zero value gives the previous behaviour, then this allows both directions of
93 version mismatch:
94
95 - To cope with a later userspace program calling an older kernel, the kernel
96 code should check that any memory beyond the size of the structure that it
97 expects is zero (effectively checking that ``param_4 == 0``).
98 - To cope with an older userspace program calling a newer kernel, the kernel
99 code can zero-extend a smaller instance of the structure (effectively
100 setting ``param_4 = 0``).
101
102 See :manpage:`perf_event_open(2)` and the ``perf_copy_attr()`` function (in
103 ``kernel/events/core.c``) for an example of this approach.
104
105
106 Designing the API: Other Considerations
107 ---------------------------------------
108
109 If your new system call allows userspace to refer to a kernel object, it
110 should use a file descriptor as the handle for that object -- don't invent a
111 new type of userspace object handle when the kernel already has mechanisms and
112 well-defined semantics for using file descriptors.
113
114 If your new :manpage:`xyzzy(2)` system call does return a new file descriptor,
115 then the flags argument should include a value that is equivalent to setting
116 ``O_CLOEXEC`` on the new FD. This makes it possible for userspace to close
117 the timing window between ``xyzzy()`` and calling
118 ``fcntl(fd, F_SETFD, FD_CLOEXEC)``, where an unexpected ``fork()`` and
119 ``execve()`` in another thread could leak a descriptor to
120 the exec'ed program. (However, resist the temptation to re-use the actual value
121 of the ``O_CLOEXEC`` constant, as it is architecture-specific and is part of a
122 numbering space of ``O_*`` flags that is fairly full.)
123
124 If your system call returns a new file descriptor, you should also consider
125 what it means to use the :manpage:`poll(2)` family of system calls on that file
126 descriptor. Making a file descriptor ready for reading or writing is the
127 normal way for the kernel to indicate to userspace that an event has
128 occurred on the corresponding kernel object.
129
130 If your new :manpage:`xyzzy(2)` system call involves a filename argument::
131
132 int sys_xyzzy(const char __user *path, ..., unsigned int flags);
133
134 you should also consider whether an :manpage:`xyzzyat(2)` version is more appropriate::
135
136 int sys_xyzzyat(int dfd, const char __user *path, ..., unsigned int flags);
137
138 This allows more flexibility for how userspace specifies the file in question;
139 in particular it allows userspace to request the functionality for an
140 already-opened file descriptor using the ``AT_EMPTY_PATH`` flag, effectively
141 giving an :manpage:`fxyzzy(3)` operation for free::
142
143 - xyzzyat(AT_FDCWD, path, ..., 0) is equivalent to xyzzy(path,...)
144 - xyzzyat(fd, "", ..., AT_EMPTY_PATH) is equivalent to fxyzzy(fd, ...)
145
146 (For more details on the rationale of the \*at() calls, see the
147 :manpage:`openat(2)` man page; for an example of AT_EMPTY_PATH, see the
148 :manpage:`fstatat(2)` man page.)
149
150 If your new :manpage:`xyzzy(2)` system call involves a parameter describing an
151 offset within a file, make its type ``loff_t`` so that 64-bit offsets can be
152 supported even on 32-bit architectures.
153
154 If your new :manpage:`xyzzy(2)` system call involves privileged functionality,
155 it needs to be governed by the appropriate Linux capability bit (checked with
156 a call to ``capable()``), as described in the :manpage:`capabilities(7)` man
157 page. Choose an existing capability bit that governs related functionality,
158 but try to avoid combining lots of only vaguely related functions together
159 under the same bit, as this goes against capabilities' purpose of splitting
160 the power of root. In particular, avoid adding new uses of the already
161 overly-general ``CAP_SYS_ADMIN`` capability.
162
163 If your new :manpage:`xyzzy(2)` system call manipulates a process other than
164 the calling process, it should be restricted (using a call to
165 ``ptrace_may_access()``) so that only a calling process with the same
166 permissions as the target process, or with the necessary capabilities, can
167 manipulate the target process.
168
169 Finally, be aware that some non-x86 architectures have an easier time if
170 system call parameters that are explicitly 64-bit fall on odd-numbered
171 arguments (i.e. parameter 1, 3, 5), to allow use of contiguous pairs of 32-bit
172 registers. (This concern does not apply if the arguments are part of a
173 structure that's passed in by pointer.)
174
175
176 Proposing the API
177 -----------------
178
179 To make new system calls easy to review, it's best to divide up the patchset
180 into separate chunks. These should include at least the following items as
181 distinct commits (each of which is described further below):
182
183 - The core implementation of the system call, together with prototypes,
184 generic numbering, Kconfig changes and fallback stub implementation.
185 - Wiring up of the new system call for one particular architecture, usually
186 x86 (including all of x86_64, x86_32 and x32).
187 - A demonstration of the use of the new system call in userspace via a
188 selftest in ``tools/testing/selftests/``.
189 - A draft man-page for the new system call, either as plain text in the
190 cover letter, or as a patch to the (separate) man-pages repository.
191
192 New system call proposals, like any change to the kernel's API, should always
193 be cc'ed to linux-api@vger.kernel.org.
194
195
196 Generic System Call Implementation
197 ----------------------------------
198
199 The main entry point for your new :manpage:`xyzzy(2)` system call will be called
200 ``sys_xyzzy()``, but you add this entry point with the appropriate
201 ``SYSCALL_DEFINEn()`` macro rather than explicitly. The 'n' indicates the
202 number of arguments to the system call, and the macro takes the system call name
203 followed by the (type, name) pairs for the parameters as arguments. Using
204 this macro allows metadata about the new system call to be made available for
205 other tools.
206
207 The new entry point also needs a corresponding function prototype, in
208 ``include/linux/syscalls.h``, marked as asmlinkage to match the way that system
209 calls are invoked::
210
211 asmlinkage long sys_xyzzy(...);
212
213 Some architectures (e.g. x86) have their own architecture-specific syscall
214 tables, but several other architectures share a generic syscall table. Add your
215 new system call to the generic list by adding an entry to the list in
216 ``include/uapi/asm-generic/unistd.h``::
217
218 #define __NR_xyzzy 292
219 __SYSCALL(__NR_xyzzy, sys_xyzzy)
220
221 Also update the __NR_syscalls count to reflect the additional system call, and
222 note that if multiple new system calls are added in the same merge window,
223 your new syscall number may get adjusted to resolve conflicts.
224
225 The file ``kernel/sys_ni.c`` provides a fallback stub implementation of each
226 system call, returning ``-ENOSYS``. Add your new system call here too::
227
228 COND_SYSCALL(xyzzy);
229
230 Your new kernel functionality, and the system call that controls it, should
231 normally be optional, so add a ``CONFIG`` option (typically to
232 ``init/Kconfig``) for it. As usual for new ``CONFIG`` options:
233
234 - Include a description of the new functionality and system call controlled
235 by the option.
236 - Make the option depend on EXPERT if it should be hidden from normal users.
237 - Make any new source files implementing the function dependent on the CONFIG
238 option in the Makefile (e.g. ``obj-$(CONFIG_XYZZY_SYSCALL) += xyzzy.o``).
239 - Double check that the kernel still builds with the new CONFIG option turned
240 off.
241
242 To summarize, you need a commit that includes:
243
244 - ``CONFIG`` option for the new function, normally in ``init/Kconfig``
245 - ``SYSCALL_DEFINEn(xyzzy, ...)`` for the entry point
246 - corresponding prototype in ``include/linux/syscalls.h``
247 - generic table entry in ``include/uapi/asm-generic/unistd.h``
248 - fallback stub in ``kernel/sys_ni.c``
249
250
251 .. _syscall_generic_6_11:
252
253 Since 6.11
254 ~~~~~~~~~~
255
256 Starting with kernel version 6.11, general system call implementation for the
257 following architectures no longer requires modifications to
258 ``include/uapi/asm-generic/unistd.h``:
259
260 - arc
261 - arm64
262 - csky
263 - hexagon
264 - loongarch
265 - nios2
266 - openrisc
267 - riscv
268
269 Instead, you need to update ``scripts/syscall.tbl`` and, if applicable, adjust
270 ``arch/*/kernel/Makefile.syscalls``.
271
272 As ``scripts/syscall.tbl`` serves as a common syscall table across multiple
273 architectures, a new entry is required in this table::
274
275 468 common xyzzy sys_xyzzy
276
277 Note that adding an entry to ``scripts/syscall.tbl`` with the "common" ABI
278 also affects all architectures that share this table. For more limited or
279 architecture-specific changes, consider using an architecture-specific ABI or
280 defining a new one.
281
282 If a new ABI, say ``xyz``, is introduced, the corresponding updates should be
283 made to ``arch/*/kernel/Makefile.syscalls`` as well::
284
285 syscall_abis_{32,64} += xyz (...)
286
287 To summarize, you need a commit that includes:
288
289 - ``CONFIG`` option for the new function, normally in ``init/Kconfig``
290 - ``SYSCALL_DEFINEn(xyzzy, ...)`` for the entry point
291 - corresponding prototype in ``include/linux/syscalls.h``
292 - new entry in ``scripts/syscall.tbl``
293 - (if needed) Makefile updates in ``arch/*/kernel/Makefile.syscalls``
294 - fallback stub in ``kernel/sys_ni.c``
295
296
297 x86 System Call Implementation
298 ------------------------------
299
300 To wire up your new system call for x86 platforms, you need to update the
301 master syscall tables. Assuming your new system call isn't special in some
302 way (see below), this involves a "common" entry (for x86_64 and x32) in
303 arch/x86/entry/syscalls/syscall_64.tbl::
304
305 333 common xyzzy sys_xyzzy
306
307 and an "i386" entry in ``arch/x86/entry/syscalls/syscall_32.tbl``::
308
309 380 i386 xyzzy sys_xyzzy
310
311 Again, these numbers are liable to be changed if there are conflicts in the
312 relevant merge window.
313
314
315 Compatibility System Calls (Generic)
316 ------------------------------------
317
318 For most system calls the same 64-bit implementation can be invoked even when
319 the userspace program is itself 32-bit; even if the system call's parameters
320 include an explicit pointer, this is handled transparently.
321
322 However, there are a couple of situations where a compatibility layer is
323 needed to cope with size differences between 32-bit and 64-bit.
324
325 The first is if the 64-bit kernel also supports 32-bit userspace programs, and
326 so needs to parse areas of (``__user``) memory that could hold either 32-bit or
327 64-bit values. In particular, this is needed whenever a system call argument
328 is:
329
330 - a pointer to a pointer
331 - a pointer to a struct containing a pointer (e.g. ``struct iovec __user *``)
332 - a pointer to a varying sized integral type (``time_t``, ``off_t``,
333 ``long``, ...)
334 - a pointer to a struct containing a varying sized integral type.
335
336 The second situation that requires a compatibility layer is if one of the
337 system call's arguments has a type that is explicitly 64-bit even on a 32-bit
338 architecture, for example ``loff_t`` or ``__u64``. In this case, a value that
339 arrives at a 64-bit kernel from a 32-bit application will be split into two
340 32-bit values, which then need to be re-assembled in the compatibility layer.
341
342 (Note that a system call argument that's a pointer to an explicit 64-bit type
343 does **not** need a compatibility layer; for example, :manpage:`splice(2)`'s arguments of
344 type ``loff_t __user *`` do not trigger the need for a ``compat_`` system call.)
345
346 The compatibility version of the system call is called ``compat_sys_xyzzy()``,
347 and is added with the ``COMPAT_SYSCALL_DEFINEn()`` macro, analogously to
348 SYSCALL_DEFINEn. This version of the implementation runs as part of a 64-bit
349 kernel, but expects to receive 32-bit parameter values and does whatever is
350 needed to deal with them. (Typically, the ``compat_sys_`` version converts the
351 values to 64-bit versions and either calls on to the ``sys_`` version, or both of
352 them call a common inner implementation function.)
353
354 The compat entry point also needs a corresponding function prototype, in
355 ``include/linux/compat.h``, marked as asmlinkage to match the way that system
356 calls are invoked::
357
358 asmlinkage long compat_sys_xyzzy(...);
359
360 If the system call involves a structure that is laid out differently on 32-bit
361 and 64-bit systems, say ``struct xyzzy_args``, then the include/linux/compat.h
362 header file should also include a compat version of the structure (``struct
363 compat_xyzzy_args``) where each variable-size field has the appropriate
364 ``compat_`` type that corresponds to the type in ``struct xyzzy_args``. The
365 ``compat_sys_xyzzy()`` routine can then use this ``compat_`` structure to
366 parse the arguments from a 32-bit invocation.
367
368 For example, if there are fields::
369
370 struct xyzzy_args {
371 const char __user *ptr;
372 __kernel_long_t varying_val;
373 u64 fixed_val;
374 /* ... */
375 };
376
377 in struct xyzzy_args, then struct compat_xyzzy_args would have::
378
379 struct compat_xyzzy_args {
380 compat_uptr_t ptr;
381 compat_long_t varying_val;
382 u64 fixed_val;
383 /* ... */
384 };
385
386 The generic system call list also needs adjusting to allow for the compat
387 version; the entry in ``include/uapi/asm-generic/unistd.h`` should use
388 ``__SC_COMP`` rather than ``__SYSCALL``::
389
390 #define __NR_xyzzy 292
391 __SC_COMP(__NR_xyzzy, sys_xyzzy, compat_sys_xyzzy)
392
393 To summarize, you need:
394
395 - a ``COMPAT_SYSCALL_DEFINEn(xyzzy, ...)`` for the compat entry point
396 - corresponding prototype in ``include/linux/compat.h``
397 - (if needed) 32-bit mapping struct in ``include/linux/compat.h``
398 - instance of ``__SC_COMP`` not ``__SYSCALL`` in
399 ``include/uapi/asm-generic/unistd.h``
400
401
402 Since 6.11
403 ~~~~~~~~~~
404
405 This applies to all the architectures listed in :ref:`Since 6.11<syscall_generic_6_11>`
406 under "Generic System Call Implementation", except arm64. See
407 :ref:`Compatibility System Calls (arm64)<compat_arm64>` for more information.
408
409 You need to extend the entry in ``scripts/syscall.tbl`` with an extra column
410 to indicate that a 32-bit userspace program running on a 64-bit kernel should
411 hit the compat entry point::
412
413 468 common xyzzy sys_xyzzy compat_sys_xyzzy
414
415 To summarize, you need:
416
417 - ``COMPAT_SYSCALL_DEFINEn(xyzzy, ...)`` for the compat entry point
418 - corresponding prototype in ``include/linux/compat.h``
419 - modification of the entry in ``scripts/syscall.tbl`` to include an extra
420 "compat" column
421 - (if needed) 32-bit mapping struct in ``include/linux/compat.h``
422
423
424 .. _compat_arm64:
425
426 Compatibility System Calls (arm64)
427 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
428
429 On arm64, there is a dedicated syscall table for compatibility system calls
430 targeting 32-bit (AArch32) userspace: ``arch/arm64/tools/syscall_32.tbl``.
431 You need to add an additional line to this table specifying the compat
432 entry point::
433
434 468 common xyzzy sys_xyzzy compat_sys_xyzzy
435
436
437 Compatibility System Calls (x86)
438 --------------------------------
439
440 To wire up the x86 architecture of a system call with a compatibility version,
441 the entries in the syscall tables need to be adjusted.
442
443 First, the entry in ``arch/x86/entry/syscalls/syscall_32.tbl`` gets an extra
444 column to indicate that a 32-bit userspace program running on a 64-bit kernel
445 should hit the compat entry point::
446
447 380 i386 xyzzy sys_xyzzy __ia32_compat_sys_xyzzy
448
449 Second, you need to figure out what should happen for the x32 ABI version of
450 the new system call. There's a choice here: the layout of the arguments
451 should either match the 64-bit version or the 32-bit version.
452
453 If there's a pointer-to-a-pointer involved, the decision is easy: x32 is
454 ILP32, so the layout should match the 32-bit version, and the entry in
455 ``arch/x86/entry/syscalls/syscall_64.tbl`` is split so that x32 programs hit
456 the compatibility wrapper::
457
458 333 64 xyzzy sys_xyzzy
459 ...
460 555 x32 xyzzy __x32_compat_sys_xyzzy
461
462 If no pointers are involved, then it is preferable to re-use the 64-bit system
463 call for the x32 ABI (and consequently the entry in
464 arch/x86/entry/syscalls/syscall_64.tbl is unchanged).
465
466 In either case, you should check that the types involved in your argument
467 layout do indeed map exactly from x32 (-mx32) to either the 32-bit (-m32) or
468 64-bit (-m64) equivalents.
469
470
471 System Calls Returning Elsewhere
472 --------------------------------
473
474 For most system calls, once the system call is complete the user program
475 continues exactly where it left off -- at the next instruction, with the
476 stack the same and most of the registers the same as before the system call,
477 and with the same virtual memory space.
478
479 However, a few system calls do things differently. They might return to a
480 different location (``rt_sigreturn``) or change the memory space
481 (``fork``/``vfork``/``clone``) or even architecture (``execve``/``execveat``)
482 of the program.
483
484 To allow for this, the kernel implementation of the system call may need to
485 save and restore additional registers to the kernel stack, allowing complete
486 control of where and how execution continues after the system call.
487
488 This is arch-specific, but typically involves defining assembly entry points
489 that save/restore additional registers and invoke the real system call entry
490 point.
491
492 For x86_64, this is implemented as a ``stub_xyzzy`` entry point in
493 ``arch/x86/entry/entry_64.S``, and the entry in the syscall table
494 (``arch/x86/entry/syscalls/syscall_64.tbl``) is adjusted to match::
495
496 333 common xyzzy stub_xyzzy
497
498 The equivalent for 32-bit programs running on a 64-bit kernel is normally
499 called ``stub32_xyzzy`` and implemented in ``arch/x86/entry/entry_64_compat.S``,
500 with the corresponding syscall table adjustment in
501 ``arch/x86/entry/syscalls/syscall_32.tbl``::
502
503 380 i386 xyzzy sys_xyzzy stub32_xyzzy
504
505 If the system call needs a compatibility layer (as in the previous section)
506 then the ``stub32_`` version needs to call on to the ``compat_sys_`` version
507 of the system call rather than the native 64-bit version. Also, if the x32 ABI
508 implementation is not common with the x86_64 version, then its syscall
509 table will also need to invoke a stub that calls on to the ``compat_sys_``
510 version.
511
512 For completeness, it's also nice to set up a mapping so that user-mode Linux
513 still works -- its syscall table will reference stub_xyzzy, but the UML build
514 doesn't include ``arch/x86/entry/entry_64.S`` implementation (because UML
515 simulates registers etc). Fixing this is as simple as adding a #define to
516 ``arch/x86/um/sys_call_table_64.c``::
517
518 #define stub_xyzzy sys_xyzzy
519
520
521 Other Details
522 -------------
523
524 Most of the kernel treats system calls in a generic way, but there is the
525 occasional exception that may need updating for your particular system call.
526
527 The audit subsystem is one such special case; it includes (arch-specific)
528 functions that classify some special types of system call -- specifically
529 file open (``open``/``openat``), program execution (``execve``/``exeveat``) or
530 socket multiplexor (``socketcall``) operations. If your new system call is
531 analogous to one of these, then the audit system should be updated.
532
533 More generally, if there is an existing system call that is analogous to your
534 new system call, it's worth doing a kernel-wide grep for the existing system
535 call to check there are no other special cases.
536
537
538 Testing
539 -------
540
541 A new system call should obviously be tested; it is also useful to provide
542 reviewers with a demonstration of how user space programs will use the system
543 call. A good way to combine these aims is to include a simple self-test
544 program in a new directory under ``tools/testing/selftests/``.
545
546 For a new system call, there will obviously be no libc wrapper function and so
547 the test will need to invoke it using ``syscall()``; also, if the system call
548 involves a new userspace-visible structure, the corresponding header will need
549 to be installed to compile the test.
550
551 Make sure the selftest runs successfully on all supported architectures. For
552 example, check that it works when compiled as an x86_64 (-m64), x86_32 (-m32)
553 and x32 (-mx32) ABI program.
554
555 For more extensive and thorough testing of new functionality, you should also
556 consider adding tests to the Linux Test Project, or to the xfstests project
557 for filesystem-related changes.
558
559 - https://linux-test-project.github.io/
560 - git://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git
561
562
563 Man Page
564 --------
565
566 All new system calls should come with a complete man page, ideally using groff
567 markup, but plain text will do. If groff is used, it's helpful to include a
568 pre-rendered ASCII version of the man page in the cover email for the
569 patchset, for the convenience of reviewers.
570
571 The man page should be cc'ed to linux-man@vger.kernel.org
572 For more details, see https://www.kernel.org/doc/man-pages/patches.html
573
574
575 Do not call System Calls in the Kernel
576 --------------------------------------
577
578 System calls are, as stated above, interaction points between userspace and
579 the kernel. Therefore, system call functions such as ``sys_xyzzy()`` or
580 ``compat_sys_xyzzy()`` should only be called from userspace via the syscall
581 table, but not from elsewhere in the kernel. If the syscall functionality is
582 useful to be used within the kernel, needs to be shared between an old and a
583 new syscall, or needs to be shared between a syscall and its compatibility
584 variant, it should be implemented by means of a "helper" function (such as
585 ``ksys_xyzzy()``). This kernel function may then be called within the
586 syscall stub (``sys_xyzzy()``), the compatibility syscall stub
587 (``compat_sys_xyzzy()``), and/or other kernel code.
588
589 At least on 64-bit x86, it will be a hard requirement from v4.17 onwards to not
590 call system call functions in the kernel. It uses a different calling
591 convention for system calls where ``struct pt_regs`` is decoded on-the-fly in a
592 syscall wrapper which then hands processing over to the actual syscall function.
593 This means that only those parameters which are actually needed for a specific
594 syscall are passed on during syscall entry, instead of filling in six CPU
595 registers with random user space content all the time (which may cause serious
596 trouble down the call chain).
597
598 Moreover, rules on how data may be accessed may differ between kernel data and
599 user data. This is another reason why calling ``sys_xyzzy()`` is generally a
600 bad idea.
601
602 Exceptions to this rule are only allowed in architecture-specific overrides,
603 architecture-specific compatibility wrappers, or other code in arch/.
604
605
606 References and Sources
607 ----------------------
608
609 - LWN article from Michael Kerrisk on use of flags argument in system calls:
610 https://lwn.net/Articles/585415/
611 - LWN article from Michael Kerrisk on how to handle unknown flags in a system
612 call: https://lwn.net/Articles/588444/
613 - LWN article from Jake Edge describing constraints on 64-bit system call
614 arguments: https://lwn.net/Articles/311630/
615 - Pair of LWN articles from David Drysdale that describe the system call
616 implementation paths in detail for v3.14:
617
618 - https://lwn.net/Articles/604287/
619 - https://lwn.net/Articles/604515/
620
621 - Architecture-specific requirements for system calls are discussed in the
622 :manpage:`syscall(2)` man-page:
623 http://man7.org/linux/man-pages/man2/syscall.2.html#NOTES
624 - Collated emails from Linus Torvalds discussing the problems with ``ioctl()``:
625 https://yarchive.net/comp/linux/ioctl.html
626 - "How to not invent kernel interfaces", Arnd Bergmann,
627 https://www.ukuug.org/events/linux2007/2007/papers/Bergmann.pdf
628 - LWN article from Michael Kerrisk on avoiding new uses of CAP_SYS_ADMIN:
629 https://lwn.net/Articles/486306/
630 - Recommendation from Andrew Morton that all related information for a new
631 system call should come in the same email thread:
632 https://lore.kernel.org/r/20140724144747.3041b208832bbdf9fbce5d96@linux-foundation.org
633 - Recommendation from Michael Kerrisk that a new system call should come with
634 a man page: https://lore.kernel.org/r/CAKgNAkgMA39AfoSoA5Pe1r9N+ZzfYQNvNPvcRN7tOvRb8+v06Q@mail.gmail.com
635 - Suggestion from Thomas Gleixner that x86 wire-up should be in a separate
636 commit: https://lore.kernel.org/r/alpine.DEB.2.11.1411191249560.3909@nanos
637 - Suggestion from Greg Kroah-Hartman that it's good for new system calls to
638 come with a man-page & selftest: https://lore.kernel.org/r/20140320025530.GA25469@kroah.com
639 - Discussion from Michael Kerrisk of new system call vs. :manpage:`prctl(2)` extension:
640 https://lore.kernel.org/r/CAHO5Pa3F2MjfTtfNxa8LbnkeeU8=YJ+9tDqxZpw7Gz59E-4AUg@mail.gmail.com
641 - Suggestion from Ingo Molnar that system calls that involve multiple
642 arguments should encapsulate those arguments in a struct, which includes a
643 size field for future extensibility: https://lore.kernel.org/r/20150730083831.GA22182@gmail.com
644 - Numbering oddities arising from (re-)use of O_* numbering space flags:
645
646 - commit 75069f2b5bfb ("vfs: renumber FMODE_NONOTIFY and add to uniqueness
647 check")
648 - commit 12ed2e36c98a ("fanotify: FMODE_NONOTIFY and __O_SYNC in sparc
649 conflict")
650 - commit bb458c644a59 ("Safer ABI for O_TMPFILE")
651
652 - Discussion from Matthew Wilcox about restrictions on 64-bit arguments:
653 https://lore.kernel.org/r/20081212152929.GM26095@parisc-linux.org
654 - Recommendation from Greg Kroah-Hartman that unknown flags should be
655 policed: https://lore.kernel.org/r/20140717193330.GB4703@kroah.com
656 - Recommendation from Linus Torvalds that x32 system calls should prefer
657 compatibility with 64-bit versions rather than 32-bit versions:
658 https://lore.kernel.org/r/CA+55aFxfmwfB7jbbrXxa=K7VBYPfAvmu3XOkGrLbB1UFjX1+Ew@mail.gmail.com
659 - Patch series revising system call table infrastructure to use
660 scripts/syscall.tbl across multiple architectures:
661 https://lore.kernel.org/lkml/20240704143611.2979589-1-arnd@kernel.org
662

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해야 한다.

새 system call patchset의 검토 단위
Core implementation + generic ABIArchitecture wiringUserspace selftestDraft man pagelinux-api@vger.kernel.org review

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-294

Kernel 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-400

System 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 사용
32-bit process가 64-bit kernel의 compat entry를 호출하는 경로
32-bit userspaceSyscall tablecompat_sys_xyzzy()Common implementation
01 32-bit layout로 argument 구성compat entry 선택compat type으로 copy·decode
02 64-bit/native 형태로 변환검증과 실제 operation 수행
03 Return value 수신ABI에 맞춰 반환결과 변환결과 반환

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-468

Compat 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-519

System 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-573

Kernel 전체의 특수 처리 확인

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를 추가하는 방안도 검토한다.

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-603

System 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-661

System 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다.