内存管理,goto的使用,内存的申请和释放,mmap,ioremap

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
",
            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
",
            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
",
            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>
",
                argv[0]);
        return -1;
    }

    int fd = open("/dev/myleds", O_RDWR);

    if(fd < 0)
    {
        perror("open failed");
        return -1;
    }
    printf("open successed,using device....
");
    
    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.
");
        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.
");
        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;
}
原文地址:https://www.cnblogs.com/DXGG-Bond/p/11892261.html