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