linux内核内存分配的方法
用户变量:用户空间定义的变量,地址为前3G内核变量:内核空间定义的变量,地址为后1G
1.1 kmalloc / kfree
void *kmalloc(size_t size, gfp_t flags)
函数功能:
1.内核空间分配内存;
2.从内核1G的直接内存映射区中分配内存
3.物理地址和虚拟地址都是连续的(一一映射)
参数:
size:要分配内存的大小
最小为32字节
最大4M (2.6.20之前内核最大128K)
flags:指定分配内存时的行为
GFP_KERNEL:告知内核,请努力把内存分配成功,如果内存不足,会进行休眠操作,
等待空闲内存出现,不能用于中断上下文。
GFP_ATOMIC:如果内存不足,不会进行休眠操作,可以用于中断上下文;
返回值:保存分配的虚拟内存的首地址
例如:
void *addr;
addr = kmalloc (0x100000, GFP_KERNEL);
void kfree(void *addr);
功能:释放内存
addr:分配的内存的首地址
1.2 __get_free_pages/free_pages
明确:linux系统,一页为4K
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
函数功能:
1.内核空间分配内存
2.从直接内存映射区分配内存
3.物理内存和虚拟内存都连续
4.大小范围:
最小为1页;
最大为4M;
参数:
gfp_mask:指定分配内存时的行为
GFP_KERNEL:
告知内核,请努力把内存分配成功,如果内存不足,会进行休眠操作,等待空闲内存出现,不能用于中断上下文
GFP_ATOMIC:
如果内存不足,不会进行休眠操作,可以用于中断上下文;
order:
order = 0,分配1页
order = 1, 分配2页
order = 2, 分配4页
order = 3, 分配8页
... ...
函数返回值:保存分配的内核虚拟内存的首地址
void free_pages(unsigned long addr, int order);
功能:释放内存
addr:首地址
order:大小
例如:
unsigned long addr;
addr = __get_free_pages(GFP_KERNEL, 4);
//注意addr数据类型的转换
free_page(addr, 4);
1.3 .vmalloc/vfree
void *vmalloc (int size)
功能:
1.从 动态内存映射区分配内存
2.大小理论上默认120M
3.虚拟上连续,物理上不一定连续
4.如果内存不足,会导致休眠,不能用于中断上下文
参数:
size:分配内存的大小
返回值:保存分配的内核虚拟内存的首地址
void vfree (void *addr);
释放内存
例如:
void *addr;
addr = vmalloc (0x200000);
vfree (addr);
经验:一般内核分配内存都是在驱动的入口函数,先把内存分配到手!
1.4.定义全局数组
static char buf[5*1024*1024];
说明:
在BSS段中,BSS段的内容不会影响到可执行文件的大小;
或者:
static char buf[5*1024*1024] = {0xaa};
//如果程序不访问使用
说明:
在数据段中,数据段的内容会影响到可执行文件的大小,但是由于程序没有访问这个数组,编译器进行优化,最终数组的大小不会影响到可执行文件的大小;
或者
static char buf[5*1024*1024] = {0xaa};
//如果程序访问使用
说明:
程序访问,并且在数据段中,最终可执行文件的大小超过5M,最终会影响到ko文件的加载速度;
课下:百度搜索:__attribute__ used unused
1.5.在内核启动参数中,添加vmalloc=250M,表明内核启动时,动态内存映射区的大小有默认的120M修改为250M (从直接内存映射区抢)
setenv bootargs root=/dev/nfs ... vmalloc=250M
save
重启
查看内核打印信息,看动态内存映射区的大小
1.6.在内核启动参数中,添加mem=8M(例子,5M 10M都可以),表明告诉内核,物理内存的最后8M要预留,
内核无需进行管理,将来驱动可以单独使用最后的8M物理内存;
将来驱动利用大名鼎鼎的ioremap函数将后面8M物理内存进行映射即可访问!
setenv bootarg root=/dev/nfs ... mem=8M
ioremap函数
明确:在内核空间不允许直接访问设备的物理地址,需要访问对应的虚拟地址;void *ioremap(unsigned long phys_addr, int size);
函数功能:
1.将物理地址映射到内核空间的虚拟地址上
2.将物理地址映射到 动态内存映射区的虚拟地址上;
将来访问映射的虚拟地址就是在访问对应的物理地址
参数:
phys_addr:设备的起始物理地址
size:设备的"物理内存"大小
"物理内存":不单单指内存,还指例如GPIO对应的硬件寄存器对应的存储空间;
返回值:保存映射的虚拟内存的首地址
如果不再使用,记得要解除地址映射关系:
void iounmap(void *addr)
功能:
解除物理地址和虚拟地址的映射关系
参数:
映射的虚拟内存的首地址
例如:
GPC0_3,GPC0_4两个硬件GPIO对应的寄存器分别为:
GPCCON:配置寄存器
GPCDATA:数据寄存器
明确:寄存器对于CPU来说是一种外设,所以CPU要访问,必须要获取寄存器的地址,分别:
GPCCON:0xE0200060,存储空间为4字节
GPCDATA:0xE0200064,存储空间为4字节
并且两个寄存器占用的地址空间是连续的;
明确:内核不能直接访问以上物理地址,需要做映射:
unsigned long *gpiocon, *gpiodata;
gpiocon保存配置寄存器的内核虚拟地址
gpiodata保存数据寄存器的内核虚拟地址
映射的方法1:
gpiocon = ioremap(0xE0200060, 4);
gpiodata = ioremap(0xE0200064, 4);
一旦有物理地址对应的内核虚拟地址,就可以访问外设
*gpiocon ...
*gpiodata ...
映射方法2:
gpiocon = ioremap(0xe0200060, 8);
gpiodata = gpiocon + 1;
*gpiocon ...
*gpiodata ...
解除映射:
iounmap(gpiocon);
#include <linux/io.h>
案例
利用ioremap实现LED开关操作,不允许使用GPIO操作库函数;
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
//定义LED开关命令
#define LED_ON 0x100001
#define LED_OFF 0x100002
//定义保存配置寄存器和数据寄存器的内核虚拟地址变量
static unsigned long *gpiocon, *gpiodata;
static long led_ioctl(struct file *file,
unsigned int cmd,
unsigned long arg)
{
//分配内核缓冲
int kindex;
//拷贝用户数据到内核
copy_from_user(&kindex, (int *)arg, sizeof(kindex));
//解析命令
switch(cmd) {
case LED_ON:
if (kindex == 1)
*gpiodata |= (1 << 3);
else if (kindex == 2)
*gpiodata |= (1 << 4);
break;
case LED_OFF:
if (kindex == 1)
*gpiodata &= ~(1 << 3);
else if (kindex == 2)
*gpiodata &= ~(1 << 4);
break;
}
printk("%s:配置寄存器=%#x, 数据寄存器=%#x\n",
__func__, *gpiocon, *gpiodata);
return 0;
}
//定义初始化硬件操作方法.unlocked_ioctl
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = led_ioctl
};
//定义初始化混杂设备对象
static struct miscdevice led_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "myled",
.fops = &led_fops
};
static int led_init(void)
{
//注册混杂设备对象
misc_register(&led_misc);
//将配置寄存器和数据寄存器的物理地址映射到内核虚拟地址上
gpiocon = ioremap(0xE0200060, 8);
gpiodata = gpiocon + 1;
//配置GPIO为输出口,输出0
*gpiocon &= ~((0xf << 12) | (0xf << 16));
*gpiocon |= ((1 << 12) | (1 << 16));
*gpiodata &= ~((1 << 3) | (1 << 4));
return 0;
}
static void led_exit(void)
{
//输出0,解除地址映射
*gpiodata &= ~((1 << 3) | (1 << 4));
iounmap(gpiocon);
//卸载混杂设备
misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//./led_test on 1
//./led_test off 1
//./led_test on 2
//./led_test off 2
//定义LED开关命令
#define LED_ON 0x100001
#define LED_OFF 0x100002
int main(int argc, char *argv[])
{
int fd;
int uindex; //用户缓冲区,保存灯的编号
if (argc != 3) {
printf("Usage:\n %s <on|off> <1|2>\n", argv[0]);
return -1;
}
fd = open("/dev/myled", O_RDWR);
if (fd < 0)
return -1;
//将灯的编号字符串转整形:“123abc” -> 123
uindex = strtoul(argv[2], NULL, 0);
//向设备发送命令和灯的编号
if (!strcmp(argv[1], "on"))
ioctl(fd, LED_ON, &uindex);
else
ioctl(fd, LED_OFF, &uindex);
close(fd);
return 0;
}