---
# System prepended metadata

title: 'Linux 核心設計: Scope-based 資源管理'
tags: [Linux Kernel Internals, ' 作業系統']

---

# 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)