Documentation/driver-api/driver-model/design-patterns.rst GitHub 원문 ↗

Linux 6.18.37 · Driver API

Device Driver Design Patterns

reentrant driver를 위한 per-device state container와 container_of() pattern을 설명합니다.

Source pathDocumentation/driver-api/driver-model/design-patterns.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

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

1. 요약·해설

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

요약과 해설

design-patterns.rst:1-116

device instance마다 state container를 할당하고 callback context로 전달하면 singleton과 global state를 피할 수 있습니다. embedded callback member만 전달되는 경우 container_of()로 정확한 owner instance를 복원합니다.

2. 영어 원문 전체

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

원문 전체 펼치기
1 =============================
2 Device Driver Design Patterns
3 =============================
4
5 This document describes a few common design patterns found in device drivers.
6 It is likely that subsystem maintainers will ask driver developers to
7 conform to these design patterns.
8
9 1. State Container
10 2. container_of()
11
12
13 1. State Container
14 ~~~~~~~~~~~~~~~~~~
15
16 While the kernel contains a few device drivers that assume that they will
17 only be probed() once on a certain system (singletons), it is custom to assume
18 that the device the driver binds to will appear in several instances. This
19 means that the probe() function and all callbacks need to be reentrant.
20
21 The most common way to achieve this is to use the state container design
22 pattern. It usually has this form::
23
24 struct foo {
25 spinlock_t lock; /* Example member */
26 (...)
27 };
28
29 static int foo_probe(...)
30 {
31 struct foo *foo;
32
33 foo = devm_kzalloc(dev, sizeof(*foo), GFP_KERNEL);
34 if (!foo)
35 return -ENOMEM;
36 spin_lock_init(&foo->lock);
37 (...)
38 }
39
40 This will create an instance of struct foo in memory every time probe() is
41 called. This is our state container for this instance of the device driver.
42 Of course it is then necessary to always pass this instance of the
43 state around to all functions that need access to the state and its members.
44
45 For example, if the driver is registering an interrupt handler, you would
46 pass around a pointer to struct foo like this::
47
48 static irqreturn_t foo_handler(int irq, void *arg)
49 {
50 struct foo *foo = arg;
51 (...)
52 }
53
54 static int foo_probe(...)
55 {
56 struct foo *foo;
57
58 (...)
59 ret = request_irq(irq, foo_handler, 0, "foo", foo);
60 }
61
62 This way you always get a pointer back to the correct instance of foo in
63 your interrupt handler.
64
65
66 2. container_of()
67 ~~~~~~~~~~~~~~~~~
68
69 Continuing on the above example we add an offloaded work::
70
71 struct foo {
72 spinlock_t lock;
73 struct workqueue_struct *wq;
74 struct work_struct offload;
75 (...)
76 };
77
78 static void foo_work(struct work_struct *work)
79 {
80 struct foo *foo = container_of(work, struct foo, offload);
81
82 (...)
83 }
84
85 static irqreturn_t foo_handler(int irq, void *arg)
86 {
87 struct foo *foo = arg;
88
89 queue_work(foo->wq, &foo->offload);
90 (...)
91 }
92
93 static int foo_probe(...)
94 {
95 struct foo *foo;
96
97 foo->wq = create_singlethread_workqueue("foo-wq");
98 INIT_WORK(&foo->offload, foo_work);
99 (...)
100 }
101
102 The design pattern is the same for an hrtimer or something similar that will
103 return a single argument which is a pointer to a struct member in the
104 callback.
105
106 container_of() is a macro defined in <linux/kernel.h>
107
108 What container_of() does is to obtain a pointer to the containing struct from
109 a pointer to a member by a simple subtraction using the offsetof() macro from
110 standard C, which allows something similar to object oriented behaviours.
111 Notice that the contained member must not be a pointer, but an actual member
112 for this to work.
113
114 We can see here that we avoid having global pointers to our struct foo *
115 instance this way, while still keeping the number of parameters passed to the
116 work function to a single pointer.
117

3. 한국어 전문 번역

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

Device driver design pattern 소개

1-12

이 문서는 device driver에서 흔히 볼 수 있는 몇 가지 design pattern을 설명합니다. subsystem maintainer는 driver developer에게 이 pattern을 따르도록 요구할 가능성이 큽니다.

  • State Container
  • container_of()

State Container와 reentrant probe

13-44

kernel에는 특정 system에서 `probe()`가 한 번만 호출된다고 가정하는 singleton driver도 조금 있지만, 일반적인 관례는 driver가 bind하는 device instance가 여러 개 나타날 수 있다고 가정하는 것입니다. 따라서 `probe()`와 모든 callback은 reentrant해야 합니다.

이를 구현하는 가장 흔한 방법이 state container design pattern이며 보통 다음 형태입니다.

struct foo {
    spinlock_t lock; /* Example member */
    (...)
};

static int foo_probe(...)
{
    struct foo *foo;

    foo = devm_kzalloc(dev, sizeof(*foo), GFP_KERNEL);
    if (!foo)
        return -ENOMEM;
    spin_lock_init(&foo->lock);
    (...)
}

`probe()`가 호출될 때마다 memory에 `struct foo` instance를 하나 생성합니다. 이것이 해당 device-driver instance의 state container입니다. state와 member에 접근해야 하는 모든 function에 이 instance를 계속 전달해야 합니다.

Per-device state container
probe(device instance)devm_kzalloc(struct foo)member 초기화driver data와 callback context에 pointer 저장해당 instance의 state만 사용

각 probe가 독립 state를 만들고 callback에 전달하는 구조입니다.

Interrupt handler에 state 전달

45-65

예를 들어 driver가 interrupt handler를 등록한다면 다음처럼 `struct foo` pointer를 context로 전달합니다.

static irqreturn_t foo_handler(int irq, void *arg)
{
    struct foo *foo = arg;
    (...)
}

static int foo_probe(...)
{
    struct foo *foo;

    (...)
    ret = request_irq(irq, foo_handler, 0, "foo", foo);
}

이렇게 하면 interrupt handler에서 항상 올바른 `foo` instance pointer를 돌려받습니다.

container_of()와 offloaded work

66-101

앞의 예제에 offloaded work를 추가하면 다음과 같습니다.

struct foo {
    spinlock_t lock;
    struct workqueue_struct *wq;
    struct work_struct offload;
    (...)
};

static void foo_work(struct work_struct *work)
{
    struct foo *foo = container_of(work, struct foo, offload);

    (...)
}

static irqreturn_t foo_handler(int irq, void *arg)
{
    struct foo *foo = arg;

    queue_work(foo->wq, &foo->offload);
    (...)
}

static int foo_probe(...)
{
    struct foo *foo;

    foo->wq = create_singlethread_workqueue("foo-wq");
    INIT_WORK(&foo->offload, foo_work);
    (...)
}

interrupt handler는 `foo->offload` work를 queue하고, work callback은 전달받은 `struct work_struct *`에서 `container_of(work, struct foo, offload)`로 자신을 포함하는 `struct foo` instance를 복원합니다.

container_of()로 owner 복원
IRQ receives struct foo *argqueue_work(&foo->offload)foo_work receives work member pointercontainer_of(work, struct foo, offload)correct per-device struct foo recovered

member pointer에서 containing state object를 얻는 callback 흐름입니다.

container_of()의 조건과 효과

102-116

callback에서 struct member 하나를 가리키는 pointer만 돌려주는 `hrtimer` 같은 mechanism에도 같은 design pattern을 적용합니다. `container_of()`는 `<linux/kernel.h>`에 정의된 macro입니다.

`container_of()`는 standard C의 `offsetof()` macro를 이용한 단순 subtraction으로 member pointer에서 그 member를 포함하는 struct pointer를 구합니다. 이를 통해 object-oriented behavior와 비슷한 동작을 구현할 수 있습니다. 이 방식이 동작하려면 contained member가 pointer가 아니라 실제 embedded member여야 합니다.

따라서 `struct foo *` instance를 가리키는 global pointer를 피하면서도 work function에는 pointer 하나만 전달하면 됩니다.