内核数据结构贯穿于整个内核代码中,这里介绍4个基本的内核数据结构。
利用这4个基本的数据结构,可以在编写内核代码时节约大量时间。
主要内容:
- 链表
- 队列
1. 链表
链表是linux内核中最简单,同时也是应用最广泛的数据结构。
内核中定义的是双向链表。
1.1 头文件简介
内核中关于链表定义的代码位于: include/linux/list.h
list.h文件中对每个函数都有注释,这里就不详细说了。
其实刚开始只要先了解一个常用的链表操作(追加,删除,遍历)的实现方法,
其他方法基本都是基于这些常用操作的。
1.2 链表代码的注意点
在阅读list.h文件之前,有一点必须注意:linux内核中的链表使用方法和一般数据结构中定义的链表是有所不同的。
一般的双向链表一般是如下的结构,
- 有个单独的头结点(head)
- 每个节点(node)除了包含必要的数据之外,还有2个指针(pre,next)
- pre指针指向前一个节点(node),next指针指向后一个节点(node)
- 头结点(head)的pre指针指向链表的最后一个节点
- 最后一个节点的next指针指向头结点(head)
具体见下图:
传统的链表有个最大的缺点就是不好共通化,因为每个node中的data1,data2等等都是不确定的(无论是个数还是类型)。
linux中的链表巧妙的解决了这个问题,linux的链表不是将用户数据保存在链表节点中,而是将链表节点保存在用户数据中。
linux的链表节点只有2个指针(pre和next),这样的话,链表的节点将独立于用户数据之外,便于实现链表的共同操作。
具体见下图:
linux链表中的最大问题是怎样通过链表的节点来取得用户数据?
和传统的链表不同,linux的链表节点(node)中没有包含用户的用户data1,data2等。
整个list.h文件中,我觉得最复杂的代码就是获取用户数据的宏定义
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
这个宏没什么特别的,主要是container_of这个宏
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member)*__mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); })
这里面的type一般是个结构体,也就是包含用户数据和链表节点的结构体。
ptr是指向type中链表节点的指针
member则是type中定义链表节点是用的名字
比如:
struct student {
int id;
char* name;
struct list_head list; };
- type是struct student
- ptr是指向stuct list的指针,也就是指向member类型的指针
- member就是 list
下面分析一下container_of宏:
// 步骤1:将数字0强制转型为type*,然后取得其中的member元素
((type *)0)->member // 相当于((struct student *)0)->list
// 步骤2:定义一个临时变量__mptr,并将其也指向ptr所指向的链表节点
const typeof(((type *)0)->member)*__mptr = (ptr);
// 步骤3:计算member字段距离type中第一个字段的距离,也就是type地址和member地址之间的差
// offset(type, member)也是一个宏,定义如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
// 步骤4:将__mptr的地址 - type地址和member地址之间的差
// 其实也就是获取type的地址
步骤1,2,4比较容易理解,下面的图以sturct student为例进行说明步骤3:
首先需要知道 ((TYPE *)0) 表示将地址0转换为 TYPE 类型的地址
由于TYPE的地址是0,所以((TYPE *)0)->MEMBER 也就是 MEMBER的地址和TYPE地址的差,如下图所示:
1.3 使用示例
构造了一个内核模块来实际使用一下内核中的链表,代码在CentOS6.3 x64上运行通过。
C代码:
点击(此处)折叠或打开
- #include<linux/init.h>
- #include<linux/slab.h>
- #include<linux/module.h>
- #include<linux/kernel.h>
- #include<linux/list.h>
-
- MODULE_LICENSE("Dual BSD/GPL");
- struct student
- {
- int id;
- char* name;
- struct list_head list;
- };
-
- void print_student(struct student*);
-
- static int testlist_init(void)
- {
- struct student *stu1, *stu2, *stu3, *stu4;
- struct student *stu;
-
- // init a list head
- LIST_HEAD(stu_head);
-
- // init four list nodes
- stu1 = kmalloc(sizeof(*stu1), GFP_KERNEL);
- stu1->id = 1;
- stu1->name = "wyb";
- INIT_LIST_HEAD(&stu1->list);
-
- stu2 = kmalloc(sizeof(*stu2), GFP_KERNEL);
- stu2->id = 2;
- stu2->name = "wyb2";
- INIT_LIST_HEAD(&stu2->list);
-
- stu3 = kmalloc(sizeof(*stu3), GFP_KERNEL);
- stu3->id = 3;
- stu3->name = "wyb3";
- INIT_LIST_HEAD(&stu3->list);
-
- stu4 = kmalloc(sizeof(*stu4), GFP_KERNEL);
- stu4->id = 4;
- stu4->name = "wyb4";
- INIT_LIST_HEAD(&stu4->list);
-
- // add the four nodes to head
- list_add (&stu1->list, &stu_head);
- list_add (&stu2->list, &stu_head);
- list_add (&stu3->list, &stu_head);
- list_add (&stu4->list, &stu_head);
-
- // print each student from 4 to 1
- list_for_each_entry(stu, &stu_head, list)
- {
- print_student(stu);
- }
- // print each student from 1 to 4
- list_for_each_entry_reverse(stu, &stu_head, list)
- {
- print_student(stu);
- }
-
- // delete a entry stu2
- list_del(&stu2->list);
- list_for_each_entry(stu, &stu_head, list)
- {
- print_student(stu);
- }
-
- // replace stu3 with stu2
- list_replace(&stu3->list, &stu2->list);
- list_for_each_entry(stu, &stu_head, list)
- {
- print_student(stu);
- }
-
- return 0;
- }
-
- static void testlist_exit(void)
- {
- printk(KERN_ALERT "*************************\n");
- printk(KERN_ALERT "testlist is exited!\n");
- printk(KERN_ALERT "*************************\n");
- }
-
- void print_student(struct student *stu)
- {
- printk (KERN_ALERT "======================\n");
- printk (KERN_ALERT "id =%d\n", stu->id);
- printk (KERN_ALERT "name=%s\n", stu->name);
- printk (KERN_ALERT "======================\n");
- }
-
- module_init(testlist_init);
- module_exit(testlist_exit)
Makefile:
点击(此处)折叠或打开
- obj-m += testlist.o
-
- #generate the path
- CURRENT_PATH:=$(shell pwd)
- #the current kernel version number
- LINUX_KERNEL:=$(shell uname -r)
- #the absolute path
- LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
- #complie object
- all:
- make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
- rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned
- #clean
- clean:
- rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned
安装,卸载内核模块以及查看内核模块的运行结果:
insmod testlist.ko rmmod testlist dmesg | tail -100
2. 队列
内核中的队列是以字节形式保存数据的,所以获取数据的时候,需要知道数据的大小。
如果从队列中取得数据时指定的大小不对的话,取得数据会不完整或过大。
2.1 头文件简介
内核中关于队列定义的头文件位于:<linux/kfifo.h> include/linux/kfifo.h
头文件中定义的函数的实现位于:kernel/kfifo.c
2.2 队列代码的注意点
内核队列编程需要注意的是:
- 队列的size在初始化时,始终设定为2的n次方
- 使用队列之前将队列结构体中的锁(spinlock)释放
2.3 使用示例
构造了一个内核模块来实际使用一下内核中的队列,代码在CentOS6.3 x64上运行通过。
C代码:
点击(此处)折叠或打开
- #include "kn_common.h"
-
- MODULE_LICENSE("Dual BSD/GPL");
- struct student
- {
- int id;
- char* name;
- };
-
- static void print_student(struct student*);
-
- static int testkfifo_init(void)
- {
- struct kfifo *fifo;
- struct student *stu1, *stu2, *stu3, *stu4;
- struct student *stu_tmp;
- char* c_tmp;
- int i;
- // !!importent init a unlocked lock
- spinlock_t sl = SPIN_LOCK_UNLOCKED;
-
- // init kfifo
- fifo = kfifo_alloc(4*sizeof(struct student), GFP_KERNEL, &sl);
-
- stu1 = kmalloc(sizeof(struct student), GFP_KERNEL);
- stu1->id = 1;
- stu1->name = "wyb1";
- kfifo_put(fifo, (char *)stu1, sizeof(struct student));
-
- stu2 = kmalloc(sizeof(struct student), GFP_KERNEL);
- stu2->id = 1;
- stu2->name = "wyb2";
- kfifo_put(fifo, (char *)stu2, sizeof(struct student));
-
- stu3 = kmalloc(sizeof(struct student), GFP_KERNEL);
- stu3->id = 1;
- stu3->name = "wyb3";
- kfifo_put(fifo, (char *)stu3, sizeof(struct student));
-
- stu4 = kmalloc(sizeof(struct student), GFP_KERNEL);
- stu4->id = 1;
- stu4->name = "wyb4";
- kfifo_put(fifo, (char *)stu4, sizeof(struct student));
-
- c_tmp = kmalloc(sizeof(struct student), GFP_KERNEL);
- printk(KERN_ALERT "current fifo length is : %d\n", kfifo_len(fifo));
- for (i=0; i < 4; i++) {
-
- kfifo_get(fifo, c_tmp, sizeof(struct student));
- stu_tmp = (struct student *)c_tmp;
- print_student(stu_tmp);
- printk(KERN_ALERT "current fifo length is : %d\n", kfifo_len(fifo));
- }
-
- printk(KERN_ALERT "current fifo length is : %d\n", kfifo_len(fifo));
- kfifo_free(fifo);
- kfree(c_tmp);
- return 0;
- }
-
- static void print_student(struct student *stu)
- {
- printk(KERN_ALERT "=========================\n");
- print_current_time(1);
- printk(KERN_ALERT "id = %d\n", stu->id);
- printk(KERN_ALERT "name = %s\n", stu->name);
- printk(KERN_ALERT "=========================\n");
- }
-
- static void testkfifo_exit(void)
- {
- printk(KERN_ALERT "*************************\n");
- printk(KERN_ALERT "testkfifo is exited!\n");
- printk(KERN_ALERT "*************************\n");
- }
-
- module_init(testkfifo_init);
- module_exit(testkfifo_exit);
其中引用的kn_common.h文件:
#include<linux/init.h>
#include<linux/slab.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/kfifo.h>
#include<linux/time.h>
void print_current_time(int);
kn_common.h对应的kn_common.c:
#include "kn_common.h"
void print_current_time(int is_new_line) {
struct timeval *tv; struct tm *t; tv = kmalloc(sizeof(struct timeval), GFP_KERNEL); t = kmalloc(sizeof(struct tm), GFP_KERNEL); do_gettimeofday(tv); time_to_tm(tv->tv_sec, 0, t); printk(KERN_ALERT "%ld-%d-%d %d:%d:%d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, (t->tm_hour + 8) % 24, t->tm_min, t->tm_sec); if (is_new_line == 1) printk(KERN_ALERT "\n"); kfree(tv); kfree(t); }
Makefile:
obj-m += fifo.o fifo-objs := testkfifo.o kn_common.o #generate the path CURRENT_PATH:=$(shell pwd) #the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL) #complie object all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned #clean clean: rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned
安装,卸载内核模块以及查看内核模块的运行结果:
insmod fifo.ko rmmod fifo dmesg | tail -40
from: http://www.cnblogs.com/wang_yb/archive/2013/04/16/3023892.html