Linux Kernel: 強大又好用的list_head結構

Linux Kernel: 強大又好用的list_head結構 程式設計者在設計一個doubly linked list時,通常會在所宣告的結構裡宣告兩個結構指標,如下所示:

struct student {
    char name[16];
    int id;
    struct student *next, *prev;
};

只要經由prev與next兩個結構指標,便能取得該doubly linked list所有資訊。

然而,Linux kernel並非引用此種作法。因此,Linux kernel定義一通用結構 (struct list_head, include/linux/list.h),用以實作doubly linked list,此結構相當簡單,如下所示:

struct list_head {
    struct list_head *next, *prev;
};

所以物件student宣告為如下:

struct student
{
    char name[16];
    int id;
    struct list_head list;
};

藉由list變數便能取得doubly linked list所有資訊。

list_head相關Marco與Function

這裡僅介紹較常用的macro與function,如欲進一步得知其它marco請參考include/linux/list.h

LIST_HEAD(name)
struct list_head name = { &(name), &(name) };
將next與prev指到自己,意味著此list為空的。

list_empty(const struct list_head *head)
retrun head->next == head;
檢查此list是否為空的。

list_add(struct list_head *new, struct list_head *head)
head->next->prev = new;
new->next = head->next;
new->prev = head;
head->next = new;
將資料加入至doubly linked list最前端。建議自己動手畫個圖,便可瞭解這幾個指標指到何處。

list_del(struct list_head *entry)
entry->next->prev = entry->prev;
entry->prev->next = entry->next;
將某一資料從中刪除。

list_entry(ptr,type,member)
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

透過此函式便能算出結構的起始位址,並做結構轉型便能取得結構的資料,此計算方式相當好用啊!!!

以struct student為例,如下所示:

(unsigned long)(&((struct student *)0)->member))) ==> 計算出list成員相對位址。通常看到這一個敘述,直覺地覺得應該會記憶體區段錯誤吧 (Segmentation Fault)!? 因為該敘述用NULL的pointer存取list成員,但在(struct student *)0)->member))前面加了&符號,就不會發生記憶體區段錯誤。因為&符號,只意味著存取list的位址,而不是資料, 經由此敘述便能取得list的位移植。如下圖所示,該敘述所得之位移植值為20。

((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) ==> 取得位移植之後,再將list的位址減去位移植,便能取得該結構的起始位址,如下圖所示 (假設結構起始位址為x),最後再做結構轉型變大功告成。

list_for_each(pos, head)
for (pos = (head)->next; pos != (head); pos = pos->next)

取得該doubly linked list所有資料。

  • Makefile
obj-m += list_head_ex.o

KDIR=/lib/modules/$(shell uname -r)/build

all:
    make -C $(KDIR) M=$(PWD) modules
clean:
    make -C $(KDIR) M=$(PWD) clean

-list_head_ex.c

/*
 * =============================================================================
 *
 *       Filename:  list_head_ex.c
 *
 *    Description:  Write a doubly linked list by using list_head structure
 *
 *        Version:  1.0
 *        Created:  Fri Oct 19 14:14:57 GMT 2007
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Adrian Huang
 *        Web Site:  http://adrianhuang.blogspot.com/
 *
 * ============================================================================
 */

#include "list_head_ex.h"

int __init list_head_init(void)
{
    struct list_head    std_head, *next;
    struct student        sl[NR_STDS];         // student list
    struct student        *studentP;     // student pointer
    int                    idx;

    /* init std_head */
    INIT_LIST_HEAD(&std_head);

    /* Check whether the std_head is empty */
    if(list_empty(&std_head) == TRUE) {
        printk(KERN_INFO "[Adrian] std_head is NULL\n");
    }

    /* Add each element to student head (std_head) */
    for(idx=0;idx<sizeof(sl)/sizeof(struct student);idx++) {
        sprintf(sl[idx].name, "Adrian Huang");
        sl[idx].id = idx + 1;
        list_add(&sl[idx].list, &std_head);
    }

    printk(KERN_INFO "======dump all elements of the list by invoking "
                    "'list_for_each'=========\n");
    /* Traverse all elements of the list */
    list_for_each(next, &std_head) {
        studentP = (struct student *) list_entry(next, struct student, list);
        printk(KERN_INFO "[Adrian] name: %s, id: %d\n",
                             studentP->name, studentP->id);
    }


    printk(KERN_INFO "\n======dump all elements of the list by invoking "
                    "'list_entry'=========\n");
    /* Traverse all elements of the list and delete each of it */
    for(idx=0;idx<sizeof(sl)/sizeof(struct student);idx++) {
        studentP = (struct student *) list_entry(std_head.next,
                                     struct student, list);

        printk(KERN_INFO "[Adrian] name: %s, id: %d\n",
                             studentP->name, studentP->id);

        list_del(std_head.next);
    }

    return 0;
}

void __exit list_head_exit(void)
{
    ;
}

module_init(list_head_init);
module_exit(list_head_exit);
  • list_head_ex.h
/*
 * =============================================================================
 *
 *       Filename:  list_head_ex.h
 *
 *    Description: Write a doubly linked list by using list_head structure
 *
 *        Version:  1.0
 *        Created:  Fri Oct 19 14:17:58 GMT 2007
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Adrian Huang
 *       Web Site:  http://adrianhuang.blogspot.com/
 *
 * =============================================================================
 */

#ifndef  LIST_HEAD_EX_INC
#define  LIST_HEAD_EX_INC

#include <linux/module.h>    /* Needed by all modules */
#include <linux/kernel.h>    /* Needed for KERN_INFO */
#include <linux/jiffies.h>
#include <linux/list.h>        /* list_head structure    */

#define    NR_STDS    5

struct student {
    char                name[16];
    int                    id;
    struct list_head    list;
};

typedef enum _Boolean { FALSE = 0, TRUE = 1 } LHBOOL; // list head boolean

#endif   /* ----- #ifndef LIST_HEAD_EX_INC  ----- */

code Download