linux驱动开发要知道的那些知识(二)------list内核链表

内核链表

链表数据结构简介

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

通常链表数据结构至少应包含两个域:数据域和指针域,数据域用于存储数据,指针域用于建立与下一个节点的联系。按照指针域的组织以及各个节点之间的联系形式,链表又可以分为单链表、双链表、循环链表等多种类型,下面分别给出这几类常见链表类型的示意图:

1. 单链表

图1 单链表
单链表是最简单的一类链表,它的特点是仅有一个指针域指向后继节点(next),因此,对单链表的遍历只能从头至尾(通常是NULL空指针)顺序进行。

2. 双链表

通过设计前驱和后继两个指针域,双链表可以从两个方向遍历,这是它区别于单链表的地方。如果打乱前驱、后继的依赖关系,就可以构成"二叉树";如果再让首节点的前驱指向链表尾节点、尾节点的后继指向首节点(如图2中虚线部分),就构成了循环链表;如果设计更多的指针域,就可以构成各种复杂的树状数据结构。

在<linux/list>

struct list_head{

struct list_head *next,prev;

}

list_head结构包含两个指向list_head结构的指针prev和next,由此可见,内核的链表具备双链表功能,实际上,通常它都组织成双循环链表。

和第一节介绍的双链表结构模型不同,这里的list_head没有数据域。在Linux内核链表中,不是在链表结构中包含数据,而是在数据结构中包含链表节点。

下图为数据结构中链表和内核链表区别:

基于topeet 4412开发板 代码示例:

***********************************************************

#include <linux/init.h>

#include <linux/module.h>

#include <linux/list.h>

#include <linux/slab.h>

#include <linux/fs.h>

MODULE_LICENSE("Dua BSD/GPL");

#define STRUD_NUM 5

struct list_head strud_list;

struct strudent{

unsigned char name[20];

unsigned long int str_id;

unsigned int      mark;

struct list_head list;

};

static struct strudent *strude=NULL;

static struct strudent *strude_tmp=NULL;

static struct list_head *pos=NULL;

static int __init strud_init(void)

{

int i=0;

printk(KERN_INFO "Test list use");

INIT_LIST_HEAD(&strud_list);

strude=kmalloc( sizeof(struct strudent)*STRUD_NUM,GFP_KERNEL);

if(strude==NULL){

printk(KERN_ERR"strude kmalloc is fail");

return -ENOMEM;

}

printk(KERN_INFO"strude kmalloc is success");

memset(strude,0,sizeof(struct strudent)*STRUD_NUM);

for(i=0;i<STRUD_NUM;i++)

{

sprintf(strude[i].name,"strudent%d",i+1);

strude[i].str_id=2013021001+i;

strude[i].mark=70+i*2;

list_add(&strude[i].list,&strud_list);

}

list_for_each(pos,&strud_list){

strude_tmp=list_entry(pos,struct strudent,list);

printk(KERN_INFO"strudent name %s\t strudent ID %d\t strudent maske %d\n" );

}

return 0;

}

static int __exit strud_exit(void)

{

int i;

printk(KERN_INFO"entry module exit...");

for(i=0;i<STRUD_NUM;i++)

{

list_del(&(strude[i].list));

}

kfree(strude);

return 0;

}

module_init(strud_init);

module_exit(strud_exit);

MODULE_AUTHOR("Songmao");

MODULE_DESCRIPTION("List tast");

***********************************************************

printk的打印级别

#define KERN_EMERG        "<0>" /* system is unusable */

#define KERN_ALERT         "<1>" /* action must be taken immediately */

#define KERN_CRIT            "<2>" /* critical conditions */

#define KERN_ERR             "<3>" /* error conditions */

#define KERN_WARNING   "<4>" /* warning conditions */

#define KERN_NOTICE       "<5>" /* normal but significant condition */

#define KERN_INFO            "<6>" /* informational */

#define KERN_DEBUG       "<7>" /* debug-level messages */

5、printk函数的使用

printk(打印级别  “要打印的信息”)

打印级别  既上面定义的几个宏

Sprintf()格式化输出

INIT_LIST_HEAD

list_add(struct list_head *new,struct list_head *head)添加链表数据

list_for_each(pos,head){...} 遍历链表其实看内核就是一个for循环的宏

list_entry(ptr,type,member)  取链表中的偏差值

list_del(struct list_head *old)删除list节点

kmalloc申请后需要memset清零下,因为kmalloc申请时并没有把它清零,如果以前申请这块地址的释放时没清零那这块地址就还有数据,不清零可能导致你数据错误。可以使用kzalloc,kzalloc实现了kmalloc以及memset的功能,一个函数起到了两个函数的作用

/* 以下为摘录************************************************************************

kmalloc() 与 kfree()  和get_free_page的区别

1,用于申请较小的、连续的物理内存:使用的是内存分配器slab一小片。申请的内存位于物理内存的映射区域。其正真的物理地址只相差一个固定的偏移。

可以用这两个宏来简单转换 __pa(address)  {virt_to_phys()} 和  __va(address){phys_to_virt()}

get_free_page()申请的内存是一整页,一页的大小一般是128K。它们的区别只有这一点不同,其它的都相同。

本质上讲,kmalloc()和get_free_page()最终调用实现都是相同的,只不过在调用最终函数时所传的flag不同而以。

2. void *kmalloc(size_t size, int flags) 分配的内存物理地址上连续,虚拟地址上也是连续

3. gfp_mask标志:

情形                                                  相应标志

进程上下文,可以睡眠                  GFP_KERNEL

进程上下文,不可以睡眠               GFP_ATOMIC

中断处理程序                            GFP_ATOMIC

软中断                                    GFP_ATOMIC

Tasklet                                  GFP_ATOMIC

用于DMA的内存,可以睡眠         GFP_DMA | GFP_KERNEL

用于DMA的内存,不可以睡眠     GFP_DMA | GFP_ATOMIC

4. void kfree(const void *ptr)

释放由kmalloc()分配出来的内存块

******************************************/

时间: 2024-10-01 03:58:58

linux驱动开发要知道的那些知识(二)------list内核链表的相关文章

linux驱动开发要知道的那些知识(三)------container_of,定时器 及系统调用

container_of宏 定义: /** * 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. *

Linux驱动开发之 三 (那些必须要了解的硬件知识 之 存储器篇)

Linux驱动开发之 三 (那些必须要了解的硬件知识 之 存储器篇) 本文重点学习存储器相关的基本知识,网络上对RAM,ROM,FLASH等有非常详细的介绍,老谢将这些知识点摘抄整理并加以注释如下.这个整理的过程也是加深记忆的过程. 1.什么是内存 在计算机的组成结构中,有一个很重要的部分,就是存储器.存储器是用来存储程序和数据的部件,对于计算机来说,有了存储器,才有记忆功能,才能保证正常工作.存储器的种类很多,按其用途可分为主存储器和辅助存储器,主存储器又称内存储器(简称内存),辅助存储器又称

Linux驱动开发之 四 (那些必须要了解的硬件知识 之 串口)

Linux驱动开发之 四 (那些必须要了解的硬件知识 之 串口) 在前面的文章中,我们了解处理器.存储器,在这篇文章中老谢想和大家聊聊在实际项目开发过程中串口的基本知识和作用. 一.串口简介 如果要非常细致的了解串口的定义,直接问度娘是最快的方式.老谢在这里就不再赘述.大致描述如下: RS-232协议,相比RS-422,RS-285有着更为广泛的应用,特别是嵌入式开发过程中,应用极为广泛,可谓工程师的眼睛.工程师们靠UART tool了解系统的运行状态.调试以及处理相关问题,而这一系列的信息交互

Linux驱动开发之 六 (那些必须要了解的硬件知识 之 仪器篇)

Linux驱动开发之 六 (那些必须要了解的硬件知识 之 仪器篇) 一.前言 在之前的文章中,老谢已经分享了不少关于嵌入式系统开发过程中必须要了解的硬件知识.作为这一小节的结束(哎呀,终于要结束了),老谢还想和大家聊聊"仪器".本文中老谢不聊仪器的具体使用方法.原理等.只想聊点轻松的,老谢结合自己的实际工作,以图文结合的方式,简单聊聊工作中使用到的仪器. 实话实说,前几篇文章基本无技术含量,包括本文.老谢坚持写这几篇的用意是: Linux驱动开发,必须以此为基础: 通过写博客的方式,让

linux驱动开发重点关注内容--摘自《嵌入式Linux驱动模板精讲与项目实践》

本文摘自本人拙著 <嵌入式Linux驱动模板精讲与项目实践> 初步看起来Linux设备驱动开发涉及内容非常多,而须要实现驱动的设备千差万别.事实上做一段时间驱动之后回首看来主要就是下面几点: (1)对驱动进行分类.先归纳为哪个类型的驱动.归类正确再利用内核提供的子系统进行开发,往往会发现事实上非常多通用的事情内核已经帮我们做了,一个优秀的驱动project师应该最大程度上利用内核的资源.内核已经实现的毕竟稳定性强.可移植性高. (2)找到内核的提供的子系统.接下来就是要制作该子系统对该类设备提

【转】linux驱动开发的经典书籍

原文网址:http://www.cnblogs.com/xmphoenix/archive/2012/03/27/2420044.html Linux驱动学习的最大困惑在于书籍的缺乏,市面上最常见的书为<linux_device_driver 3rd Edition>,这是一本很经典的书,无奈Linux的东东还是过于庞大,这本侧重于实战的书籍也只能停留在基本的接口介绍上,更深入的东东只能靠我们自己摸索了.但万事总有一个开头,没有对Linux驱动整体框架的把握是很难做一个优秀的驱动开发者的.除了

驱动编程思想之初体验 --------------- 嵌入式linux驱动开发之点亮LED

这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的住,不然真像一些人说的,学了一年嵌入式感觉还没找到门. 不能再扯了,涉及到linux的驱动开发知识面灰常广,再扯文章就会变得灰常长.首先还是回到led驱动的本身上,自从linux被移植到arm上后,做驱动开发的硬件知识要求有所降低,很多都回归到了软件上,这是系统编程的一大特点,当然 ,也不排除有很多

嵌入式linux驱动开发之点亮led未遂(驱动编程思想之初体验)

有了上两篇文章的基础,我们就可以开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的住,不然真像一些人说的,学了一年嵌入式感觉还没找到门. 另外实践很重要,一年多以前就知道了arm,那时整天用单片机的思维去yy着arm,直到前段时间弄来一块arm板,烧上linux系统后才知道,坑呀!根本不是那回事,所以实践是学习计算机类最重要的基本素质,如果整天看书,那基本上

linux 驱动开发-模块的构建

1.模块的含义 linux 是采用模块化的方式构建的,允许内核在运行时动态地向其中插入或从中删除代码,这些代码(包扩函数,数据,模块入口函数,模块出口函数)被一并组合 在一个单独的二进制镜像,就是所谓的可装载内核模块. 模块可以是基本的内核镜像尽可能小,同时可以方便地对新功能进行调试,还可以实现热插拔(后续会学习如何实现设备的热插拔功能,暂时无需深究),和内核的核心子系统不一样,模块文件需要有入口点和出口点. 模块与应用程序的区别: a.模块和库函数类似,一个模块通常包含若干函数和数据,每个函数