author: hjjdebug
date: 2026年 05月 05日 星期二 07:03:19 CST
descrip: 简单驱动管理,内核链表宏及container_of宏演示代码


1. 3个内核宏, LIST_HEAD, container_of, list_for_each

看内核代码时,碰到的三个个典型宏,
LIST_HEAD, container_of, list_for_each
它们到处在用,到底什么意思呢?
这些宏,不容易看懂, 因为宏本身是一堆代码的组合.

2. 3个宏的定义

下面是定义,看了几乎还是不懂, 尽管代码很少,算是宏中比较简单的了.
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name)
struct list_head name = LIST_HEAD_INIT(name)

// 内核核心宏:通过成员指针找回结构体指针
#define container_of(ptr, type, member)
(type *)((char *)(ptr) - (unsigned long)&((type *)0)->member)

// 遍历链表
#define list_for_each(pos, head)
for (pos = (head)->next; pos != (head); pos = pos->next)

3. 3个宏的预处理展开

怎么才能看懂, 写小测试程序,看宏的预处理过程, 则可以秒懂!
后面附程序.
main 函数中
LIST_HEAD(tty_drivers);
展开后为 struct list_head tty_drivers = { &(tty_drivers), &(tty_drivers) };
原来是定义了一个局部变量. 宏就是变戏法!

list_for_each(pos, &tty_drivers)
宏展开后为:
for (pos = (&tty_drivers)->next; pos != (&tty_drivers); pos = pos->next)
宏展开后易懂,展开前却不易懂,因为被包起来了,
宏的存在只是方便书写而以.

drv = container_of(pos, tty_driver_t, list);
// 宏展开后为,
drv = (tty_driver_t *)((char *)(pos) - (unsigned long)&((tty_driver_t *)0)->list);
一眼看懂是赋值语句,移动指针pos

用这些宏,写一小段测试代码,看其展开后的结果,跟踪调试,就能掌握了.
宏怎样展开,可以直接看预处理输出.

4. 附测试代码: 简单的内核管理驱动

调试一下代码,更有收获.

p$ cat list_demo.c
#include <stdio.h>
#include <string.h>

// ====================== 模拟内核链表基础定义 ======================
struct list_head {
    struct list_head *next, *prev;
};

//这些宏,不容易看懂, 而看了预处理, 则可以秒懂!
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)

// 内核核心宏:通过成员指针找回结构体指针
#define container_of(ptr, type, member) \
    (type *)((char *)(ptr) - (unsigned long)&((type *)0)->member)

// 遍历链表
#define list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

// 添加节点到链表,在head后插队, 代码只有4行,但步步关键,次序也不要变(防止出错).
static inline void list_add(struct list_head *new, struct list_head *head)
{
	//在head后插队,需要调整3个节点的指针,自己(new),head的next指针,和head后面那个的prev指针
    new->next = head->next; //在head 处断开,让head后面的人做自己后面的人
    new->prev = head;      //让head做自己前面的人,这样,自己节点就处理好了,前后指针正常
    head->next->prev = new; //修复下一个人的前向指针,原来它指向head,现在要指向我.
    head->next = new;       //修复head的后向指针,原来它指向下一个人,现在要指向我.
}



// ====================== 我们自己的设备结构(模拟 tty_driver) ======================
// 不管什么驱动,总得要有名称和设备号
typedef struct {
    int major;          // 主设备号
    char name[20];      // 驱动名
    struct list_head list;  // 内核链表节点(内嵌)
} tty_driver_t;

// ====================== 测试代码 ======================
int main()
{
    // 1. 创建链表头:内核里的 LIST_HEAD(tty_drivers)
    // 不看预处理真是难懂,看了预处理秒懂! 原来是定义了一个局部变量.
    // 展开后为 struct list_head tty_drivers = { &(tty_drivers), &(tty_drivers) };
    LIST_HEAD(tty_drivers);

    // 2. 模拟创建3个TTY驱动(像ACM、tty、ttyS驱动)
    tty_driver_t driver1 = {.major = 4, .name = "tty"};
    tty_driver_t driver2 = {.major = 5, .name = "tty_ctrl"};
    tty_driver_t driver3 = {.major = 166, .name = "acm_tty"};

    // 3. 把驱动链入链表(内核 tty_register_driver 干的事,驱动管理)
    list_add(&driver1.list, &tty_drivers);
    list_add(&driver2.list, &tty_drivers);
    list_add(&driver3.list, &tty_drivers);

    // 4. 遍历链表 + container_of 找回完整结构体(内核最常用写法)
    struct list_head *pos;
    tty_driver_t *drv;

    printf("==== 遍历内核 tty_drivers 链表 ====\n");
//   宏展开后为for语句: 宏语句就像变戏法.
//    for (pos = (&tty_drivers)->next; pos != (&tty_drivers); pos = pos->next) {
    list_for_each(pos, &tty_drivers) {
		
        // 核心:通过链表节点 pos → 找回整个 tty_driver_t
// 宏展开后为计算pos位置,展开后一眼看懂
//        drv = (tty_driver_t *)((char *)(pos) - (unsigned long)&((tty_driver_t *)0)->list);
        drv = container_of(pos, tty_driver_t, list);
        printf("驱动:%-10s 主设备号:%d\n", drv->name, drv->major);
    }

    return 0;
}

5 附Makefile

make preprocess 可生成.i 预处理文件

$ cat Makefile 
# 编译器
CC = gcc

# 编译参数
CFLAGS = -Wall -g

# 目标程序名
TARGET = list_demo

# 源文件
SRC = list_demo.c
# 预处理输出文件
PREPROC = $(SRC:.c=.i)

# 默认:编译
all: $(TARGET)

# 编译可执行文件
$(TARGET): $(SRC)
	$(CC) $(CFLAGS) -o $@ $^

# ==================== 新增:生成预处理文件 ====================
preprocess: $(SRC)
	$(CC) -E $(SRC) -o $(PREPROC)
	@echo "✅ 预处理完成,输出到: $(PREPROC)"

# 运行
run: $(TARGET)
	./$(TARGET)

# 清理
clean:
	rm -f $(TARGET) $(PREPROC)

6. 程序执行结果

附结果


$ make run
./list_demo
==== 遍历内核 tty_drivers 链表 ====
驱动:acm_tty    主设备号:166
驱动:tty_ctrl   主设备号:5
驱动:tty        主设备号:4
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐