Bootstrap

Linux内核内存分配方法

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;
}

;