Introduction
Lists are a sequence of elements where each element points to the next one. Lists in the Linux kernel are used to manage various kernel resources and data structures. They provide a flexible framework for storing and accessing information, making it easier for developers to work with complex systems.
Imagine you have a collection of items, but you don’t know how many there are beforehand. The number of items can change while your program is running, depending on external factors. How do you manage and keep track of these dynamically changing items? This is where lists come in handy in the Linux kernel.
Lists provide a way to store and manage such dynamic collections of items efficiently. They offer a flexible framework for adding, removing, and accessing elements as needed. With lists, you can easily add new elements to the collection, remove existing ones, and navigate through the list to find specific items.
In the Linux kernel, there’s an internal system that handles lists using specialized structures and functions. These structures and functions enable developers to perform various operations on lists, such as adding, removing, and traversing elements. In this chapter, we’ll explore how to utilize these features to manipulate lists effectively within the Linux kernel.
Initialising a list
struct list_head list; is the list structure which is used to maintain a list. Every element must contain this list structure inorder to add in the list. Let’s take a device element which contains an integer to hold the device number and a string to hold device name.
struct onz_node {
int dev_no;
char dev_name[20];
struct list_head list;
};
The number of devices will increase and decrease based on the connected devices. All the elements have to be added one after the other. But before that the head of this list has to be chosen. For that we have to take another variable/member.
struct list_head dev_head;
Now we got head of the list and elements. It’s time for initialising the head.
INIT_LIST_HEAD();
void INIT_LIST_HEAD(struct list_head *list);
list: list_head structure to be initialised.
This initialises the head of the list so the elements can add from here.
Adding an element to list
When you want to add an element to a list, there are different methods depending on where you want to add it. if you want to add it at the end of the list, use list_add_tail().
void list_add_tail(struct list_head *new, struct list_head *head)
new: new entry to be added
head: head node of the list
If you want to add it at the beginning use list_add().
void list_add(struct list_head *new, struct list_head *head);
new : new entry to be added.
head : head node of the list.
Traversing in list
Once you’ve put things into the list, sometimes you need to look at what’s inside. This is called traversing. To go through each item in the list, use list_for_each_entry().
list_for_each_entry(pos, head, member)
pos: entry which traverses through the list.
head: head of the list.
member: name of the list member in the element.
Removing an element from list
list_del(node)
node: the element’s list_head structure to be deleted from the list.
Sample Program on lists in linux kernel
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/string.h>
#include <linux/slab.h>
static struct list_head head;
/* device element */
struct onz_node {
int dev_no; // device number
char dev_name[30]; // name of the device
struct list_head list;
};
static void add_device_entries(void)
{
struct onz_node *entry;
int i;
pr_info("adding devices into list\n");
for (i = 0; i < 10; i++) {
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
entry->dev_no = 100 + i;
sprintf(entry->dev_name, "onz_%d", i);
list_add_tail(&entry->list, &head);
}
pr_info("10 devices added into list\n");
}
static void print_device_entries(void)
{
struct onz_node *entry;
pr_info("Printing devices in the list\n");
if (list_empty(&head)) {
pr_err("list is empty\n");
return;
}
list_for_each_entry(entry, &head, list)
pr_info("device number:%d device_name:%s\n", entry->dev_no,
entry->dev_name);
}
static void delete_device_entries(void)
{
struct onz_node *entry;
int i;
pr_info("Deleting devices from the list\n");
for (i = 0; i < 10; i++) {
entry = list_last_entry(&head, struct onz_node, list);
list_del(&entry->list);
}
if (list_empty(&head))
pr_err("list is empty\n");
}
static int __init my_module_init(void)
{
printk("%s\n", __func__);
INIT_LIST_HEAD(&head);
add_device_entries();
print_device_entries();
return 0;
}
static void __exit my_module_exit(void)
{
printk("%s\n", __func__);
delete_device_entries();
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
output:
#insmod list_test.ko
[12689.830118] my_module_init<br>adding devices into list<br>[12689.830133] 10 devices added into list<br>[12689.830134] Printing devices in the list<br>[12689.830136] device number:100 device_name:onz_0<br>[12689.830139] device number:101 device_name:onz_1<br>[12689.830142] device number:102 device_name:onz_2<br>[12689.830144] device number:103 device_name:onz_3<br>[12689.830146] device number:104 device_name:onz_4<br>[12689.830148] device number:105 device_name:onz_5<br>[12689.830150] device number:106 device_name:onz_6<br>[12689.830152] device number:107 device_name:onz_7<br>[12689.830154] device number:108 device_name:onz_8<br>[12689.830156] device number:109 device_name:onz_9
#rmmod list_test.ko
[12692.774873] my_module_exit<br>[12692.774880] Deleting devices from the list
References:
https://www.kernel.org/doc/html/latest/core-api/kernel-api.html?highlight=register_chrdev_region