热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

Linux内核通用链表学习小结

描述 在linux内核中封装了一个通用的双向链表库,这个通用的链表库有很好的扩展性和封装性,它给我们提供了一个固定的指针域结构体,我们在使

描述

在linux内核中封装了一个通用的双向链表库,这个通用的链表库有很好的扩展性和封装性,它给我们提供了一个固定的指针域结构体,我们在使用的时候,只需要在我们定义的数据域结构体中包含这个指针域结构体就可以了,具体的实现、链接并不需要我们关心,只要调用提供给我们的相关接口就可以完成了。

传统的链表结构

struct node{
  int key;
  int val;
  node* prev;
  node* next;
 }

linux 内核通用链表库结构

提供给我们的指针域结构体:

struct list_head {
  struct list_head *next, *prev;
};

我们只需要包含它就可以:

struct node{
  int val;
  int key;
  struct list_head* head;
}

可以看到通过这个 list_head 结构就把我们的数据层跟驱动层分开了,而内核提供的各种操作方法接口也只关心 list_head 这个结构,也就是具体链接的时候也只链接这个list_head 结构,并不关心你数据层定义了什么类型.

一些接口宏定义

//初始化头指针
#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \
  struct list_head name = LIST_HEAD_INIT(name)

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

//获取节点首地址(不是list_head地址,是数据层节点首地址)
#define list_entry(ptr, type, member) \
  container_of(ptr, type, member)

//container_of在Linux内核中是一个常用的宏,用于从包含在某个
//结构中的指针获得结构本身的指针,通俗地讲就是通过结构体变
//量中某个成员的首地址进而获得整个结构体变量的首地址
#define container_of(ptr, type, member) ({     \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);  \
    (type *)( (char *)__mptr - offsetof(type,member) );})

#define offsetof(s,m) (size_t)&(((s *)0)->m)

使用方式

typedef struct node{
  int val;
  int key;
  struct list_head* list;
}node;

//初始化头指针
LIST_HEAD(head);

//创建节点
node* a = malloc(sizeof(node));
node* b = malloc(sizeof(node));

//插入链表 方式一
list_add(&a->list,&head);
list_add(&b->list,&head);

//插入链表 方式二
list_add_tail(&a->list,&head);
list_add_tail(&b->list,&head);

//遍历链表  
struct list_head* p;
struct node* n;
__list_for_each(p,head){
  //返回list_head地址,然后再通过list_head地址反推
  //节点结构体首地址.
  n = list_entry(pos,struct node,list);
}

list_add 接口,先入后出原则,有点类似于栈

list_add-先入后出模式

list_add_tail 接口,先入先出原则,有点类似于fifo

list_add-先入先出模式

我们的链表节点,实际在内存中的展示形态

节点描述

可以看到最终的形态是,通过指向每个结构体里面的 list_head 类型指针,然后把它们串联起来的

list_entry 接口,通过结构体变量某个成员的地址,反推结构体首地址,就像 __list_for_each 接口只返回 list_head 地址,所以我们要通过这个成员地址在去获取它本身的结构体首地址,底层实现方法 container_of 宏

反推结构体首地址

举个例子

这个例子包括简单的增、删、遍历

#include  
#include  
#include  
#include  
#include  
 
MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("David Xie"); 
MODULE_DESCRIPTION("List Module"); 
MODULE_ALIAS("List module"); 
 
struct student //代表一个实际节点的结构 
{ 
  char name[100]; 
  int num; 
  struct list_head list;  //内核链表里的节点结构 
}; 
 
struct student *pstudent;    
struct student *tmp_student; 
struct list_head student_list;  
struct list_head *pos; 
 
int mylist_init(void) 
{ 
  int i = 0; 
   
  //初始化一个链表,其实就是把student_list的prev和next指向自身 
  INIT_LIST_HEAD(&student_list);  
   
  pstudent = kmalloc(sizeof(struct student)*5,GFP_KERNEL);//向内核申请5个student结构空间 
  memset(pstudent,0,sizeof(struct student)*5); //清空,这两个函数可以由kzalloc单独做到 
   
  for(i=0;i<5;i++) 
  { //为结构体属性赋值 
    sprintf(pstudent[i].name,"Student%d",i+1); 
    pstudent[i].num = i+1;  
    //加入链表节点,list_add的话是在表头插入,list_add_tail是在表尾插入 
    list_add( &(pstudent[i].list), &student_list);//参数1是要插入的节点地址,参数2是链表头地址 
  }  
   
  list_for_each(pos,&student_list) //list_for_each用来遍历链表,这是个宏定义 
                   //pos在上面有定义 
  { 
    //list_entry用来提取出内核链表节点对应的实际结构节点,即根据struct list_head来提取struct student 
    //第三个参数list就是student结构定义里的属性list 
    //list_entry的原理有点复杂,也是linux内核的一个经典实现,这个在上面那篇链接文章里也有讲解 
    tmp_student = list_entry(pos,struct student,list); 
    //打印一些信息,以备验证结果 
    printk("<0>student %d name: %s/n",tmp_student->num,tmp_student->name); 
  } 
   
  return 0; 
} 
 
 
void mylist_exit(void) 
{   
  int i ; 
  /* 实验:将for换成list_for_each来遍历删除结点,观察要发生的现象,并考虑解决办法 */ 
  for(i=0;i<5;i++) 
  { 
    //额,删除节点,只要传个内核链表节点就行了 
    list_del(&(pstudent[i].list));    
  } 
  //释放空间 
  kfree(pstudent); 
} 
 
module_init(mylist_init); 
module_exit(mylist_exit); 

结束

linux 内核提供的这个通用链表库里面还有很多其他的接口,这里没有详细的一一举例,有兴趣的可以自己去看看,在源码包 include/linux/list.h 文件里面,不过通过阅读一些源代码确实对我们也有很大的提高,可以看看高手是如何去设计并实现,还可以学到一些技巧以及对代码细节的掌握~~.

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • 本文介绍了在rhel5.5操作系统下搭建网关+LAMP+postfix+dhcp的步骤和配置方法。通过配置dhcp自动分配ip、实现外网访问公司网站、内网收发邮件、内网上网以及SNAT转换等功能。详细介绍了安装dhcp和配置相关文件的步骤,并提供了相关的命令和配置示例。 ... [详细]
  • 近年来,大数据成为互联网世界的新宠儿,被列入阿里巴巴、谷歌等公司的战略规划中,也在政府报告中频繁提及。据《大数据人才报告》显示,目前全国大数据人才仅46万,未来3-5年将出现高达150万的人才缺口。根据领英报告,数据剖析人才供应指数最低,且跳槽速度最快。中国商业结合会数据剖析专业委员会统计显示,未来中国基础性数据剖析人才缺口将高达1400万。目前BAT企业中,60%以上的招聘职位都是针对大数据人才的。 ... [详细]
  • Python语法上的区别及注意事项
    本文介绍了Python2x和Python3x在语法上的区别,包括print语句的变化、除法运算结果的不同、raw_input函数的替代、class写法的变化等。同时还介绍了Python脚本的解释程序的指定方法,以及在不同版本的Python中如何执行脚本。对于想要学习Python的人来说,本文提供了一些注意事项和技巧。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • Linux磁盘的分区、格式化的观察和操作步骤
    本文介绍了如何观察Linux磁盘的分区状态,使用lsblk命令列出系统上的所有磁盘列表,并解释了列表中各个字段的含义。同时,还介绍了使用parted命令列出磁盘的分区表类型和分区信息的方法。在进行磁盘分区操作时,根据分区表类型选择使用fdisk或gdisk命令,并提供了具体的分区步骤。通过本文,读者可以了解到Linux磁盘分区和格式化的基本知识和操作步骤。 ... [详细]
  • 本文介绍了Linux系统中正则表达式的基础知识,包括正则表达式的简介、字符分类、普通字符和元字符的区别,以及在学习过程中需要注意的事项。同时提醒读者要注意正则表达式与通配符的区别,并给出了使用正则表达式时的一些建议。本文适合初学者了解Linux系统中的正则表达式,并提供了学习的参考资料。 ... [详细]
  • Ubuntu 9.04中安装谷歌Chromium浏览器及使用体验[图文]
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 本文讨论了在数据库打开和关闭状态下,重新命名或移动数据文件和日志文件的情况。针对性能和维护原因,需要将数据库文件移动到不同的磁盘上或重新分配到新的磁盘上的情况,以及在操作系统级别移动或重命名数据文件但未在数据库层进行重命名导致报错的情况。通过三个方面进行讨论。 ... [详细]
author-avatar
holygame
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有