Linux Kernel Programming–Linked List

Linux kernel has linked-list as a built-in data structure defined in /lib/modules/$(uname -r)/build/include/linux/list.h. Compared with common implementations of linked list, the Linux kernel version allows one to embedded the pre-defined linked-list node into any data structure to form a linked list.

0. Comparison of Common Linked List and Linux Kernel Linked List

Suppose we want to implement a linked list of data structure Person. The common implementation is as below,

struct Person {

    char name[30];

    unsigned int weight;

    unsigned char gender;

    struct Person* prev;

    struct Person* next;

};

The corresponding Linux kernel implementation would be,

struct Person {

    char name[30];

    unsigned int weight;

    unsigned char gender;

    struct list_head list; /* kernel's list structure */

};

The list_head structure is defined in list.h,

struct list_head {

        struct list_head *next, *prev;

};

The Linux implementation is a circular linked list. The details about how to create and use a linked list is covered below.

1. Create a Linked List in Linux Kernel Programming

1.1 Create a list head

The first step is to create a list head, which points to the head of the linked list. There’re two methods. Follow the example above, we can do,

struct Person personList;

LIST_HEAD_INIT(&personList.list);

Or

LIST_HEAD(personList);

Both pieces of code achieve the same effects.

1.2 Create list node

There’re two ways to create a new node of the data structure Person we defined above. To create a node dynamically,

struct Person* aNewPersonPointer;

aNewPersonPointer = kmalloc(sizeof(*aNewPersonPointer), GFP_KERNEL);

strcpy(aNewPersonPointer->name, "roman10");

aNewPersonPointer->weight = 130;

aNewPersonPointer->gender = 1;

INIT_LIST_HEAD(&aNewPersonPointer->list);

Or you can create a node statically,

struct Person aNewPerson = {

   .name = strcpy(aNewPerson.name, "roman10"),

   .weight = 130,

   .gender = 1,

   .list = LIST_HEAD_INIT(aNewPerson.list),

};

1.3 Add a list node

Linux kernel provides the following function to add a linked list node,

void list_add(struct list_head *new, struct list_head *head);

You can pass any list node as head, the new node will be inserted immediately after the head. If you pass the last element as head, this function can be used to implement a stack. Follow tht previous example,

list_add(&aNewPerson->list, &personList.list);

There’s another function to add a linked list node,

void list_add_tail(struct list_head *new, struct list_head *head);

Similarly, you can pass any node as head, the new node will be inserted right before the head. If you pass the first node as head, the new node will be inserted before the first node. As the Linux implementation of linked list is circular, it’s equivalent to inserting the new node after the last node. Therefore, this function can be used to implement a queue.

Follow the previous example,

list_add_tail(&aNewPerson->list, &personList.list);

2. Delete a Node from the List

Linux kernel provides a function to delete a node from the list,

void list_del(struct list_head *entry);

It will delete the entry node from the list. This function removes the entry node from the linked list by disconnect prev and next pointers from the list, but it doesn’t free any memory space allocated for entry node. Follow the example above,

list_del(&aNewPerson->list);

3. Linked List Traversal

The list.h header defines a macro list_for_each(pos, head) for linked-list traversal. Both pos and head are pointers to the list_head. pos is used as a loop cursor, and head is the head node of the linked list. In our example,

struct list_head *node;

struct Person *aPerson;

list_for_each(node, &personList.list) {

    //access the list node through node

    aPerson = list_entry(node, struct Person, list);

}

list_entry is a macro of 3 inputs,

list_entry(ptr, type, member)

where ptr is a pointer to list_head, type is the structure that list_head is embedded in, in our case, struct Person, and member is the name of the list_head within the defined structure, list in our case.

An easier way to loop through the linked list is also provided, which combines the loop and list_entry function,

list_for_each_entry(pos, head, member)

In our example,

struct Person *aPerson;

list_for_each_entry(aPerson, &personList.list, list) {

    //access the member from aPerson

}

There’s a third way to loop through the linked list, used to loop through the list while removing node from it.

list_for_each_entry_safe(pos, n, head, member)

where n is another pointer to user-defined structure used as temporary storage. In our example,

struct Person *aPerson, *tempPerson;

list_for_each_entry_safe(aPerson, tempPerson, &personList.list, list) {

    //access the member from aPerson

list_del(&aPerson->list);

kfree(aPerson);

}

4. Other Functions

There’re many other functions defined in list.h, including move list node from one linked list to another, combine two linked list, etc. One can refer to list.h header file for more detailed information.

5. Sample Program

Below is a kernel module that shows the basic usage of the functions covered above,

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/list.h>

 

MODULE_LICENSE("GPL");

MODULE_DESCRIPTION("hello");

MODULE_AUTHOR("Liu Feipeng/roman10");

 

struct Person {

    char name[30];

    unsigned int weight;

    unsigned char gender;

    struct list_head list; /* kernel's list structure */

};

 

struct Person personList;

 

int init_module() {

    struct Person *aNewPerson, *aPerson;

    unsigned int i;

 

    printk(KERN_INFO "initialize kernel module\n");

    INIT_LIST_HEAD(&personList.list);    //or LIST_HEAD(mylist); 

 

    /* adding elements to mylist */

    for(i=0; i<5; ++i){

        aNewPerson = kmalloc(sizeof(*aNewPerson), GFP_KERNEL);

        strcpy(aNewPerson->name, "roman10");

        aNewPerson->weight = 130*i;

        aNewPerson->gender = 1;

        INIT_LIST_HEAD(&aNewPerson->list);

        /* add the new node to mylist */

        list_add_tail(&(aNewPerson->list), &(personList.list));

    }

    printk(KERN_INFO "traversing the list using list_for_each_entry()\n");

    list_for_each_entry(aPerson, &personList.list, list) {

        //access the member from aPerson

        printk(KERN_INFO "Person: %s; weight: %d; gender: %s\n", aPerson->name, aPerson->weight, aPerson->gender==0?"Female":"Male");

    }

    printk(KERN_INFO "\n");

 

    return 0;

}

 

void cleanup_module() {

    struct Person *aPerson, *tmp;

    printk(KERN_INFO "kernel module unloaded.\n");

    printk(KERN_INFO "deleting the list using list_for_each_entry_safe()\n");

    list_for_each_entry_safe(aPerson, tmp, &personList.list, list){

         printk(KERN_INFO "freeing node %s\n", aPerson->name);

         list_del(&aPerson->list);

         kfree(aPerson);

    }

}

You can compile the program using the makefile below (save the text to Makefile),

obj-m += test_list.o
all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

 

Note that the spaces matter in the makefile above, so it’s best to copy and paste the text.

Type the following command to load the kernel module,

Type the following command to load the kernel module,

sudo insmod test_list.ko

Then check the /var/log/messages, you’ll see the output below,

Jul 29 00:47:45 roman10-laptop kernel: [36732.888683] initialize kernel module

Jul 29 00:47:45 roman10-laptop kernel: [36732.888696] traversing the list using list_for_each_entry()

Jul 29 00:47:45 roman10-laptop kernel: [36732.888708] Person: roman10; weight: 0; gender: Male

Jul 29 00:47:45 roman10-laptop kernel: [36732.888718] Person: roman10; weight: 130; gender: Male

Jul 29 00:47:45 roman10-laptop kernel: [36732.888728] Person: roman10; weight: 260; gender: Male

Jul 29 00:47:45 roman10-laptop kernel: [36732.888738] Person: roman10; weight: 390; gender: Male

Jul 29 00:47:45 roman10-laptop kernel: [36732.888749] Person: roman10; weight: 520; gender: Male

Then unload the kernel module by typing,

sudo rmmod test_list.ko

Then there’ll be output as below,

Jul 29 00:50:40 roman10-laptop kernel: [36908.371315] kernel module unloaded.

Jul 29 00:50:40 roman10-laptop kernel: [36908.371327] deleting the list using list_for_each_entry_safe()

Jul 29 00:50:40 roman10-laptop kernel: [36908.371337] freeing node roman10

Jul 29 00:50:40 roman10-laptop kernel: [36908.371345] freeing node roman10

Jul 29 00:50:40 roman10-laptop kernel: [36908.371352] freeing node roman10

Jul 29 00:50:40 roman10-laptop kernel: [36908.371359] freeing node roman10

Jul 29 00:50:40 roman10-laptop kernel: [36908.371367] freeing node roman10

If you want to know more about kernel modules and the Linux commands about kernel modules, you can refer here.

Note:

This post is part of the tutorial: How to write a Linux Firewall in Less than 1000 Lines

Part 1: Overview

Part 2: Command Line Arguments Parsing in glibc

Part 3.1: Linux Kernel Module Basics and Hello World

Part 3.2: Linux Kernel Programming – Linked List

Part 3.3 Linux Kernel Programming – Memory Allocation

Part 4.1: How to Filter Network Packets using Netfilter – Part 1 Netfilter Hooks

Part 4.2 How to Filter Network Packets using Netfilter – Part 2 Implement the Hook Function

Part 5: Linux procfs Virtual File System

Part 6: Put Everything Together

References:
Linux Kernel Linked List Explained: http://isis.poly.edu/kulesh/stuff/src/klist/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值