2017-04-20 17:54:42 qq_36656660 阅读数 139
  • 4.9.链表&状态机与多线程

    本课程主要目标是让大家彻底全面的掌握链表这一数据结构。因为链表在内核驱动开发中属于很重要的基础技术,因此用十几节课来彻底深入讲解和实践。

    30925 人正在学习 去看看 朱有鹏


链表简介

链表是一种常用的数据结构,它通过指针将一系列数据节点连接成一条数据链。相对于数组,链表具有更好的动态性,建立链表时无需预先知道数据总量,可以随机分配空间,可以随机分配空间,可以高效地在链表中的任意位置实时插入或删除数据。链表的开销主要是访问的顺序性和组织链的空间损失。Linux内核链表为双向循环链表~


Linux内核链表函数清单

创建链表:INIT_LIST_HEAD()
插入节点:list_add()
尾插节点:list_add_tail()
删除节点:list_del()
取出节点:list_entry()
遍历链表:list_for_each()


Linux内核链表实现

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)

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

static inline void list_add \
(struct list_head *new, struct list_head *head)
{   
    __list_add (new, head, head->next);
}

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;
}
static inline void list_add_tail\
(struct list_head *new, struct list_head *head)
{
    __list_add (new, head->prev, head);
}

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

static inline void list_del(struct list_head *entry)
{
    __list_del (entry->prev, entry->next);
    entry->prev = 0xbeefdead;
    entry->next = 0xdeadbeef;
}

#define list_entry(ptr,type,member) container_of(ptr,type,member)

#define __list_for_each(pos,head) \
    for(pos=(head)->next;prefetch(pos->next),pos!=(head);pos=pos->next)

offset = sizeof(type);
void *entry = &pos – offset;

测试模块-Linux内核链表使用范例

#include <linux/init.h>
#include <linux/module.h>
#include <linux/list.h>
MODULE_LICENSE("GPL");
typedef struct tag_score
{   
    int num;
    int english;
    int math;
    struct list_head list;
} SCORE;

struct list_head score_head; //用来存放链表头的变量
struct list_head *pos;
struct tag_score stu1,stu2,stu3;
struct tag_score *tmp;

static int hello_init (void)
{   
    INIT_LIST_HEAD (&score_head);//初始化链表头变量
    stu1.num = 1;
    stu1.english = 71;
    stu1.math = 61;
    stu2.num = 2;
    stu2.english = 72;
    stu2.math = 62;
    stu3.num = 3;
    stu3.english = 73;
    stu3.math = 63;
    list_add_tail (&(stu1.list), &score_head);
    list_add_tail (&(stu2.list), &score_head);
    list_add_tail (&(stu3.list), &score_head);
    printk("list create and build for students' score!\n");
    return 0;
}

static void hello_exit (void)
{
    list_for_each(pos, &score_head)
    {   //tmp(tag_score *), pos(list_head *), list(list_head)
        tmp = list_entry (pos, struct tag_score, list);
        printk("num: %d, english: %d, math: %d\n",\
            tmp->num,tmp->english,tmp->math);
    }
    list_del (&(stu1.list));
    list_del (&(stu2.list));
    list_del (&(stu3.list));
    return ;
}

module_init (hello_init);
module_exit (hello_exit);

测试模块hello.ko对应Makefile:

obj-m:=hello.o
#hello-objs:=add.o
KDIR:=/home/redhat6/linux-mini2440
all:
    make modules-C $(KDIR)M=$(PWD)ARCH=arm CROSS_COMPILE=arm-linux-
clean:
    rm -f *.o *.ko *.mod* *.sym* *.ord*

编译开发板测试模块:

[root@redhat6 hello]# ls
hello.c  Makefile
[root@redhat6 hello]# make
make modules-C /home/redhat6/linux-mini2440M=/home/redhat6/hello ARCH=arm CROSS_COMPILE=arm-linux-
make[1]: Entering directory `/home/redhat6/linux-mini2440'
CC [M]  /home/redhat6/hello/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC      /home/redhat6/hello/hello.mod.o
LD [M]  /home/redhat6/hello/hello.ko
make[1]: Leaving directory `/home/redhat6/linux-mini2440'
[root@redhat6 hello]# cp hello.ko /home/redhat6/rootfs/
cp: overwrite `/home/redhat6/rootfs/hello.ko'? y
[root@redhat6 hello]# 

开发板进行测试模块测试:

# lsmod
# insmod hello.ko 
list create and build for students' score!
# lsmod 
hello 1020 0 - Live 0xbf006000
# rmmod hello
num: 1, english: 71, math: 61
num: 2, english: 72, math: 62
num: 3, english: 73, math: 63
rmmod: module 'hello' not found
# 
# 内核链表的移植工作作为个人作业来完成!

2014-10-17 19:40:47 u012041952 阅读数 532
  • 4.9.链表&状态机与多线程

    本课程主要目标是让大家彻底全面的掌握链表这一数据结构。因为链表在内核驱动开发中属于很重要的基础技术,因此用十几节课来彻底深入讲解和实践。

    30925 人正在学习 去看看 朱有鹏
 linux内核中有很多用的很经典的数据结构,链表就算其中之一,还有队列,
哈希查找,红黑树查找等等,链表的设计很经典,就连很多开发内核的黑客们
都觉得内核中链表的设计是他们引以自豪的一部分。我觉得内核链表的好主要
体现为两点,1是可扩展性,2是封装。可扩展性肯定是必须的,内核一直都是
在发展中的,所以代码都不能写成死代码,要方便修改和追加。将链表常见的
操作都进行封装,使用者只关注接口,不需关注实现。分析内核中的链表我们
可以做些什么呢?我觉得可以将其复用到用户态编程中,以后在用户态下编程
就不需要写一些关于链表的代码了,直接将内核中list.h中的代码拷贝过来用。
也可以整理出my_list.h,在以后的用户态编程中直接将其包含到C文件中。
-------------------------------------------------------------------------------------------
1,链表的创建和初始化。
     内核的链表一般都是双向循环链表,学过数据结构的人都知道,双向循环
链表的效率是最高的,找头节点,尾节点,直接前驱,直接后继时间复杂度都是O(1),而使用单链表,单向循环链表或其他形式的链表是不能完成的。
其实内存中开辟出一个头节点然后将其初始化好就完成了链表的创建。

  1. static inline void INIT_LIST_HEAD(struct list_head *list)
  2. {
  3.     list->next = list;
  4.     list->prev = list;
  5. }
开辟好头节点后,然后将结构体中的next域和prev域都指向自己即OK。
例如:(用户态下一个例子)

  1. struct stu {
  2.     int num;
  3.     char name[20];
  4.     struct list_head list;
  5. };
  1.     
  2. struct stu *head = (struct stu *)malloc(sizeof(struct stu));
  3. if (head == NULL) {
  4.     printf("file,%s line,%d:malloc error!\n",__FILE__,__LINE__);
  5.     exit(1);
  6. }
  7. INIT_LIST_HEAD(&head->list);
上面的代码就完成了一个双向循环链表的创建。图示如下:


2.链表的节点的插入。上面建立的只是一个空链表,肯定要向链表中插入一些
节点,插入的方式很多,下面主要分析list_add_tail函数,该函数实现的是
“尾插”。

  1. static inline void __list_add(struct list_head *new,
  2.                    struct list_head *prev,
  3.                     struct list_head *next)
  4. {
  5.       next->prev = new;
  6.       new->next = next;
  7.       new->prev = prev;
  8.       prev->next = new;
  9. }

  10. /**
  11.  * list_add_tail - add a new entry
  12.  * @new: new entry to be added
  13.  * @head: list head to add it before
  14.  *
  15.  * Insert a new entry before the specified head.
  16.  * This is useful for implementing queues.
  17.  */
  18.  static inline void list_add_tail(struct list_head *new, struct list_head *head)
  19.  {
  20.       __list_add(new, head->prev, head);
  21.  }
其实,双向循环链表插入节点很简单,也不需要辅助指针,就几个指针变来变去的,画个图就清楚了
如果光靠脑子还不好想。示意图如下:

3,链表的遍历。
      遍历就是使用线性表的方式吧链表上的每个节点都走一遍,内核中定
义了一个宏来实现。
  1. #define list_for_each(pos, head) \
  2.      for (pos = (head)->next; prefetch(pos->next), pos != (head); \
  3.              pos = pos->next)
可以看出,使用了辅助指针pos,pos是从第一节点开始的,并没有访问头节点,
直到pos到达头节点指针head的时候结束。

  1. /**
  2.   * list_entry - get the struct for this entry
  3.   * @ptr: the &struct list_head pointer.
  4.   * @type: the type of the struct this is embedded in.
  5.   * @member: the name of the list_struct within the struct.
  6.   */
  7. #define list_entry(ptr, type, member) \
  8.        container_of(ptr, type, member)
  1. #ifndef container_of
  2. /**
  3.  * container_of - cast a member of a structure out to the containing structure
  4.  * 
  5.  * @ptr: the pointer to the member.
  6.  * @type: the type of the container struct this is embedded in.
  7.  * @member: the name of the member within the struct.
  8.  *
  9.  */
  10. #define container_of(ptr, type, member) ({ \
  11.          const typeof( ((type *)0)->member ) *__mptr = (ptr); \
  12.           (type *)( (char *)__mptr - offsetof(type,member) );})
  13. #endif
list_entry主要是实现从内层结构体返回到外层结构体,就拿上面的struct stu
结构体来说吧,现在假如知道struct stu内部list结构体的指针pos,如何来获得
该结构体对应的struct stu的结构体指针?这儿主要是使用container_of这个宏
来实现的,该宏主要是用了一个技巧,主要是求出内层结构体字段到外层的偏移量,计算的方式是,将外层结构体的地址设置为0,然后就有,该字段的地址 = 该字段到外层结构体的偏移量。

遍历的时候都是遍历pos,然后再由pos返回到pnode。然后就可以访问
strudent中的任何一个字段了。
4,链表的释放。
     由于链表的节点都是动态开辟的空间,必须要释放,不然容易造成内存泄漏。
 释放也是一个节点一个节点的释放,故也需要遍历链表。   

  1. /**
  2.  * list_for_each_safe - iterate over a list safe against removal of list entry
  3.  * @pos: the &struct list_head to use as a loop cursor.
  4.  * @n: another &struct list_head to use as temporary storage
  5.  * @head: the head for your list.
  6.  */
  7. #define list_for_each_safe(pos, n, head) \
  8.     for (pos = (head)->next, n = pos->next; pos != (head); \
  9.         pos = n, n = pos->next)
该遍历链表和一般的链表遍历不一样,这时候是释放整条链表,必须要重
新引入一个辅助指针n,如果使用list_for_each就会出错。
  1. static inline void __list_del(struct list_head * prev, struct list_head * next)
  2. {
  3.      next->prev = prev;
  4.      prev->next = next;
  5. }


  6. static inline void list_del(struct list_head *entry)
  7. {
  8.      __list_del(entry->prev, entry->next);
  9.      entry->next = LIST_POISON1;
  10.      entry->prev = LIST_POISON2;
  11. }
list_del将会将该节点与外界的“联系”切断,然后就可以使用free释放了
(如果是内核态就用kfree或vfree)。

最后将会只剩下头节点,再将头节点释放,将完成了整条链表的释放。

2015-04-18 21:26:10 zhuwenfeng215 阅读数 280
  • 4.9.链表&状态机与多线程

    本课程主要目标是让大家彻底全面的掌握链表这一数据结构。因为链表在内核驱动开发中属于很重要的基础技术,因此用十几节课来彻底深入讲解和实践。

    30925 人正在学习 去看看 朱有鹏

1. 内核链表是特殊的双向循环链表,链表节点如下:

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

2. 内核链表-函数

1. INIT_LIST_HEAD:   初始化链表
2. list_add:        在链表头插入节点
3. list_add_tail:   在链表尾插入节点
4. list_del:        删除节点
5. list_entry:      取出节点
6. list_for_each:   遍历链表

3. 内核链表实现分析

内核链表在头文件 <linux/list.h>中声明

struct score
{
	int num;<span style="white-space:pre">	</span>          /* 编号 */
	int english;              /* 英语成绩 */
	int math;                 /* 数学成绩 */
	struct list_head list;    /* 链表节点 */
};
上面结构中,score中的list.next指向下一个链表节点,list.prev指向前一个链表节点

问题是我们怎样通过链表节点找到父结构中包含的其他变量呢?

/**
 * 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_struct within the struct.
 */
#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:	the pointer to the member.
 * @type:	the type of the container struct this is embedded in.
 * @member:	the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({			\
	const typeof(((type *)0)->member) * __mptr = (ptr);	\
	(type *)((char *)__mptr - offsetof(type, member)); })

在c语言中,一个给定结构中的变量偏移在编译时就已经固定下来了,使用宏container_of()可以方便的从链表指针找到父结构中包含的变量

深入理解container_of:   typeof是GNU C对标准C的扩展,作用是获取变量的类型,(type*)0;就是假设地址0处存放的是一个type类型的结构体变量,这一行是先取member的类型,然后再定义一个此类型的指针变量_mptr,并将链表节点的指针变量赋值给它,使用const是确保此处定义的指针变量不会被修改,增强了安全性。

(char*)_mptr转化成字节型指针,(char*)_mptr-offsetof(type,member)用来求出结构体的起始地址,然后又被转化为type*类型。

再来分析offsetof,这也是一个宏 :

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
&((TYPE*)0->MEMBER)就是MEMBER的地址,而基地址已设为0,这样转化为了TYPE的偏移量,然后强制转化为tsize_t类型,即int型。

4.内核链表的使用

#include <linux/module.h>
#include <linux/init.h>
#include <linux/list.h>

struct score
{
	int num;
	int english;
	int math;
	struct list_head list;
};

struct list_head score_head;
struct score stu1,stu2,stu3;
struct list_head *pos;
struct score *tmp;

int mylist_init()
{
	INIT_LIST_HEAD(&score_head);
	
	stu1.num = 1;
	stu1.english = 90;
	stu1.math = 98;
	list_add_tail(&(stu1.list),&score_head);
	
	stu2.num = 2;
	stu2.english = 92;
	stu2.math = 91;
	list_add_tail(&(stu2.list),&score_head);
	
	stu3.num = 3;
	stu3.english = 94;
	stu3.math = 95;
	list_add_tail(&(stu3.list),&score_head);
	
	list_for_each(pos,&score_head)
	{
		tmp = list_entry(pos,struct score,list);
		printk(KERN_WARNING"No %d,english is %d,math is %d\n",tmp->num,tmp->english,tmp->math);
	}
	return 0;
}

void mylist_exit()
{
	list_del(&(stu1.list));
	list_del(&(stu2.list));
}

module_init(mylist_init);
module_exit(mylist_exit);

运行结果:
# insmod mylist.ko 
No 1,english is 90,math is 98
No 2,english is 92,math is 91
No 3,english is 94,math is 95


2017-09-23 19:18:11 sugar__26 阅读数 423
  • 4.9.链表&状态机与多线程

    本课程主要目标是让大家彻底全面的掌握链表这一数据结构。因为链表在内核驱动开发中属于很重要的基础技术,因此用十几节课来彻底深入讲解和实践。

    30925 人正在学习 去看看 朱有鹏

一、常规链表的缺陷:
第一:每一种节点都是特殊的,导致每一条链表都是特殊的,因此 每一种链表的增删查改也都是特殊的。注意:特殊的不具备有通用性
第二:当每一种节点处于变化的数据结构网络中时,节点指针无法指向稳定不变的节点。

比如:有一千多个节点,那么用常规的链表来插入就需要一千种插入函数。

二、常规链表和内核链表的区别:
1.一个包含数据域(常规链表),一个不包含数据域(内核链表)。
三、内核链表
内核链表的原理:
1.将链表结构抽象出来:去除节点中的具体数字,只保留表示逻辑的双向指针。形成一条只包含逻辑的“纯粹的链表”。
2.将此链表“寄宿”于具体数据的节点之中,使之贯穿于这些节点。
3.统一内核链表的操作接口
①初始化链表:INIT_LIST_HEAD()/LIST_HEAD_INIT()
②插入节点:list_add()/list_add_tail()
③删除节点:list_del()/list_del_init()
④移动节点:list_move()/list_move_tail()
⑤合并链表:list_splice()
⑥后向遍历:list_for_each()/list__for_each_safe()/list__for_each_entry()
⑦前向遍历:list_for_each_prev()/list__for_each_entry_prev()
⑧判断是否为空:list_empty()
⑨取得宿主节点指针:list_entry()
4.图形加深认识
这里写图片描述这里写图片描述
5.下面通过内核链表的源码做一项练习:奇偶分离。
项目:

#include <stdio.h>
#include <stdlib.h>
#include "commonheader.h"
#include "list.h"

//一个具体的“大结构体”
struct node
{
    int data;

    //只包含链表逻辑的“小结构体”
    struct list_head root;
};

//初始化
struct node *init_list(void)
{
    struct node *head = malloc(sizeof(struct node));   //头节点

    if(head != NULL)
    {
        INIT_LIST_HEAD(&head->root);         //引用"list.h" 让 list 指向自己
    }
    //这里就是头指针指向一个空链表
}

//创建新的节点
struct node *new_node(int data)
{
    struct node *new = calloc(1, sizeof(struct node));
    if(new != NULL)
    {
        new->data = data;   
    }
    return new;
}

//显示
void show(struct node *head)
{
    struct list_head *pos;                     //小结构体的指针 
    struct node *p;

    list_for_each(pos, &head->root)           //这个在内核链表宏定义是一个for循环
    {
        p = list_entry(pos, struct node, root);      //这个是从小结构体获得大结构体的指针
        printf("%d\t", p->data);
    }
    printf("\n");
}

//奇偶排列
void rearrange(struct node *head)
{
    struct list_head *pos, *tmp;
    struct node *p;

    int flag = 0;

    list_for_each_prev(pos, &head->root)             //不断的往前找
    {
        p = list_entry(pos, struct node, root);
        if(p->data % 2 == 0 && flag != 0)         // flag != 0 避免一开始就是偶数
        {
            list_move_tail(pos, &head->root);    //将 p 指向的节点移动到头节点之前
            pos = tmp;                           //如果是偶数  保存这个位置                  
        }
        else
        {
            tmp = pos;                       //如果是奇数 保存这个位置  防止出现死循环
        }
        flag = 1;   
    }

}

int main(int argc, char **argv)
{
    struct node *head;
    head = init_list();

    //获得用户输入的数据
    int n;
    scanf("%d", &n);

    int i;
    for(i = 0; i <= n; i++)
    {
        struct node *new = new_node(i);

        //注意:
        //1. new 和 head 是指向大结构体的
        //2.我们操作的是小结构体(也就是内核链表)
        //3.所以要加上取地址符号 & 
        list_add_tail(&new->root, &head->root);
    }

    show(head);

    rearrange(head);
    show(head);
    return 0;
}

运行结果:
这里写图片描述

2019-03-19 00:38:59 qq_24622489 阅读数 224
  • 4.9.链表&状态机与多线程

    本课程主要目标是让大家彻底全面的掌握链表这一数据结构。因为链表在内核驱动开发中属于很重要的基础技术,因此用十几节课来彻底深入讲解和实践。

    30925 人正在学习 去看看 朱有鹏

这里写自定义目录标题


Linux内核链表的核心思想是:在用户自定义的结构A中声明list_head类型的成员p,这样每个结构类型为A的变量a中,都拥有同样的成员p,如下:

struct A{

int property;

struct list_head p;

}

其中,list_head结构类型定义如下:

struct list_head {

struct list_head *next,*prev;

};

list_head拥有两个指针成员,其类型都为list_head,分别为前驱指针prev和后驱指针next。

假设:

(1)多个结构类型为A的变量a1…an,其list_head结构类型的成员为p1…pn

(2)一个list_head结构类型的变量head,代表头节点

使:

(1)head.next= p1 ; head.prev = pn

(2) p1.prev = head,p1.next = p2;

(3)p2.prev= p1 , p2.next = p3;

(n)pn.prev= pn-1 , pn.next = head

以上,则构成了一个循环链表。

因p是嵌入到a中的,p与a的地址偏移量可知,又因为head的地址可知,所以每个结构类型为A的链表节点a1…an的地址也是可以计算出的,从而可实现链表的遍历,在此基础上,则可以实现链表的各种操作。

下面是从linux内核中移植出来的简单链表,list.h和list.c:

list.h:


#ifndef _INIT_LIST_H_
#define _INIT_LIST_H_
 
#ifndef offsetof
/* Offset of member MEMBER in a struct of type TYPE. */
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
 
struct listnode
{
    struct listnode *next;
    struct listnode *prev;
};
 
#define node_to_item(node, container, member) \
    (container *) (((char*) (node)) - offsetof(container, member))
 
#define list_declare(name) \
    struct listnode name = { \
        .next = &name, \
        .prev = &name, \
    }
 
#define list_for_each(node, list) \
    for (node = (list)->next; node != (list); node = node->next)
 
#define list_for_each_reverse(node, list) \
    for (node = (list)->prev; node != (list); node = node->prev)
 
void list_init(struct listnode *list);
void list_add_tail(struct listnode *list, struct listnode *item);
void list_remove(struct listnode *item);
 
#define list_empty(list) ((list) == (list)->next)
#define list_head(list) ((list)->next)
#define list_tail(list) ((list)->prev)

#endif

list.c:


#include "list.h"
 
void list_init(struct listnode *node)
{
    node->next = node;
    node->prev = node;
}
 
void list_add_tail(struct listnode *head, struct listnode *item)
{
    item->next = head;
    item->prev = head->prev;
    head->prev->next = item;
    head->prev = item;
}
 
void list_remove(struct listnode *item)
{
    item->next->prev = item->prev;
    item->prev->next = item->next;
}

测试代码list_test.c:

#include<stdio.h>
#include<stdlib.h>
#include "list.h"
 
#define STUDENT_FREE_MEMORY
 
//声明链表节点
typedef struct {
	int id;
	char *name;
	struct listnode _list;
}student;
 
//遍历函数指针
typedef void (*student_foreach_fun)(student *stu,void *data);
 
 
//声明链表
static list_declare(student_list);
 
//添加节点
int student_add(struct listnode *list,student *stu)
{
	list_init(&stu->_list);
	list_add_tail(list,&stu->_list);	
}
 
//删除节点,释放节点空间
int student_del(struct listnode *list,int id)
{
	struct listnode *node;
	student *stu;
	list_for_each(node,list){
		stu = node_to_item(node,student,_list);
		if(id == stu->id){
			printf("list_del, id:%d,name:%s\n",stu->id,stu->name);
			list_remove(node);
#ifdef STUDENT_FREE_MEMORY	
			//释放节点空间
			free(stu);
			stu = NULL;
#endif
			return 1;
			
		}
		
	}
 
	return 0;
}
 
//节点遍历
void student_foreach(struct listnode *list,student_foreach_fun fun,void *data)
{
	struct listnode *node;
	student *stu;
	list_for_each(node,list){
		stu = node_to_item(node,student,_list);
		fun(stu,data);
	}
 
}
 
//打印节点信息
void student_print(student *stu,void *data)
{
	printf("id:%d,name:%s\n",stu->id,stu->name);
}
 
int main()
{
	int i,len;
	student *stu;
	char *stu_name[]={"tonny","andy","michael","leslie","john"};
	
	
	len = sizeof(stu_name)/sizeof(stu_name[0]);
	//添加节点
	for(i=0;i<len;i++){
		stu = calloc(1,sizeof(student));
		stu->id = i + 1;
		stu->name = stu_name[i];
 
		student_add(&student_list,stu);
	}
 
	//打印所有节点
	student_foreach(&student_list,student_print,(void *)0);
	
	//删除节点
	student_del(&student_list,1);
	student_foreach(&student_list,student_print,(void *)0);
 
	//删除节点
	student_del(&student_list,5);
	student_foreach(&student_list,student_print,(void *)0);
	
	return 0;
	
 
}

Makefile:


TARGET=list_test
SRC=list_test.c list.c
#SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
CFLAGS=-g -Wall -o
 
$(TARGET):$(SRC)
	gcc $(SRC) $(CFLAGS) $(TARGET)
clean:
	rm $(OBJ) $(TARGET)


作者:xnwyd
来源:CSDN
原文:https://blog.csdn.net/xnwyd/article/details/7359373

Linux内核链表使用

阅读数 211

没有更多推荐了,返回首页