← Documents Documentation/process/botching-up-ioctls.rst GitHub 원문 ↗

Linux 6.18.37 · UAPI 설계

ioctl ABI를 망가뜨리지 않는 방법

32/64비트 layout, 확장 가능한 struct, 입력 검증, restart 가능한 wait, clock과 timeout, handle 수명과 ioctl 대안을 설명합니다.

Source pathDocumentation/process/botching-up-ioctls.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

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

1. 요약·해설

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

Driver별 ioctl이 필요한 경우

botching-up-ioctls.rst:1-21

서로 구조가 크게 다른 GPU의 execution unit과 memory를 하나의 가짜 generic interface로 묶는 시도는 실용적이지 않았다. 그래서 각 DRM driver는 memory allocation과 command submission을 위한 고유 ioctl 집합을 갖는다.

Driver에 맞는 interface를 만들 수 있다는 장점과 함께 ABI를 잘못 고정할 위험도 커진다. 이 문서는 drm/i915에서 겪은 시행착오를 바탕으로 ioctl의 command 의미 자체보다 layout, validation, wait, recovery와 lifetime 같은 기술 조건을 정리한다.

32비트 compat layer를 피하는 자료형과 정렬

botching-up-ioctls.rst:24-49
  • UAPI struct에는 __u32, __s64처럼 크기가 고정된 정수형만 사용한다.
  • 모든 field를 자연 정렬 크기에 맞추고 explicit padding을 둔다. 32비트 platform은 64비트 값을 항상 8-byte 정렬하지 않지만 64비트 platform은 그렇게 할 수 있다.
  • 64비트 field가 있는 struct 전체 크기를 64-bit 배수로 맞춘다. 그렇지 않으면 32비트와 64비트에서 sizeof가 달라져 struct array 전달과 size 검사에서 ABI가 깨진다.
  • User pointer는 UAPI에서 __u64로 표현한다. Userspace에서는 uintptr_t를 거쳐 변환하고 kernel에서는 void __user *로 즉시 바꾼다.
  • Kernel에서는 u64_to_user_ptr()를 사용해 정수와 pointer 크기 차이 warning을 피하고 sparse가 __user pointer 검사를 수행할 수 있게 한다.

Raw __u64 pointer 값을 내부 code 깊숙이 전달하면 address-space annotation이 사라져 sparse가 잘못된 dereference를 찾기 어렵다. copy_from_user() 직전까지 integer로 들고 가는 방식은 피한다.

앞으로 확장할 수 있는 ABI

botching-up-ioctls.rst:52-81

한 번 공개된 ioctl ABI는 오래 유지해야 하므로 첫 version부터 확장 방식을 정해야 한다. 새 ioctl이나 flag를 old kernel이 확실히 거절한다면 지원 여부를 probe할 수 있지만, 과거 구현이 unknown bit를 무시했다면 별도의 feature flag나 revision query가 필요하다.

Struct 끝에 field를 붙이는 방식도 kernel과 userspace 사이 크기 차이를 zero extension하는 것만으로 충분하지 않다. New userspace가 old kernel에 새 field를 보냈을 때 old kernel이 그 field를 무시했다는 사실을 userspace가 알아야 하므로 feature discovery가 함께 있어야 한다.

사용하지 않는 field, flag와 모든 padding은 반드시 0인지 검사하고 아니면 ioctl을 거절한다. 처음부터 random stack garbage를 허용하면 그 값도 기존 ABI의 허용 동작으로 굳어져 나중에 해당 bit나 field를 새 의미로 사용할 수 없다.

Compiler가 암묵적으로 넣은 padding에도 userspace garbage가 들어갈 수 있으므로 struct를 explicit padding으로 설계한다. 이 규칙과 feature probing을 확인하는 작은 testcase를 처음부터 만든다.

입력 검증과 복구 경로

botching-up-ioctls.rst:84-124
  • Array element count와 element size의 곱셈 overflow를 검사한다.
  • 모든 integer의 overflow, underflow와 hardware field width에 맞춘 clamping을 검사한다.
  • 실패 조건마다 독립된 testcase를 둔다. 목표 조건 이전의 다른 validation에서 먼저 거절되지 않도록 나머지 입력은 완전히 유효하게 만든다.
  • 각 testcase에서 예상 errno가 정확히 반환되는지 확인한다.
  • Signal로 system call이 중단될 수 있으므로 가능하면 ioctl을 restart 가능하게 설계하고 userspace helper의 재시도 semantics를 일관되게 유지한다.
  • Restart가 불가능한 code path라면 task가 kill 가능하게 만들고, hardware hang을 대비해 timeout이나 hangcheck를 마지막 안전장치로 둔다.
  • Hangcheck와 waiter 사이 deadlock처럼 recovery 경로의 드문 경쟁 조건도 별도 test한다.

GPU는 드문 상태에서 실제로 멈출 수 있다. 정상 경로만 검증하면 privileged ioctl의 input validation 누락이 root exploit로 이어질 수 있고, recovery lock 순서 오류는 전체 display server를 unkillable state로 만들 수 있다.

Clock, timestamp와 timeout

botching-up-ioctls.rst:127-165

기준 clock은 CLOCK_MONOTONIC을 사용한다. System clock이나 독립 hardware counter처럼 다른 clock domain에서 얻은 timestamp는 어느 clock인지 userspace에 알려야 한다. Clock은 미세하게 어긋나므로 성능 측정 도구가 domain을 알아야 보정할 수 있다.

  • 시간 표현은 __s64 seconds와 __u64 nanoseconds 조합을 사용한다.
  • 입력 timestamp가 normalized form인지 검사하고 잘못된 값은 거절한다.
  • Timeout은 absolute time으로 표현한다. Relative timeout은 restart 때마다 rounding되어 총 대기 시간이 계속 늘어날 수 있다.
  • Synchronous wait ioctl 대신 poll 가능한 file descriptor로 asynchronous event를 전달하는 설계를 검토한다.
  • 이미 완료된 event, 정상 wait 완료, timeout 종료의 return value를 각각 test한다.

특히 frame counter처럼 느린 clock을 relative timeout에 사용하면 signal restart마다 반올림 오차가 누적돼 animation이 끊길 정도로 wait가 연장될 수 있다. Absolute deadline은 여러 번 restart되어도 전체 상한을 유지한다.

Handle과 resource lifetime

botching-up-ioctls.rst:168-200

완전한 DRM driver는 GPU를 위한 작은 OS처럼 많은 object와 resource handle을 userspace에 노출한다. 동적으로 만든 resource의 lifetime은 항상 file descriptor lifetime에 연결한다.

  • Process 사이 공유가 필요하면 resource와 fd를 1:1로 대응시키는 방식을 고려한다. Unix domain socket의 fd passing은 userspace lifetime 관리도 단순하게 만든다.
  • 모든 관련 fd에 O_CLOEXEC 지원을 제공해 exec 뒤 의도치 않은 resource 누수를 막는다.
  • 기본 namespace는 per-fd private로 두고 공유는 명시적으로 수행하게 한다.
  • Object가 실제 device-global identity를 가질 때만 per-device global namespace를 선택한다.
  • 공유 object를 여러 번 import했는지 구분해야 한다면 shared fd의 inode number 같은 안정된 identity를 검토한다.

DRM modeset ABI에서 device-global connector와 보통 공유되지 않는 framebuffer가 같은 namespace를 쓰는 사례는 scope를 넓게 잡았을 때의 문제를 보여 준다. Object 소유권과 visibility를 먼저 정한 뒤 handle space를 설계해야 한다.

새 ioctl이 항상 답은 아니다

botching-up-ioctls.rst:202-226

Driver-private interface는 generic subsystem 논의보다 빨리 만들 수 있고 새 개념을 시험할 때 필요할 수도 있다. 그러나 나중에 generic interface가 생기면 private ABI와 generic ABI 두 개를 기한 없이 유지하게 된다.

  • Device별 설정이나 수명이 비교적 고정된 child object attribute에는 sysfs가 더 적합할 수 있다.
  • Test suite에서만 필요한 불안정한 interface라면 stable ABI를 약속하지 않는 debugfs가 더 적합할 수 있다.
  • Event-driven application에는 blocking ioctl보다 poll 가능한 event fd가 잘 맞을 수 있다.

인기 있는 driver와 수명이 긴 hardware에서 공개 ioctl은 사실상 영구 ABI가 된다. 새 hardware generation에서 deprecated 처리해도 실제 사용자가 사라질 때까지 여러 해 동안 호환 code를 유지해야 하므로 첫 공개 전에 layout, error와 extension test를 끝내야 한다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 =================================
2 (How to avoid) Botching up ioctls
3 =================================
4
5 From: https://blog.ffwll.ch/2013/11/botching-up-ioctls.html
6
7 By: Daniel Vetter, Copyright © 2013 Intel Corporation
8
9 One clear insight kernel graphics hackers gained in the past few years is that
10 trying to come up with a unified interface to manage the execution units and
11 memory on completely different GPUs is a futile effort. So nowadays every
12 driver has its own set of ioctls to allocate memory and submit work to the GPU.
13 Which is nice, since there's no more insanity in the form of fake-generic, but
14 actually only used once interfaces. But the clear downside is that there's much
15 more potential to screw things up.
16
17 To avoid repeating all the same mistakes again I've written up some of the
18 lessons learned while botching the job for the drm/i915 driver. Most of these
19 only cover technicalities and not the big-picture issues like what the command
20 submission ioctl exactly should look like. Learning these lessons is probably
21 something every GPU driver has to do on its own.
22
23
24 Prerequisites
25 -------------
26
27 First the prerequisites. Without these you have already failed, because you
28 will need to add a 32-bit compat layer:
29
30 * Only use fixed sized integers. To avoid conflicts with typedefs in userspace
31 the kernel has special types like __u32, __s64. Use them.
32
33 * Align everything to the natural size and use explicit padding. 32-bit
34 platforms don't necessarily align 64-bit values to 64-bit boundaries, but
35 64-bit platforms do. So we always need padding to the natural size to get
36 this right.
37
38 * Pad the entire struct to a multiple of 64-bits if the structure contains
39 64-bit types - the structure size will otherwise differ on 32-bit versus
40 64-bit. Having a different structure size hurts when passing arrays of
41 structures to the kernel, or if the kernel checks the structure size, which
42 e.g. the drm core does.
43
44 * Pointers are __u64, cast from/to a uintptr_t on the userspace side and
45 from/to a void __user * in the kernel. Try really hard not to delay this
46 conversion or worse, fiddle the raw __u64 through your code since that
47 diminishes the checking tools like sparse can provide. The macro
48 u64_to_user_ptr can be used in the kernel to avoid warnings about integers
49 and pointers of different sizes.
50
51
52 Basics
53 ------
54
55 With the joys of writing a compat layer avoided we can take a look at the basic
56 fumbles. Neglecting these will make backward and forward compatibility a real
57 pain. And since getting things wrong on the first attempt is guaranteed you
58 will have a second iteration or at least an extension for any given interface.
59
60 * Have a clear way for userspace to figure out whether your new ioctl or ioctl
61 extension is supported on a given kernel. If you can't rely on old kernels
62 rejecting the new flags/modes or ioctls (since doing that was botched in the
63 past) then you need a driver feature flag or revision number somewhere.
64
65 * Have a plan for extending ioctls with new flags or new fields at the end of
66 the structure. The drm core checks the passed-in size for each ioctl call
67 and zero-extends any mismatches between kernel and userspace. That helps,
68 but isn't a complete solution since newer userspace on older kernels won't
69 notice that the newly added fields at the end get ignored. So this still
70 needs a new driver feature flags.
71
72 * Check all unused fields and flags and all the padding for whether it's 0,
73 and reject the ioctl if that's not the case. Otherwise your nice plan for
74 future extensions is going right down the gutters since someone will submit
75 an ioctl struct with random stack garbage in the yet unused parts. Which
76 then bakes in the ABI that those fields can never be used for anything else
77 but garbage. This is also the reason why you must explicitly pad all
78 structures, even if you never use them in an array - the padding the compiler
79 might insert could contain garbage.
80
81 * Have simple testcases for all of the above.
82
83
84 Fun with Error Paths
85 --------------------
86
87 Nowadays we don't have any excuse left any more for drm drivers being neat
88 little root exploits. This means we both need full input validation and solid
89 error handling paths - GPUs will die eventually in the oddmost corner cases
90 anyway:
91
92 * The ioctl must check for array overflows. Also it needs to check for
93 over/underflows and clamping issues of integer values in general. The usual
94 example is sprite positioning values fed directly into the hardware with the
95 hardware just having 12 bits or so. Works nicely until some odd display
96 server doesn't bother with clamping itself and the cursor wraps around the
97 screen.
98
99 * Have simple testcases for every input validation failure case in your ioctl.
100 Check that the error code matches your expectations. And finally make sure
101 that you only test for one single error path in each subtest by submitting
102 otherwise perfectly valid data. Without this an earlier check might reject
103 the ioctl already and shadow the codepath you actually want to test, hiding
104 bugs and regressions.
105
106 * Make all your ioctls restartable. First X really loves signals and second
107 this will allow you to test 90% of all error handling paths by just
108 interrupting your main test suite constantly with signals. Thanks to X's
109 love for signal you'll get an excellent base coverage of all your error
110 paths pretty much for free for graphics drivers. Also, be consistent with
111 how you handle ioctl restarting - e.g. drm has a tiny drmIoctl helper in its
112 userspace library. The i915 driver botched this with the set_tiling ioctl,
113 now we're stuck forever with some arcane semantics in both the kernel and
114 userspace.
115
116 * If you can't make a given codepath restartable make a stuck task at least
117 killable. GPUs just die and your users won't like you more if you hang their
118 entire box (by means of an unkillable X process). If the state recovery is
119 still too tricky have a timeout or hangcheck safety net as a last-ditch
120 effort in case the hardware has gone bananas.
121
122 * Have testcases for the really tricky corner cases in your error recovery code
123 - it's way too easy to create a deadlock between your hangcheck code and
124 waiters.
125
126
127 Time, Waiting and Missing it
128 ----------------------------
129
130 GPUs do most everything asynchronously, so we have a need to time operations and
131 wait for outstanding ones. This is really tricky business; at the moment none of
132 the ioctls supported by the drm/i915 get this fully right, which means there's
133 still tons more lessons to learn here.
134
135 * Use CLOCK_MONOTONIC as your reference time, always. It's what alsa, drm and
136 v4l use by default nowadays. But let userspace know which timestamps are
137 derived from different clock domains like your main system clock (provided
138 by the kernel) or some independent hardware counter somewhere else. Clocks
139 will mismatch if you look close enough, but if performance measuring tools
140 have this information they can at least compensate. If your userspace can
141 get at the raw values of some clocks (e.g. through in-command-stream
142 performance counter sampling instructions) consider exposing those also.
143
144 * Use __s64 seconds plus __u64 nanoseconds to specify time. It's not the most
145 convenient time specification, but it's mostly the standard.
146
147 * Check that input time values are normalized and reject them if not. Note
148 that the kernel native struct ktime has a signed integer for both seconds
149 and nanoseconds, so beware here.
150
151 * For timeouts, use absolute times. If you're a good fellow and made your
152 ioctl restartable relative timeouts tend to be too coarse and can
153 indefinitely extend your wait time due to rounding on each restart.
154 Especially if your reference clock is something really slow like the display
155 frame counter. With a spec lawyer hat on this isn't a bug since timeouts can
156 always be extended - but users will surely hate you if their neat animations
157 starts to stutter due to this.
158
159 * Consider ditching any synchronous wait ioctls with timeouts and just deliver
160 an asynchronous event on a pollable file descriptor. It fits much better
161 into event driven applications' main loop.
162
163 * Have testcases for corner-cases, especially whether the return values for
164 already-completed events, successful waits and timed-out waits are all sane
165 and suiting to your needs.
166
167
168 Leaking Resources, Not
169 ----------------------
170
171 A full-blown drm driver essentially implements a little OS, but specialized to
172 the given GPU platforms. This means a driver needs to expose tons of handles
173 for different objects and other resources to userspace. Doing that right
174 entails its own little set of pitfalls:
175
176 * Always attach the lifetime of your dynamically created resources to the
177 lifetime of a file descriptor. Consider using a 1:1 mapping if your resource
178 needs to be shared across processes - fd-passing over unix domain sockets
179 also simplifies lifetime management for userspace.
180
181 * Always have O_CLOEXEC support.
182
183 * Ensure that you have sufficient insulation between different clients. By
184 default pick a private per-fd namespace which forces any sharing to be done
185 explicitly. Only go with a more global per-device namespace if the objects
186 are truly device-unique. One counterexample in the drm modeset interfaces is
187 that the per-device modeset objects like connectors share a namespace with
188 framebuffer objects, which mostly are not shared at all. A separate
189 namespace, private by default, for framebuffers would have been more
190 suitable.
191
192 * Think about uniqueness requirements for userspace handles. E.g. for most drm
193 drivers it's a userspace bug to submit the same object twice in the same
194 command submission ioctl. But then if objects are shareable userspace needs
195 to know whether it has seen an imported object from a different process
196 already or not. I haven't tried this myself yet due to lack of a new class
197 of objects, but consider using inode numbers on your shared file descriptors
198 as unique identifiers - it's how real files are told apart, too.
199 Unfortunately this requires a full-blown virtual filesystem in the kernel.
200
201
202 Last, but not Least
203 -------------------
204
205 Not every problem needs a new ioctl:
206
207 * Think hard whether you really want a driver-private interface. Of course
208 it's much quicker to push a driver-private interface than engaging in
209 lengthy discussions for a more generic solution. And occasionally doing a
210 private interface to spearhead a new concept is what's required. But in the
211 end, once the generic interface comes around you'll end up maintaining two
212 interfaces. Indefinitely.
213
214 * Consider other interfaces than ioctls. A sysfs attribute is much better for
215 per-device settings, or for child objects with fairly static lifetimes (like
216 output connectors in drm with all the detection override attributes). Or
217 maybe only your testsuite needs this interface, and then debugfs with its
218 disclaimer of not having a stable ABI would be better.
219
220 Finally, the name of the game is to get it right on the first attempt, since if
221 your driver proves popular and your hardware platforms long-lived then you'll
222 be stuck with a given ioctl essentially forever. You can try to deprecate
223 horrible ioctls on newer iterations of your hardware, but generally it takes
224 years to accomplish this. And then again years until the last user able to
225 complain about regressions disappears, too.
226

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

GPU별 ioctl에서 얻은 ABI 교훈

1-21

Daniel Vetter가 drm/i915 개발 경험을 바탕으로 작성한 2013년 문서다. 서로 완전히 다른 GPU의 execution unit과 memory를 하나의 통합 interface로 관리하려는 시도는 실용적이지 않았다. 그래서 driver마다 memory allocation과 GPU work submission용 ioctl을 제공하게 되었다.

한 번만 쓰이는 가짜 generic interface를 피할 수 있지만 driver별 ABI를 잘못 설계할 가능성도 늘어난다. 이 문서는 command submission의 큰 설계보다 반복해서 실수하기 쉬운 기술 세부 사항을 다룬다.

32-bit compat를 피하기 위한 layout 전제

24-49
  • Fixed-size integer만 사용한다. Userspace typedef 충돌을 피하도록 __u32, __s64 같은 kernel ABI type을 쓴다.
  • 모든 field를 natural size에 align하고 explicit padding을 넣는다. 32-bit platform은 64-bit value를 64-bit boundary에 align하지 않을 수 있지만 64-bit platform은 그렇게 하므로 layout을 명시해야 한다.
  • 64-bit type이 있는 structure 전체 크기를 64 bit 배수로 padding한다. 그렇지 않으면 32-bit와 64-bit에서 sizeof가 달라져 structure array 전달과 drm core의 size check가 깨진다.
  • Pointer는 __u64로 표현한다. Userspace에서는 uintptr_t와 변환하고 kernel에서는 void __user *와 즉시 변환한다. Raw __u64 pointer를 내부까지 끌고 가지 않아야 sparse 검사를 살릴 수 있다. Kernel에서는 u64_to_user_ptr()로 size warning을 피할 수 있다.

Forward와 backward compatibility

52-81
  • Userspace가 새 ioctl 또는 extension 지원 여부를 분명히 알아낼 방법을 제공한다. 옛 kernel이 새 flag와 mode를 확실히 거부하지 않는다면 driver feature flag나 revision number가 필요하다.
  • Structure 끝에 새 flag나 field를 추가할 확장 계획을 마련한다. drm core의 size check와 zero-extension은 도움이 되지만 새 userspace가 옛 kernel에서 추가 field가 무시된 사실을 알지 못하므로 feature flag가 여전히 필요하다.
  • Unused field, flag, padding이 모두 0인지 검사하고 아니면 ioctl을 거부한다. Random stack garbage를 받아들이면 그 값도 ABI 일부가 되어 field를 미래에 사용할 수 없다. Compiler가 만든 implicit padding에도 garbage가 들어갈 수 있으므로 explicit padding이 필요하다.
  • 위의 모든 동작에 간단한 testcase를 둔다.

Input validation과 error path test

84-104
  • Array size overflow와 일반 integer over/underflow, clamping 문제를 모두 검사한다. 예를 들어 12-bit hardware sprite position에 unclamped display-server 값을 직접 쓰면 cursor가 screen을 wrap할 수 있다.
  • 각 input validation failure에 별도 testcase를 만들고 예상 error code를 확인한다.
  • Subtest 하나에서는 오직 한 error path만 검사한다. 나머지 input은 완전히 valid하게 만들어 앞선 check가 먼저 거부하며 목표 path의 bug와 regression을 가리지 않게 한다.

Restartable ioctl과 killable hang

106-124

모든 ioctl을 restartable하게 만든다. X server는 signal을 많이 사용하므로 test suite에 계속 signal을 보내는 것만으로 error handling path 대부분을 시험할 수 있다. Restart semantic은 userspace library helper까지 일관되게 유지한다. i915 set_tiling ioctl의 불일치처럼 한번 공개한 이상한 semantic은 kernel과 userspace에서 영구히 유지해야 할 수 있다.

Restartable하게 만들 수 없는 path라도 stuck task는 kill 가능해야 한다. GPU failure 때문에 unkillable X process가 system 전체를 멈추게 해서는 안 된다. State recovery가 어렵다면 timeout이나 hangcheck를 마지막 safety net으로 둔다.

Hangcheck code와 waiter 사이 deadlock처럼 까다로운 recovery corner case에도 testcase를 작성한다.

Clock domain과 time 표현

127-149

GPU operation은 대부분 asynchronous라 시간 측정과 outstanding work 대기가 필요하지만 어렵다.

  • Reference time은 항상 CLOCK_MONOTONIC을 사용한다. ALSA, DRM, V4L도 이를 기본으로 쓴다.
  • Kernel system clock과 독립 hardware counter처럼 다른 clock domain에서 나온 timestamp는 userspace에 명시한다. Clock mismatch를 없앨 수 없어도 performance tool이 보정할 수 있다.
  • Command stream performance counter sampling처럼 raw clock value에 접근할 수 있다면 노출을 고려한다.
  • Time은 __s64 seconds와 __u64 nanoseconds 조합으로 표현한다.
  • Input time이 normalize되었는지 검사하고 아니면 거부한다. Native struct ktime은 seconds와 nanoseconds 모두 signed integer이므로 주의한다.

Absolute timeout과 asynchronous event

151-165

Timeout은 absolute time으로 표현한다. Restartable ioctl에서 relative timeout을 쓰면 restart마다 rounding되어 전체 wait가 기한 없이 늘어날 수 있다. Display frame counter처럼 느린 reference clock이면 animation stutter로 나타날 수 있다.

Timeout이 있는 synchronous wait ioctl 대신 poll 가능한 file descriptor로 asynchronous event를 전달하는 방식도 고려한다. Event-driven application main loop에 더 자연스럽다.

이미 완료된 event, 성공한 wait, timeout된 wait의 return value가 모두 일관되고 목적에 맞는지 corner-case testcase로 확인한다.

Resource lifetime과 namespace

168-200

완전한 DRM driver는 특정 GPU를 위한 작은 운영체제처럼 많은 object와 resource handle을 userspace에 노출한다.

  • 동적으로 생성한 resource lifetime을 항상 file descriptor lifetime에 연결한다. Process 사이 공유가 필요하면 1:1 mapping과 Unix-domain socket의 fd passing을 고려해 userspace lifetime 관리도 단순화한다.
  • 항상 O_CLOEXEC를 지원한다.
  • Client 사이를 충분히 격리한다. 기본은 sharing을 명시적으로 수행하게 하는 private per-fd namespace다. Object가 진정 device-unique할 때만 global per-device namespace를 사용한다.
  • DRM modeset의 connector 같은 per-device object와 대부분 공유하지 않는 framebuffer object가 같은 namespace를 쓰는 것은 반례다. Framebuffer는 기본 private인 별도 namespace가 더 적합했다.
  • Userspace handle의 uniqueness 요구를 설계한다. Shareable object를 다른 process에서 import했을 때 이미 본 object인지 구분해야 한다. Shared fd의 inode number를 unique identifier로 쓰는 방안이 있지만 kernel에 full virtual filesystem 구현이 필요하다.

모든 문제에 새 ioctl이 필요한 것은 아니다

202-218

Driver-private interface는 generic solution 논의보다 빨리 만들 수 있고 새 개념을 먼저 시험할 때 필요할 수도 있다. 하지만 이후 generic interface가 생기면 두 interface를 기한 없이 함께 유지해야 한다.

Per-device setting이나 lifetime이 비교적 고정된 child object에는 sysfs attribute가 더 적합할 수 있다. Test suite만 필요한 interface라면 stable ABI를 약속하지 않는 debugfs가 낫다.

첫 ABI를 사실상 영구히 유지해야 한다

220-225

Driver가 널리 쓰이고 hardware platform이 오래 유지되면 공개한 ioctl ABI는 사실상 영구히 유지해야 한다. 새 hardware generation에서 나쁜 ioctl을 deprecate해도 수년이 걸리고, 마지막 사용자가 regression을 보고할 수 없게 될 때까지 또 수년이 필요하다. 그래서 첫 설계를 올바르게 만드는 것이 핵심이다.