Linux双向循环链表的实现

//
// DoubleListTest.cpp
// 用于模拟测试Linux内核中的双向循环链表实现
// Linux实现位于源代码树的include/linux/list.h
// @Author Yrh
// @Time 2014年12月16日19:05:35至2014年12月17日12:41:36
//

//  0. Linux双向循环链表简介 
//  1. 定义和初始化
//  2. 通用链表操作接口
//  3.1 添加节点
//  3.2 删除节点
//  3.3 移动节点
//  3.4 链表判空
//  4 链表合并
//  5 获取宿主对象指针
//  6.1 List-head链表遍历
//  6.2 遍历宿主对象
//  7 如何使用Linux中的双循环链表

//  在linux内核中,有大量的数据结构需要用到双循环链表,例如进程、文件、模块、页面等。若采用双循环链表的传统实现
//  方式,需要为这些数据结构维护各自的链表,并且为每个链表都要设计插入、删除等操作函数。因为用来维持链表的next
//  和prev指针指向对应类型的对象,因此一种数据结构的链表操作函数不能用于操作其它数据结构的链表。
// 
//  在Linux源代码树的include/linux/list.h文件中,采用了一种类型无关的双循环链表实现方式。其思想是将指针prev和
//  next从具体的数据结构中提取出来构成一种通用的"双链表"数据结构list_head。如果需要构造某类对象的特定链表,则在
//  其结构(被称为宿主数据结构)中定义一个类型为list_head类型的成员,通过这个成员将这类对象连接起来,形成所需链
//  表,并通过通用链表函数对其进行操作。其优点是只需编写通用链表函数,即可构造和操作不同对象的链表,而无需为每
//  类对象的每种列表编写专用函数,实现了代码的重用。
//
 

#include <stdio.h>

/*----------------获取宿主指针-------------------*/
/*------------!!!!思想精华所在!!!!---------------*/
// offsetof、typeof、containerof 宏
#define __compiler_offsetof(a,b) __builtin_offsetof(a,b)

#undef offsetof
#ifdef __compiler_offsetof
#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
#else
#define offsetof(TYPE,MEMBER) ((size_t) &((TYPE *)0) -> MEMBER)
#endif

#define container_of(ptr, type, member) ({ \
const typeof(((type *)0) -> member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member));})

// list_entry 宏 获取当前list_head链表结点所在的宿主结构项
// @ptr 当前 list_head 结点的指针,即指向宿主结构体中的 list_head 成员
// @type 宿主数据结构的定义类型
// @member 宿主结构体类型定义中的 list_head 成员
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/*
图片
                                   list_entry 宏
*/
 
// 扩展替换即为
// #define list_entry(ptr, type, member) \
//  ((type *)((char *)(ptr) - (unsigned long)(&((type *)0) -> member)))
// 例如,我们需要访问foo链表(链表头为 head )中收个元素,则如此调用:
// list_entry(head -> next, struct foo, list)
// 经过C预处理的文字替换,内容就成为:
// ((struct foo *)((char *)(head -> next) - (unsigned long)(&((struct foo *)0) -> list)))

// 双向循环列链表结构体
struct list_head
{
struct list_head *next,*prev; // 双向链表的前驱和后继
};
/*
图片 
                     list_head 特点
*/ 

/*----------------创建并初始化双循环链表-------------------*/

// 定义和初始化
#define LIST_HEAD_INIT(name) {&(name),&(name)}  // name 为 struct list_head 类型的一个变量
// &(name)为该结构体变量的地址。

#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)  // LIST_HEAD 宏可以同时完成定义链表头,
// 并初始化这个双循环队列

//#define INIT_LIST_HEAD(ptr) do { \ // 动态初始化一个已经存在的list_head对象
// (ptr) -> next = (ptr); \ // ptr 为 struct list_head 指针
// (ptr) -> prev = (ptr); \
//} while(0)

static inline void INIT_LIST_HEAD(struct list_head *ptr)
{
    ptr -> next = ptr;
    ptr -> prev = ptr;
}

/*----------------添加一个结点-------------------*/

static inline void __list_add(
struct list_head *newList,
struct list_head *prev,
struct list_head *next)
{
next -> prev = newList;
newList -> next = next;
newList -> prev = prev;
prev -> next = newList;
}

// 在 head 和 head -> next 之间插入 newList 结点
// head 结点不一定是头结点
static inline void list_add(
struct list_head *newList,
struct list_head *head)
{
__list_add(newList, head, head -> next);
}

// 在 head -> prev 和 head 之间插入 newList 结点
// 若 head 结点是头结点,也相当于在尾结点后添加一个 newList 结点
static inline void list_add_tail(
struct list_head *newList,
struct list_head *head)
{
__list_add(newList, head -> prev, head);
}

/*
图片  
                  list_add & list_add_tail
*/ 


/*----------------删除一个结点-------------------*/
// 此操作均仅仅是把结点从双循环链表中拿掉
// 用户需要自己负责释放该结点对应的数据结构所占用的空间
// 这个空间本来就是用户分配的

static inline void __list_del(
struct list_head *prev,
struct list_head *next)
{
next -> prev = prev;
prev -> next = next;
// 此函数即删除的是 prev 和 next 之间的结点
}

#define LIST_POISON1 ((void *) 0x00100100)
#define LIST_POISON2 ((void *) 0x00200200)

// 删除 entry 结点,同时将 entry 所指向的结点指针域封死
static inline void list_del(struct list_head *entry)
{
__list_del(entry -> prev, entry -> next);
// entry -> next = LIST_POISON1;
// entry -> prev = LIST_POISON2;
    entry -> next = NULL;
    entry -> prev = NULL;
}
// 对LIST_POISON1,LIST_POISON2 的解释说明
// Linux 内核中解释:These are non-NULL pointers that will result in page faults
// under circumstances, used to verify that nobody uses non-initialized list entries.
// 常规思想是:entry -> next = NULL; entry -> prev = NULL;
// 保证不可通过该节点进行访问

// 删除 entry 结点,同时调用 LIST_HEAD_INIT() 把被删除结点
// 作为链表头构建一个新的空双循环链表
static inline void list_del_init(struct list_head *entry)
{
__list_del(entry -> prev, entry -> next);
INIT_LIST_HEAD(entry);
}

/*----------------移动一个结点-------------------*/

// 删除了 list 结点,将其插入到 head 和 head -> next 结点之间
static inline void list_move(
struct list_head *list,
struct list_head *head)
{
__list_del(list -> prev, list -> next);
list_add(list, head);
}

// 删除了 list 结点,将其插入到 head 结点的前面
// 如果 head 是头结点,则插入到链表的结尾
static inline void list_move_tail(
struct list_head *list,
struct list_head *head)
{
__list_del(list -> prev, list -> next);
list_add_tail(list, head);
}

/*----------------判断链表是否为空-------------------*/

static inline int list_empty(const struct list_head *head)
{
return head -> next == head;
// 若链表为空,则头结点的 prev 和 next 都指向它本身
// 因此返回 1 表示链表为空,反之链表不为空
}

static inline int list_empty_careful(const struct list_head *head)
{
struct list_head *next =head -> next;
return (next == head) && (next == head -> prev);
// 1.只有一个头结点 head 则 head -> next == head -> prev == head 因此返回 1
// 2.有两个结点,head -> next == head -> prev == 下一个结点,但 head != head -> next 因此返回0
// 3.有3个及3个以上的结点,很容易分析
}

/*----------------两个链表拼接-------------------*/

static inline void __list_splice(
struct list_head *list,
struct list_head *head)
{
struct list_head *first = list -> next;
struct list_head *last = list -> prev;
struct list_head *at = head -> next;

first -> prev = head;
head -> next = first;

last -> next = at;
at -> prev = last;
}

static inline void list_splice(
struct list_head *list,
struct list_head *head)
{
if(!list_empty(list))
__list_splice(list, head);
}

static inline void list_splice_init(
struct list_head *list,
struct list_head *head)
{
if(!list_empty(list))
{
__list_splice(list, head);
INIT_LIST_HEAD(list);
}
}

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


// prefetch 为了提高遍历速度,采用了预取
static inline void prefetch(void *x)
{
    asm volatile("prefetcht0 %0" :: "m" (*(unsigned long *)x));
// 将x指针强制类型转换为 unsigned long * 型,然后取出该内存操作数,送入高速缓存
}

// __list_for_each 宏
#define __list_for_each(pos, head) \
for(pos = (head) -> next; pos != head; pos = pos -> next)
// __list_for_each 定位于那些简单的表的遍历,此时表项较少,不需要缓存

// list_for_each_prev 宏
#define list_for_each_prev(pos, head) \
for(pos = (head) -> prev; prefetch(pos -> prev), pos != (head); \
pos = pos -> prev)
// 反向遍历结点

// list_for_each_safe 宏
// 如果在遍历链表的时候包含有删除或移动当前连接结点的操作
// 由于这些操作会修改遍历的指针,这样会导致遍历的中断
// 因此,需要将下一个结点的地址缓存下来,避免丢失
#define list_for_each_safe(pos, n, head) \
for(pos = (head) -> next, n = pos -> next; pos != (head); \
pos = n, n = pos -> next)

/*---------------- 宿主遍历-------------------*/
// list_for_each_entry 宏
// @pos 传入的遍历指针,指向数据结构
// @head 链表头,为 list_head 结构
// @member list_head 结构在宿主结构中的成员名
#define list_for_each_entry(pos, head, member) \
for(pos = list_entry((head) -> next, typeof(*pos), member); \
prefetch(pos -> member.next), &pos -> member != (head); \
pos = list_entry(pos -> member.next, typeof(*pos), member))

// list_for_each_entry_reverse 宏
#define list_for_each_entry_reverse(pos, head, member)  \
for(pos = list_entry((head) -> prev, typeof(*pos), member); \
prefetch(pos -> member.prev), &pos -> member != (head); \
pos = list_entry(pos -> member.prev, typeof(*pos), member))

// 如果遍历不是从链表头开始,而是从已知的某个 pos 结点开始
// 则可以使用 list_for_each_entry_continue(pos, head, member)
// 但为了确保 pos 的初始值有效,Linux 专门提供了一个
// list_prepare_entry(pos, head, member) 宏
// 如果 pos 有值,则其不变
// 如果 pos 无值,则从链表头强制扩展一个虚 pos 指针
// 将其返回值作为 list_for_each_entry_continue(pos, head, member) 中 pos 的参数

// 内核中的list_prepare_entry 宏代码
#define list_prepare_entry(pos, head, member) \
((pos)?:list_entry(head, typeof(*pos), member))
// 其表达式等效于:(pos)? (pos) : list_entry(head, typeof(*pos), member)

// list_for_each_entry_continue 宏
#define list_for_each_entry_continue(pos, head, member) \
for(pos = list_entry(pos -> member.next, typeof(*pos), member); \
prefetch(pos -> member.next), &pos -> member != (head);  \
pos = list_entry(pos -> member.next, typeof(*pos), member))
// 此时虽然不是以头结点开始,但仍然是以头结点为结束点的,即没有遍历完整个链表
// 且注意,这里不是从 pos 开始的,而是从 pos -> member.next 即下一个结点开始

// list_for_each_entry_safe 宏
#define list_for_each_entry_safe(pos, n, head, member) \
for(pos = list_entry((head) -> next, typeof(*pos), member),  \
n = list_entry(pos -> member.next, typeof(*pos), member);  \
&pos -> member != (head); \
pos = n, n = list_entry(n -> member.next, typeof(*pos), member))
// 同 list_for_each_safe

// list_for_each_entry_safe_continue 宏
#define list_for_each_entry_safe_continue(pos, n, head, member) \
for(pos = list_entry(pos -> member.next, typeof(*pos), member), \
n = list_entry(pos -> member.next, typeof(*pos), member); \
&pos -> member != (head); \
pos = n, n = list_entry(n -> member.next, typeof(*n), member))

/*---------------- 用法示例-------------------*/

// 只是对其中注释部分作了翻译。
// #include <stdio.h>
// #include <stdlib.h>
// #include "list.h"
// struct kool_list{
//     int to;
//     struct list_head list;
//     int from;
// };
//
// int main(int argc, char **argv){
//     struct kool_list *tmp;
//     struct list_head *pos, *q;
//     unsigned int i;
//     struct kool_list mylist;
//     INIT_LIST_HEAD(&mylist.list);
//     /* 您也可以使用宏LIST_HEAD(mylist)来声明并初始化这个链表 */
//     /*向链表中添加元素*/
//     for(i=5; i!=0; --i){
//         tmp= (struct kool_list *)malloc(sizeof(struct kool_list));
//
//         /*INIT_LIST_HEAD(&tmp->list); 调用这个函数将初始化一个动态分配的list_head。也可以不调用它,因为在后面调用的add_list()中将设置next和prev域。*/
//         printf("enter to and from:");
//         scanf("%d %d", &tmp->to, &tmp->from);
//         /*将tmp添加到mylist链表中*/
//         list_add(&(tmp->list), &(mylist.list));
//         /*也可以使用list_add_tail()将新元素添加到链表的尾部。*/
//     }
//     printf("\n");
//     /*现在我们得到了数据结构struct kool_list的一个循环链表,我们将遍历这个链表,并打印其中的元素。*/
//     /*list_for_each()定义了一个for循环宏,第一个参数用作for循环的计数器,换句话说,在整个循环过程中它指向了当前项的list_head。第二个参数是指向链表的指针,在宏中保持不变。*/
//     printf("traversing the list using list_for_each()\n");
//     list_for_each(pos, &mylist.list){
//         /*此刻:pos->next指向了下一项的list变量,而pos->prev指向上一项的list变量。而每项都是struct kool_list类型。但是,我们需要访问的是这些项,而不是项中的list变量。因此需要调用list_entry()宏。*/
//         tmp= list_entry(pos, struct kool_list, list);
//         /*给定指向struct list_head的指针,它所属的宿主数据结构的类型,以及它在宿主数据结构中的名称,list_entry返回指向宿主数据结构的指针。例如,在上面一行, list_entry()返回指向pos所属struct kool_list项的指针。*/
//         printf("to= %d from= %d\n", tmp->to, tmp->from);
//     }
//     printf("\n");
//     /* 因为这是一个循环链表,我们也可以向前遍历。只需要将list_for_each替换为list_for_each_prev。我们也可以使用list_for_each_entry()遍历链表,在给定类型的项间进行循环。例如:*/
//     printf("traversing the list using list_for_each_entry()\n");
//     list_for_each_entry(tmp, &mylist.list, list)
//     printf("to= %d from= %d\n", tmp->to, tmp->from);
//     printf("\n");
//
//     /*下面将释放这些项。因为我们调用list_del()从链表中删除各项,因此需要使用list_for_each()宏的"安全"版本,即list_for_each_safe()。务必注意,如果在循环中有删除项(或把项从一个链表移动到另一个链表)的操作,必须使用这个宏。*/
//     printf("deleting the list using list_for_each_safe()\n");
//     list_for_each_safe(pos, q, &mylist.list){
//         tmp= list_entry(pos, struct kool_list, list);
//         printf("freeing item to= %d from= %d\n", tmp->to, tmp->from);
//         list_del(pos);
//         free(tmp);
//     }
//     return 0;
// }
//
// 注意:上述代码在使用gcc编译时需要加上__KERNEL__定义。
//

// 主函数
int main()
{
printf("Linux双循环链表实现代码及使用示例\n");

return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自动控制节水灌溉技术的高低代表着农业现代化的发展状况,灌溉系统自动化水平较低是制约我国高效农业发展的主要原因。本文就此问题研究了单片机控制的滴灌节水灌溉系统,该系统可对不同土壤的湿度进行监控,并按照作物对土壤湿度的要求进行适时、适量灌水,其核心是单片机和PC机构成的控制部分,主要对土壤湿度与灌水量之间的关系、灌溉控制技术及设备系统的硬件、软件编程各个部分进行了深入的研究。 单片机控制部分采用上下位机的形式。下位机硬件部分选用AT89C51单片机为核心,主要由土壤湿度传感器,信号处理电路,显示电路,输出控制电路,故障报警电路等组成,软件选用汇编语言编程。上位机选用586型以上PC机,通过MAX232芯片实现同下位机的电平转换功能,上下位机之间通过串行通信方式进行数据的双向传输,软件选用VB高级编程语言以建立友好的人机界面。系统主要具有以下功能:可在PC机提供的人机对话界面上设置作物要求的土壤湿度相关参数;单片机可将土壤湿度传感器检测到的土壤湿度模拟量转换成数字量,显示于LED显示器上,同时单片机可采用串行通信方式将此湿度值传输到PC机上;PC机通过其内设程序计算出所需的灌水量和灌水时间,且显示于界面上,并将有关的灌水信息反馈给单片机,若需灌水,则单片机系统启动鸣音报警,发出灌水信号,并经放大驱动设备,开启电磁阀进行倒计时定时灌水,若不需灌水,即PC机上显示的灌水量和灌水时间均为0,系统不进行灌水。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值