一、数据存储与字节序
(1)什么是大端字节序: (2)什么是小端字节序: (3)什么是网络字节序:
端模式分为:小端字节序和大端字节序,也就是字节在内存中的顺序。 小端字节序:低字节存于内存低地址;高字节存于内存高地址。如一个long型数据0x12345678
0x0029f458 0x78 0x0029f459 0x56 0x0029f45a 0x34 0x0029f45b 0x12
在以上数据存放于内存中的表现形式中,0x0029f458 < 0x0029f459 < 0x0029f45a < 0x0029f45b,
可以知道内存的地址是由低到高的顺序;而数据的字节也是由低到高的,故以上字节序是小端字节序。
大端字节序:高字节存于内存低地址;低字节存于内存高地址。 0x0029f458 0x12 0x0029f459 0x34 0x0029f45a 0x56
0x0029f45b 0x79
在以上数据存放于内存中的表现形式中,0x0029f458 < 0x0029f459 < 0x0029f45a < 0x0029f45b,
可以知道内存的地址是由低到高的顺序;而数据的字节却是由高到低的,故以上字节序是大端字节序。
网络字节序:就是大端字节序。规定不同系统间通信一律采用网络字节序。 在VC中的实验如下: int temp = 0x12345678;
调试中,该变量在内存中的字节数据是78 56 34 12,内存中的存放地址是:0x0029f458,0x0029f459,
0x0029f45a,0x0029f45b;刚好符合低位存于低地址中,说明VC遵循小端字节序。
二、Linux内核通用链表
1、内核通用链表的定义 2、怎样创建链表 3、怎样向链表添加元素 4、怎样从链表删除元素 5、怎样遍历链表
6、list_entry(...)宏的实现原理 1、内核通用链表的定义 struct list_head
{ struct list_head *next, *prev; };
2、 新建一个链表
实际上Linux只定义了链表节点,并没有专门定义链表头,那么一个链表结构是如何建立起来的呢?让我们来看看LIST_HEAD()这个宏:
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) 其中的name是struct list_head结构的变量的地址,而不是包含struct list_head的数据结构的变量的地址
3、 插入\\删除\\搬移\\合并
a)插入
对链表的插入操作有两种:在表头插入和在表尾插入。Linux为此提供了两个接口: static inline void list_add(struct list_head *new, struct list_head *head) static inline void list_add_tail(struct list_head *new, struct list_head *head) b) 删除
static inline void list_del(struct list_head *entry); c) 搬移
Linux提供了将原本属于一个链表的节点移动到另一个链表的操作,并根据插入到新链表的位置分为两类:
static inline void list_move(struct list_head *list, struct list_head *head); static inline void list_move_tail(struct list_head *list, struct list_head *head); d) 合并
除了针对节点的插入、删除操作,Linux链表还提供了整个链表的插入功能: static inline void list_splice(struct list_head *list, struct list_head *head);
4、遍历
a) 由链表节点到数据项变量
list_entry宏是用来根据list_head指针查找链表所嵌入的结构体的地址,具体实现是依赖宏container_of:
#define list_entry(ptr, type, member) container_of(ptr, type, member)
#define container_of(ptr, type, member) ({ const typeof( ((type *)0)->member ) *__mptr = (ptr);
(type *)( (char *)__mptr - offsetof(type,member) );})
#define offsetof(type, member) ((size_t) &((type *)0)-> member)
container_of有三个参数, ptr是成员变量的指针, type是指结构体的类型, member是成员变量的名字。 container_of 的作用就是在已知某一个成员变量的名字、指针和结构体类型的情况下,计算结构体的指针,也就是计算结构体的起始地址。 计算的方法其实很简单,就是用该成员变量的指针减去它于type结构体起始位置的偏移量。在这个定义中,typeof( ((type
*)0)->member ) 就是获得 member 的类型, 然后定义了一个临时的常量指针 __mptr, 指向 member 变量。 把 __mptr 转换成 char * 类型, 因为 offsetof 得到的偏移量是以字节为单位。 两者相减得到结构体的起始位置, 再强制转换成 type 类型。
offsetof在这里,TYPE表示一个结构体的类型,MEMBER是结构体中的一个成员变量的名字。offsetof 宏的作用是计算成员变量 MEMBER 相对于结构体起始位置的内存偏移量,以字节(Byte)为单位。
b) 遍历宏
宏list_entry的定义如下:
#define list_entry(ptr, type, member)\\
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) 1) ((type *)0) 将0转型为type类型指针,这里就是list_user类
型。 2) ((type *)0)->member 访问结构中的数据成员member,即list成
员。 3) &((type *)0)->member) 取出数据成员member的地址,即list的地址。 4) (unsigned long)(&((type *)0)->member)) 将上一步的地址进行类型转换得到一个unsigned long 型的数,也就是member所在结构实例的偏移地址,即得到list成员在list_user宿主中的偏移。
5) (char *)(ptr) 将宿主中list的地址转换为char *型,便于按字节进行计算。 6) ptr的值减去list在该宿主中的偏移,得到该宿主的首地址的值。
7) 对上一部的结果进行(type *)转型,获得宿主首地址,即list_user节点的地址。
内核链表应用举例(可不看)
双循环链表是linux内核常用的数据结构,这也是linux链表的一个非常有特色的地方。而涉及到链表的函数有链表的定义、链表头的初始化、链表的插入、链表的遍历、链表的删除和链表的回收。下面通过一个内核模块来说明链表的相关操作。