linux内核中链表代码分析---list.h头文件分析(一)
来源:互联网 发布:js 数字格式化成2位 编辑:程序博客网 时间:2024/05/23 01:23
- linux内核中链表代码分析---list.h头文件分析(一)
- 16年2月27日17:13:14
- 在学习数据结构时,有一个重要的知识点就是链表。对于链表的一些基本操作,它的最好学习资料就是内核中的list.h头文件,内核中大量的使用链表,都是基于此文件的,下面来仔细分析它:
- (一) 结构体的定义
- 首先需要明确的一点是,在数据结构书中,大部分的链表定义是这样的(双向链表):
- typedef struct DulNode {
- ElemType data;
- DulNode *prior, *next;
- }DuLNode, *DuLinkList;
- 在链表里面包含data数据域和链表的指针域,由于这个ElemType的不同,对于每一种数据类型,都需要定义各自的链表结构。那怎么行,太麻烦了~
- 在linux内核中,对于需要通过链表组织起来的数据通常都是在相应的结构体里面包含一个struct list_head的成员,这个成员里面只包含链表的指针域,这样就将链表指针的操作抽象出来。
- 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)
- 申请一个变量LIST_HEAD(temp)等价于 struct list_head temp = {&(temp), &(temp)};
- 附带知识:
- 1、对成员赋值
- 例如结构体struct st1 {
- int a;
- int b;
- int c;
- }
- 1.1 用{}形式
- struct st1 st1 = {1,2,3);
- 1.2 linux kernel风格.
- struct st1 st1 = {
- .a = 1;
- .b = 2;
- };
- //注此风格(即在成员变量之前加点“.”),可以不按成员变量的顺序进行赋值。如可以为
- struct st1 st1 = {
- .c = 3;
- .a = 1;
- .b = 2;
- };
- 2、对整体赋值.
- struct st1 a, b;
- b = a;
- 3、结构体作为函数返回值对另一个结构体赋值.
- struct st1 func1();
- struct st1 a = func1();
- (二)结构体的初始化
- static inline void INIT_LIST_HEAD(struct list_head *list)
- {
- list->next = list;
- list->prev = list;
- }
- 初始化结构体list,使它的next和prev指针都指向它自己,这是一个链表的初始状态,判断一个链表是否为空的方法就是判断它的next是否指向它本身(后面讲解)。
- (三) 增加结点
- 增加结点可以分为在头插法和尾插法,如下所示:
- 头插法:
- static inline void list_add(struct list_head *new, struct list_head *head)
- {
- __list_add(new, head, head->next);
- }
- 尾插法:
- static inline void list_add_tail(struct list_head *new, struct list_head *head)
- {
- __list_add(new, head->prev, head);
- }
- 他们都调用的是__list_add这个函数,可以看出来linux中对于相同的代码做了很好的封装。下面来看这个核心的__list_add函数:
- static inline void __list_add(struct list_head *new,
- struct list_head *prev,
- struct list_head *next)
- {
- next->prev = new;
- new->next = next;
- new->prev = prev;
- prev->next = new;
- }
- 如下图所示,对于一个名称为new的结点想要插入到名称为prev和next的两个结点之间的话,它的指针应该这样设置:
- 对于上述两种插入方法头插法和尾插法,他们的内部实现相同,只是插入的结点位置不同。内核中的链表是双向循环链表,所以头插法是将名字为new的结点插入到head结点和head->next结点之间。同样的,尾插法就是将名字为new的结点插入到head->prev结点(双向循环链表中的最后一个结点)和head结点之间。
- (四) 删除结点
- 删除结点的核心操作是__list_del函数,如下所示:
- static inline void __list_del(struct list_head * prev, struct list_head * next)
- {
- next->prev = prev;
- prev->next = next;
- }
- 这个函数是表示将entry结点的前一个和后一个结点建立联系的步骤。在这又是体现linux封装思想的一个地方,如果你想要删除名字为entry的结点,你只需要将entry的前一个和后一个结点作为参数传给这个函数即可。下面几个函数就是这样做的:
- static inline void __list_del_entry(struct list_head *entry)
- {
- __list_del(entry->prev, entry->next);
- }
- 这个__list_del_entry函数就是如上面咱们分析那样调用__list_del函数的。这个函数是平时所常用的。
- static inline void list_del(struct list_head *entry)
- {
- __list_del(entry->prev, entry->next);
- entry->next = LIST_POISON1;
- entry->prev = LIST_POISON2;
- }
- 至于这个list_del函数,它把entry的prev、next指针分别设为LIST_POSITION2和LIST_POSITION1两个特殊值,这样设置是为了保证不在 链表中的节点项不可访问(对LIST_POSITION1和LIST_POSITION2的访问都将引起页故障)。
- 下面这个list_del_init函数,它除了将entry从链表中删除以外,还将entry初始化为一个空的链表。
- static inline void list_del_init(struct list_head *entry)
- {
- __list_del_entry(entry);
- INIT_LIST_HEAD(entry);
- }
- (五) 替换结点
- static inline void list_replace(struct list_head *old,
- struct list_head *new)
- {
- new->next = old->next;
- new->next->prev = new;
- new->prev = old->prev;
- new->prev->next = new;
- }
- 这个函数就是将old结点替换成new结点,函数代码很好理解,就不画图来表示了。
- static inline void list_replace_init(struct list_head *old,
- struct list_head *new)
- {
- list_replace(old, new);
- INIT_LIST_HEAD(old);
- }
- 这个list_replace_init函数,除了将old就诶点替换成new结点外,同时将old结点初始化为空链表。
- (六) 搬移结点
- static inline void list_move(struct list_head *list, struct list_head *head)
- {
- __list_del_entry(list);
- list_add(list, head);
- }
- static inline void list_move_tail(struct list_head *list,
- struct list_head *head)
- {
- __list_del_entry(list);
- list_add_tail(list, head);
- }
- 表示将list这个结点从它所在的链表中删除,然后将它重新插入到一个新的链表中。前一种方法是采用头插法的方式,后一种方法是采用尾插法的方式。
- (七)判断list结点是不是链表head的最后一项:
- static inline int list_is_last(const struct list_head *list,
- const struct list_head *head)
- {
- return list->next == head;
- }
- (八) 判断head这个链表是否为空,在上面我们提到,在初始化的时候,将一个链表头的prev和next指向它本身,在这,我们就是通过判断这个来判断链表是否为空。
- static inline int list_empty(const struct list_head *head)
- {
- return head->next == head;
- }
- static inline int list_empty_careful(const struct list_head *head)
- {
- struct list_head *next = head->next;
- return (next == head) && (next == head->prev);
- }
- list_empty()函数和list_empty_careful()函数都是用来检测链表是否为空的。但是稍有区别的就是第一个链 表使用的检测方法是判断表头的结点的下一个结点是否为其本身,如果是则返回为1,否则返回0。第二个 函数使用的检测方法是判断表头的前一个结点和后一个结点是否为其本身,如果同时满足则返回1,否则
- 返回0。
- 这主要是为了应付另一个cpu正在处理同一个链表而造成next、prev不一致的情况。但代码注释也承认, 这一安全保障能力有限:除非其他cpu的链表操作只有list_del_init(),否则仍然不能保证安全,也就是说, 还是需要加锁保护。
- 下面这个函数用来判断是否一个链表只有一个成员(不算head头结点):
- static inline int list_is_singular(const struct list_head *head)
- {
- return !list_empty(head) && (head->next == head->prev);
- }
- 正因为内核中的链表是双向循环链表,所以才能用有上述的比较方式,如果只有一个结点的话,head->next == head->prev。
- (九) 左旋链表
- static inline void list_rotate_left(struct list_head *head)
- {
- struct list_head *first;
- if (!list_empty(head)) {
- first = head->next;
- list_move_tail(first, head);
- }
- }
- 之前一直看不懂这个函数为什么叫左旋链表函数,后来画了一个示意图后明白了,下面这个示意图只是示意所用,它代表内核中的双向循环链表,注意它的两个结点head头结点和first结点。分析上面这个list_rotate_left函数,可以看出来,first结点是head头结点以后的第一个结点。
- 之后调用list_move_tail函数,先将这个first结点删除,然后再移动到head链表的尾部。如下图所示:
- 经过这个操作,就好像这个循环链表向左旋转一样。
- (十)分割链表
- static inline void __list_cut_position(struct list_head *list,
- struct list_head *head, struct list_head *entry)
- {
- struct list_head *new_first = entry->next;
- list->next = head->next;
- list->next->prev = list;
- list->prev = entry;
- entry->next = list;
- head->next = new_first;
- new_first->prev = head;
- }
- 在执行这个操作之前,链表是这样的:它是以head为头部,entry是这个链表其中一项。list是一个空的头部,它是将剪切下来的结点加进来的链表。head是被剪切的链表。
- 经过__list_cut_position这个函数,他们发生了变化,变化以后是这样的:
- 不理解的地方自己手动画画图就清楚了,下面来看 list_cut_position这个函数,如下所示:
- static inline void list_cut_position(struct list_head *list,
- struct list_head *head, struct list_head *entry)
- {
- if (list_empty(head))
- return;
- if (list_is_singular(head) &&
- (head->next != entry && head != entry))
- return;
- if (entry == head)
- INIT_LIST_HEAD(list);
- else
- __list_cut_position(list, head, entry);
- }
- 它进行了一些判断语句,如果被剪切的head链表为空的话,就直接返回;如果被剪切的head链表只有一个结点,并且entry不是head或者head->next任意一个的话,就代表出错了,直接返回;如果entry正好等于head的话,就不用剪切了,直接对list进行初始化就行。
- (十一)合并链表
- 它的核心函数就是下面这个__list_splice函数:
- static inline void __list_splice(const struct list_head *list,
- struct list_head *prev,
- struct list_head *next)
- {
- struct list_head *first = list->next;
- struct list_head *last = list->prev;
- first->prev = prev;
- prev->next = first;
- last->next = next;
- next->prev = last;
- }
- 可以理解为将list这个链表插入到prev和next这两个结点之间。
- static inline void list_splice(const struct list_head *list,
- struct list_head *head)
- {
- if (!list_empty(list))
- __list_splice(list, head, head->next);
- }
- 如果理解了上面那个__list_splice函数的话,这个list_splice函数应该就好理解了,它就是把list这个链表插入到head结点和head->next结点之间,类似于头插法。
- static inline void list_splice_tail(struct list_head *list,
- struct list_head *head)
- {
- if (!list_empty(list))
- __list_splice(list, head->prev, head);
- }
- 这个list_splice_tail函数就是把list链表插入到head->prev结点和head结点之间,类似与尾插法。
- 但是上面两个函数都有一个缺点,就是这个list结点的prev和next指针都还指向原来的位置,它没有改变,但是这两个链表已经进行了合并,这样就会发生混乱,于是就产生了下面两个函数,他们在合并两个链表的同时,将list链表初始化了。
- static inline void list_splice_init(struct list_head *list,
- struct list_head *head)
- {
- if (!list_empty(list)) {
- __list_splice(list, head, head->next);
- INIT_LIST_HEAD(list);
- }
- }
- static inline void list_splice_tail_init(struct list_head *list,
- struct list_head *head)
- {
- if (!list_empty(list)) {
- __list_splice(list, head->prev, head);
- INIT_LIST_HEAD(list);
- }
- }
- 到这位置,链表的一些基本操作就算分析完了,还剩下链表的遍历等操作,他们需要用到linux内核中container_of这个宏的一些知识。我们在分析完这些知识以后在进行链表的遍历等操作。
0
上一篇:数据结构---线性表的链式表示和实现(二)
下一篇:内核中container_of宏的详细分析
相关热门文章
- SHTML是什么_SSI有什么用...
- 卡尔曼滤波的原理说明...
- shell中字符串操作
- 关于java中的“错误:找不到或...
- linux设备驱动归纳总结...
- linux dhcp peizhi roc
- 关于Unix文件的软链接
- 求教这个命令什么意思,我是新...
- sed -e "/grep/d" 是什么意思...
- 谁能够帮我解决LINUX 2.6 10...
给主人留下些什么吧!~~
评论热议
0 0
- linux内核中链表代码分析---list.h头文件分析(一)
- linux内核list.h头文件分析(一)
- linux内核中链表代码分析---list.h头文件分析(二)
- linux内核list.h头文件分析
- linux内核list.h头文件分析(二)
- linux内核list.h头文件分析(三)
- linux内核list.h头文件分析(四)
- linux内核list.h头文件分析(七)——list.h应用
- linux内核list.h头文件分析(五)——hlist分析
- linux内核list.h头文件分析(六)——hlist分析
- linux内核list.h分析(一)
- list.h头文件分析
- list.h头文件分析
- linux内核中的list.h文件中线性链表的分析(一)
- Linux内核的List.h分析
- list.h linux内核链表分析
- linux内核list.h分析(二)
- Linux下~Hash表的构建与应用(包括内核文件list.h分析)
- 数据结构---线性表的链式表示和实现(一)
- java设计模式进阶_composite
- 下拉列表的el表达式
- 数据结构---线性表的链式表示和实现(二)
- 经典好文:Java动态代理实现
- linux内核中链表代码分析---list.h头文件分析(一)
- 内核中container_of宏的详细分析
- VS2010/VS2012 设置全局头文件和库路径
- linux内核中链表代码分析---list.h头文件分析(二)
- V4L2学习记录
- 顺序栈的操作
- 用RecyclerView实现新闻列表页,包括头部的图片轮播,两种Item显示方式,下拉刷新和上拉加载以及限制列表的加载条目数
- 栈的应用之数制转换
- 栈的应用之括号匹配的检验
原创粉丝点击
热门IT博客
热门问题
老师的惩罚
人脸识别
我在镇武司摸鱼那些年
重生之率土为王
我在大康的咸鱼生活
盘龙之生命进化
天生仙种
凡人之先天五行
春回大明朝
姑娘不必设防,我是瞎子
华为nova进水了怎么办
苹果6变形了怎么办
苹果下载软件慢怎么办
衣服挂起包了怎么办
学校档案袋拆了怎么办
档案袋封条坏了怎么办
大学档案袋拆开了怎么办
车辆档案袋拆开了怎么办
摩拜单车锁不上怎么办
tpu手机壳会变黄怎么办
tpu手机壳发黄怎么办
透明保护壳发黄怎么办
白色硅胶变黄怎么办
橡胶皮套变黄怎么办
10天新生儿发热怎么办
硅胶手机套变黄了怎么办
红米没有输入法怎么办
vivox7电池坏了怎么办
大人发烧感觉冷怎么办
玩手机手腕疼怎么办
手腕手指筋骨疼怎么办
华为卡开机画面怎么办
手机内存卡损坏怎么办
玩崩坏3卡怎么办
荣耀v10信号差怎么办
荣耀9上网慢怎么办
麦芒5玩游戏卡怎么办
华为手机特别卡怎么办
华为平板太卡怎么办
sim卡太大了怎么办
赛季皮肤错过了怎么办
华为平板m3刷机后怎么办?
不是全网通手机怎么办
联通合约机丢失怎么办
合约手机掉了怎么办
移动合约机丢失怎么办
如果合约机违约怎么办
话费合约不交钱怎么办
华为mate10突然关机怎么办
vivo手机网络差怎么办
长城宽带玩游戏卡怎么办