玩转内核链表list_head,3个超级哇塞的的例子_linux链表链接list_head例子

/\*\*
 \* list\_for\_each\_prev - iterate over a list backwards
 \* @pos: the &struct list\_head to use as a loop cursor.
 \* @head: the head for your list.
 \*/
#define list\_for\_each\_prev(pos, head) \
 for (pos = (head)->prev; pos != (head); pos = pos->prev)

而且,list.h 中也提供了list_replace(节点替换) list_move(节点移位) ,翻转,查找等接口,这里就不在一一分析了。

五. 宿主结构

1.找出宿主结构 list_entry(ptr, type, member)

上面的所有操作都是基于list_head这个链表进行的,涉及的结构体也都是:

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

其实,正如文章一开始所说,我们真正更关心的是包含list_head这个结构体字段的宿主结构体,因为只有定位到了宿主结构体的起始地址,我们才能对对宿主结构体中的其它有意义的字段进行操作。

struct mylist
{
    int type;
    char name[MAX_NAME_LEN];
  struct list_head list;  
};

那我们如何根据list这个字段的地址而找到宿主结构node1的位置呢?list.h中定义如下:

/\*\*
 \* list\_entry - get the struct for this entry
 \* @ptr: the &struct list\_head pointer.
 \* @type: the type of the struct this is embedded in.
 \* @member: the name of the list\_head within the struct.
 \*/
#define list\_entry(ptr, type, member) \
 container\_of(ptr, type, member)

list.h中提供了list_entry宏来实现对应地址的转换,但最终还是调用了container_of宏,所以container_of宏的伟大之处不言而喻。

2 container_of

做linux驱动开发的同学是不是想到了LDD3这本书中经常使用的一个非常经典的宏定义!

container\_of(ptr, type, member)

在LDD3这本书中的第三章字符设备驱动,以及第十四章驱动设备模型中多次提到,我觉得这个宏应该是内核最经典的宏之一。那接下来让我们揭开她的面纱:

此宏在内核代码 kernel/include/linux/kernel.h中定义(此处kernel版本为3.10;新版本4.13之后此宏定义改变,但实现思想保持一致)
在这里插入图片描述而offsetof定义在 kernel/include/linux/stddef.h ,如下:
在这里插入图片描述

举个例子,来简单分析一下container_of内部实现机制。

例如:

struct   test
{
    int       a;
    short   b;
    char    c;
};
struct  test  \*p = (struct test \*)malloc(sizeof(struct  test));
test\_function(&(p->b));

int  test\_function(short \*addr_b)
{
      //获取struct test结构体空间的首地址
     struct  test \*addr;
     addr =   container\_of(addr_b,struct test,b);
}

展开container_of宏,探究内部的实现:

typeof (  ( (struct test   \*)0 )->b ) ;                        (1)
typeof (  ( (struct test   \*)0 )->b )   \*__mptr =  addr_b ;    (2)
(struct test \*)( (char \*)__mptr  -  offsetof(struct test,b))   (3)

(1) 获取成员变量b的类型 ,这里获取的就是short 类型。这是GNU_C的扩展语法。

(2) 用获取的变量类型,定义了一个指针变量 __mptr ,并且将成员变量 b的首地址赋值给它

(3) 这里的offsetof(struct test,b)是用来计算成员b在这个struct test 结构体的偏移。__mptr

是成员b的首地址, 现在 减去成员b在结构体里面的偏移值,算出来的是不是这个结构体的

首地址呀 。

3. 宿主结构的遍历

我们可以根据结构体中成员变量的地址找到宿主结构的地址,并且我们可以对成员变量所建立的链表进行遍历,那我们是不是也可以通过某种方法对宿主结构进行遍历呢?

答案肯定是可以的,内核在list.h中提供了下面的宏:

/\*\*
 \* list\_for\_each\_entry - iterate over list of given type
 \* @pos: the type \* to use as a loop cursor.
 \* @head: the head for your list.
 \* @member: the name of the list\_head within the struct.
 \*/
#define list\_for\_each\_entry(pos, head, member) \
 for (pos = list\_first\_entry(head, typeof(\*pos), member); \
 &pos->member != (head); \
 pos = list\_next\_entry(pos, member))

其中,list_first_entry 和 list_next_entry宏都定义在list.h中,分别代表:获取第一个真正的宿主结构的地址;获取下一个宿主结构的地址。它们的实现都是利用list_entry宏。

/\*\*
 \* list\_first\_entry - get the first element from a list
 \* @ptr: the list head to take the element from.
 \* @type: the type of the struct this is embedded in.
 \* @member: the name of the list\_head within the struct.
 \*
 \* Note, that list is expected to be not empty.
 \*/
#define list\_first\_entry(ptr, type, member) \
 list\_entry((ptr)->next, type, member)

/\*\*
 \* list\_next\_entry - get the next element in list
 \* @pos: the type \* to cursor
 \* @member: the name of the list\_head within the struct.
 \*/
#define list\_next\_entry(pos, member) \
 list\_entry((pos)->member.next, typeof(\*(pos)), member)

最终实现了宿主结构的遍历

#define list\_for\_each\_entry(pos, head, member) \
 for (pos = list\_first\_entry(head, typeof(\*pos), member); \
 &pos->member != (head); \
 pos = list\_next\_entry(pos, member))

首先pos定位到第一个宿主结构地址,然后循环获取下一个宿主结构地址,如果查到宿主结构中的member成员变量(宿主结构中struct list_head定义的字段)地址为head,则退出,从而实现了宿主结构的遍历。如果要循环对宿主结构中的其它成员变量进行操作,这个遍历操作就显得特别有意义了。

我们用上面的 nod结构举个例子:

struct my_list \*pos_ptr = NULL ;
list_for_each_entry (pos_ptr, &myhead, list )
{
         printk ("val = %d\n" , pos_ptr->val);
}

实例1 一个简单的链表的实现

为方便起见,本例把内核的list.h文件单独拷贝出来,这样就可以独立于内核来编译测试。

功能描述:

本例比较简单,仅仅实现了单链表节点的创建、删除、遍历。

#include "list.h"
#include <stdio.h>
#include <string.h>

#define MAX\_NAME\_LEN 32
#define MAX\_ID\_LEN 10

struct list_head myhead;

#define I2C\_TYPE 1
#define SPI\_TYPE 2

char \*dev_name[]={
  "none",
  "I2C",
  "SPI"
};

struct mylist
{
    int type;
    char name[MAX_NAME_LEN];
  struct list_head list;  
};

void display\_list(struct list_head \*list_head)
{
  int i=0;
  struct list_head \*p;
  struct mylist \*entry;
  printf("-------list---------\n");
  list\_for\_each(p,list_head)
  {
    printf("node[%d]\n",i++);
    entry=list\_entry(p,struct mylist,list);
    printf("\ttype: %s\n",dev_name[entry->type]);
    printf("\tname: %s\n",entry->name);
  }
  printf("-------end----------\n");
}

int main(void)
{

  struct mylist node1;
  struct mylist node2;

  INIT\_LIST\_HEAD(&myhead);

  node1.type = I2C_TYPE;
  strcpy(node1.name,"yikoulinux");

  node2.type = I2C_TYPE;
  strcpy(node2.name,"yikoupeng");

  list\_add(&node1.list,&myhead);
  list\_add(&node2.list,&myhead);

  display\_list(&myhead);

  list\_del(&node1.list);

  display\_list(&myhead);
  return 0;
}

运行结果
在这里插入图片描述

实例2 如何在一个链表上管理不同类型的节点

功能描述:

本实例主要实现在同一个链表上管理两个不同类型的节点,实现增删改查的操作。

结构体定义

一个链表要想区分节点的不同类型,那么节点中必须要有信息能够区分该节点类型,为了方便节点扩展,我们参考Linux内核,定义一个统一类型的结构体:

struct device
{
    int type;
    char name[MAX_NAME_LEN];
    struct list_head list;
};

其中成员type表示该节点的类型:

#defineI2C\_TYPE 1
#define SPI\_TYPE 2

有了该结构体,我们要定义其他类型的结构体只需要包含该结构体即可,这个思想有点像面向对象语言的基类,后续派生出新的属性叫子类,说到这,一口君又忍不住想挖个坑,写一篇如何用C语言实现面向对象思想的继承、多态、interface。

下面我们定义2种类型的结构体:
i2c这种类型设备的专用结构体:

struct i2c_node
{
  int data;
  unsigned int reg;
  struct device dev;
};

spi这种类型设备的专用结构体:

struct spi_node
{  
  unsigned int reg;
  struct device dev;
};

我特意让两个结构体大小类型不一致。

结构类型

链表头结点定义

structlist_head device_list;

根据之前我们讲解的思想,这个链表链接起来后,应该是以下这种结构:

在这里插入图片描述

节点的插入

我们定义的节点要插入链表仍然是要依赖list_add(),既然我们定义了struct device这个结构体,那么我们完全可以参考linux内核,针对不同的节点封装函数,要注册到这个链表只需要调用该函数即可。

实现如下:

设备i2c的注册函数如下:

void i2c\_register\_device(struct device\*dev)
{
  dev.type = I2C_TYPE;
  strcpy(dev.name,"yikoulinux");
  list\_add(&dev->list,&device_list);  
}

设备spi的注册函数如下:

void spi\_register\_device(struct device\*dev)
{
  dev.type = SPI_TYPE;
  strcpy(dev.name,"yikoupeng");
  list\_add(&dev->list,&device_list);  
}

我们可以看到注册函数功能是填充了struct device 的type和name成员,然后再调用list_add()注册到链表中。这个思想很重要,因为Linux内核中许许多多的设备节点也是这样添加到其他的链表中的。要想让自己的C语言编程能力得到质的提升,一定要多读内核代码,即使看不懂也要坚持看,古人有云:代码读百遍其义自见。

节点的删除

同理,节点的删除,我们也统一封装成函数,同样只传递参数device即可:

void i2c\_unregister\_device(struct device \*device)
{
// struct i2c\_node \*i2c\_device=container\_of(dev, struct i2c\_node, dev);
  list\_del(&device->list);
}
void spi\_unregister\_device(struct device \*device)
{
// struct spi\_node \*spi\_device=container\_of(dev, struct spi\_node, dev);
  list\_del(&device->list);
}

在函数中,可以用container_of提取出了设备节点的首地址,实际使用中可以根据设备的不同释放不同的资源。

宿主结构的遍历

节点的遍历,在这里我们通过设备链表device_list开始遍历,假设该节点名是node,通过list_for_each()可以得到node->dev->list的地址,然后利用container_of 可以得到node->dev、node的地址。

void display\_list(struct list_head \*list_head)
{
  int i=0;
  struct list_head \*p;
  struct device \*entry;

  printf("-------list---------\n");
  list\_for\_each(p,list_head)
  {
    printf("node[%d]\n",i++);
    entry=list\_entry(p,struct device,list);

    switch(entry->type)
    {
      case I2C_TYPE:
                           display\_i2c\_device(entry);
        break;
      case SPI_TYPE:
        display\_spi\_device(entry);
        break;
      default:
        printf("unknown device type!\n");
        break;
    }
    display\_device(entry);
  }
  printf("-------end----------\n");
}

由以上代码可知,利用内核链表的统一接口,找个每一个节点的list成员,然后再利用container_of 得到我们定义的标准结构体struct device,进而解析出节点的类型,调用对应节点显示函数,这个地方其实还可以优化,就是我们可以在struct device中添加一个函数指针,在xxx_unregister_device()函数中可以将该函数指针直接注册进来,那么此处代码会更精简高效一些。如果在做项目的过程中,写出这种面向对象思想的代码,那么你的地址是肯定不一样的。读者有兴趣可以自己尝试一下。

void display\_i2c\_device(struct device \*device)
{
  struct i2c_node \*i2c_device=container\_of(device, struct i2c_node, dev);
  printf("\t i2c\_device->data: %d\n",i2c_device->data);
  printf("\t i2c\_device->reg: %#x\n",i2c_device->reg);
}

void display\_spi\_device(struct device \*device)
{
  struct spi_node \*spi_device=container\_of(device, struct spi_node, dev);
  printf("\t spi\_device->reg: %#x\n",spi_device->reg);
}

上述代码提取出来宿主节点的信息。

实例代码

#include "list.h"
#include <stdio.h>
#include <string.h>

#define MAX\_NAME\_LEN 32
#define MAX\_ID\_LEN 10

struct list_head device_list;

#define I2C\_TYPE 1
#define SPI\_TYPE 2

char \*dev_name[]={
  "none",
  "I2C",
  "SPI"
};

struct device
{
  int type;
  char name[MAX_NAME_LEN];
  struct list_head list;
};

struct i2c_node
{
  int data;
  unsigned int reg;
  struct device dev;
};
struct spi_node
{  
  unsigned int reg;
  struct device dev;
};
void display\_i2c\_device(struct device \*device)
{
  struct i2c_node \*i2c_device=container\_of(device, struct i2c_node, dev);

  printf("\t i2c\_device->data: %d\n",i2c_device->data);
  printf("\t i2c\_device->reg: %#x\n",i2c_device->reg);
}
void display\_spi\_device(struct device \*device)
{
  struct spi_node \*spi_device=container\_of(device, struct spi_node, dev);

  printf("\t spi\_device->reg: %#x\n",spi_device->reg);
}
void display\_device(struct device \*device)
{
    printf("\t dev.type: %d\n",device->type);
    printf("\t dev.type: %s\n",dev_name[device->type]);
    printf("\t dev.name: %s\n",device->name);
}
void display\_list(struct list_head \*list_head)
{
  int i=0;
  struct list_head \*p;
  struct device \*entry;

  printf("-------list---------\n");
  list\_for\_each(p,list_head)
  {
    printf("node[%d]\n",i++);
    entry=list\_entry(p,struct device,list);

    switch(entry->type)
    {
      case I2C_TYPE:
        display\_i2c\_device(entry);
        break;
      case SPI_TYPE:
        display\_spi\_device(entry);
        break;
      default:
        printf("unknown device type!\n");
        break;
    }
    display\_device(entry);
  }
  printf("-------end----------\n");
}
void i2c\_register\_device(struct device\*dev)
{
  struct i2c_node \*i2c_device=container\_of(dev, struct i2c_node, dev);

  i2c_device->dev.type = I2C_TYPE;
  strcpy(i2c_device->dev.name,"yikoulinux");

  list\_add(&dev->list,&device_list);  
}
void spi\_register\_device(struct device\*dev)
{
  struct spi_node \*spi_device=container\_of(dev, struct spi_node, dev);

  spi_device->dev.type = SPI_TYPE;
  strcpy(spi_device->dev.name,"yikoupeng");

  list\_add(&dev->list,&device_list);  
}
void i2c\_unregister\_device(struct device \*device)
{
  struct i2c_node \*i2c_device=container\_of(dev, struct i2c_node, dev);

  list\_del(&device->list);
}
void spi\_unregister\_device(struct device \*device)
{
  struct spi_node \*spi_device=container\_of(dev, struct spi_node, dev);

  list\_del(&device->list);
}
int main(void)
{

  struct i2c_node dev1;
  struct spi_node dev2;

  INIT\_LIST\_HEAD(&device_list);
  dev1.data = 1;
  dev1.reg = 0x40009000;
  i2c\_register\_device(&dev1.dev);
  dev2.reg  = 0x40008000;
  spi\_register\_device(&dev2.dev);  
  display\_list(&device_list);
  unregister\_device(&dev1.dev);
  display\_list(&device_list);  
  return 0;
}

代码主要功能:

117-118          :定义两个不同类型的节点dev1,dev2;
120                 :初始化设备链表;
121-122、124:初始化节点数据;
123/125          :向链表device_list注册这两个节点;
126                :显示该链表;
127                :删除节点dev1;
128                 :显示该链表。

程序运行截图
在这里插入图片描述

读者可以试试如何管理更多类型的节点。

实例3 实现节点在两个链表上自由移动

功能描述:

初始化两个链表,实现两个链表上节点的插入和移动。每个节点维护大量的临时内存数据。

节点创建

节点结构体创建如下:

struct mylist{
  int number;
  char type;
    char \*pmem;  //内存存放地址,需要malloc
  struct list_head list;
};

需要注意成员pmem,因为要维护大量的内存,我们最好不要直定义个很大的数组,因为定义的变量位于栈中,而一般的系统给栈的空间是有限的,如果定义的变量占用空间太大,会导致栈溢出,一口君曾经就遇到过这个bug。

链表定义和初始化

链表定义如下:

structlist_head active_head;
struct list_head free_head;

初始化

INIT\_LIST\_HEAD(&free_head);
INIT\_LIST\_HEAD(&active_head);

这两个链表如下:

在这里插入图片描述

关于节点,因为该实例是从实际项目中剥离出来,节点启示是起到一个缓冲去的作用,数量不是无限的,所以在此我们默认最多10个节点。

我们不再动态创建节点,而是先全局创建指针数组,存放这10个节点的地址,然后将这10个节点插入到对应的队列中。

数组定义:

structmylist\*list_array[BUFFER_NUM];

这个数组只用于存放指针,所以定义之后实际情况如下:

在这里插入图片描述

初始化这个数组对应的节点:

static ssize_t buffer\_ring\_init()
{
  int i=0;
  for(i=0;i<BUFFER_NUM;i++){
    list_array[i]=malloc(sizeof(struct mylist));
    INIT\_LIST\_HEAD(&list_array[i]->list);
    list_array[i]->pmem=kzalloc(DATA_BUFFER_SIZE,GFP_KERNEL);
  }
  return 0;
}

5:为下标为i的节点分配实际大小为sizeof(structmylist)的内存
6:初始化该节点的链表
7:为pmem成员从堆中分配一块内存

初始化完毕,链表实际情况如下:

节点插入

static ssize_t insert\_free\_list\_all()
{
  int i=0;

  for(i=0;i<BUFFER_NUM;i++){
    list\_add(&list_array[i]->list,&free_head);
  }
  return 0;
}

8:用头插法将所有节点插入到free_head链表中

所有节点全部插入free链表后,结构图如下:

在这里插入图片描述

遍历链表

虽然可以通过数组遍历链表,但是实际在操作过程中,在链表中各个节点的位置是错乱的。所以最好从借助list节点来查找各个节点。

show\_list(&free_head);
show\_list(&active_head);

代码实现如下:

void show\_list(struct list_head \*list_head)
{
  int i=0;
  struct mylist\*entry,\*tmp;
  //判断节点是否为空
  if(list\_empty(list_head)==true)
  {
    return;
  }
  list\_for\_each\_entry\_safe(entry,tmp,list_head,list)
  {
    printf("[%d]=%d\t",i++,entry->number);
    if(i%4==0)
    {
      printf("\n");
    }
  }  
}

节点移动

将节点从active_head链表移动到free_head链表,有点像生产者消费者模型中的消费者,吃掉资源后,就要把这个节点放置到空闲链表,让生产者能够继续生产数据,所以这两个函数我起名eat、spit,意为吃掉和吐,希望你们不要觉得很怪异。

int eat\_node()
{
  struct mylist\*entry=NULL;

  if(list\_empty(&active_head)==true)
  {
    printf("list active\_head is empty!-----------\n");
  }
  entry=list\_first\_entry(&active_head,struct mylist,list);
  printf("\t eat node=%d\n",entry->number);
  list\_move\_tail(&entry->list,&free_head);
}

节点移动的思路是:

  1. 利用list_empty判断该链表是否为空
  2. 利用list_first_entry从active_head链表中查找到一个节点,并用指针entry指向该节点
  3. 利用list_move_tail将该节点移入到free_head链表,注意此处不能用list_add,因为这个节点我要从原链表把他删除掉,然后插入到新链表。

将节点从free_head链表移动到active_head链表。

spit\_node()
{
  struct mylist\*entry=NULL;

  if(list\_empty(&free_head)==true)
  {
    printf("list free\_head is empty!-----------\n");
  }
  entry=list\_first\_entry(&free_head,struct mylist,list);
  printf("\t spit node=%d\n",entry->number);
  list\_move\_tail(&entry->list,&active_head);
}

大部分功能讲解完了,下面我们贴下完整代码。

代码实例

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <byteswap.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include "list.h" // linux-3.14/scripts/kconfig/list.h

#undef NULL
#define NULL ((void \*)0)

enum {
  false  = 0,
  true  = 1
};

#define DATA\_TYPE 0x14
#define SIG\_TYPE 0x15

struct mylist{
  int number;
  char type;
  char \*pmem;
  struct list_head list;
};
#define FATAL do { fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n",\_\_LINE\_,\_\_FILE\_\_,errno,strerror(errno));exit(1);}while(0)
struct list_head active_head;
struct list_head free_head;
#define BUFFER\_NUM 10
#define DATA\_BUFFER\_SIZE 512
struct mylist\*list_array[BUFFER_NUM];


int born\_number(int number)
{
  struct mylist \*entry=NULL;

  if(list\_empty(&free_head)==true)
  {
    printf("list free\_head is empty!----------------\n");
  }
  entry = list\_first\_entry(&free_head,struct mylist,list);
  entry->type = DATA_TYPE;
  entry->number=number;
  list\_move\_tail(&entry->list,&active_head);  
}

int eat\_node()
{
  struct mylist\*entry=NULL;

## 最后

**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。**

**因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

![img](https://img-blog.csdnimg.cn/img_convert/c2891f91eef6e8fa8a9eeab30de6065e.png)

![img](https://img-blog.csdnimg.cn/img_convert/5f7b84731c18ea03addf93ae5916be8c.jpeg)

![img](https://img-blog.csdnimg.cn/img_convert/4f1906d31c71d3578054afe71de20147.png)

 ![img](https://img-blog.csdnimg.cn/img_convert/d70b478fe027fa694688741353e2f767.png)

![img](https://img-blog.csdnimg.cn/img_convert/c54a4036833d46a9a370933107021cb9.png)

![img](https://img-blog.csdnimg.cn/img_convert/ad274d2ca1236792f99e2ce94992021b.png)

![](https://img-blog.csdnimg.cn/img_convert/7094b1c45f0cf991a3e646746dd94a08.png)

 

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!**

[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618654289)

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**!!


;
  }
  entry = list\_first\_entry(&free_head,struct mylist,list);
  entry->type = DATA_TYPE;
  entry->number=number;
  list\_move\_tail(&entry->list,&active_head);  
}

int eat\_node()
{
  struct mylist\*entry=NULL;

## 最后

**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。**

**因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

[外链图片转存中...(img-Ce2ImiN9-1715795063214)]

[外链图片转存中...(img-x56VDZMT-1715795063215)]

[外链图片转存中...(img-Lj3VaDLn-1715795063215)]

 [外链图片转存中...(img-nZ6OfmeD-1715795063215)]

[外链图片转存中...(img-gz8dqrHf-1715795063216)]

[外链图片转存中...(img-PEIqpAmq-1715795063216)]

[外链图片转存中...(img-1G5DbA1q-1715795063217)]

 

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!**

[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618654289)

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**!!


一、linux内核链表 1、普通链表的数据区域的局限性 之前定义数据区域时直接int data,我们认为我们的链表中需要存储的是一个int类型的数。但是实际上现实编程中链接中的节点不可能这么简单,而是多种多样的。 一般实际项目中的链表,节点中存储的数据其实是一个结构体,这个结构体中包含若干的成员,这些成员加起来构成了我们的节点数据区域。 2、一般性解决思路:即把数据区封装为一个结构体 (1)因为链表实际解决的问题是多种多样的,所以内部数据区域的结构体构成也是多种多样的。 这样也导致了不同程序当中的链表总体构成是多种多样的。 我们无法通过一套泛性的、普遍适用的操作函数来访问所有的链表,意味着我们设计一个链表就得写一套链表的操作函数(节点创建、插入、删除、遍历……)。 (2)实际上深层次分析会发现 不同的链表虽然这些方法不能通用需要单独写,但是实际上内部的思路和方法是相同的,只是函数的局部地区有不同。 实际上链表操作是相同的,而涉及到数据区域的操作就有不同 (3)问题 能不能有一种办法把所有链表中操作方法里共同的部分提取出来用一套标准方法实现,然后把不同的部分留着让具体链表的实现者自己去处理。 3、内核链表的设计思路 (1)内核链表中实现一个纯链表的封装,以及纯链表的各种操作函数 纯链表就是没有数据区域,只有前后向指针; 各种操作函数是节点创建、插入、删除、遍历。 这个纯链表本身自己没有任何用处,它的用法是给我们具体链表作为核心来调用。 4、list.h文件简介 (1)内核中核心纯链表的实现在include/linux/list.h文件中 (2)list.h中就是一个纯链表的完整封装,包含节点定义和各种链表操作方法。 二、内核链表的基本算法和使用简介 1、内核链表的节点创建、删除、遍历等 2、内核链表的使用实践 (1)问题:内核链表只有纯链表,没有数据区域,怎么使用? 使用方法是将内核链表作为将来整个数据结构的结构体的一个成员内嵌进去。类似于公司收购,实现被收购公司的功能。 这里面要借助container_of宏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值