리눅스 2.4 버전까지 시스템 버퍼에 쌓여있는 내용을 디스크로 기록하는 데몬은 bdflush / kupdate 2가지가 존재하였다. 아래 코드를 보면 kernel_thread 를 사용하여 부팅 초기에 각 데몬을 thread 로 생성하여 실행하는 것을 볼 수 있다.
bdflush 데몬은 모든 dirty buffer 를 디스크로 기록(write_some_buffers)하는 역할을 수행한다. 모든 dirty buffer 를 디스크에 기록한 이후에는 bdflush_wait 이라는 wait queue 에서 sleep(interruptible_sleep_on) 한다. 이후에 1) 새로운 메모리를 요청하거나, 2) 불필요한 버퍼를 free 하거나 3) write 시점에 특정 조건을 만족하게 되면 다시 bdflush 데몬을 깨워주도록 구현되어 있다.
다음은 bdflush 의 핵심 코드 부분이다.
interruptible_sleep_on 으로 bdflush_wait queue 에서 sleep 하는 thread 를 깨워주는 함수는 wakup_bdflush 이다. 그럼 어떤 상황에서 bdflush 를 깨우는지 확인해보자 다음은 wakeup_bdflush 함수가 호출되는 부분이다.
<linux 2.4.31 fs/buffer.c>
1. static void free_more_memory(void)
- 메모리를 더 얻어오는 함수. bdflush 를 깨워준 후에 free 메모리를 얻어오는 함수 호출
2. void balance_dirty(void)
- write 가 이루어진 시점에 호출되어 balance_dirty_state() 를 호출하여 bdflush 를 깨울 조건을 판단. 조건을 만족하는 경우에만 bdflush 를 호출
3. int fastcall try_to_free_buffers(struct page * page, unsigned int gfp_mask)
- 사용되지 않는 버퍼를 free 시키는 함수.
위 내용을 확인해보면 bdflush 는 메모리를 확보하기 위해서 또는 전체 시스템에서 dirty buffer 가 차지하는 메모리가 일정 이상 넘지 않도록 하기위해서 호출된다는 것을 알 수 있다.
그럼 버퍼를 디스크로 기록하는 또다른 데몬인 kupdate 에 대해서 살펴보자. kupdate 데몬은 bdf_prm.b_un.interval (5초)마다 주기적으로 깨어나서 dirty 된지 30초가 지난 버퍼들만 디스크로 내려주는 기능을 한다.(sync_old_buffers)
각 버퍼가 dirty 된 시간은 어떻게 계산하는 것일까? __mark_dirty / set_buffer_flushtime 함수를 보면 모든 버퍼들은 dirty 가 되는 순간에 현재 jiffies 값에 bdf_prm.b_un.age_buffer(30초)를 더한 값을 버퍼 헤더에 기록해준다. 그러면 kupdate 가 깨어나서 sync_old_buffers 함수를 수행하는데, sync_old_buffers 함수에서 현재 jiffies 값과 버퍼 헤더의 jiffies 값을 비교하여 30초가 지난 버퍼만 디스크에 기록해준다.
이러한 구조는 리눅스 2.5 이상에서 구조가 완전히 바뀐다. 리눅스 2.5 이상 버전에서 아무리 찾아봐도 bdflush 라는 이름의 함수가 존재하지 않는다. 대신 pdflush 라는 함수가 존재하는데, 구조를 잘 살펴보면, 2.4 에 비해서 조금은 구조화되고 깔끔해 진 것을 알 수 있다.
우선 pdflush 는 주어진 함수 포인터를 수행하는 커널 thread 로 구성되어 있다. 함수 포인터는 특정 함수에 의해서 등록되며, 함수 포인터를 등록해주는 함수는 자고 있는 pdflush thread 를 깨워준다.
기본적으로 커널이 부팅되면 2개의 thread 가 미리 생성된다. 생성된 pdflush 는 기본적으로 sleep 상태로 들어간다. 그리고 필요에 따라 깨워지며 최대 8개까지 thread 가 생성되어 동시에 수행될 수 있다.
아래 함수를 보면 부팅 초기에 pdflush thread 를 2개 생성하는 것을 확인 할 수 있다.
앞서 말한 바와 같이 pdflush 는 등록된 함수 포인터를 수행한다. 즉, 특정 작업을 수행하도록 설계되어 있지 않다. 사용자(커널 프로그래머)가 데몬으로 수행하기위해 필요한 함수를 pdflush_operation 을 사용하여 등록하는 구조로 되어 있다.
start_one_pdflush_thread 함수는 kernel_thread 함수를 사용하여 pdflush 함수를 thread 로 생성하여 수행한다. pdflush 함수는 궁극적으로 __pdflush 를 호출하는데, pdflush 의 핵심 부분이 __pdflush 함수이다. 그럼 __pdflush 가 어떤 방식으로 동작하는 지를 알아보자. 아래 코드에서 빨간색으로 되어있는 부분이 pdflush 함수의 중요한 부분이다.
최소 생성된 pdflush thread 는 1) 에서 설명한 것과 같이 pdflush_list 에 자신의 구조체를 넣고 sleep 한다. (sleep 하고 있는 pdflush 는 곧 설명할 pdflush_operation 함수를 호출하여 깨워준다.) 깨워진 thread 는 pdflush_operation 함수에서 등록된 함수 포인터를 사용하여 2) 부분에서 해당 함수를 수행한다. 수행이 끝나면 현재 3), 4) 와 같이 pdflush thread 상태를 확인하여 thread 를 더 만들 것인지 존재하는 thread 를 종료할 것인지를 결정한다.
즉 pdflush 데몬은 필요에 따라서 thread 개수를 동적으로 조절하면서 데몬이 할일을 수행한다. 즉 필요하면 증가시키고, 일이없어서 쉬고 있는 경우에는 해당 thread 를 종료시켜서 resource 를 확보한다.
그럼 리눅스 2.4 에서 사용되었던 bdflush / kupdate 즉 dirty buffer 를 디스크로 기록하는 함수가 리눅스 2.5 이상에서는 어떻게 동작하는지 확인해보자. __pdflush 코드 1)에서 보면 pdflush_list 에 넣고 누군가 깨워주기전까지 계속 sleep 하는 것으로 구현되어 있다. 따라서 pdflush_list 에 접근하여 자고 있는 thread 를 깨워주는 코드를 찾아보자.
커널을 찾아보면 pdflush_operation 함수에서 pdflush_list 를 사용하고 있다. 즉 pdflush_operation 이라는 함수에 함수 포인터와 해당 함수의 파라미터를 pdflush_list 에 달려있던 pdflush_work 구조체를 통하여 argument 로 넘기고 원하는 thread 를 pdflush thread 구조를 통해서 수행한다는 것을 알 수 있다.
여기까지 pdflush thread 를 사용하기 위해서 pdflush_operation 함수를 사용하여 원하는 함수를 등록하고 실행할 수 있다는 것을 확인하였다.
그럼 pdflush_operation 을 호출하는 상황은 어떤 경우가 있는지 확인해보자. 아래 내용은 pdflush_operation 을 호출해주는 부분을 찾아 리스팅 해놓은 것이다.
아래 내용을 리눅스 2.4 버전에서 wakeup_bdflush 를 호출한 부분과 비교해보면 유사하다는 것을 알 수 있을 것이다. 즉 2.4 에서는 bdflush / kupdate 와 같이 직접 thread 를 만들어서 수행하던 것을 2.6 버전에서는 pdflush 라는 하나의 구조를 사용하여 2.4 에서 수행해주던 bdflush / kupdate 기능을 모두 사용할 수 있도록 한 것이다.
이러한 공통 구조는 커널 프로그래밍에서 필요한 thread 가 있을 경우에 따로 thread 를 생성하지 않고 제공된 구조를 사용할 수 있게 함으로써 커널 코드를 좀더 효율적이고 분석하기 쉽게 해준다.
리눅스 2.5 이상 버전에서 사용된 background_writeout 함수는 리눅스 2.4 이하 버전에서 사용되던 bdflush 기능을 수행하고, 리눅스 2.5 이상에서 사용된 wb_kupdate 는 리눅스 2.4 이하 버전에서 kupdate 데몬을 대체한다.
마지막으로 background_writeout 함수를 살펴보면 write_inodes 함수를 호출하여 superblock 에 달려있는 모든 dirty inodes 에 대해서 dirty buffer 를 디스크로 기록하는 기능을 수행한다.
이상 dirty buffer 를 디스크로 기록해주던 thread 가 2.6 커널에서 어떤 식으로 구현되어있는지 살펴보았다.
<linux 2.4.31 fs/buffer.c>
static int __init bdflush_init(void)
{
static struct completion startup __initdata = COMPLETION_INITIALIZER(startup);
kernel_thread(bdflush, &startup, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
wait_for_completion(&startup);
kernel_thread(kupdate, &startup, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
wait_for_completion(&startup);
return 0;
}
static int __init bdflush_init(void)
{
static struct completion startup __initdata = COMPLETION_INITIALIZER(startup);
kernel_thread(bdflush, &startup, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
wait_for_completion(&startup);
kernel_thread(kupdate, &startup, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
wait_for_completion(&startup);
return 0;
}
bdflush 데몬은 모든 dirty buffer 를 디스크로 기록(write_some_buffers)하는 역할을 수행한다. 모든 dirty buffer 를 디스크에 기록한 이후에는 bdflush_wait 이라는 wait queue 에서 sleep(interruptible_sleep_on) 한다. 이후에 1) 새로운 메모리를 요청하거나, 2) 불필요한 버퍼를 free 하거나 3) write 시점에 특정 조건을 만족하게 되면 다시 bdflush 데몬을 깨워주도록 구현되어 있다.
다음은 bdflush 의 핵심 코드 부분이다.
<linux 2.4.31 fs/buffer.c>
for (;;) {
(생략)
while (ndirty > 0) {
spin_lock(&lru_list_lock);
if (!write_some_buffers(NODEV))
break;
ndirty -= NRSYNC;
}
if (ndirty > 0 || bdflush_stop())
interruptible_sleep_on(&bdflush_wait);
}
for (;;) {
(생략)
while (ndirty > 0) {
spin_lock(&lru_list_lock);
if (!write_some_buffers(NODEV))
break;
ndirty -= NRSYNC;
}
if (ndirty > 0 || bdflush_stop())
interruptible_sleep_on(&bdflush_wait);
}
interruptible_sleep_on 으로 bdflush_wait queue 에서 sleep 하는 thread 를 깨워주는 함수는 wakup_bdflush 이다. 그럼 어떤 상황에서 bdflush 를 깨우는지 확인해보자 다음은 wakeup_bdflush 함수가 호출되는 부분이다.
<linux 2.4.31 fs/buffer.c>
1. static void free_more_memory(void)
- 메모리를 더 얻어오는 함수. bdflush 를 깨워준 후에 free 메모리를 얻어오는 함수 호출
2. void balance_dirty(void)
- write 가 이루어진 시점에 호출되어 balance_dirty_state() 를 호출하여 bdflush 를 깨울 조건을 판단. 조건을 만족하는 경우에만 bdflush 를 호출
3. int fastcall try_to_free_buffers(struct page * page, unsigned int gfp_mask)
- 사용되지 않는 버퍼를 free 시키는 함수.
위 내용을 확인해보면 bdflush 는 메모리를 확보하기 위해서 또는 전체 시스템에서 dirty buffer 가 차지하는 메모리가 일정 이상 넘지 않도록 하기위해서 호출된다는 것을 알 수 있다.
그럼 버퍼를 디스크로 기록하는 또다른 데몬인 kupdate 에 대해서 살펴보자. kupdate 데몬은 bdf_prm.b_un.interval (5초)마다 주기적으로 깨어나서 dirty 된지 30초가 지난 버퍼들만 디스크로 내려주는 기능을 한다.(sync_old_buffers)
각 버퍼가 dirty 된 시간은 어떻게 계산하는 것일까? __mark_dirty / set_buffer_flushtime 함수를 보면 모든 버퍼들은 dirty 가 되는 순간에 현재 jiffies 값에 bdf_prm.b_un.age_buffer(30초)를 더한 값을 버퍼 헤더에 기록해준다. 그러면 kupdate 가 깨어나서 sync_old_buffers 함수를 수행하는데, sync_old_buffers 함수에서 현재 jiffies 값과 버퍼 헤더의 jiffies 값을 비교하여 30초가 지난 버퍼만 디스크에 기록해준다.
<linux 2.4.31 fs/buffers>
for (;;) {
(생략) ...
interval = bdf_prm.b_un.interval;
if (interval) {
tsk->state = TASK_INTERRUPTIBLE;
schedule_timeout(interval);
} else {
tsk->state = TASK_STOPPED;
schedule(); /* wait for SIGCONT */
}
(생략) ...
sync_old_buffers();
if (laptop_mode)
fsync_dev(NODEV);
run_task_queue(&tq_disk);
}
for (;;) {
(생략) ...
interval = bdf_prm.b_un.interval;
if (interval) {
tsk->state = TASK_INTERRUPTIBLE;
schedule_timeout(interval);
} else {
tsk->state = TASK_STOPPED;
schedule(); /* wait for SIGCONT */
}
(생략) ...
sync_old_buffers();
if (laptop_mode)
fsync_dev(NODEV);
run_task_queue(&tq_disk);
}
이러한 구조는 리눅스 2.5 이상에서 구조가 완전히 바뀐다. 리눅스 2.5 이상 버전에서 아무리 찾아봐도 bdflush 라는 이름의 함수가 존재하지 않는다. 대신 pdflush 라는 함수가 존재하는데, 구조를 잘 살펴보면, 2.4 에 비해서 조금은 구조화되고 깔끔해 진 것을 알 수 있다.
우선 pdflush 는 주어진 함수 포인터를 수행하는 커널 thread 로 구성되어 있다. 함수 포인터는 특정 함수에 의해서 등록되며, 함수 포인터를 등록해주는 함수는 자고 있는 pdflush thread 를 깨워준다.
기본적으로 커널이 부팅되면 2개의 thread 가 미리 생성된다. 생성된 pdflush 는 기본적으로 sleep 상태로 들어간다. 그리고 필요에 따라 깨워지며 최대 8개까지 thread 가 생성되어 동시에 수행될 수 있다.
아래 함수를 보면 부팅 초기에 pdflush thread 를 2개 생성하는 것을 확인 할 수 있다.
<linux 2.6.30-5 mm/pdflush.c>
static int __init pdflush_init(void)
{
(생략) ...
for (i = 0; i < MIN_PDFLUSH_THREADS; i++)
start_one_pdflush_thread();
return 0;
}
static int __init pdflush_init(void)
{
(생략) ...
for (i = 0; i < MIN_PDFLUSH_THREADS; i++)
start_one_pdflush_thread();
return 0;
}
앞서 말한 바와 같이 pdflush 는 등록된 함수 포인터를 수행한다. 즉, 특정 작업을 수행하도록 설계되어 있지 않다. 사용자(커널 프로그래머)가 데몬으로 수행하기위해 필요한 함수를 pdflush_operation 을 사용하여 등록하는 구조로 되어 있다.
start_one_pdflush_thread 함수는 kernel_thread 함수를 사용하여 pdflush 함수를 thread 로 생성하여 수행한다. pdflush 함수는 궁극적으로 __pdflush 를 호출하는데, pdflush 의 핵심 부분이 __pdflush 함수이다. 그럼 __pdflush 가 어떤 방식으로 동작하는 지를 알아보자. 아래 코드에서 빨간색으로 되어있는 부분이 pdflush 함수의 중요한 부분이다.
<linux 2.6.28-15 mm/pdflush.c>
static int __pdflush(struct pdflush_work *my_work)
{
current->flags |= PF_FLUSHER | PF_SWAPWRITE;
set_freezable();
my_work->fn = NULL;
my_work->who = current;
INIT_LIST_HEAD(&my_work->list);
spin_lock_irq(&pdflush_lock);
nr_pdflush_threads++;
for ( ; ; ) {
struct pdflush_work *pdf;
set_current_state(TASK_INTERRUPTIBLE);
list_move(&my_work->list, &pdflush_list); --> 1) pdflsuh_list 에 current work 을 넣고 sleep 한다.
my_work->when_i_went_to_sleep = jiffies;
spin_unlock_irq(&pdflush_lock);
schedule(); --> 1) TASK_INTERRUPTIBLE 이기 때문에 다른 thread 가 깨워주워야 다시 실행된다.
try_to_freeze();
spin_lock_irq(&pdflush_lock);
if (!list_empty(&my_work->list)) {
/*
* Someone woke us up, but without removing our control
* structure from the global list. swsusp will do this
* in try_to_freeze()->refrigerator(). Handle it.
*/
my_work->fn = NULL;
continue;
}
if (my_work->fn == NULL) {
printk("pdflush: bogus wakeup\n");
continue;
}
spin_unlock_irq(&pdflush_lock);
(*my_work->fn)(my_work->arg0); --> 2) 사용자가 등록한 furnction 을 수행한다. \
어디서 함수를 등록하는 것인가?
/* 3) pdflush_list 가 empty 인지 1초가 지난 후에도 list 가 비어있는 경우, 즉 모든 pdflush thread 가
계속 작업을 수행하고 있다는 것을 의미하는 경우에 pdflush thread 가 8개 이하인 경우에 새로운
pdflush thread 를 생성한다. */
if (time_after(jiffies, last_empty_jifs + 1 * HZ)) {
/* unlocked list_empty() test is OK here */
if (list_empty(&pdflush_list)) {
/* unlocked test is OK here */
if (nr_pdflush_threads < MAX_PDFLUSH_THREADS)
start_one_pdflush_thread();
}
}
spin_lock_irq(&pdflush_lock);
my_work->fn = NULL;
/* 4) pdflush_list 가 empty 인 경우에는 pdflush 개수와 무관하게 pdflush_list 에 넣고 다시 sleep 하고,
그렇지 않은 경우라면, 현재 thread 개수가 3개 이상이고, sleep 후 1초가 지난 pdflush thread 를
종료한다. */
if (list_empty(&pdflush_list))
continue;
if (nr_pdflush_threads <= MIN_PDFLUSH_THREADS)
continue;
pdf = list_entry(pdflush_list.prev, struct pdflush_work, list);
if (time_after(jiffies, pdf->when_i_went_to_sleep + 1 * HZ)) {
/* Limit exit rate */
pdf->when_i_went_to_sleep = jiffies;
break; /* exeunt */
}
}
nr_pdflush_threads--;
spin_unlock_irq(&pdflush_lock);
return 0;
}
static int __pdflush(struct pdflush_work *my_work)
{
current->flags |= PF_FLUSHER | PF_SWAPWRITE;
set_freezable();
my_work->fn = NULL;
my_work->who = current;
INIT_LIST_HEAD(&my_work->list);
spin_lock_irq(&pdflush_lock);
nr_pdflush_threads++;
for ( ; ; ) {
struct pdflush_work *pdf;
set_current_state(TASK_INTERRUPTIBLE);
list_move(&my_work->list, &pdflush_list); --> 1) pdflsuh_list 에 current work 을 넣고 sleep 한다.
my_work->when_i_went_to_sleep = jiffies;
spin_unlock_irq(&pdflush_lock);
schedule(); --> 1) TASK_INTERRUPTIBLE 이기 때문에 다른 thread 가 깨워주워야 다시 실행된다.
try_to_freeze();
spin_lock_irq(&pdflush_lock);
if (!list_empty(&my_work->list)) {
/*
* Someone woke us up, but without removing our control
* structure from the global list. swsusp will do this
* in try_to_freeze()->refrigerator(). Handle it.
*/
my_work->fn = NULL;
continue;
}
if (my_work->fn == NULL) {
printk("pdflush: bogus wakeup\n");
continue;
}
spin_unlock_irq(&pdflush_lock);
(*my_work->fn)(my_work->arg0); --> 2) 사용자가 등록한 furnction 을 수행한다. \
어디서 함수를 등록하는 것인가?
/* 3) pdflush_list 가 empty 인지 1초가 지난 후에도 list 가 비어있는 경우, 즉 모든 pdflush thread 가
계속 작업을 수행하고 있다는 것을 의미하는 경우에 pdflush thread 가 8개 이하인 경우에 새로운
pdflush thread 를 생성한다. */
if (time_after(jiffies, last_empty_jifs + 1 * HZ)) {
/* unlocked list_empty() test is OK here */
if (list_empty(&pdflush_list)) {
/* unlocked test is OK here */
if (nr_pdflush_threads < MAX_PDFLUSH_THREADS)
start_one_pdflush_thread();
}
}
spin_lock_irq(&pdflush_lock);
my_work->fn = NULL;
/* 4) pdflush_list 가 empty 인 경우에는 pdflush 개수와 무관하게 pdflush_list 에 넣고 다시 sleep 하고,
그렇지 않은 경우라면, 현재 thread 개수가 3개 이상이고, sleep 후 1초가 지난 pdflush thread 를
종료한다. */
if (list_empty(&pdflush_list))
continue;
if (nr_pdflush_threads <= MIN_PDFLUSH_THREADS)
continue;
pdf = list_entry(pdflush_list.prev, struct pdflush_work, list);
if (time_after(jiffies, pdf->when_i_went_to_sleep + 1 * HZ)) {
/* Limit exit rate */
pdf->when_i_went_to_sleep = jiffies;
break; /* exeunt */
}
}
nr_pdflush_threads--;
spin_unlock_irq(&pdflush_lock);
return 0;
}
최소 생성된 pdflush thread 는 1) 에서 설명한 것과 같이 pdflush_list 에 자신의 구조체를 넣고 sleep 한다. (sleep 하고 있는 pdflush 는 곧 설명할 pdflush_operation 함수를 호출하여 깨워준다.) 깨워진 thread 는 pdflush_operation 함수에서 등록된 함수 포인터를 사용하여 2) 부분에서 해당 함수를 수행한다. 수행이 끝나면 현재 3), 4) 와 같이 pdflush thread 상태를 확인하여 thread 를 더 만들 것인지 존재하는 thread 를 종료할 것인지를 결정한다.
즉 pdflush 데몬은 필요에 따라서 thread 개수를 동적으로 조절하면서 데몬이 할일을 수행한다. 즉 필요하면 증가시키고, 일이없어서 쉬고 있는 경우에는 해당 thread 를 종료시켜서 resource 를 확보한다.
그럼 리눅스 2.4 에서 사용되었던 bdflush / kupdate 즉 dirty buffer 를 디스크로 기록하는 함수가 리눅스 2.5 이상에서는 어떻게 동작하는지 확인해보자. __pdflush 코드 1)에서 보면 pdflush_list 에 넣고 누군가 깨워주기전까지 계속 sleep 하는 것으로 구현되어 있다. 따라서 pdflush_list 에 접근하여 자고 있는 thread 를 깨워주는 코드를 찾아보자.
커널을 찾아보면 pdflush_operation 함수에서 pdflush_list 를 사용하고 있다. 즉 pdflush_operation 이라는 함수에 함수 포인터와 해당 함수의 파라미터를 pdflush_list 에 달려있던 pdflush_work 구조체를 통하여 argument 로 넘기고 원하는 thread 를 pdflush thread 구조를 통해서 수행한다는 것을 알 수 있다.
<linux 2.6.28-15 mm/pdflush.c>
int pdflush_operation(void (*fn)(unsigned long), unsigned long arg0)
{
unsigned long flags;
int ret = 0;
BUG_ON(fn == NULL); /* Hard to diagnose if it's deferred */
spin_lock_irqsave(&pdflush_lock, flags);
if (list_empty(&pdflush_list)) {
ret = -1;
} else {
struct pdflush_work *pdf;
pdf = list_entry(pdflush_list.next, struct pdflush_work, list);
list_del_init(&pdf->list);
if (list_empty(&pdflush_list))
last_empty_jifs = jiffies;
pdf->fn = fn; --> 실행하고 싶은 함수 포인터 등록
pdf->arg0 = arg0; --> 위 함수 포인터에서 사용될 argument 등록
wake_up_process(pdf->who); --> 자고있는 pdflush thread 를 깨워준다.
}
spin_unlock_irqrestore(&pdflush_lock, flags);
return ret;
}
int pdflush_operation(void (*fn)(unsigned long), unsigned long arg0)
{
unsigned long flags;
int ret = 0;
BUG_ON(fn == NULL); /* Hard to diagnose if it's deferred */
spin_lock_irqsave(&pdflush_lock, flags);
if (list_empty(&pdflush_list)) {
ret = -1;
} else {
struct pdflush_work *pdf;
pdf = list_entry(pdflush_list.next, struct pdflush_work, list);
list_del_init(&pdf->list);
if (list_empty(&pdflush_list))
last_empty_jifs = jiffies;
pdf->fn = fn; --> 실행하고 싶은 함수 포인터 등록
pdf->arg0 = arg0; --> 위 함수 포인터에서 사용될 argument 등록
wake_up_process(pdf->who); --> 자고있는 pdflush thread 를 깨워준다.
}
spin_unlock_irqrestore(&pdflush_lock, flags);
return ret;
}
여기까지 pdflush thread 를 사용하기 위해서 pdflush_operation 함수를 사용하여 원하는 함수를 등록하고 실행할 수 있다는 것을 확인하였다.
그럼 pdflush_operation 을 호출하는 상황은 어떤 경우가 있는지 확인해보자. 아래 내용은 pdflush_operation 을 호출해주는 부분을 찾아 리스팅 해놓은 것이다.
아래 내용을 리눅스 2.4 버전에서 wakeup_bdflush 를 호출한 부분과 비교해보면 유사하다는 것을 알 수 있을 것이다. 즉 2.4 에서는 bdflush / kupdate 와 같이 직접 thread 를 만들어서 수행하던 것을 2.6 버전에서는 pdflush 라는 하나의 구조를 사용하여 2.4 에서 수행해주던 bdflush / kupdate 기능을 모두 사용할 수 있도록 한 것이다.
이러한 공통 구조는 커널 프로그래밍에서 필요한 thread 가 있을 경우에 따로 thread 를 생성하지 않고 제공된 구조를 사용할 수 있게 함으로써 커널 코드를 좀더 효율적이고 분석하기 쉽게 해준다.
리눅스 2.5 이상 버전에서 사용된 background_writeout 함수는 리눅스 2.4 이하 버전에서 사용되던 bdflush 기능을 수행하고, 리눅스 2.5 이상에서 사용된 wb_kupdate 는 리눅스 2.4 이하 버전에서 kupdate 데몬을 대체한다.
background_writeout 함수로 호출하는 부분
<linux 2.6.28-15 mm/page-writeback.c>
1. static void balance_dirty_pages(struct address_space *mapping)
2. int wakeup_pdflush(long nr_pages)
a. <fs/buffer.c>static void free_more_memory(void)
b. <fs/sync.c> static void do_sync(unsigned logn wait)
c. <mm/vmscan.c>
static unsigned long do_try_to_free_pages(struct zonelist *zonelist, struct scan_control *sc)
wb_kupdate 함수로 호출하는 부분
<linux 2.6.28-15 mm/page-writeback.c>
1. static void wb_timer_fn(unsigned long unused)
<linux 2.6.28-15 mm/page-writeback.c>
1. static void balance_dirty_pages(struct address_space *mapping)
2. int wakeup_pdflush(long nr_pages)
a. <fs/buffer.c>static void free_more_memory(void)
b. <fs/sync.c> static void do_sync(unsigned logn wait)
c. <mm/vmscan.c>
static unsigned long do_try_to_free_pages(struct zonelist *zonelist, struct scan_control *sc)
wb_kupdate 함수로 호출하는 부분
<linux 2.6.28-15 mm/page-writeback.c>
1. static void wb_timer_fn(unsigned long unused)
마지막으로 background_writeout 함수를 살펴보면 write_inodes 함수를 호출하여 superblock 에 달려있는 모든 dirty inodes 에 대해서 dirty buffer 를 디스크로 기록하는 기능을 수행한다.
static void background_writeout(unsigned long _min_pages)
{
(생략)
for ( ; ; ) {
(생략)
writeback_inodes(&wbc);
min_pages -= MAX_WRITEBACK_PAGES - wbc.nr_to_write;
if (wbc.nr_to_write > 0 || wbc.pages_skipped > 0) {
/* Wrote less than expected */
if (wbc.encountered_congestion || wbc.more_io)
congestion_wait(WRITE, HZ/10);
(생략)
}
}
{
(생략)
for ( ; ; ) {
(생략)
writeback_inodes(&wbc);
min_pages -= MAX_WRITEBACK_PAGES - wbc.nr_to_write;
if (wbc.nr_to_write > 0 || wbc.pages_skipped > 0) {
/* Wrote less than expected */
if (wbc.encountered_congestion || wbc.more_io)
congestion_wait(WRITE, HZ/10);
(생략)
}
}
이상 dirty buffer 를 디스크로 기록해주던 thread 가 2.6 커널에서 어떤 식으로 구현되어있는지 살펴보았다.
'IT 기술 > 리눅스 커널' 카테고리의 다른 글
sparse : kernel static analysis tool (0) | 2013.02.12 |
---|---|
커널 로컬 버전 세팅 (0) | 2012.11.09 |
Linux kernel CPU Frequency 변경(DVFS) 코드 (2) | 2011.02.11 |
kjournald 에 IPPRIO_CLASS_RT 권한 부여 (0) | 2010.05.03 |
fork 와 vfork 의 차이점 (0) | 2009.08.28 |