요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
===================================================
Adding reference counters (krefs) to kernel objects
===================================================
:Author: Corey Minyard <minyard@acm.org>
:Author: Thomas Hellström <thomas.hellstrom@linux.intel.com>
A lot of this was lifted from Greg Kroah-Hartman's 2004 OLS paper and
presentation on krefs, which can be found at:
- http://www.kroah.com/linux/talks/ols_2004_kref_paper/Reprint-Kroah-Hartman-OLS2004.pdf
- http://www.kroah.com/linux/talks/ols_2004_kref_talk/
Introduction
============
krefs allow you to add reference counters to your objects. If you
have objects that are used in multiple places and passed around, and
you don't have refcounts, your code is almost certainly broken. If
you want refcounts, krefs are the way to go.
To use a kref, add one to your data structures like::
struct my_data
{
.
.
struct kref refcount;
.
.
};
The kref can occur anywhere within the data structure.
Initialization
==============
You must initialize the kref after you allocate it. To do this, call
kref_init as so::
struct my_data *data;
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
kref_init(&data->refcount);
This sets the refcount in the kref to 1.
Kref rules
==========
Once you have an initialized kref, you must follow the following
rules:
1) If you make a non-temporary copy of a pointer, especially if
it can be passed to another thread of execution, you must
increment the refcount with kref_get() before passing it off::
kref_get(&data->refcount);
If you already have a valid pointer to a kref-ed structure (the
refcount cannot go to zero) you may do this without a lock.
2) When you are done with a pointer, you must call kref_put()::
kref_put(&data->refcount, data_release);
If this is the last reference to the pointer, the release
routine will be called. If the code never tries to get
a valid pointer to a kref-ed structure without already
holding a valid pointer, it is safe to do this without
a lock.
3) If the code attempts to gain a reference to a kref-ed structure
without already holding a valid pointer, it must serialize access
where a kref_put() cannot occur during the kref_get(), and the
structure must remain valid during the kref_get().
For example, if you allocate some data and then pass it to another
thread to process::
void data_release(struct kref *ref)
{
struct my_data *data = container_of(ref, struct my_data, refcount);
kfree(data);
}
void more_data_handling(void *cb_data)
{
struct my_data *data = cb_data;
.
. do stuff with data here
.
kref_put(&data->refcount, data_release);
}
int my_data_handler(void)
{
int rv = 0;
struct my_data *data;
struct task_struct *task;
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
kref_init(&data->refcount);
kref_get(&data->refcount);
task = kthread_run(more_data_handling, data, "more_data_handling");
if (task == ERR_PTR(-ENOMEM)) {
rv = -ENOMEM;
kref_put(&data->refcount, data_release);
goto out;
}
.
. do stuff with data here
.
out:
kref_put(&data->refcount, data_release);
return rv;
}
This way, it doesn't matter what order the two threads handle the
data, the kref_put() handles knowing when the data is not referenced
any more and releasing it. The kref_get() does not require a lock,
since we already have a valid pointer that we own a refcount for. The
put needs no lock because nothing tries to get the data without
already holding a pointer.
In the above example, kref_put() will be called 2 times in both success
and error paths. This is necessary because the reference count got
incremented 2 times by kref_init() and kref_get().
Note that the "before" in rule 1 is very important. You should never
do something like::
task = kthread_run(more_data_handling, data, "more_data_handling");
if (task == ERR_PTR(-ENOMEM)) {
rv = -ENOMEM;
goto out;
} else
/* BAD BAD BAD - get is after the handoff */
kref_get(&data->refcount);
Don't assume you know what you are doing and use the above construct.
First of all, you may not know what you are doing. Second, you may
know what you are doing (there are some situations where locking is
involved where the above may be legal) but someone else who doesn't
know what they are doing may change the code or copy the code. It's
bad style. Don't do it.
There are some situations where you can optimize the gets and puts.
For instance, if you are done with an object and enqueuing it for
something else or passing it off to something else, there is no reason
to do a get then a put::
/* Silly extra get and put */
kref_get(&obj->ref);
enqueue(obj);
kref_put(&obj->ref, obj_cleanup);
Just do the enqueue. A comment about this is always welcome::
enqueue(obj);
/* We are done with obj, so we pass our refcount off
to the queue. DON'T TOUCH obj AFTER HERE! */
The last rule (rule 3) is the nastiest one to handle. Say, for
instance, you have a list of items that are each kref-ed, and you wish
to get the first one. You can't just pull the first item off the list
and kref_get() it. That violates rule 3 because you are not already
holding a valid pointer. You must add a mutex (or some other lock).
For instance::
static DEFINE_MUTEX(mutex);
static LIST_HEAD(q);
struct my_data
{
struct kref refcount;
struct list_head link;
};
static struct my_data *get_entry()
{
struct my_data *entry = NULL;
mutex_lock(&mutex);
if (!list_empty(&q)) {
entry = container_of(q.next, struct my_data, link);
kref_get(&entry->refcount);
}
mutex_unlock(&mutex);
return entry;
}
static void release_entry(struct kref *ref)
{
struct my_data *entry = container_of(ref, struct my_data, refcount);
list_del(&entry->link);
kfree(entry);
}
static void put_entry(struct my_data *entry)
{
mutex_lock(&mutex);
kref_put(&entry->refcount, release_entry);
mutex_unlock(&mutex);
}
The kref_put() return value is useful if you do not want to hold the
lock during the whole release operation. Say you didn't want to call
kfree() with the lock held in the example above (since it is kind of
pointless to do so). You could use kref_put() as follows::
static void release_entry(struct kref *ref)
{
/* All work is done after the return from kref_put(). */
}
static void put_entry(struct my_data *entry)
{
mutex_lock(&mutex);
if (kref_put(&entry->refcount, release_entry)) {
list_del(&entry->link);
mutex_unlock(&mutex);
kfree(entry);
} else
mutex_unlock(&mutex);
}
This is really more useful if you have to call other routines as part
of the free operations that could take a long time or might claim the
same lock. Note that doing everything in the release routine is still
preferred as it is a little neater.
The above example could also be optimized using kref_get_unless_zero() in
the following way::
static struct my_data *get_entry()
{
struct my_data *entry = NULL;
mutex_lock(&mutex);
if (!list_empty(&q)) {
entry = container_of(q.next, struct my_data, link);
if (!kref_get_unless_zero(&entry->refcount))
entry = NULL;
}
mutex_unlock(&mutex);
return entry;
}
static void release_entry(struct kref *ref)
{
struct my_data *entry = container_of(ref, struct my_data, refcount);
mutex_lock(&mutex);
list_del(&entry->link);
mutex_unlock(&mutex);
kfree(entry);
}
static void put_entry(struct my_data *entry)
{
kref_put(&entry->refcount, release_entry);
}
Which is useful to remove the mutex lock around kref_put() in put_entry(), but
it's important that kref_get_unless_zero is enclosed in the same critical
section that finds the entry in the lookup table,
otherwise kref_get_unless_zero may reference already freed memory.
Note that it is illegal to use kref_get_unless_zero without checking its
return value. If you are sure (by already having a valid pointer) that
kref_get_unless_zero() will return true, then use kref_get() instead.
Krefs and RCU
=============
The function kref_get_unless_zero also makes it possible to use rcu
locking for lookups in the above example::
struct my_data
{
struct rcu_head rhead;
.
struct kref refcount;
.
.
};
static struct my_data *get_entry_rcu()
{
struct my_data *entry = NULL;
rcu_read_lock();
if (!list_empty(&q)) {
entry = container_of(q.next, struct my_data, link);
if (!kref_get_unless_zero(&entry->refcount))
entry = NULL;
}
rcu_read_unlock();
return entry;
}
static void release_entry_rcu(struct kref *ref)
{
struct my_data *entry = container_of(ref, struct my_data, refcount);
mutex_lock(&mutex);
list_del_rcu(&entry->link);
mutex_unlock(&mutex);
kfree_rcu(entry, rhead);
}
static void put_entry(struct my_data *entry)
{
kref_put(&entry->refcount, release_entry_rcu);
}
But note that the struct kref member needs to remain in valid memory for a
rcu grace period after release_entry_rcu was called. That can be accomplished
by using kfree_rcu(entry, rhead) as done above, or by calling synchronize_rcu()
before using kfree, but note that synchronize_rcu() may sleep for a
substantial amount of time.
Functions and structures
========================
.. kernel-doc:: include/linux/kref.h
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
문서 정보
1-13Kernel object에 reference counter(kref) 추가하기 (Adding reference counters (krefs) to kernel objects)
저자: Corey Minyard <minyard@acm.org>
저자: Thomas Hellström <thomas.hellstrom@linux.intel.com>
이 문서의 많은 내용은 Greg Kroah-Hartman의 2004 OLS kref 논문과 발표에서 가져왔습니다.
- http://www.kroah.com/linux/talks/ols_2004_kref_paper/Reprint-Kroah-Hartman-OLS2004.pdf
- http://www.kroah.com/linux/talks/ols_2004_kref_talk/
kref 소개와 embedding
14-34소개 (Introduction)
Kref를 사용하면 object에 reference counter를 추가할 수 있습니다. 여러 곳에서 사용하고 전달하는 object에 refcount가 없다면 code는 거의 확실히 잘못되어 있습니다. Refcount가 필요하면 kref를 사용해야 합니다.
Kref를 사용하려면 다음과 같이 data structure에 하나를 추가합니다.
struct my_data
{
.
.
struct kref refcount;
.
.
};
Kref는 data structure 안의 어느 위치에 있어도 됩니다.
kref 초기화
35-49초기화 (Initialization)
Kref를 할당한 뒤에는 반드시 초기화해야 합니다. 다음과 같이 `kref_init`을 호출합니다.
struct my_data *data;
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
kref_init(&data->refcount);
이 호출은 kref의 refcount를 1로 설정합니다.
kref의 세 가지 규칙
50-79Kref 규칙 (Kref rules)
초기화된 kref를 사용하려면 다음 규칙을 지켜야 합니다.
- 임시가 아닌 pointer 사본을 만들 때, 특히 다른 execution thread로 전달할 수 있다면 전달하기 전에 `kref_get()`으로 refcount를 증가시켜야 합니다.
- Pointer 사용을 마치면 `kref_put()`을 호출해야 합니다. 마지막 reference라면 release routine이 호출됩니다.
- 유효한 pointer를 이미 보유하지 않은 상태에서 kref가 있는 structure의 reference를 얻으려면 `kref_get()` 도중 `kref_put()`이 일어날 수 없도록 접근을 serialize해야 하며, `kref_get()` 동안 structure가 유효하게 남아 있어야 합니다.
첫 번째 규칙의 reference 획득 호출은 다음과 같습니다.
kref_get(&data->refcount);
Refcount가 0이 될 수 없는 kref structure의 유효한 pointer를 이미 가지고 있다면 lock 없이 `kref_get()`을 호출할 수 있습니다.
두 번째 규칙의 reference 해제 호출은 다음과 같습니다.
kref_put(&data->refcount, data_release);
이 pointer의 마지막 reference라면 release routine이 호출됩니다. 유효한 pointer를 보유하지 않고 kref structure의 pointer를 얻으려는 code가 없다면 이 작업도 lock 없이 안전하게 수행할 수 있습니다.
두 thread 사이에서 object 전달하기
80-134예를 들어 data를 할당해 다른 thread가 처리하도록 전달하는 code는 다음과 같습니다.
void data_release(struct kref *ref)
{
struct my_data *data = container_of(ref, struct my_data, refcount);
kfree(data);
}
void more_data_handling(void *cb_data)
{
struct my_data *data = cb_data;
.
. do stuff with data here
.
kref_put(&data->refcount, data_release);
}
int my_data_handler(void)
{
int rv = 0;
struct my_data *data;
struct task_struct *task;
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
kref_init(&data->refcount);
kref_get(&data->refcount);
task = kthread_run(more_data_handling, data, "more_data_handling");
if (task == ERR_PTR(-ENOMEM)) {
rv = -ENOMEM;
kref_put(&data->refcount, data_release);
goto out;
}
.
. do stuff with data here
.
out:
kref_put(&data->refcount, data_release);
return rv;
}
두 thread가 어떤 순서로 data를 처리하든 `kref_put()`이 더는 reference되지 않는 시점을 판단해 data를 release합니다. 이미 자신이 reference를 보유한 유효한 pointer가 있으므로 `kref_get()`에는 lock이 필요하지 않습니다. Pointer를 보유하지 않은 code가 data를 얻으려 하지 않으므로 put에도 lock이 필요 없습니다.
위 예제에서는 success path와 error path 모두 `kref_put()`을 두 번 호출합니다. `kref_init()`과 `kref_get()`이 reference count를 두 번 증가시켰으므로 두 번 감소시켜야 합니다.
handoff 전에 reference 얻기
135-152첫 번째 규칙의 '전에(before)'는 매우 중요합니다. 다음과 같이 handoff 뒤에 reference를 얻으면 안 됩니다.
task = kthread_run(more_data_handling, data, "more_data_handling");
if (task == ERR_PTR(-ENOMEM)) {
rv = -ENOMEM;
goto out;
} else
/* BAD BAD BAD - get is after the handoff */
kref_get(&data->refcount);
자신이 상황을 정확히 안다고 가정해 이런 구문을 사용하지 마십시오. Locking 때문에 합법적인 특수 상황이 있더라도 나중에 다른 사람이 code를 변경하거나 복사할 수 있습니다. 나쁜 style이므로 사용하지 않아야 합니다.
get/put 대신 reference 소유권 넘기기
153-168일부 상황에서는 get과 put을 최적화할 수 있습니다. Object 사용을 끝내면서 다른 작업을 위해 enqueue하거나 다른 곳으로 넘긴다면 get 직후 put을 수행할 이유가 없습니다.
/* Silly extra get and put */
kref_get(&obj->ref);
enqueue(obj);
kref_put(&obj->ref, obj_cleanup);
그 대신 enqueue만 수행하십시오. Reference 소유권을 queue에 넘겼으며 이후 object를 건드리면 안 된다는 comment를 남기는 것이 좋습니다.
enqueue(obj);
/* We are done with obj, so we pass our refcount off
to the queue. DON'T TOUCH obj AFTER HERE! */
유효한 pointer 없이 reference 얻기
169-210마지막 규칙인 세 번째 규칙이 가장 다루기 어렵습니다. 각각 kref를 가진 item의 list에서 첫 item을 얻는 경우, 유효한 pointer를 아직 보유하지 않았으므로 item을 list에서 꺼낸 뒤 바로 `kref_get()`을 호출할 수 없습니다. 이 접근에는 mutex 또는 다른 lock을 추가해야 합니다.
static DEFINE_MUTEX(mutex);
static LIST_HEAD(q);
struct my_data
{
struct kref refcount;
struct list_head link;
};
static struct my_data *get_entry()
{
struct my_data *entry = NULL;
mutex_lock(&mutex);
if (!list_empty(&q)) {
entry = container_of(q.next, struct my_data, link);
kref_get(&entry->refcount);
}
mutex_unlock(&mutex);
return entry;
}
static void release_entry(struct kref *ref)
{
struct my_data *entry = container_of(ref, struct my_data, refcount);
list_del(&entry->link);
kfree(entry);
}
static void put_entry(struct my_data *entry)
{
mutex_lock(&mutex);
kref_put(&entry->refcount, release_entry);
mutex_unlock(&mutex);
}
kref_put() 반환값으로 lock 범위 줄이기
211-236전체 release operation 동안 lock을 유지하고 싶지 않을 때 `kref_put()`의 반환값이 유용합니다. 예를 들어 lock을 잡은 채 `kfree()`를 호출하고 싶지 않다면 다음처럼 사용할 수 있습니다.
static void release_entry(struct kref *ref)
{
/* All work is done after the return from kref_put(). */
}
static void put_entry(struct my_data *entry)
{
mutex_lock(&mutex);
if (kref_put(&entry->refcount, release_entry)) {
list_del(&entry->link);
mutex_unlock(&mutex);
kfree(entry);
} else
mutex_unlock(&mutex);
}
Free 과정의 일부로 오래 걸리거나 같은 lock을 획득할 수 있는 다른 routine을 호출해야 할 때 특히 유용합니다. 다만 모든 작업을 release routine 안에서 수행하는 쪽이 조금 더 깔끔하므로 여전히 선호됩니다.
kref_get_unless_zero() 최적화
237-275위 예제는 `kref_get_unless_zero()`를 사용해 다음과 같이 최적화할 수도 있습니다.
static struct my_data *get_entry()
{
struct my_data *entry = NULL;
mutex_lock(&mutex);
if (!list_empty(&q)) {
entry = container_of(q.next, struct my_data, link);
if (!kref_get_unless_zero(&entry->refcount))
entry = NULL;
}
mutex_unlock(&mutex);
return entry;
}
static void release_entry(struct kref *ref)
{
struct my_data *entry = container_of(ref, struct my_data, refcount);
mutex_lock(&mutex);
list_del(&entry->link);
mutex_unlock(&mutex);
kfree(entry);
}
static void put_entry(struct my_data *entry)
{
kref_put(&entry->refcount, release_entry);
}
이 방식은 `put_entry()`에서 `kref_put()` 주위의 mutex lock을 없앨 때 유용합니다. 다만 `kref_get_unless_zero()`는 lookup table에서 entry를 찾는 것과 같은 critical section 안에 있어야 합니다. 그렇지 않으면 이미 free된 memory를 참조할 수 있습니다.
`kref_get_unless_zero()`의 반환값을 확인하지 않고 사용하는 것은 잘못입니다. 이미 유효한 pointer를 보유해 이 함수가 true를 반환한다고 확신할 수 있다면 대신 `kref_get()`을 사용하십시오.
Kref와 RCU
276-324Kref와 RCU (Krefs and RCU)
`kref_get_unless_zero`를 사용하면 위 예제의 lookup에 RCU locking을 사용할 수도 있습니다.
struct my_data
{
struct rcu_head rhead;
.
struct kref refcount;
.
.
};
static struct my_data *get_entry_rcu()
{
struct my_data *entry = NULL;
rcu_read_lock();
if (!list_empty(&q)) {
entry = container_of(q.next, struct my_data, link);
if (!kref_get_unless_zero(&entry->refcount))
entry = NULL;
}
rcu_read_unlock();
return entry;
}
static void release_entry_rcu(struct kref *ref)
{
struct my_data *entry = container_of(ref, struct my_data, refcount);
mutex_lock(&mutex);
list_del_rcu(&entry->link);
mutex_unlock(&mutex);
kfree_rcu(entry, rhead);
}
static void put_entry(struct my_data *entry)
{
kref_put(&entry->refcount, release_entry_rcu);
}
`release_entry_rcu`를 호출한 뒤 RCU grace period 동안 `struct kref` member가 유효한 memory에 남아 있어야 합니다. 위처럼 `kfree_rcu(entry, rhead)`를 사용하거나 `kfree` 전에 `synchronize_rcu()`를 호출하면 됩니다. 다만 `synchronize_rcu()`는 상당히 오래 sleep할 수 있습니다.
함수와 structure
325-328함수와 structure (Functions and structures)
Kref API의 kernel-doc 원문은 `include/linux/kref.h`에서 가져옵니다.
.. kernel-doc:: include/linux/kref.h
요약과 해설
kref.rst:1-328Kref는 여러 실행 경로가 공유하는 kernel object의 lifetime을 reference count로 보호합니다. `kref_init()`은 최초 소유권 하나를 만들고, pointer의 비임시 사본을 넘기기 전에 `kref_get()`으로 소유권을 추가하며, 사용을 마치면 `kref_put()`으로 반환합니다.
핵심 경계는 유효한 pointer를 이미 보유했는지 여부입니다. 보유했다면 count가 0이 될 수 없어 보통 lock 없이 get/put할 수 있지만, lookup table이나 list에서 새 pointer를 찾는 경로는 검색과 reference 획득을 같은 critical section에서 serialize해야 합니다.
`kref_get_unless_zero()`는 이미 0이 된 object의 부활을 막으며 mutex 또는 RCU lookup에 사용할 수 있습니다. RCU 경로에서는 마지막 put 뒤에도 `struct kref`가 grace period 동안 살아 있도록 `kfree_rcu()`나 `synchronize_rcu()`를 사용해야 합니다.