# Linux 核心設計: Scope-based 資源管理
## 前言
C 語言中最為人所知的缺點之一,是不提供新語言(例如 Rust)常見的資源管理功能。這導致使用 C 語言編寫的程式(包括 Linux 核心)容易因設計的失誤,導致記憶體洩漏或鎖(lock)的釋放與鎖定的錯誤。
一個實際的例子是 Linux 中大量存在的 "goto error" 設計。例如依序配置 A、B、C 三個物件的情況,當中途配置錯誤時,錯誤處理必須依相反順序釋放已配置的資源。下面是 pseudo code。
```cpp
A = kmalloc(...);
if (!B)
goto err_A;
B = kmalloc(...);
if (!B)
goto err_B;
C = kmalloc(...);
err_B:
kfree(B);
err_A:
kfree(A);
```
在這樣已經存在多個展開條件的程式碼路徑上,加上資源的釋放方式可能各不相同,新增新的資源很困難且容易出錯。
幸運的是,編譯器設計上常提供語言規格外的擴充,能夠彌補其中的部分缺陷。在 Linux 核心中,開發人員就擅用這些功能,提供一系列的 [helpers](https://elixir.bootlin.com/linux/v6.17.1/source/include/linux/cleanup.h),透過編譯器處理相關操作,以助於維護 LIFO(last in first out)展開順序,從而避免意外洩漏。
## `__attribute__((cleanup))`
具體可以加強 C 語言對資源管理能力擴充的是 `__attribute__((cleanup))` 這個特殊的屬性。
使用方式的案例如下。首先,我們定義一個資源釋放的輔助函式,例如 `auto_kfree`,接著我們在變數上加註這個屬性並設定資源釋放所需使用的函式。
```cpp
void auto_kfree(void **p) { kfree(*p); }
struct foo *foo_ptr __attribute__((__cleanup__(auto_kfree))) = NULL;
/* ... */
foo_ptr = kmalloc(sizeof(struct foo));
```
如此一來,對於動態分配給 `foo_ptr` 的記憶體,一旦編譯器判斷超出其使用範圍(scope),將確保對其呼叫 `kfree()`。這協助後續的開發者不必額外思考資源的釋放。
雖然此功能並非很新的編譯器擴充,不過正式引入核心是直到 2023 年,Peter Zijlstra 提交了藉由該屬性加強對鎖的管理之想法 [ [PATCH v2 0/2] Lock and Pointer guards](https://lore.kernel.org/lkml/20230526205204.861311518@infradead.org/)。隨著對此的熱烈討論,Linus Torvalds 鼓勵 Peter 將其擴展至更廣泛的運用,最終在 [[PATCH v3 00/57] Scope-based Resource Management](https://lore.kernel.org/lkml/20230612090713.652690195@infradead.org/) 此機制的設計定案。直至今日,已經有許多子系統利用相關機制來更妥善的加強資源管理。
## 實作
### `DEFINE_FREE`
要對一個物件(變數)施加自動釋放的能力,Linux 的開發者可以善用 `cleanup.h` 底下提供的 [`DEFINE_FREE`](https://elixir.bootlin.com/linux/v6.17.1/source/include/linux/cleanup.h#L210)。
```cpp
#define DEFINE_FREE(_name, _type, _free) \
static inline void __free_##_name(void *p) { _type _T = *(_type *)p; _free; }
```
從上述展開可以看到,以 `DEFINE_FREE(kfree,void *,if(_T)kfree(_T))` 這個使用方式為例,該 macro 會創造一個特殊的函式 `__free_kfree`,並為其加上 `__attribute__((cleanup))` 的屬性。該函式被用來針對 `void *` 類型的指標,而釋放的實作是判斷該指標是否為空,若否則以 `kfree()` 釋放之。
藉由 `__attribute__((cleanup))`,當變數超出生命週期的 scope 時,就會自動釋放。開發者可以不必顯式的 `kfree`。
```cpp
#define __free(_name) __cleanup(__free_##_name)
#define __cleanup(func) __attribute__((__cleanup__(func)))
```
實際的應用,例如以下案例:
```cpp
void *p __free(kfree) = kmalloc(...);
```
則當 p 超出 scope 時,如果 p 非 NULL,編譯器將自動為其呼叫 `__free_kfree`。
### `return_ptr`
```cpp
#define __get_and_null(p, nullvalue) \
({ \
__auto_type __ptr = &(p); \
__auto_type __val = *__ptr; \
*__ptr = nullvalue; \
__val; \
})
#define no_free_ptr(p) \
((typeof(p)) __must_check_fn((__force const volatile void *)__get_and_null(p, NULL)))
#define return_ptr(p) return no_free_ptr(p)
```
也會有一些狀況是我們不想要指標被自動釋放,例如將指標從函式返回呼叫者時。此時可以善加利用 [`return_ptr`](https://elixir.bootlin.com/linux/v6.17.1/source/include/linux/cleanup.h#L230) 這個巨集。g實作透過將 `p` 的值複製到另一個變數,將原本的 `p` 設為 NULL,然後回傳複製的值,從而避免不預期的自動釋放。
### `DEFINE_CLASS`
透過 `__attribute__((cleanup))` 可以達成更進階的資源管理應用。在 `cleanup.h` 中提供了 [`DEFINE_CLASS`](https://elixir.bootlin.com/linux/v6.17.1/source/include/linux/cleanup.h#L275) 來實現類式現代語言(例如 C++)中提供的 `class` 語法,以支援 destructor 和 constructor 的定義。
```cpp
#define DEFINE_CLASS(_name, _type, _exit, _init, _init_args...) \
typedef _type class_##_name##_t; \
static inline void class_##_name##_destructor(_type *p) \
{ _type _T = *p; _exit; } \
static inline _type class_##_name##_constructor(_init_args) \
{ _type t = _init; return t; }
```
例如對於 file descriptor 的管理,可以建立名為 `fdget` 的新 class 來管理 reference 的獲取和釋放,以避免不當使用產生的 leak。
以 `DEFINE_CLASS(fdget, struct fd, fdput(_T), fdget(fd), int fd)` 來說,展開如下:
```cpp
typedef struct fd class_fdget_t;
static inline void class_fdget_destructor(struct fd *p)
{ struct fd _T = *p; fdput(_T); }
static inline struct fd class_fdget_constructor(int fd)
{ struct fd t = fdget(fd); return t; }
```
```cpp
#define CLASS(_name, var) \
class_##_name##_t var __cleanup(class_##_name##_destructor) = \
class_##_name##_constructor
```
則使用 `CLASS()` macro,可以用來定義屬於此 class 的變數。例如 `CLASS(fdget, f)(fd)`,會被展開成 `class_fdget_t var __cleanup(class_fdget_destructor) = class_fdget_constructor(fd)`。從這裡可以看出該變數將藉由 constructor 被建立,並在退出生命週期時由編譯器自動呼叫 destructor。
### `DEFINE_GUARD`
在 `cleanup.h` 底下也支援定義與 lock 相關的 class 之 macro [`DEFINE_GUARD`](https://elixir.bootlin.com/linux/v6.17.1/source/include/linux/cleanup.h#L378)。
```cpp
#define DEFINE_GUARD(_name, _type, _lock, _unlock) \
DEFINE_CLASS(_name, _type, if (!__GUARD_IS_ERR(_T)) { _unlock; }, ({ _lock; _T; }), _type _T); \
DEFINE_CLASS_IS_GUARD(_name)
```
`DEFINE_GUARD` 基於 `DEFINE_CLASS`,用來強化 lock 的獲取與釋放。
```cpp
#define guard(_name) \
CLASS(_name, __UNIQUE_ID(guard))
```
透過 [`guard()`](https://elixir.bootlin.com/linux/v6.17.1/source/include/linux/cleanup.h#L400) macro,可以用來定義屬於此類型鎖的變數。且這個 macro 會為變數加上特殊的前後綴,保證其為獨一無二的名稱。
舉例來說,以下的用法定義了能夠自動釋放的 mutex。
```cpp
DEFINE_GUARD(mutex, struct mutex *, mutex_lock(_T), mutex_unlock(_T)):
```
`DEFINE_GUARD` 對於錯誤處理程式碼的重構起到有效作用。在 Linux 中,一個常見的設計是在函數尾端執行清理,並在發生錯誤時使用 goto 跳到清理程式碼中的適當位置。範例如下:
```cpp
err = -EBUMMER;
mutex_lock(&the_lock);
if (!setup_first_thing())
goto out;
if (!setup_second_thing())
goto out2;
/* ... */
out2:
cleanup_first_thing();
out:
mutex_unlock(&the_lock);
return err;
```
當需要回收釋放的資源變多,這種模式將仰賴大量 goto,實作上容易出現錯誤。而藉由 "guard",我們可以將上述程式碼簡化如以下。
```cpp
guard(mutex)(&the_lock);
CLASS(first_thing, first)(...);
if (!first or !setup_second_thing())
return -EBUMMER;
return 0;
```
## Reference
* [Scope-based resource management for the kernel](https://lwn.net/Articles/934679/)
* [Scope-based Cleanup Helpers](https://docs.kernel.org/core-api/cleanup.html)
* [Linux Kernel Development - Automatic Cleanup 1/2](https://hackerbikepacker.com/kernel-auto-cleanup-1)