内核内存分配函数
-
函数名 kmalloc
头文件<linux/slab.h>函数原型: void *kmalloc(size_t size,int flags)
参数:
(1)size:需要分配的内存大小
(2)flag:分配标志,它控制 kmalloc 的行为
GFP_ATOMIC:用来在进程上下文中之外的代码(包含中断)中分配内存,从不睡眠
GFP_KERNEL:在进程上下文中分配,可能会睡眠(16M-896M)
_GFP_DMA:要求分配能够DMA的内存区(在16M以下的页帧)
_GFP_HIGHMEM:分配内存位于高端内存(896M以上)
**按页分配:**如果模块需要分配大块内存,那么使用面向页的分配技术(一页的大小:4K)
- get _zeroed_page(unsigned int flags)
返回指向新页面的指针,并将页面清零 - __get _free_page(unsigned int flags)
返回指向新页面的指针,但是页面不清零 - __get _free_pages(unsigned int flags,unsigned int older)
分配若干连续页面,返回指向该内存区域的指针,但不清零这段内存区域
在分配完内存后,使用完之后,需要释放所分配的内存区域
内存释放函数:
- void free_page(unsigned long addr)
- void free_pages(unsigned long addr,unsigned long order)
永久内存映射区
对于896MB以上的高端内存,可以使用该区域来访问
方法;
- 使用 alloc_page(_GFP_HIGHMEM) 来分配高端内存
- 使用 kmap 函数将分配到的高端内存,映射到该区域
内核链表
链表在C语言中对于大家来说都不陌生:
*定义
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作。
而在linux内核中也有链表的数据结构,在linux内核中使用大量的链表结构来组织来数据,他与传统链表的区别是:
头文件: linux/list.h
链表数据结构定义:
struct list_head
{
struct list_head *next,*prev;
}
内核链表具有 双向链表 的功能:实际上是:双向循环链表
linux内核中提供的链表操作主要有:
-
初始化链表头
INIT_LIST_HEAD(list_head *head) -
插入节点
list_add(struct list_head *new,struct list_head *head)
list_add_tail(struct list_head *new,struct list_head *head) //尾插 -
删除节点
list_del(struct list_head *entry) -
提取数据结构
list_entry(ptr,type,member) 返回指针
参数:
ptr : 指向 list_head 的指针
type:类型
member:成员名 -
遍历:
list_for_each(struct list_head *pos,struct list_head *head)
参数 head 为遍历的链表头,pos 为位置
内核链表的实现举例:
#include <linux/module.h>
#include <linux/list.h>
#include <linux/slab.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");
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()
{
int i = 0;
INIT_LIST_HEAD(&student_list);
pstudent = kmalloc(sizeof(struct student)*5,GFP_KERNEL);
memset(pstudent,0,sizeof(struct student)*5);
for(i = 0; i < 5; i++)
{
sprintf(pstudent[i].name,"Student%d",i+1);
pstudent[i].num = i+1;
list_add(&(pstudent[i].list),&student_list);
}
list_for_each(pos,&student_list)
{
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()
{
int i;
for(i = 0; i < 5; i++)
{
list_del(&(pstudent[i].list));
}
kfree(pstudent);
}
module_init(mylist_init);
module_exit(mylist_exit);
然后编写相应的 Makefile
ifneq ($(KERNELRELEASE),)
obj-m := list.o
else
KDIR := /home/zhangbin/mini6410/linux-2.6.38
all:
make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-
clean:
rm -f *.ko *.o *.mod.c *.symvers
endif
之后把生成 .ko 文件拷贝到开发板(这里用到的开发板是 mini6410 )中,之后通过指令
insmod list.ko
把链表模块安装到内核中去。
演示效果如下:
内核定时器
介绍:内核定时器被组织成双向链表,并使用 struct timer_list 结构
定时器用于控制某个函数(定时器处理函数)在未来的某个特定时间执行。内核定时器注册的处理函数只执行一次–不是循环执行。
在介绍定时器之前先介绍几个概念:
度量时间差
时钟中断由系统的定时硬件以周期性的时间间隔产生,这个间隔(即频率)由内核根据HZ来确定,HZ是一个与体系结构无关的常数,可以配置(50-1200),在x86平台,默认值1000
每当时钟中断发生时,全局变量 jiffies(unsigned long)加1,因此 jiffies 记录了自linux启动后时钟中断发生的次数。驱动程序常常利用jiffies来计算不同事件今年的时间间隔。
延时执行
如果对延时的精度要求不高,最简单的实现方法如下—忙等待
unsigned long j = jiffies + jit_delay*HZ;
while(jiffies < j)
{
// do nothing
}
struct timer_list
{
struct list_head entry; //内核使用
unsigned long expires; //超时的jiffies值
void (*function)(unsigned long ); //超时处理函数
unsigned long data; //超时处理函数参数
struct tvec_base *base; //内核使用
}
一些 相关的函数*
- void init_timer(struct timer_list *timer) .
初始化定时器队列结构 - void add_timer(struct timer_list *timer)
启动定时器 - void del_timer(struct timer_list *timer)
在定时器超时前将它删除,定时器超时后,系统会自动将它删除
内核定时器代码实现:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <asm/uaccess.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhangbin");
MODULE_DESCRIPTION("Timer Module");
MODULE_ALIAS("timer module");
struct timer_list timer;
void timer_function(int para)
{
printk("<0>Timer Expired and para is %d !!\n",para);
}
int timer_init()
{
init_timer(&timer);
timer.data = 5;
timer.expires = jiffies + (3 * HZ); //2表示2s后执行处理函数
timer.function = timer_function;
add_timer(&timer);
return 0;
}
void timer_exit()
{
del_timer(&timer);
}
module_init(timer_init);
module_exit(timer_exit);
同样需要编写 Makefile 如下:
ifneq ($(KERNELRELEASE),)
obj-m := timer.o
else
KDIR := /home/zhangbin/mini6410/linux-2.6.38
all:
make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-
clean:
rm -f *.ko *.o *.mod.c *.symvers
endif
执行 make 生成 timer.ko 文件,将timer.ko 文件拷贝到开发板(mini6410)中进行测试
命令: insmod timer.ko
执行效果如下: