- 출처 : https://www.ibm.com/developerworks/kr/library/l-timers-list/
- 위의 게시물에서 Kernel 의 자료구조체에 대한 내용이 나와서 따로 보기좋게 정리한다.
커널의 List 구조체
앞에서 언급한 대로 list는 유용한 구조이기 때문에 커널에서는 일반적인 용도로 사용할 수 있는 효율적인 구현을 제공한다. 또한 지금까지 살펴본 API에도 list 가 있다. 양방향 연결 목록 API를 이해하면 이 효율적인 데이터 구조를 사용하여 개발 작업을 수행하고 목록을 활용하는 커널의 코드를 이해하는 데 많은 도움이 된다. 이제 커널 list API를 간단하게 살펴보자.
이 API는 list head (앵커)뿐만 아니라 구조체 내 목록 포인터를 나타내는 데도 사용되는 list_head
구조체를 제공한다. list 의 기능이 포함된 샘플 구조체를 살펴보자(Listing 3 참조). 이 샘플 구조체에는 오브젝트 연결에 사용되는 list_head
구조체가 추가되어 있다. 그리고 일부 GCC 매직(./include/kernel/kernel.h에 정의된 list_entry
및 container_of
)을 통해 구조체의 어디에서나 list_head
구조체를 추가할 수 있으며 목록 포인터에서 수퍼 오브젝트로 역참조할 수 있다.
struct my_data_structure {
int value;
struct list_head list;
};
모든 목록 구현에서와 마찬가지로 목록에 대한 앵커 역할을 수행할 목록 헤드가 필요하다. 이 작업은 일반적으로 목록에 대한 선언 및 초기화를 제공하는 LIST_HEAD
매크로를 통해 수행된다. 이 매크로는 사용자가 오브젝트를 추가할 수 있는 list_head
오브젝트 구조체를 작성한다.
LIST_HEAD( new_list )
또한 LIST_HEAD_INIT
매크로를 사용하여 수동으로 목록 헤드를 작성할 수 있다 (예를 들어, 목록 헤드가 다른 구조체에 있는 경우).
기본 초기화가 완료되면 list_add
및 list_del
함수를 포함한 여러 함수를 사용하여 목록을 조작할 수 있다. 이제 이 API의 사용법을 더 잘 보여 주는 예제 코드를 살펴보자.
list API 예제
Listing 4에서는 수많은 목록 API 함수를 탐색하는 간단한 커널 모듈을 제공한다(./include/linux/list.h에서 많은 함수를 찾아볼 수 있음). 이 예제에서는 두 개의 목록을 작성하고,init_module
함수를 이용하여 목록을 채운 다음 cleanup_module
함수에서 목록을 조작한다.
먼저 일부 데이터와 두 개의 목록 헤더를 포함하는 데이터 구조(my_data_struct
)를 작성한다. 이 예제에서는 한 오브젝트를 여러 목록에 동시에 삽입할 수 있다는 것을 보여 준다. 그런 다음 두 개의 목록 헤드(my_full_list
및 my_odd_list
)를 작성한다.
init_module
함수에서 10개의 데이터 오브젝트를 작성한 다음 list_add
함수를 사용하여 목록에 로드한다(my_full_list
에 모든 오브젝트를 로드하고 my_odd_list
에 모든 홀수 값 오브젝트를 로드함). list_add
는 두 개의 인수를 사용한다. 첫 번째 인수는 사용할 오브젝트 내의 목록 참조이고 두 번째 인수는 목록 앵커이다. 이는 목록 참조가 있는 수퍼 오브젝트를 식별하는 커널의 내부 기능을 사용하여 데이터 오브젝트를 여러 목록에 로드할 수 있음을 보여 준다.
cleanup_module
함수는 목록 API의 몇 가지 추가 기능을 보여 준다. 첫 번째 기능은 목록 반복을 쉽게 수행할 수 있도록 지원하는 list_for_each
매크로이다. 이 매크로를 사용하려면 현재 오브젝트에 대한 참조(pos
)와 반복할 목록 참조를 제공해야 한다. 각 반복마다 list_head
참조가 리턴되며, 리턴된 참조를 list_entry
에 제공하여 컨테이너 오브젝트(사용자의 데이터 구조)를 식별할 수 있다. 사용자의 구조와 구조 내의 목록 변수를 지정한다. 이 변수는 내부적으로 컨테이너를 역참조하는 데 사용된다.
홀수 목록을 표시하기 위해 또 하나의 반복 매크로인 list_for_each_entry
를 사용한다. 이 매크로는 사용자의 데이터 구조를 자동으로 제공하므로 list_entry
를 수행하지 않아도 되기 때문에 더 간단하다.
마지막으로 할당된 요소를 해제하기 위해 list_for_each_safe
를 사용하여 목록을 반복한다. 이 매크로를 사용하면 목록 항목을 제거(반복 작업의 일부로서 수행할 작업)하지 않으면서 안전하게 목록을 반복할 수 있다. list_entry
를 사용하여 데이터 오브젝트를 찾은(커널 풀에 다시 해제하기 위해) 다음 list_del
을 사용하여 목록의 항목을 해제한다.
#include
#include
#include
MODULE_LICENSE( "GPL" );
struct my_data_struct {
int value;
struct list_head full_list;
struct list_head odd_list;
};
LIST_HEAD( my_full_list );
LIST_HEAD( my_odd_list );
int init_module( void )
{
int count;
struct my_data_struct *obj;
for (count = 1 ; count < 11 ; count++) {
obj = (struct my_data_struct *)
kmalloc( sizeof(struct my_data_struct), GFP_KERNEL );
obj->value = count;
list_add( &obj->full_list, &my_full_list );
if (obj->value & 0x1) {
list_add( &obj->odd_list, &my_odd_list );
}
}
return 0;
}
void cleanup_module( void )
{
struct list_head *pos, *q;
struct my_data_struct *my_obj;
printk("Emit full list\n");
list_for_each( pos, &my_full_list ) {
my_obj = list_entry( pos, struct my_data_struct, full_list );
printk( "%d\n", my_obj->value );
}
printk("Emit odd list\n");
list_for_each_entry( my_obj, &my_odd_list, odd_list ) {
printk( "%d\n", my_obj->value );
}
printk("Cleaning up\n");
list_for_each_safe( pos, q, &my_full_list ) {
struct my_data_struct *tmp;
tmp = list_entry( pos, struct my_data_struct, full_list );
list_del( pos );
kfree( tmp );
}
return;
}
헤드 대신 목록의 후미에 추가하고(list_add_tail
), 목록을 결합하고(list_splice
), 목록의 컨텐츠를 테스트하는(list_empty
) 등의 기능을 제공하는 여러 다양한 함수가 있다. 참고자료에서 커널 목록 함수에 대한 자세한 정보를 확인할 수 있다.