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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章