SW 개발

[Linux Kernel] 커널 list 자료구조체의 사용 방법

. . . 2012. 3. 29. 15:33
반응형

커널의 List 구조체

앞에서 언급한 대로 list는 유용한 구조이기 때문에 커널에서는 일반적인 용도로 사용할 수 있는 효율적인 구현을 제공한다. 또한 지금까지 살펴본 API에도 list 가 있다. 양방향 연결 목록 API를 이해하면 이 효율적인 데이터 구조를 사용하여 개발 작업을 수행하고 목록을 활용하는 커널의 코드를 이해하는 데 많은 도움이 된다. 이제 커널 list API를 간단하게 살펴보자.

이 API는 list head (앵커)뿐만 아니라 구조체 내 목록 포인터를 나타내는 데도 사용되는 list_head 구조체를 제공한다. list 의 기능이 포함된 샘플 구조체를 살펴보자(Listing 3 참조). 이 샘플 구조체에는 오브젝트 연결에 사용되는 list_head 구조체가 추가되어 있다. 그리고 일부 GCC 매직(./include/kernel/kernel.h에 정의된 list_entrycontainer_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_addlist_del 함수를 포함한 여러 함수를 사용하여 목록을 조작할 수 있다. 이제 이 API의 사용법을 더 잘 보여 주는 예제 코드를 살펴보자.

list API 예제

Listing 4에서는 수많은 목록 API 함수를 탐색하는 간단한 커널 모듈을 제공한다(./include/linux/list.h에서 많은 함수를 찾아볼 수 있음). 이 예제에서는 두 개의 목록을 작성하고,init_module 함수를 이용하여 목록을 채운 다음 cleanup_module 함수에서 목록을 조작한다.

먼저 일부 데이터와 두 개의 목록 헤더를 포함하는 데이터 구조(my_data_struct)를 작성한다. 이 예제에서는 한 오브젝트를 여러 목록에 동시에 삽입할 수 있다는 것을 보여 준다. 그런 다음 두 개의 목록 헤드(my_full_listmy_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) 등의 기능을 제공하는 여러 다양한 함수가 있다. 참고자료에서 커널 목록 함수에 대한 자세한 정보를 확인할 수 있다.

반응형