第二章:结构之术
涉及内核源码: , ,
楔子
林小源顺着指针走了一圈,回到了原点。
他现在知道了:内核中的一切都通过指针连接。但他也发现了一个问题——指针只告诉他"从这里到那里",却不告诉他"那里有什么"。
他看到一个指针指向一片内存。那片内存里装的是什么?是一个数字?一个字符?还是一整个进程的状态?
他不知道。
于是他开始观察那片内存的内容。他发现,那些数据并不是杂乱无章地堆放在一起——它们按照某种规则排列,形成了有意义的整体。
他看到一个包含 、、、 等字段的东西。这些字段被组织在一起,共同描述了一个进程的全部信息。
后来他才知道,这叫结构体。
而那个东西,叫 。
他花了很多时间研究结构体的布局。他发现字段之间有间隙(填充字节),他发现字段的排列顺序会影响结构体的大小,他发现有一个神奇的宏能从任意一个字段的指针反推出整个结构体的地址。
那个宏叫 。
他第一次看到它时,完全无法理解。一个字段的指针怎么能反推出整个结构体?
直到他理解了 ——把地址 0 当作结构体的起始地址,取成员的地址就是偏移量。
那一刻,他觉得自己看到了内核世界最精妙的设计。
一、结构体的内存布局
林小源第一次见到结构体大师,是在一片内存的废墟中。
那是一个由碎片拼凑而成的巨大人形——每一块碎片上都刻着一个字段名:、、、……碎片之间有宽有窄,有的紧贴在一起,有的隔着一条明显的缝隙。
"你是 ?"林小源问。
人形低下头,声音沉稳如磐石:"我是所有进程的容器。每个进程的状态、优先级、名称——都存储在我的身体里。但你看到了吗?我的身体并不紧凑。"
林小源确实看到了。那些碎片之间有一些被填充为零的间隙——编译器插入的填充字节。
"为什么要有这些空隙?"
"对齐。"人形说,"CPU 读取内存时,不是一字节一字节读的——它按固定大小的块读取。如果一个 4 字节的 int 恰好跨在两个块的边界上,CPU 就要读两次,拼接结果,浪费时间。所以编译器在字段之间插入填充字节,保证每个字段都落在它'自然'的边界上。"
人形举起一只手——那只手由 char a(1字节)、int b(4字节)、char c(1字节)、long d(8字节)组成。"如果按这个顺序排列,a 后面要填充 3 字节让 b 对齐到 4 字节边界,c 后面要填充 7 字节让 d 对齐到 8 字节边界。总共 24 字节。"
然后它换了一只手——long d、int b、char a、char c。"但如果把大的放前面,小的放后面——d 占 8 字节,b 占 4 字节,a 和 c 各 1 字节,只需要在最后填充 2 字节。总共 16 字节。"
"同样的字段,不同的顺序,大小差了 8 字节?"
"在内核中,结构体可能有成百上千个实例。8 字节的浪费乘以一千,就是 8KB。乘以一百万,就是 8MB。内核开发者必须精心安排字段顺序,既保证对齐又减少浪费。"
二、packed — 紧凑布局
人形带着林小源来到一面特殊的墙前。墙上挂着一张网络数据包的蓝图——版本号、首部长度、服务类型、总长度、标识、标志、生存时间、协议、校验和、源地址、目的地址——密密麻麻地排列着,每个字段之间的间隔精确到字节。
"这是 IP 首部,"人形说,"网络协议的格式是固定的,一字节都不能多、一字节都不能少。但按照正常的对齐规则,编译器会在字段之间插入填充字节。"
"那怎么办?"
"用 __attribute__((packed))。"人形的声音带着一丝郑重,"这个属性告诉编译器:不要插入任何填充。一字不差地按照我定义的顺序排列。"
林小源看着那张蓝图。每个字段紧密排列,没有任何间隙——就像一块被压缩到极致的合金。
"但 packed 是有代价的,"人形警告道,"未对齐的内存访问在某些架构上会触发异常或性能惩罚。x86 可以忍,ARM 可能直接抛出异常。所以内核用 和 来安全地访问这些字段。"
"就像……用特殊的工具去拆精密的零件?"
"不错的比喻。"人形微微点头,"packed 用于硬件寄存器映射、网络协议头这些必须精确控制布局的场景。普通的内核数据结构——别用它。"
三、位域 — 比字节更精细
在那面墙的旁边,林小源发现了一块更加精密的面板。面板只有八个字节——64 位——却被分割成了十几个区域,每个区域只有几个比特位宽。
"这是页表项,"人形说,"一个 64 位的整数,里面塞满了信息。"
它指着面板上的刻痕:"第 0 位——是否在内存中。第 1 位——是否可写。第 2 位——用户态是否可访问。第 5 位——是否被访问过。第 6 位——是否被写过。第 12 到 51 位——物理地址。第 63 位——是否禁止执行。"
林小源瞪大了"眼睛"。64 位里塞了十几个标志和一个 40 位的物理地址?
"这就是位域,"人形说,"比字节更精细的控制。内核中的页表项、中断描述符、设备寄存器——到处都是位域。"
"那内核代码是用位域语法写的吗?"
人形摇头:"不。内核通常更喜欢用显式的位移和掩码操作。因为位域的布局是编译器定义的——哪个位在前、哪个位在后,标准没有规定。用位移和掩码,程序员自己控制每一位的位置,可移植性更强。"
四、offsetof — 寻找字段的位置
人形带着林小源来到一座钟楼前。钟楼的正面是一面巨大的表盘,但表盘上没有数字——只有偏移量。 在 0, 在 8, 在 12, 在 16, 在 20。
"这是 的结果,"人形说,"它告诉你每个字段在结构体中的起始位置。"
林小源盯着表盘看了很久。"这些偏移量是怎么算出来的?"
人形的嘴角微微上扬——如果一块碎片拼成的脸能微笑的话:"想象一下。你有一个结构体的指针,但这个指针的值是 0——NULL。你访问它的某个字段,编译器会算出那个字段相对于起始地址的偏移。因为起始地址是 0,所以字段的地址本身就是偏移量。"
"所以 offsetof(struct task_struct, pid) 就是……"
"就是 &((struct task_struct *)0)->pid 的值。把 NULL 当作结构体的起始地址,取 的地址。听起来疯狂,但编译器在编译期就能算出结果——没有任何运行时开销。"
林小源想了一会儿。这个技巧简单到近乎粗暴,却又精妙到让人叹服。
五、container_of — 内核最著名的宏
钟楼的顶端,有一扇门。门上刻着三个字母:。
人形站在门前,声音变得庄重:"这是内核中使用最广泛的宏。它做的事情——从一个成员的指针,反推出包含该成员的外层结构体的指针。"
"从一个部分,反推整体?"
"正是。"人形推开门。门内是一片空旷的空间,中央放着一个 结构体。 字段的地址被一根光线标出——林小源只能看到这一个字段的指针,看不到整个结构体。
"假设你只有 字段的指针,"人形说,"你怎么找到它所属的 ?"
林小源摇头。
"很简单。 在结构体中的偏移量是已知的——用 可以算出来。那么 的起始地址就是 的地址减去这个偏移量。"
人形在空中画了一道公式:
task_struct * = (char *)pid_ptr - offsetof(struct task_struct, pid)林小源盯着公式看了三遍。然后他突然明白了——就像你看到一只手从一扇窗户里伸出来,你知道手的主人就站在窗户里面。手的位置减去手臂的长度,就是身体的位置。
"从已知推未知。从部分推整体。"人形说,"这就是 。内核的链表遍历、设备驱动回调、文件操作——到处都依赖它。它让 C 语言实现了'继承':只要把基类嵌入到派生类中,就能通过基类指针找到派生类对象。"
六、内核链表 — 双向循环的艺术
他们从钟楼上下来,来到了一个环形广场。广场上站着一圈人影,每个人影都伸着两只手——左手拉着前面那个人影的右手,右手拉着后面那个人影的左手。环环相扣,没有起点,没有终点。
"这是内核链表,"人形说,"双向循环链表。"
林小源走近了看。每个人影的"身体"里都嵌着一个 结构——两个指针,一个指向前一个节点,一个指向后一个节点。
"等等,"林小源皱眉,"链表节点不是应该包含数据吗?这个 只有两个指针,没有数据。"
人形的声音里带着赞许:"好问题。用户态的链表把 指针嵌入到数据结构中。内核的链表反过来——把链表节点嵌入到数据结构中。"
它指着其中一个人影:"这个 是嵌入在 中的。你不能直接从 得到 ——但你可以用 从 的地址反推出 的地址。"
林小源恍然大悟:"所以 遍历链表时,其实是在用 不断地从链表节点反推出外层结构体?"
"正是。这就是内核链表的精妙之处—— 不关心它被嵌入到什么结构体中。同一组链表操作函数可以用于进程列表、文件列表、模块列表——只要结构体里有 成员。通用、简洁、高效。"
环形广场上的人影继续转动着,左手拉右手,右手拉左手,永不停歇。
道藏笔记
内核启示
是内核面向对象的基石。 在 C++ 中,编译器帮你做"向下转型";在内核中, 手动完成这件事。
理解 ,就理解了内核的设计哲学:不信任编译器的魔法,一切都在程序员的掌控之中。
有趣的是,内核中的链表遍历宏 正是 最广泛的应用场景。这个宏在内核源码中出现了数千次——每一次进程遍历、每一次文件扫描、每一次设备枚举,背后都是 在默默工作。甚至内核的红黑树()也依赖类似的技巧:通过树节点反推出包含它的外层结构体。
换句话说,你现在学到的这个宏,是日后理解进程调度、内存管理、文件系统的基础。它看似简单,却是内核世界中最常用的"魔法"之一。
代码典籍
struct bad_layout {
char a; /* 1 字节 + 3 字节填充 */
int b; /* 4 字节 */
char c; /* 1 字节 + 7 字节填充 */
long d; /* 8 字节 */
};
struct good_layout {
long d; /* 8 字节 */
int b; /* 4 字节 */
char a; /* 1 字节 */
char c; /* 1 字节 + 2 字节填充 */
};
printf("=== 结构体对齐 ===\n");
printf("bad_layout: %zu 字节\n", sizeof(struct bad_layout));
printf("good_layout: %zu 字节\n", sizeof(struct good_layout));
printf("\n");
printf("bad_layout 字段偏移:\n");
printf(" a: %zu\n", offsetof(struct bad_layout, a));
printf(" b: %zu\n", offsetof(struct bad_layout, b));
printf(" c: %zu\n", offsetof(struct bad_layout, c));
printf(" d: %zu\n", offsetof(struct bad_layout, d));
printf("\ngood_layout 字段偏移:\n");
printf(" d: %zu\n", offsetof(struct good_layout, d));
printf(" b: %zu\n", offsetof(struct good_layout, b));
printf(" a: %zu\n", offsetof(struct good_layout, a));
printf(" c: %zu\n", offsetof(struct good_layout, c));#include <stdio.h>
#include <stddef.h>
struct bad_layout {
char a; /* 1 字节 + 3 字节填充 */
int b; /* 4 字节 */
char c; /* 1 字节 + 7 字节填充 */
long d; /* 8 字节 */
};
struct good_layout {
long d; /* 8 字节 */
int b; /* 4 字节 */
char a; /* 1 字节 */
char c; /* 1 字节 + 2 字节填充 */
};
int main() {
printf("=== 结构体对齐 ===\n");
printf("bad_layout: %zu 字节\n", sizeof(struct bad_layout));
printf("good_layout: %zu 字节\n", sizeof(struct good_layout));
printf("\n");
printf("bad_layout 字段偏移:\n");
printf(" a: %zu\n", offsetof(struct bad_layout, a));
printf(" b: %zu\n", offsetof(struct bad_layout, b));
printf(" c: %zu\n", offsetof(struct bad_layout, c));
printf(" d: %zu\n", offsetof(struct bad_layout, d));
printf("\ngood_layout 字段偏移:\n");
printf(" d: %zu\n", offsetof(struct good_layout, d));
printf(" b: %zu\n", offsetof(struct good_layout, b));
printf(" a: %zu\n", offsetof(struct good_layout, a));
printf(" c: %zu\n", offsetof(struct good_layout, c));
return 0;
}struct __attribute__((packed)) network_header {
uint8_t version_ihl; /* 1 字节: 版本 + 首部长度 */
uint8_t tos; /* 1 字节: 服务类型 */
uint16_t total_length; /* 2 字节: 总长度 */
uint16_t id; /* 2 字节: 标识 */
uint16_t flags_offset; /* 2 字节: 标志 + 片偏移 */
uint8_t ttl; /* 1 字节: 生存时间 */
uint8_t protocol; /* 1 字节: 协议 */
uint16_t checksum; /* 2 字节: 校验和 */
uint32_t src_addr; /* 4 字节: 源地址 */
uint32_t dst_addr; /* 4 字节: 目的地址 */
};
struct normal_header {
uint8_t version_ihl;
uint8_t tos;
uint16_t total_length;
uint16_t id;
uint16_t flags_offset;
uint8_t ttl;
uint8_t protocol;
uint16_t checksum;
uint32_t src_addr;
uint32_t dst_addr;
};
printf("=== packed vs 普通结构体 ===\n");
printf("packed 大小: %zu 字节 (应该是 20)\n",
sizeof(struct network_header));
printf("普通大小: %zu 字节\n",
sizeof(struct normal_header));
printf("\n");
printf("packed 字段偏移:\n");
printf(" version_ihl: %zu\n", offsetof(struct network_header, version_ihl));
printf(" tos: %zu\n", offsetof(struct network_header, tos));
printf(" total_length:%zu\n", offsetof(struct network_header, total_length));
printf(" src_addr: %zu\n", offsetof(struct network_header, src_addr));
printf(" dst_addr: %zu\n", offsetof(struct network_header, dst_addr));
printf("\n注意: packed 结构体不能取地址的字段可能未对齐!\n");
printf("内核中用 get_unaligned() 访问未对齐字段\n");#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
struct __attribute__((packed)) network_header {
uint8_t version_ihl; /* 1 字节: 版本 + 首部长度 */
uint8_t tos; /* 1 字节: 服务类型 */
uint16_t total_length; /* 2 字节: 总长度 */
uint16_t id; /* 2 字节: 标识 */
uint16_t flags_offset; /* 2 字节: 标志 + 片偏移 */
uint8_t ttl; /* 1 字节: 生存时间 */
uint8_t protocol; /* 1 字节: 协议 */
uint16_t checksum; /* 2 字节: 校验和 */
uint32_t src_addr; /* 4 字节: 源地址 */
uint32_t dst_addr; /* 4 字节: 目的地址 */
};
struct normal_header {
uint8_t version_ihl;
uint8_t tos;
uint16_t total_length;
uint16_t id;
uint16_t flags_offset;
uint8_t ttl;
uint8_t protocol;
uint16_t checksum;
uint32_t src_addr;
uint32_t dst_addr;
};
int main() {
printf("=== packed vs 普通结构体 ===\n");
printf("packed 大小: %zu 字节 (应该是 20)\n",
sizeof(struct network_header));
printf("普通大小: %zu 字节\n",
sizeof(struct normal_header));
printf("\n");
printf("packed 字段偏移:\n");
printf(" version_ihl: %zu\n", offsetof(struct network_header, version_ihl));
printf(" tos: %zu\n", offsetof(struct network_header, tos));
printf(" total_length:%zu\n", offsetof(struct network_header, total_length));
printf(" src_addr: %zu\n", offsetof(struct network_header, src_addr));
printf(" dst_addr: %zu\n", offsetof(struct network_header, dst_addr));
printf("\n注意: packed 结构体不能取地址的字段可能未对齐!\n");
printf("内核中用 get_unaligned() 访问未对齐字段\n");
return 0;
}/* 模拟页表项的位域布局 */
struct pte {
uint64_t present : 1; /* 第 0 位: 是否在内存中 */
uint64_t writable : 1; /* 第 1 位: 是否可写 */
uint64_t user : 1; /* 第 2 位: 用户态是否可访问 */
uint64_t pwt : 1; /* 第 3 位: Page Write-Through */
uint64_t pcd : 1; /* 第 4 位: Page Cache Disable */
uint64_t accessed : 1; /* 第 5 位: 是否被访问过 */
uint64_t dirty : 1; /* 第 6 位: 是否被写过 */
uint64_t pat : 1; /* 第 7 位: Page Attribute Table */
uint64_t global : 1; /* 第 8 位: 全局页 */
uint64_t available : 3; /* 第 9-11 位: 可用 */
uint64_t address : 40; /* 第 12-51 位: 物理地址 */
uint64_t reserved : 11; /* 第 52-62 位: 保留 */
uint64_t nx : 1; /* 第 63 位: No Execute */
} __attribute__((packed));
struct pte entry = {0};
uint64_t raw;
enum {
PTE_PRESENT_BIT = 0,
PTE_WRITABLE_BIT = 1,
PTE_USER_BIT = 2,
PTE_ADDRESS_SHIFT = 12,
PTE_NX_BIT = 63,
};
#define BIT_ULL(n) (1ULL << (n))
#define PTE_ADDR_MASK (((1ULL << 40) - 1) << PTE_ADDRESS_SHIFT)
printf("=== 页表项 (PTE) 位域 ===\n");
printf("PTE 大小: %zu 字节\n\n", sizeof(struct pte));
/* 设置一个页表项 */
entry.present = 1;
entry.writable = 1;
entry.user = 1;
entry.address = 0x12345; /* 物理页帧号 */
entry.nx = 0;
raw = BIT_ULL(PTE_PRESENT_BIT) |
BIT_ULL(PTE_WRITABLE_BIT) |
BIT_ULL(PTE_USER_BIT) |
(0x12345ULL << PTE_ADDRESS_SHIFT);
printf("显式掩码布局:\n");
printf(" present bit: %d\n", PTE_PRESENT_BIT);
printf(" writable bit:%d\n", PTE_WRITABLE_BIT);
printf(" user bit: %d\n", PTE_USER_BIT);
printf(" address: bit %d..51\n", PTE_ADDRESS_SHIFT);
printf(" nx bit: %d\n\n", PTE_NX_BIT);
printf("原始 PTE: 0x%016llx\n", (unsigned long long)raw);
printf("present: %s\n", (raw & BIT_ULL(PTE_PRESENT_BIT)) ? "是" : "否");
printf("PFN: 0x%llx\n",
(unsigned long long)((raw & PTE_ADDR_MASK) >> PTE_ADDRESS_SHIFT));
printf("\n警告: 位域的布局是编译器定义的!\n");
printf("内核通常用位移和掩码代替位域,以保证可移植性\n");#include <stdio.h>
#include <stdint.h>
/* 模拟页表项的位域布局 */
struct pte {
uint64_t present : 1; /* 第 0 位: 是否在内存中 */
uint64_t writable : 1; /* 第 1 位: 是否可写 */
uint64_t user : 1; /* 第 2 位: 用户态是否可访问 */
uint64_t pwt : 1; /* 第 3 位: Page Write-Through */
uint64_t pcd : 1; /* 第 4 位: Page Cache Disable */
uint64_t accessed : 1; /* 第 5 位: 是否被访问过 */
uint64_t dirty : 1; /* 第 6 位: 是否被写过 */
uint64_t pat : 1; /* 第 7 位: Page Attribute Table */
uint64_t global : 1; /* 第 8 位: 全局页 */
uint64_t available : 3; /* 第 9-11 位: 可用 */
uint64_t address : 40; /* 第 12-51 位: 物理地址 */
uint64_t reserved : 11; /* 第 52-62 位: 保留 */
uint64_t nx : 1; /* 第 63 位: No Execute */
} __attribute__((packed));
int main() {
struct pte entry = {0};
uint64_t raw;
enum {
PTE_PRESENT_BIT = 0,
PTE_WRITABLE_BIT = 1,
PTE_USER_BIT = 2,
PTE_ADDRESS_SHIFT = 12,
PTE_NX_BIT = 63,
};
#define BIT_ULL(n) (1ULL << (n))
#define PTE_ADDR_MASK (((1ULL << 40) - 1) << PTE_ADDRESS_SHIFT)
printf("=== 页表项 (PTE) 位域 ===\n");
printf("PTE 大小: %zu 字节\n\n", sizeof(struct pte));
/* 设置一个页表项 */
entry.present = 1;
entry.writable = 1;
entry.user = 1;
entry.address = 0x12345; /* 物理页帧号 */
entry.nx = 0;
raw = BIT_ULL(PTE_PRESENT_BIT) |
BIT_ULL(PTE_WRITABLE_BIT) |
BIT_ULL(PTE_USER_BIT) |
(0x12345ULL << PTE_ADDRESS_SHIFT);
printf("显式掩码布局:\n");
printf(" present bit: %d\n", PTE_PRESENT_BIT);
printf(" writable bit:%d\n", PTE_WRITABLE_BIT);
printf(" user bit: %d\n", PTE_USER_BIT);
printf(" address: bit %d..51\n", PTE_ADDRESS_SHIFT);
printf(" nx bit: %d\n\n", PTE_NX_BIT);
printf("原始 PTE: 0x%016llx\n", (unsigned long long)raw);
printf("present: %s\n", (raw & BIT_ULL(PTE_PRESENT_BIT)) ? "是" : "否");
printf("PFN: 0x%llx\n",
(unsigned long long)((raw & PTE_ADDR_MASK) >> PTE_ADDRESS_SHIFT));
printf("\n警告: 位域的布局是编译器定义的!\n");
printf("内核通常用位移和掩码代替位域,以保证可移植性\n");
return 0;
}#define my_offsetof(type, member) \
((size_t)&((type *)0)->member)
struct task_struct {
long state;
int pid;
int prio;
unsigned int policy;
char comm[16];
};
printf("=== offsetof ===\n");
printf("标准 offsetof:\n");
printf(" state: %zu\n", offsetof(struct task_struct, state));
printf(" pid: %zu\n", offsetof(struct task_struct, pid));
printf(" prio: %zu\n", offsetof(struct task_struct, prio));
printf(" policy: %zu\n", offsetof(struct task_struct, policy));
printf(" comm: %zu\n", offsetof(struct task_struct, comm));
printf("\n自定义 offsetof:\n");
printf(" state: %zu\n", my_offsetof(struct task_struct, state));
printf(" pid: %zu\n", my_offsetof(struct task_struct, pid));
printf(" comm: %zu\n", my_offsetof(struct task_struct, comm));
printf("\n原理: 把 NULL(0) 当作结构体起始地址,\n");
printf("取成员的地址就是该成员的偏移量\n");#include <stdio.h>
#include <stddef.h>
#define my_offsetof(type, member) \
((size_t)&((type *)0)->member)
struct task_struct {
long state;
int pid;
int prio;
unsigned int policy;
char comm[16];
};
int main() {
printf("=== offsetof ===\n");
printf("标准 offsetof:\n");
printf(" state: %zu\n", offsetof(struct task_struct, state));
printf(" pid: %zu\n", offsetof(struct task_struct, pid));
printf(" prio: %zu\n", offsetof(struct task_struct, prio));
printf(" policy: %zu\n", offsetof(struct task_struct, policy));
printf(" comm: %zu\n", offsetof(struct task_struct, comm));
printf("\n自定义 offsetof:\n");
printf(" state: %zu\n", my_offsetof(struct task_struct, state));
printf(" pid: %zu\n", my_offsetof(struct task_struct, pid));
printf(" comm: %zu\n", my_offsetof(struct task_struct, comm));
printf("\n原理: 把 NULL(0) 当作结构体起始地址,\n");
printf("取成员的地址就是该成员的偏移量\n");
return 0;
}#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
struct task_struct {
long state;
int pid;
int prio;
char comm[16];
};
struct task_struct task = {
.state = 0,
.pid = 42,
.prio = 120,
.comm = "swapper/0",
};
/* 假设我们只有 pid 字段的指针 */
int *pid_ptr = &task.pid;
/* 用 container_of 反推出 task_struct 指针 */
struct task_struct *recovered =
container_of(pid_ptr, struct task_struct, pid);
printf("=== container_of ===\n");
printf("原始结构体地址: %p\n", (void *)&task);
printf("pid 字段地址: %p\n", (void *)pid_ptr);
printf("反推得到的地址: %p\n", (void *)recovered);
printf("地址相同: %s\n", (&task == recovered) ? "是" : "否");
printf("\n");
printf("反推得到的 task:\n");
printf(" pid: %d\n", recovered->pid);
printf(" prio: %d\n", recovered->prio);
printf(" comm: %s\n", recovered->comm);#include <stdio.h>
#include <stddef.h>
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
struct task_struct {
long state;
int pid;
int prio;
char comm[16];
};
int main() {
struct task_struct task = {
.state = 0,
.pid = 42,
.prio = 120,
.comm = "swapper/0",
};
/* 假设我们只有 pid 字段的指针 */
int *pid_ptr = &task.pid;
/* 用 container_of 反推出 task_struct 指针 */
struct task_struct *recovered =
container_of(pid_ptr, struct task_struct, pid);
printf("=== container_of ===\n");
printf("原始结构体地址: %p\n", (void *)&task);
printf("pid 字段地址: %p\n", (void *)pid_ptr);
printf("反推得到的地址: %p\n", (void *)recovered);
printf("地址相同: %s\n", (&task == recovered) ? "是" : "否");
printf("\n");
printf("反推得到的 task:\n");
printf(" pid: %d\n", recovered->pid);
printf(" prio: %d\n", recovered->prio);
printf(" comm: %s\n", recovered->comm);
return 0;
}#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
/* 内核的链表节点 */
struct list_head {
struct list_head *next;
struct list_head *prev;
};
/* 带数据的结构体 */
struct task_struct {
long state;
int pid;
char comm[16];
struct list_head tasks; /* 链表节点嵌入其中 */
};
/* 初始化链表头 */
static inline void INIT_LIST_HEAD(struct list_head *list) {
list->next = list;
list->prev = list;
}
/* 添加节点(尾插) */
static inline void list_add_tail(struct list_head *new,
struct list_head *head) {
new->prev = head->prev;
new->next = head;
head->prev->next = new;
head->prev = new;
}
/* 通过链表节点获取外层结构体 */
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/* 遍历链表 */
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
struct list_head task_list;
INIT_LIST_HEAD(&task_list);
struct task_struct t1 = { .state = 0, .pid = 1, .comm = "init" };
struct task_struct t2 = { .state = 0, .pid = 2, .comm = "kthreadd" };
struct task_struct t3 = { .state = 0, .pid = 3, .comm = "rcu_gp" };
list_add_tail(&t1.tasks, &task_list);
list_add_tail(&t2.tasks, &task_list);
list_add_tail(&t3.tasks, &task_list);
printf("=== 内核链表遍历 ===\n");
struct task_struct *pos;
list_for_each_entry(pos, &task_list, tasks) {
printf("PID: %d, 名称: %s\n", pos->pid, pos->comm);
}
printf("\n链表节点地址:\n");
printf(" t1.tasks: %p\n", (void *)&t1.tasks);
printf(" t2.tasks: %p\n", (void *)&t2.tasks);
printf(" t3.tasks: %p\n", (void *)&t3.tasks);#include <stdio.h>
#include <stddef.h>
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
/* 内核的链表节点 */
struct list_head {
struct list_head *next;
struct list_head *prev;
};
/* 带数据的结构体 */
struct task_struct {
long state;
int pid;
char comm[16];
struct list_head tasks; /* 链表节点嵌入其中 */
};
/* 初始化链表头 */
static inline void INIT_LIST_HEAD(struct list_head *list) {
list->next = list;
list->prev = list;
}
/* 添加节点(尾插) */
static inline void list_add_tail(struct list_head *new,
struct list_head *head) {
new->prev = head->prev;
new->next = head;
head->prev->next = new;
head->prev = new;
}
/* 通过链表节点获取外层结构体 */
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/* 遍历链表 */
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
int main() {
struct list_head task_list;
INIT_LIST_HEAD(&task_list);
struct task_struct t1 = { .state = 0, .pid = 1, .comm = "init" };
struct task_struct t2 = { .state = 0, .pid = 2, .comm = "kthreadd" };
struct task_struct t3 = { .state = 0, .pid = 3, .comm = "rcu_gp" };
list_add_tail(&t1.tasks, &task_list);
list_add_tail(&t2.tasks, &task_list);
list_add_tail(&t3.tasks, &task_list);
printf("=== 内核链表遍历 ===\n");
struct task_struct *pos;
list_for_each_entry(pos, &task_list, tasks) {
printf("PID: %d, 名称: %s\n", pos->pid, pos->comm);
}
printf("\n链表节点地址:\n");
printf(" t1.tasks: %p\n", (void *)&t1.tasks);
printf(" t2.tasks: %p\n", (void *)&t2.tasks);
printf(" t3.tasks: %p\n", (void *)&t3.tasks);
return 0;
}结构之术试炼
内核链表通过成员地址反推出宿主结构体时,最关键的宏是什么?