内核内存管理(lv11-day8)

文章目录

        • 1 内核内存管理框架
        • 2 内核中常用动态分配
          • 2.1 kmalloc
          • 2.2 vmalloc(可以睡眠)
          • 2.3 kmalloc & vmalloc 的比较
          • 2.5分配选择原则:
        • 3 IO访问-------访问外设控制器的寄存器
        • 4 led驱动
          • 4.1读原理图
          • 4.2 查阅SOC芯片手册
          • 4.3 编写驱动
            • 4.3.1 设计设备数据类型
            • 4.3.2 其它
        • 5 led设备驱动代码

1 内核内存管理框架

内核将物理内存等分成N块,4KB称之为一页,每页都用一个struct page来表示,采用伙伴关系算法维护。
内核内存管理(lv11-day8)_第1张图片

3G~3G+896M:低端内存,直接映射 虚拟地址 = 3G + 物理地址
细分为:ZONE_DMA、ZONE_NORMAL
​ 分配方式:

kmalloc:小内存分配,slab算法
get_free_page:整页分配,2的n次方页,n最大为10

大于3G+896M:高端内存
​ 细分为:vmalloc区、持久映射区、固定映射区

分配方式:vmalloc:虚拟地址连续,物理地址不连续

内核地址空间划分图:
内核内存管理(lv11-day8)_第2张图片

2 内核中常用动态分配
2.1 kmalloc

kmalloc() 申请的内存位于直接映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB。
函数原型

/**
  * @brief          动态申请内存
  * @param[in]      size_t size:申请的内存大小  gfp_t flags:分配内存的方法
  * 较常用的 flags
  * GFP_ATOMIC:
  * 分配内存的过程是一个原子过程,分配内存的过程不会被(高优先级进程或中断)打断;
  * GFP_KERNEL:
  * 正常分配内存;
  * GFP_DMA:
  * 给DMA控制器分配内存,需要使用该标志(DMA要求分配虚拟地址和物理地址连续)。
  * @retval         申请的内存地址
  */
void *kmalloc(size_t size, gfp_t flags);

flags 的参考用法:

|– 进程上下文,可以睡眠      GFP_KERNEL  
|– 异常上下文,不可以睡眠     GFP_ATOMIC
|  |–中断处理程序        GFP_ATOMIC
|  |– 软中断          GFP_ATOMIC  
|  |–Tasklet          GFP_ATOMIC
|– 用于DMA的内存,可以睡眠   GFP_DMA|GFP_KERNEL  
|– 用于DMA的内存,不可以睡眠   GFP_DMA |GFP_ATOMIC

对应的内存释放函数为:

/**
  * @brief          释放内存
  * @param[in]      const void *objp: kmalloc返回的内存地址  
  * @retval         NULL
  */
void kfree(const void *objp);

2.2 vmalloc(可以睡眠)

vmalloc() 函数则会在虚拟内存空间给出一块连续的内存区,但这片连续的虚拟内存在物理内存中并不一定连续。由于vmalloc() 没有保证申请到的是连续的物理内存,因此对申请的内存大小没有限制,如果需要申请较大的内存空间就需要用此函数了。

/**
  * @brief          申请内存
  * @param[in]      unsigned long size: 申请内存的大小  
  * @retval         申请的内存地址
  */
void *vmalloc(unsigned long size);

对应的内存释放函数为:

/**
  * @brief          释放内存
  * @param[in]      const void *addr:vmalloc的返回地址 
  * @retval         NULL
  */
void vfree(const void *addr);

注意:vmalloc() 和 vfree() 可以睡眠,因此不能从异常上下文调用。

2.3 kmalloc & vmalloc 的比较

kmalloc()、kzalloc()、vmalloc() 的共同特点是:

  1. 用于申请内核空间的内存;
  2. 内存以字节为单位进行分配;
  3. 所分配的内存虚拟地址上连续;

kmalloc()、kzalloc()、vmalloc() 的区别是:

  1. kzalloc 是强制清零的 kmalloc 操作;(以下描述不区分 kmalloc 和 kzalloc)
  2. kmalloc 分配的内存大小有限制(128KB),而 vmalloc 没有限制;
  3. kmalloc 可以保证分配的内存物理地址是连续的,但是 vmalloc 不能保证;
  4. kmalloc 分配内存的过程可以是原子过程(使用 GFP_ATOMIC),而 vmalloc 分配内存时则可能产生阻塞;
  5. kmalloc 分配内存的开销小,因此 kmalloc 比 vmalloc 要快;

一般情况下,内存只有在要被 DMA 访问的时候才需要物理上连续,但为了性能上的考虑,内核中一般使用 kmalloc(),而只有在需要获得大块内存时才使用 vmalloc()。

2.5分配选择原则:
  1. 小内存(< 128k)用kmalloc,大内存用vmalloc或get_free_page
  2. 如果需要比较大的内存,并且要求使用效率较高时用get_free_page,否则用vmalloc
3 IO访问-------访问外设控制器的寄存器

两种方式:

  1. IO端口:X86上用IO指令访问
  2. IO内存:外设寄存器在SOC芯片手册上都有相应物理地址

IO 操作函数

/**
  * @brief          实现IO管脚的映射
  * @param[in]      offset:该管脚的偏移地址
                    Size:该管脚映射空间的大小 
  * @retval         成功返回映射的虚拟地址,失败NULL
  */
static inline void __iomem *ioremap(unsigned long offset, unsigned long size)
/**
  * @brief          解除io管脚的映射
  * @param[in]      addr:io管脚映射的地址             
  * @retval         
  */
static inline void iounmap(volatile void __iomem *addr)
/**
  * @brief          读取寄存器的值
  * @param[in]      addr 地址            
  * @retval         读到的数据
  */
unsigned readb(void *addr);//1字节 或ioread8(void *addr)
unsigned readw(void *addr);//2字节 或ioread16(void *addr)
unsigned readl(void *addr);//4字节 或ioread32(void *addr)

/**
  * @brief              向指定的寄存器中,写入数据。
  * @param[in]      value:待写入寄存器中的数据
                             Address:寄存器的虚拟地址         
  * @retval           NULL
  */
void writeb(unsigned value, void *addr);//1字节 或iowrite8(u8 value, void *addr)
void writew(unsigned value, void *addr);//2字节 或iowrite16(u16 value, void *addr)
voidwritel(unsigned value, void *addr);//4字节 或iowrite32(u32 value, void *addr)
4 led驱动
4.1读原理图

内核内存管理(lv11-day8)_第3张图片

通过原理图可知F14412LED为高电平导通.

4.2 查阅SOC芯片手册

fs4412 总共有4个led灯其操作寄存器和地址如下:

GPX2_7 led2
GPX2CON----0x11000C40—28~31-----0001
GPX2DAT-----0x11000C44-----7

GPX1_0 led3
GPX1CON----0x11000C20—0~3-----0001
GPX1DAT----0x11000C24-----0

GPF3_4 led4
GPF3CON----0x114001E0—16~19-----0001
GPF3DAT----0x114001E4-----4

GPF3_5 led5
GPF3CON----0x114001E0—20~23-----0001
GPF3DAT----0x114001E4-----5

4.3 编写驱动
4.3.1 设计设备数据类型
struct myled_dev
{
   
struct cdev mydev;
unsigned long * led2con;
unsigned long * led2dat;
unsigned long * led3con;
unsigned long * led3dat;
unsigned long * led4con;
unsigned long * led4dat;
unsigned long * led5con;
unsigned long * led5dat;
};
4.3.2 其它

b. 考虑需要支持的函数
c. 模块入口:ioremap + 设置成输出
d. 模块出口:iounmap
e.编写关灯函数和开灯函数,实现ioctl

5 led设备驱动代码

myled.h

#ifndef MY_CHAR_H
#define MY_CHAR_H
#include 
#define LED_DEV_MAGIC 'g'
//#define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC,1,int*)
//#define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC,2,int*)
#define MY_LED_OFF _IO(LED_DEV_MAGIC,0)
#define MY_LED_ON  _IO

你可能感兴趣的:(驱动开发,arm开发,linux,嵌入式硬件,ubuntu)