1、内存管理 (将物理内存映射到内核空间(3G~4G)并使用)
深入内核: 伙伴系统
1.1基本概念
1)linux内核管理内存是以物理内存页为单位
一个物理内存页通常为4KB
内核会为每个物理内存页创建如下结构变量
struct page {
//记录该物理内存页被引用的次数 为0 代表空闲页
atomic_t _count
...
}
2) 内核管理内存时对所有的内存并不是一视同仁
低端内存: 介于0~896M(可调)的内存称为低端内存
采用静态映射方式
该段内存的虚拟地址
虚拟地址=0xc0000000 + 物理偏移
高端内存:>896M(可调)的内存称为高端内存
采用动态映射方式
使用该段物理内存时
动态建立和虚拟地址的映射关系
使用完毕后立即解除该映射关系
1.2 内核中动态申请内存的方式
1.2.1 按页分配内存
方式一:
//连续申请2^order个物理内存页
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)
//完成申请到的物理内存页的映射
//返回起始虚拟地址
void *page_address(const struct page *page)
方式二:
//连续申请2^order物理内存页 并映射
//返回对应的起始虚拟地址
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
void free_pages(unsigned long addr, unsigned int order)
addr, __get_free_pages的返回值
order,连续释放2^order个物理内存页
方式三:
//申请2^0个物理内存页 并返回映射后的起始虚拟地址
__get_free_page(gfp_mask)
1.2.2 按字节分配内存
void *kmalloc(size_t size, gfp_t flags)
size, 要连续申请的字节数
flags, 常用的取值
GFP_KERNEL:申请内存不成功时 阻塞等待 不能用于中断上下文
GFP_ATOMIC:申请不成功 立即返回错误信息
void kfree(const void *objp)
objp, kmalloc的返回值
void *vmalloc(unsigned long size)
size, 要连续申请的字节数
分配的空间位于高端内存
void vfree(const void *addr)
addr, vmalloc的返回值
kmalloc和vmalloc的区别:
kmalloc申请得到的物理内存一定是连续的
vmalloc申请得到的物理内存不一定连续
1.2.3建立映射后
可以使用以下函数完成物理地址和虚拟地址的转换
phys_addr_t virt_to_phys(const volatile void *x)
void *phys_to_virt(phys_addr_t x)
2、ioremap(将特殊功能寄存器地址映射到内核空间(3~4G))
2.1 基本概念
统一编址, 内存和外设使用同一套编号(0~4G)
ARM
加载存储指令:ldr / str
mov r1, #0x48000000
ldr r0, [r1]
str r0, [r1]
ldr r1, #0xc001c020
ldr r0, [r1]
str r0, [r1]
独立编址,内存一套编号,外设一套编号
X86
给出地址0x100
mov指令 地址0x100 指的是内存
in/out指令 操作外设
linux内核中将使用统一编址的外设称为I/O内存
将使用独立编址的外设称为I/O端口
2.2 如何编程操作特殊功能寄存器(外设)
1)申请I/O内存
request_mem_region(start,n,name)
start,要申请使用的I/O内存的起始物理地址
n, 要申请的连续字节数
name, 名称
2)映射I/O内存
void __iomem *ioremap(phys_addr_t start, unsigned long n)
start, 要映射的起始物理地址
n, 要映射的字节数
返回值,映射之后的起始虚拟地址
例如 start=0x48000000 n =0x100
返回值为0xc0008000
意味着
虚拟地址 物理地址
0xc0008000 0x48000000
0xc0008001 0x48000001
0xc0008002 0x48000002
。。。 。。。
0xc00080ff 0x480000ff
3)访问I/O内存
方式一:
*((volatile unsigned int *)addr)
方式二:
readl(addr)
writel(val, addr)
addr, 是虚拟地址
4)取消映射
void iounmap(void __iomem *addr)
addr,ioremap时的返回值
5)释放I/O内存
release_mem_region(start,n)
start, 要释放的起始物理地址
n, 连续释放的字节数
ioremap的意义:
对于GPIO管脚来说 控制方式有两种
1)gpio库函数
2)ioremap 之后直接操作特殊功能寄存器
如果操作uart控制器 i2c控制器
只能通过ioremap映射特殊功能寄存器 然后操作
3、mmap(将内存/特殊功能寄存器映射到用户空间(0~3G))
3.1 应用编程
fd = open("a.txt", ...)
addr = mmap(....,fd,size)
/*文件写入*/
addr[10] = ‘c‘;
3.2 嵌入式环境
fd=("/dev/xxx", ...)
addr=mmap(..., fd, size)
------------------------------------
sys_mmap
xxx_mmap(struct file filp*, struct vm_area_struct *vma)
{
remap_pfn_range(vma, vma->vm_start,
要映射到用户空间物理地址>>12 (页号),
vma->vm_end - vma->vm_start, vma->vm_page_prot);
}
3.2.1通过mmap将LCD的显存映射到用户空间
3.2.2将camer的缓存映射到用户空间
总结:(非重点)
如果在用户空间需要对该设备执行mmap操作
1)设置驱动函数struct file_operations要实现mmap
2) xxx_mmap函数中要调用remap_pfn_range
3)remap_pfn_range的调用方式
remap_pfn_range(vma, vma->vm_start,
要映射起始物理地址>>12,
vma->vm_end-vma->vm_start,
vma->vm_page_prot);
fd = open("/dev/xxx", ....)
//addr中保存的虚拟地址对应的物理地址
//就是remap_pfn_range第三个参数
addr = mmap(..., fd,size);
通常在实际驱动编程过程不需要实现该函数
它违背了linux内核的初衷
实际开发过程只会把camer和lcd 显存(缓存)映射到用户空间
这类映射函数内核中已经实现完毕了
例如:lcd的映射函数是内核中的fb_mmap
#include "../../global.h" #include <linux/vmalloc.h> #include <linux/slab.h> unsigned long pages_addr; void *kmalloc_addr; void *vmalloc_addr; int __init kernelspace_init(void) { int ret = 0; /*申请2^3个物理内存页 并映射*/ pages_addr = __get_free_pages(GFP_KERNEL, 3); if(!pages_addr) { printk("<1>" "get pages failed!"); ret = -ENOMEM; goto failure_pages; } printk("<1>" "get pages vir=%#x phys=%#x\n", pages_addr, virt_to_phys(pages_addr)); /*申请200字节*/ kmalloc_addr = kmalloc(200, GFP_KERNEL); if(!kmalloc_addr) { ret = -ENOMEM; goto failure_kmalloc; } printk("<1>" "kmalloc vir=%#x phys=%#x\n", kmalloc_addr, virt_to_phys(kmalloc_addr)); vmalloc_addr = vmalloc(1024*10); if(!vmalloc_addr) { ret = -ENOMEM; goto failure_vmalloc; } printk("<1>" "vmalloc vir=%#x phys=%#x\n", vmalloc_addr, virt_to_phys(vmalloc_addr)); return 0; failure_vmalloc: kfree(kmalloc_addr); failure_kmalloc: free_pages(pages_addr, 3); failure_pages: return ret; } void __exit kernelspace_exit(void) { vfree(vmalloc_addr); kfree(kmalloc_addr); free_pages(pages_addr, 3); } module_init(kernelspace_init); module_exit(kernelspace_exit);
#include "../../global.h" #include <linux/io.h> #include <linux/ioport.h> #define GPIOC_START (0xc001c000) #define GPIOC_SIZE (0x24) static void __iomem *base = NULL; int __init led_drv_init(void) { unsigned int data = 0; /*1 申请I/O内存*/ request_mem_region(GPIOC_START,GPIOC_SIZE,"led1"); /*2 映射I/O内存*/ base = ioremap(GPIOC_START, GPIOC_SIZE); /*3 访问I/O内存*/ data = readl(base+0x20); data &= ~(3<<24); data |= (1<<24); writel(data, base+0x20); writel(readl(base+0x04)|(1<<12), base+0x04); writel(readl(base+0x00)&(~(1<<12)), base+0x00); return 0; } void __exit led_drv_exit(void) { writel(readl(base+0x00)|(1<<12), base+0x00); /*4 取消映射*/ iounmap(base); /*5 释放I/O内存*/ release_mem_region(GPIOC_START, GPIOC_SIZE); } module_init(led_drv_init); module_exit(led_drv_exit);
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/gpio.h> #include <mach/platform.h> #include <linux/uaccess.h> #include <linux/device.h> #include <linux/mm.h> MODULE_LICENSE("GPL"); #define CMD_LED_ON 0x10001 #define CMD_LED_OFF 0x10002 struct class *cls = NULL; /*1 定义struct cdev类型变量*/ struct cdev led_cdev; dev_t dev = 0; int led_open(struct inode *inode, struct file *filp) { return 0; } int led_release(struct inode *inode, struct file *filp) { return 0; } int k_status = 1; //灭灯 ssize_t led_write(struct file *filp, const char __user *buf, size_t len, loff_t *offset) { return len; } ssize_t led_read(struct file *filp, char __user *buf, size_t len, loff_t *offset) { return len; } long led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { return 0; } int led_mmap(struct file *filp, struct vm_area_struct *vma) { /*关闭对该段区域读写时的cache特性 *确保对寄存器的写入操作及时完成 * */ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); /*建立映射关系*/ remap_pfn_range(vma,//对象指针 //映射后的起始虚拟地址 vma->vm_start, //页号(按页对齐) 0xc001c000>>12, vma->vm_end - vma->vm_start, vma->vm_page_prot//访问属性 ); return 0; } struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, .write = led_write, .read = led_read, .unlocked_ioctl = led_ioctl, .mmap = led_mmap, }; int __init led_drv_init(void) { /*申请注册设备号*/ alloc_chrdev_region(&dev, 8, 1, "myleds"); /*2 初始化cdev*/ cdev_init(&led_cdev, &led_fops); /*3 注册cdev*/ cdev_add(&led_cdev, dev, 1); /*4 自动创建设备文件*/ /*会导致 "/sys/class/LEDS/" */ cls = class_create(THIS_MODULE, "LEDS"); /*会导致 "/sys/class/LEDS/myleds/"*/ device_create(cls, NULL, dev, NULL, "myleds"); return 0; } void __exit led_drv_exit(void) { /*自动销毁设备文件*/ device_destroy(cls, dev); class_destroy(cls); /*4 注销cdev*/ cdev_del(&led_cdev); /*5 注销设备号*/ unregister_chrdev_region(dev, 1); } module_init(led_drv_init); module_exit(led_drv_exit);
#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/mman.h> /* *./test <on/off> * */ int main(int argc ,char *argv[]) { void *gpioc_base = NULL; volatile unsigned int *gpiocout= NULL; volatile unsigned int *gpiocoutenb= NULL; volatile unsigned int *gpiocaltfn0= NULL; unsigned int tmp = 0; if(argc != 2) { printf("usage: %s <on/off>\n", argv[0]); return -1; } int fd = open("/dev/myleds", O_RDWR); if(fd < 0) { perror("open failed"); return -1; } printf("open successed,using device....\n"); gpioc_base = mmap(NULL,0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); gpiocout = (volatile unsigned int *)(gpioc_base+0); gpiocoutenb = (volatile unsigned int *)(gpioc_base+4); gpiocaltfn0 = (volatile unsigned int *)(gpioc_base+0x20); /*选择功能1*/ *gpiocaltfn0 &= ~(3<<24); *gpiocaltfn0 |= (1<<24); /*设置为输出模式*/ *gpiocoutenb |= (1<<12); if(strcmp(argv[1], "on") == 0) *gpiocout &= ~(1<<12); else *gpiocout |= 1<<12; /*取消映射*/ munmap(gpioc_base, 0x1000); close(fd); return 0; }
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <linux/fb.h> #include <sys/mman.h> #define _COLOR_RED 0x00ff0000 #define _COLOR_GREEN 0x0000ff00 #define _COLOR_BLUE 0x000000ff static struct fb_fix_screeninfo fb_fix ={0}; static struct fb_var_screeninfo fb_var ={0}; long screen_size=0; int *fb32 =NULL; int main() { int fd = -1; int x,y; fd =open("/dev/fb0",O_RDWR); if(fd < 0) { printf("open dev fb0 fail.\n"); return -1; } //get lcd param ioctl(fd,FBIOGET_FSCREENINFO,&fb_fix); ioctl(fd,FBIOGET_VSCREENINFO,&fb_var); //显存的大小 screen_size = fb_var.xres*fb_var.yres*(fb_var.bits_per_pixel/8); fb32 =mmap(0,screen_size,PROT_READ |PROT_WRITE,MAP_SHARED,fd,0); if(fb32 == NULL) { printf("mmap framebuffer fail.\n"); return -1; } /*将以下代码替换为显示tarena_logo图片*/ for(y=0;y< fb_var.yres/3;y++) { for(x=0;x< fb_var.xres;x++) { *(fb32 +y*fb_var.xres + x) = _COLOR_RED; } } for(;y< fb_var.yres*2/3;y++) { for(x=0;x< fb_var.xres;x++) { *(fb32 +y*fb_var.xres + x) = _COLOR_GREEN; } } for(;y< fb_var.yres;y++) { for(x=0;x< fb_var.xres;x++) { *(fb32 +y*fb_var.xres + x) = _COLOR_BLUE; } } munmap(fb32,screen_size); close(fd); return 0; }
内存管理,goto的使用,内存的申请和释放,mmap,iomap
原文:https://www.cnblogs.com/DXGG-Bond/p/11892261.html