Linux 驱动之内核空间分配内存

【摘要】内核空间和用户空间其实有很大的不同 。c语言的时候应该学过,从用户空间看,每个进程都傻乎乎的以为自己有4G的内存空间,其中位于高地址(3G-4G)的1G空间给内核用,另外的3G0-3G)都是它一个人独占的。所以用户空间很慷慨的把3G的空间分了好几个区域,如堆、栈、代码段等。其中,malloc()分配的空间位于堆,而程序中的自动变量,如你在函数内定义的“inti”,它是放在栈上,同时。用户空间的栈是可变栈,即随着数据的增多,对应函数的栈空间也会增多。跟每个用户空间的进程不一样,内核只有1G的空间,同时,除了自己本身有进程运行外,内核还要允许用户空间进程调用系统调用进入内核空间去执行。所以,内核对此相当吝啬,它规定在内核中的每个进程都只有4KB8KB32位下)的定长栈。出于这样的原因,大的数据结构就不能在栈中分配,只能请求内核分配新的空间来存放数据,如函数kmalloc()


一、内存的基本单位


在介绍分配内存空间的函数前,我们还要了解一下内存是怎么被划分的。


内核不仅知道用户空间中看到的1G内核空间是假的,它还知道实际的物理内存是多少(我的开发板是64M)。所以,内核的其中一个任务就是,当这段虚假内存中的数据需要调用时,内核把这段虚拟内存与实际的物理内存对应上,运行完后又把两段内存的对应关系撤销掉给另外的虚拟内存用。


既然知道虚拟内存与物理内存的关系,那它们是怎么对应的,难道是一个一个字节?如果这样子做的话内核肯定觉得崩溃。


页是内存管理的基本单位。内存管理器(MMU,用于虚拟地址与物理地址之间的转换)通常以页为单位进行出来。页是内存管理的最小单位。在32位的系统中,一页的大小为4KB。所以,64M的物理内存将被分为16384个页。每一个物理页对应地用一个structpage来维护,注意,该结构体是用来维护物理页,而不是虚拟也,结构体记录该页是否被使用,对应的虚拟地址是多少等信息。


由于内存访问的限制,内核又把内存分成了3个区。

如有些硬件的访问只能在24位的地址空间寻址,出于这总访问限制,linux把前16MB划分为ZONE_DMA——用于直接内存访问(MDA)。

x86体系里,高于896M的内存空间称为高端内存,这段内存区域的页和普通的内存页操作后有差异,这段区域划分为ZONE_HIGHMEM

剩下的,加载这两段区域之间的就是我们平时用的普通内存区域——ZONE_NORMAL


这这里要注意一下:

1)这些分区是指linux自己分的,当然,如果普通分区不够用,当然也可以占用其他区的空间。

2)分区的大小是根据体系结构而定的,一般的ARM下,ZONE_NORMAL就是所有的可用内存区域。


二、分配内存时使用的标记gfp_mask


在讲如何分配内存之前,先讲一下分配内存时将会用到的gfp_mask。简单地讲,这个标记指定了分配内存时的要求。具体分三类:

行为修饰符:表示内核应当如何分配内存,如指定不能休眠等。

区修饰符:指定内存将要分配到上面讲的三个区中的哪一个。

类型标记:这包含了上面两种修饰符(或运算),这些标记是为了让用户更好地去使用。


标记有很多,我这里不一一介绍,需要的可以自己查阅《linux内核设计与实现(第三版)》P238页。这里我讲两个常用的类型标记:

1GFP_KERNEL最常用的标记,用于可睡眠的进程上下文。

2GFP_ATOMIC使用了这个标记,内存分配函数不会引起随眠

3GFP_USER当需要给用户空间分配内存空间时使用该标记。


三、分配内存的第一种方法——按页分配


这是内核提供的一种请求内存的底层机制,都是以页为单位分配内存。以下函数包含在<linux/gfh.h>

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

struct page *p;

char *s;

static int __init test_init(void) //模块初始化函数
{

	unsigned long virt, phys;

	#define SWITCH 0 //通过定义这个来切换校验这两种不同的方法
	
#if SWITCH

//alloc 2 pages

	p = alloc_pages(GFP_KERNEL, 1);

	if (NULL == p){ 	//必须检验错误

		printk("alloc page error!\n");

		return - ENOMEM;
	}

	s = page_address(p);

#else

		s = (char *)__get_free_pages(GFP_KERNEL, 1);

		if (NULL == s){

		printk("alloc page error!\n");

		return - ENOMEM;

	}

#endif

	phys = __pa((unsigned long)s); //通过虚拟地址获得对应的物理地址
	virt = (unsigned long)__va(phys); //通过物理地址获得对应的虚拟地址
	printk("<p->virtual, s>[%p]\n", s); //打印获得的虚拟地址
	printk("<phys>[%p]\n", (void *)phys); //打印对应的物理地址
	printk("<virt>[%p]\n", (void *)virt); //再打印虚拟地址,其实就是分配函数返回的地址
	memcpy(s, "hello Linux world!", 20);
	printk("hello Linux world!\n");
	return 0;

 }



static void __exit test_exit(void) //模块卸载函数
{
#if SWITCH

		__free_pages(p, 1);
#else

		free_pages((unsigned long)s, 1);

#endif
	
	printk("good bye kernel\n");

}

 module_init(test_init);
 module_exit(test_exit);

 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("ZP1015");
 MODULE_VERSION("2.6.3"); 
insmod allocpages.ko

dmesg

[ 5437.186462] <p->virtual, s>[e7a40000]
[ 5437.186465] <phys>[27a40000]
[ 5437.186466] <virt>[e7a40000]
[ 5437.186468] hello Linux world!

四、分配内存的第二种方法——kmalloc()

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

char *s;

static int __init test_init(void) //模块初始化函数
{
	s = kmalloc(20, GFP_KERNEL);
	memcpy(s, "hello mm", 20);

	printk("hello kernel [%s]\n", s);
	return 0;
}

static void __exit test_exit(void) //模块卸载函数
{
	kfree(s);
	printk("good bye kernel\n");
}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZP1015");



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狂奔的乌龟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值