模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。
#include <linux/module.h> //任何模块程序的编写都要包含这个头文件,它包含了对模块的结构定义以及版本控制
#include <linux/init.h> //包含了宏_init(指定初始化函数)和_exit(指定清除函数)
#include <linux/kernel.h> //这个头文件包含了常用的内核函数static int __init mmh_init(void) //__init函数将mmh_init()标记为初始化函数,在模块装载到内核时调用mmh_init()
{printk(KERN_EMERG"Hello Kernel!n"); //printk()函数由内核定义的,功能与C库中的printf()相近,它把打印的信息发到终端或系统日志printk(KERN_ALERT"Hello Kernel!n");printk(KERN_CRIT"Hello Kernel!n");printk(KERN_ERR"Hello Kernel!n");printk(KERN_WARNING"Hello Kernel!n");printk(KERN_NOTICE"Hello Kernel!n");printk(KERN_INFO"Hello Kernel!n");printk(KERN_DEBUG"Hello Kernel!n");return 0; //返回非0表示模块初始化失败,无法载入
}static void __exit mmh_cleanup(void) //mmh_cleanup()函数是模块退出和清理函数
{printk(KERN_ALART"Goodbye!Leaving n"); //在模块卸载前,将这句话打印到日志
}module_init(mmh_init); //模块的加载函数,当通过insmod命令加载模块时,模块的加载函数会自动被内核执行,完成本模块相关初始化工作
module_exit(mmh_cleanup); //模块的卸载函数,当通过rmmod命令卸载模块时,模块的卸载函数会自动被内核执行,注销由模块提供的所有功能
MODULE_LICENSE("GPL"); //模块许可证(GNU general public license),如果不声明,模块加载时将收到内核被污染的警告
#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 */
obj-m:=mmh.o //产生mmh模块的目标文件,在这句话中.o的文件名要与编译.c文件名一致
CURRENT_PATH:=$(shell pwd) //将模块源码路径保存在CURRENT_PATH中
LINUX_KERNEL:=$(shell uname -r) //将当前内核版本保存在LINUX_KERNEL中
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
all:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules //编译模块
clean:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean //清理
我们要知道,数组可以存放一组相同数据类型的数据,系统将为数组分配一片连续的内存空间。数组定义时必须指出数组的大小,但在实际应用中,大小有时很难准确给出,太大会造成系统浪费,太小又无法满足要求,要是能根据实际需要动态的申请空间,需要时就申请,使用完毕就归还给系统就好了。所以说可以利用C语言中动态申请空间malloc函数和释放空间free函数,并通过链表来实现,这里介绍一下malloc函数和free函数(使用这两个函数前要包含#include<stdlib.h>这个系统头文件)
void *malloc(unsigned int size);
功能:内存的动态存储区分配一块大小为“size”的连续内存空间,若内存有满足大小要求的内存空间,系统则返回一个指向该内存空间首地址的指针,若无法分配,则返回一个NULL指针。因此在调用这个函数时,可以通过判断返回的指针是否为NULL来判断申请空间是否成功。
void free(p);
功能:将指针p所指向的内存空间释放,归还给系统。系统的内存空间是有限的,不可能无限的分配下去,编写的程序应尽量节约系统资源,用完后及时释放空间,便于其他变量或程序使用,提高系统内存空间的利用率。
struct node
{int data; //数据部分struct node *next; //指针部分
};
#include <stdio.h>
#include <stdlib.h>struct node //定义结构体类型,类型名为node
{int data;struct node *next;
};struct node* creat() //creat函数实现构建链表,函数返回值类型为结构体指针型,将创建的表头结点head返回给主调函数
{struct node *head,*p,*s;int num,mark;if(head=(struct node*)malloc(sizeof(struct node)))==NULL) //head作为链表的表头结点,调用malloc函数申请node类型大小的空间,返回值赋给head,若返回值为NULL,表示申请失败,执行exit函数结束程序{printf("内存分配失败:n");exit(0);}head->data=0; //表头结点内容为0head->next=NULL; //表头指针置空p=head; //指针p指向head指针printf("请输入mark的值,申请空间输入1,不申请输入0:");scanf("%d",&mark);while(mark==1) //循环语句判断mark为1时表示用户需要申请空间{if(s=(struct node*)malloc(sizeof(struct node)))==NULL){printf("内存分配失败");exit(0);}printf("请输入结点的内容:");scanf("%d",&num);s->data=num; //为结点赋值s->next=NULL; //结点s的指针置空p->next=s; //将s与前一个指针相连p=s; //p指向该结点printf("请输入mark的值,申请空间置1,不申请置0:");scanf("%d",&mark);}return head; //返回表头结点指针
}int main(void)
{struct node *h,*p,*s;h=creat();printf("链表各结点的值依次是:n");p=h;while(p->next!=NULL) //遍历{s=p->next;printf("the number is ==>%dn",s->data);p=s;}
}
struct node
{int data;struct node *next;
};
struct node
{int data;struct node *prev;struct node *next;
};
循环链表
通常链表的最后一个元素不再有下一个元素,所以将链尾元素的后指针置为NULL,以说明它是最后一个元素,但在循环链表中,链表的末尾元素指向链表的首元素,并且在循环双链表中链表的首结点的前驱结点指向尾结点。
所以说循环双链表提供了最大的灵活性,所以它就成为了linux内核的标准链表。
关于list_head
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= p1 ; head.prev = pn
(2) p1.prev = head = p2;
(3)p2.prev= p1 , p2.next = p3;
…
(n)pn.prev= pn-1 , pn.next = head
以上,则构成了一个循环链表。
因p是嵌入到a中的,p与a的地址偏移量可知,又因为head的地址可知,所以每个结构类型为A的链表节点a1…an的地址也是可以计算出的,从而可实现链表的遍历,在此基础上,则可以实现链表的各种操作。
#include <linux/init.h> //包含了宏_init(指定初始化函数)和_exit(指定清除函数)
#include <linux/kernel.h> //这个头文件包含了常用的内核函数
#include <linux/module.h> //任何模块程序的编写都要包含这个头文件,它包含了对模块的结构定义以及版本控制
#include <linux/slab.h> //包含了kmalloc等内存分配函数的定义
#include <linux/list.h> //linux内核通用链表
struct mmh_list
{int num;struct list_head list;
};
struct mmh_list headnode;
INIT_LIST_HEAD(&headnode.list);
for(i=0;i<3;i++)
{listnode=(struct mmh_list *)kmalloc(sizeof(struct mmh_list),GFP_KERNEL);listnode->num=i+1;list_add_tail(&listnode->list,&head.list);
}
其中kmalloc是个功能强大且高速的工具,所分配到的内存在物理内存中连续且保持原有的数据。
原型是:
#include <linux/slab.h>
static inline void *kmalloc(size_t size, int flags)
其中static表示这个函数为静态函数,即对函数作用域的一种限制,也就是说函数的作用域仅限于本文件,inline就是编译程序在调用这个函数时就立即展开这个函数。
而GFP_KERNEL 是最常用的标志,意思是这个分配代表运行在内核空间进程运行。内核正常分配内存。当空闲内存较少时,可能进入休眠来等待一个页面。当前进程休眠时,内核会采取适当的动作来获取空闲页。所以使用 GFP_KERNEL 来分配内存的函数必须是可重入,且不能在原子上下文中运行。
#define list_for_each(pos, head) for (pos = (head)->next, prefetch(pos->next); pos != (head); pos = pos->next, prefetch(pos->next))
但这种遍历仅仅是找到一个个结点在链表中的偏移位置pos,关键是要通过pos获得结点的起始地址,从来可以引用结点中的域,于是list.h中定义了list_entry()宏:
#define list_entry(ptr, type, member) container_of(ptr, type, member)
这里的container_of,它定义在linux/kernel.h中,其源码如下:
#define container_of(ptr, type, member) ({ const typeof( ((type *)0)->member ) *__mptr = (ptr); (type *)( (char *)__mptr - offsetof(type,member) );})
这里简明的说一下:
const typeof( ((type *)0)->member ) *__mptr = (ptr);
typeof是GNU C对标准C的扩展,它的作用是根据变量获取变量类型。typeof获取了type结构体的member成员的变量类型然后定义了一个指向该变量类型的指针_mptr,并将实际结构体该成员变量的指针的值赋给 _mptr,从而临时变量 _mptr中保存了type结构体成员member在内存中分配的地址值。
(type *)( (char *)__mptr - offsetof(type,member) );
这块代码是利用_mptr的地址,减去type结构体成员变量member相对于结构体首地址的偏移地址,得到的自然是结构体首地址,即该结构体指针。
i=1;
list_for_each_safe(pos,n,&headnode.list)
{list_del(pos);p=list_entry(pos,struct mmh_list,list);kfree(p);
}
这里不调用list_for_each()宏而调用list_for_each_safe()进行删除前的遍历。
删除函数的源码如下:
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->next = LIST_POISON1;entry->prev = LIST_POISON2;
}
可以看出,当执行删除操作时,被删除的结点的两个指针指向一个固定的位置。而list_for_each(pos,head)中的pos指针在遍历过程中向后移动,即pos=pos->next,如果执行了list_del()操作,pos将指向这个固定位置的next,prev,而此时的next,prev没有任何指向,必然出错,而list_for_each_safe(pos,n,head)宏解决了上面的问题:
#define list_for_each_safe(pos, n, head) for (pos = (head)->next, n = pos->next; pos != (head); pos = n, n = pos->next)
它采用一个同pos同样类型的指针n来暂存将要被删除的结点指针pos,从而使得删除操作不影响pos指针。
本文发布于:2024-02-04 10:53:31,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170705525954927.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |