← Documents Documentation/filesystems/files.rst GitHub 원문 ↗

Linux 6.18.37 · Filesystems

File management in the Linux kernel

RCU 기반 fdtable 조회·갱신과 SLAB_TYPESAFE_BY_RCU file reference 검증의 전문 번역입니다.

Source pathDocumentation/filesystems/files.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

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

1. 요약·해설

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

요약·해설

files.rst:1-123

fdtable reader는 RCU와 전용 lookup helper를 사용하고, writer는 `files->file_lock` 아래에서 pointer를 매번 다시 확인해야 합니다.

`SLAB_TYPESAFE_BY_RCU`에서는 reference count 증가만으로 object identity가 보장되지 않으므로 pointer가 증가 전후 같은지 검증해야 합니다.

Reader와 writer 분리
reader: RCU read-side 진입`files_fdtable()` 또는 fdget helper 사용writer: `file_lock` 획득table 확장 가능 호출 뒤 pointer reloadreference와 identity 확인 후 file 접근

fdtable 동시성의 핵심 규칙입니다.

2. 영어 원문 전체

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

원문 전체 펼치기
1 .. SPDX-License-Identifier: GPL-2.0
2
3 ===================================
4 File management in the Linux kernel
5 ===================================
6
7 This document describes how locking for files (struct file)
8 and file descriptor table (struct files) works.
9
10 Up until 2.6.12, the file descriptor table has been protected
11 with a lock (files->file_lock) and reference count (files->count).
12 ->file_lock protected accesses to all the file related fields
13 of the table. ->count was used for sharing the file descriptor
14 table between tasks cloned with CLONE_FILES flag. Typically
15 this would be the case for posix threads. As with the common
16 refcounting model in the kernel, the last task doing
17 a put_files_struct() frees the file descriptor (fd) table.
18 The files (struct file) themselves are protected using
19 reference count (->f_count).
20
21 In the new lock-free model of file descriptor management,
22 the reference counting is similar, but the locking is
23 based on RCU. The file descriptor table contains multiple
24 elements - the fd sets (open_fds and close_on_exec, the
25 array of file pointers, the sizes of the sets and the array
26 etc.). In order for the updates to appear atomic to
27 a lock-free reader, all the elements of the file descriptor
28 table are in a separate structure - struct fdtable.
29 files_struct contains a pointer to struct fdtable through
30 which the actual fd table is accessed. Initially the
31 fdtable is embedded in files_struct itself. On a subsequent
32 expansion of fdtable, a new fdtable structure is allocated
33 and files->fdtab points to the new structure. The fdtable
34 structure is freed with RCU and lock-free readers either
35 see the old fdtable or the new fdtable making the update
36 appear atomic. Here are the locking rules for
37 the fdtable structure -
38
39 1. All references to the fdtable must be done through
40 the files_fdtable() macro::
41
42 struct fdtable *fdt;
43
44 rcu_read_lock();
45
46 fdt = files_fdtable(files);
47 ....
48 if (n <= fdt->max_fds)
49 ....
50 ...
51 rcu_read_unlock();
52
53 files_fdtable() uses rcu_dereference() macro which takes care of
54 the memory barrier requirements for lock-free dereference.
55 The fdtable pointer must be read within the read-side
56 critical section.
57
58 2. Reading of the fdtable as described above must be protected
59 by rcu_read_lock()/rcu_read_unlock().
60
61 3. For any update to the fd table, files->file_lock must
62 be held.
63
64 4. To look up the file structure given an fd, a reader
65 must use either lookup_fdget_rcu() or files_lookup_fdget_rcu() APIs. These
66 take care of barrier requirements due to lock-free lookup.
67
68 An example::
69
70 struct file *file;
71
72 rcu_read_lock();
73 file = lookup_fdget_rcu(fd);
74 rcu_read_unlock();
75 if (file) {
76 ...
77 fput(file);
78 }
79 ....
80
81 5. Since both fdtable and file structures can be looked up
82 lock-free, they must be installed using rcu_assign_pointer()
83 API. If they are looked up lock-free, rcu_dereference()
84 must be used. However it is advisable to use files_fdtable()
85 and lookup_fdget_rcu()/files_lookup_fdget_rcu() which take care of these
86 issues.
87
88 6. While updating, the fdtable pointer must be looked up while
89 holding files->file_lock. If ->file_lock is dropped, then
90 another thread expand the files thereby creating a new
91 fdtable and making the earlier fdtable pointer stale.
92
93 For example::
94
95 spin_lock(&files->file_lock);
96 fd = locate_fd(files, file, start);
97 if (fd >= 0) {
98 /* locate_fd() may have expanded fdtable, load the ptr */
99 fdt = files_fdtable(files);
100 __set_open_fd(fd, fdt);
101 __clear_close_on_exec(fd, fdt);
102 spin_unlock(&files->file_lock);
103 .....
104
105 Since locate_fd() can drop ->file_lock (and reacquire ->file_lock),
106 the fdtable pointer (fdt) must be loaded after locate_fd().
107
108 On newer kernels rcu based file lookup has been switched to rely on
109 SLAB_TYPESAFE_BY_RCU instead of call_rcu(). It isn't sufficient anymore
110 to just acquire a reference to the file in question under rcu using
111 atomic_long_inc_not_zero() since the file might have already been
112 recycled and someone else might have bumped the reference. In other
113 words, callers might see reference count bumps from newer users. For
114 this is reason it is necessary to verify that the pointer is the same
115 before and after the reference count increment. This pattern can be seen
116 in get_file_rcu() and __files_get_rcu().
117
118 In addition, it isn't possible to access or check fields in struct file
119 without first acquiring a reference on it under rcu lookup. Not doing
120 that was always very dodgy and it was only usable for non-pointer data
121 in struct file. With SLAB_TYPESAFE_BY_RCU it is necessary that callers
122 either first acquire a reference or they must hold the files_lock of the
123 fdtable.
124

3. 한국어 전문 번역

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

기존 lock model과 RCU fdtable

1-37

이 문서는 file object인 `struct file`과 file descriptor table인 `struct files`의 locking을 설명합니다.

Linux 2.6.12까지 fd table은 `files->file_lock`과 `files->count`로 보호했습니다. `file_lock`은 table의 모든 file-related field 접근을 직렬화하고, `count`는 `CLONE_FILES`로 복제된 task, 보통 POSIX thread 사이의 table 공유를 추적했습니다. 마지막 task의 `put_files_struct()`가 fd table을 해제하며 각 `struct file` 자체는 `f_count` reference count로 보호됩니다.

새 lock-free fd management에서도 reference counting은 비슷하지만 locking은 RCU 기반입니다. fd set인 `open_fds`, `close_on_exec`, file pointer array, set·array size를 모두 별도 `struct fdtable`에 모아 lock-free reader에게 update 전체가 atomic하게 보이게 합니다.

`files_struct`는 `fdtable` pointer를 통해 실제 table에 접근합니다. 처음에는 fdtable이 `files_struct` 안에 embedded되어 있고, 확장 시 새 `fdtable`을 할당해 `files->fdtab`이 가리키게 합니다. 이전 structure는 RCU로 해제되므로 reader는 old 또는 new table 중 완전한 하나만 봅니다.

fdtable 확장 공개
초기 embedded `fdtable` 사용확장 시 새 `fdtable` 할당·채우기`files->fdtab` pointer를 새 table로 publishreader는 RCU 안에서 old 또는 new table 관찰grace period 뒤 old table 해제

lock-free reader에게 table 교체가 atomic하게 보이는 이유입니다.

.. SPDX-License-Identifier: GPL-2.0

===================================
File management in the Linux kernel
===================================

This document describes how locking for files (struct file)
and file descriptor table (struct files) works.

Up until 2.6.12, the file descriptor table has been protected
with a lock (files->file_lock) and reference count (files->count).
->file_lock protected accesses to all the file related fields
of the table. ->count was used for sharing the file descriptor
table between tasks cloned with CLONE_FILES flag. Typically
this would be the case for posix threads. As with the common
refcounting model in the kernel, the last task doing
a put_files_struct() frees the file descriptor (fd) table.
The files (struct file) themselves are protected using
reference count (->f_count).

In the new lock-free model of file descriptor management,
the reference counting is similar, but the locking is
based on RCU. The file descriptor table contains multiple
elements - the fd sets (open_fds and close_on_exec, the
array of file pointers, the sizes of the sets and the array
etc.). In order for the updates to appear atomic to
a lock-free reader, all the elements of the file descriptor
table are in a separate structure - struct fdtable.
files_struct contains a pointer to struct fdtable through
which the actual fd table is accessed. Initially the
fdtable is embedded in files_struct itself. On a subsequent
expansion of fdtable, a new fdtable structure is allocated
and files->fdtab points to the new structure. The fdtable
structure is freed with RCU and lock-free readers either
see the old fdtable or the new fdtable making the update
appear atomic. Here are the locking rules for
the fdtable structure -

RCU read와 fd lookup 규칙 1~5

38-86

모든 fdtable reference는 `files_fdtable()` macro를 통해 얻어야 합니다. 이 macro는 lock-free dereference에 필요한 memory barrier를 처리하는 `rcu_dereference()`를 사용하므로 pointer read는 `rcu_read_lock()`과 `rcu_read_unlock()` 사이에서 해야 합니다.

struct fdtable *fdt;

rcu_read_lock();
fdt = files_fdtable(files);
...
rcu_read_unlock();

fd table을 읽을 때는 RCU read-side critical section이 필수이고, update할 때는 `files->file_lock`을 잡아야 합니다.

fd로 `struct file`을 찾는 reader는 `lookup_fdget_rcu()` 또는 `files_lookup_fdget_rcu()`를 사용해야 합니다. 두 API가 lock-free lookup에 필요한 barrier와 reference 획득을 처리합니다. 반환된 file은 사용 뒤 `fput(file)`로 놓습니다.

struct file *file;

rcu_read_lock();
file = lookup_fdget_rcu(fd);
rcu_read_unlock();
if (file) {
        ...
        fput(file);
}

fdtable과 file structure는 lock-free lookup 대상이므로 설치할 때 `rcu_assign_pointer()`, 직접 lookup할 때 `rcu_dereference()`를 써야 합니다. 실무에서는 세부 조건을 처리하는 `files_fdtable()`과 `lookup_fdget_rcu()` 계열을 사용하는 것이 권장됩니다.

fdtable synchronization 규칙
작업필수 API·lock
fdtable pointer read`rcu_read_lock()` + `files_fdtable()`
fd table update`files->file_lock`
file lookup by fd`lookup_fdget_rcu()` 또는 `files_lookup_fdget_rcu()`
pointer publish`rcu_assign_pointer()`
직접 lock-free dereference`rcu_dereference()`
file reference release`fput()`

read, update, publish와 lookup에 필요한 primitive입니다.


1. All references to the fdtable must be done through
   the files_fdtable() macro::

        struct fdtable *fdt;

        rcu_read_lock();

        fdt = files_fdtable(files);
        ....
        if (n <= fdt->max_fds)
                ....
        ...
        rcu_read_unlock();

   files_fdtable() uses rcu_dereference() macro which takes care of
   the memory barrier requirements for lock-free dereference.
   The fdtable pointer must be read within the read-side
   critical section.

2. Reading of the fdtable as described above must be protected
   by rcu_read_lock()/rcu_read_unlock().

3. For any update to the fd table, files->file_lock must
   be held.

4. To look up the file structure given an fd, a reader
   must use either lookup_fdget_rcu() or files_lookup_fdget_rcu() APIs. These
   take care of barrier requirements due to lock-free lookup.

   An example::

        struct file *file;

        rcu_read_lock();
        file = lookup_fdget_rcu(fd);
        rcu_read_unlock();
        if (file) {
                ...
                fput(file);
        }
        ....

5. Since both fdtable and file structures can be looked up
   lock-free, they must be installed using rcu_assign_pointer()
   API. If they are looked up lock-free, rcu_dereference()
   must be used. However it is advisable to use files_fdtable()
   and lookup_fdget_rcu()/files_lookup_fdget_rcu() which take care of these
   issues.

Update 중 stale pointer와 object 재사용

87-123

fdtable을 update할 때 pointer는 `files->file_lock`을 잡은 상태에서 다시 얻어야 합니다. lock을 놓는 동안 다른 thread가 table을 확장하면 예전 pointer는 stale이 됩니다.

특히 `locate_fd()`는 `file_lock`을 놓았다가 다시 잡을 수 있으므로 호출 뒤에 `files_fdtable(files)`로 `fdt`를 다시 load한 다음 `__set_open_fd()`와 `__clear_close_on_exec()`를 수행해야 합니다.

spin_lock(&files->file_lock);
fd = locate_fd(files, file, start);
if (fd >= 0) {
        /* locate_fd() may have expanded fdtable, load the ptr */
        fdt = files_fdtable(files);
        __set_open_fd(fd, fdt);
        __clear_close_on_exec(fd, fdt);
        spin_unlock(&files->file_lock);
        ...
}

최신 kernel의 RCU file lookup은 `call_rcu()` 대신 `SLAB_TYPESAFE_BY_RCU`에 의존합니다. object가 이미 recycle되어 새 user가 reference를 올렸을 수 있으므로 RCU 안에서 `atomic_long_inc_not_zero()`만 성공했다고 원래 file reference를 얻었다고 볼 수 없습니다.

따라서 reference count를 올리기 전과 뒤의 pointer가 같은지 검증해야 합니다. 이 pattern은 `get_file_rcu()`와 `__files_get_rcu()`에서 볼 수 있습니다.

RCU lookup에서 reference를 먼저 획득하지 않고 `struct file` field에 접근하거나 검사할 수 없습니다. 예전에도 non-pointer data에만 간신히 적용할 수 있는 위험한 방식이었고, `SLAB_TYPESAFE_BY_RCU`에서는 caller가 reference를 먼저 얻거나 fdtable의 `files_lock`을 보유해야 합니다.

`SLAB_TYPESAFE_BY_RCU` file 획득
RCU lookup으로 file pointer 관찰`atomic_long_inc_not_zero()`로 reference 획득 시도reference 증가 전후 pointer identity 재검증같으면 `struct file` field 접근다르면 reference를 놓고 lookup 재시도

recycled object를 다른 file로 오인하지 않는 검증 순서입니다.


6. While updating, the fdtable pointer must be looked up while
   holding files->file_lock. If ->file_lock is dropped, then
   another thread expand the files thereby creating a new
   fdtable and making the earlier fdtable pointer stale.

   For example::

        spin_lock(&files->file_lock);
        fd = locate_fd(files, file, start);
        if (fd >= 0) {
                /* locate_fd() may have expanded fdtable, load the ptr */
                fdt = files_fdtable(files);
                __set_open_fd(fd, fdt);
                __clear_close_on_exec(fd, fdt);
                spin_unlock(&files->file_lock);
        .....

   Since locate_fd() can drop ->file_lock (and reacquire ->file_lock),
   the fdtable pointer (fdt) must be loaded after locate_fd().

On newer kernels rcu based file lookup has been switched to rely on
SLAB_TYPESAFE_BY_RCU instead of call_rcu(). It isn't sufficient anymore
to just acquire a reference to the file in question under rcu using
atomic_long_inc_not_zero() since the file might have already been
recycled and someone else might have bumped the reference. In other
words, callers might see reference count bumps from newer users. For
this is reason it is necessary to verify that the pointer is the same
before and after the reference count increment. This pattern can be seen
in get_file_rcu() and __files_get_rcu().

In addition, it isn't possible to access or check fields in struct file
without first acquiring a reference on it under rcu lookup. Not doing
that was always very dodgy and it was only usable for non-pointer data
in struct file. With SLAB_TYPESAFE_BY_RCU it is necessary that callers
either first acquire a reference or they must hold the files_lock of the
fdtable.