Linux内核中的内存管理

基本概念

Linux内核管理内存时以物理页(4k)为单位内核会为每一个物理内存页建立一个数据结构

1
2
3
4
struct page{
//内存被引用的次数,为0表示空闲页
atomic_t _count; //引用计数
};

Linux内核在管理内存时,并不一视同仁

高端内存:物理内存超过896M(大小可调)的空间

动态映射的策略,使用时进行映射,使用完毕之后ima接触映射

低端内存:介于0~896M的内存,映射关系是固定的,虚拟地址 = 0xc0000000 + 物理偏移

内核中动态申请内存的方法

按页申请

方式一

alloc_pages(gfd_mask, order)

order,要申请2^order

void *page_address(const struct page *page)

page指定的物理内存进行映射,返回映射后的虚拟地址

方式二

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)

要申请2^order个物理内存页

方式三

__get_free_page申请一页的物理内存

get_zeroed_page(gfp_t gfp_mask)申请一个内存页,并清零

释放物理内存页:

void free_pages(unsigned long addr, unsigned int order)

按字节申请

方式一

kmalloc / kfree

kmalloc(size, flags)

size要申请的字节个数

flags常用的取值:GFP_KERNELGFP_ATMIC

GFP_KERNEL申请内存的过程中,可能发生阻塞,所以不能用于中断上下文

GFP_ATMIC申请内存的过程中,一旦阻塞立即返回,可以用于中断上下文

kmalloc申请的内存是连续的,执行效率高

方式二

vmalloc / vfree

vmalloc(size)

size要申请的字节个数

vmalloc得到的虚拟地址也是连续的,但是将来对应的物理地址可能不连续

vfree(ptr);一定不要在中断上下文中使用

映射之后的转换

前提是必须已经建立映射

虚拟地址到物理地址之间的转换virt_to_phys

物理地址到虚拟地址之间的转换static inline void *phys_to_virt(phys_addr_t x)

ioremap

在Linux内核态直接访问特殊功能寄存器

统一编址:将内存和外设(特殊功能寄存器)使用一套编号,ARM、mips中使用的是统一编址

独立编制:使用不同编号

将外设称为IO端口

使用步骤

申请I/O内存(告诉linux内核我要使用)

reques_mem_region(start,n,name)

start要申请的起始物理地址

n要连续申请的字节数

name名称

static inline void __iomem *ioremap(phys_addr_t offset, unsigned long size)

offset要映射的起始物理地址

size要连续映射的字节数

返回值是映射后的虚拟地址

通过映射后的虚拟地址访问I/O内存

1
2
3
writeb		*((volatile unsigned char *) ptr) = 0x100;
writew
writel

解除映射

static inline void iounmap(void __iomem *addr)

释放IO内存(告诉Linux内核不用了)

release_mem_region

release_region(start,n)

mmap

实际编程中有两种mmap的使用场景

  1. 映射普通文件
  2. 映射设备文件

映射LED设备,在LED设备中实现mmap的功能,该函数将控制LED的特殊功能寄存器对应的物理地址映射到0~3G的虚拟地址空间,达到在用户空间直接操作硬件的效果

硬件操作函数集合中mmp,可以将硬件的物理地址映射到用户空间,从而在用户空间直接空着硬件

可以使用mmap函数直接将LCD显存映射到用户空间,这样做的好处在于可以减小数据的拷贝的过程

image-20230220162046556

g-sensor驱动在Linux中的实现

IIC控制器的驱动在Linux中已经实现了

1
2
3
4
5
6
7
8
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops,
};

设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct i2c_client {
unsigned short flags; /* div., see below */
//设备地址
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
//设备名称
char name[I2C_NAME_SIZE];

struct i2c_adapter *adapter; /* the adapter we sit on */
struct i2c_driver *driver; /* and our access routines */
//基类
struct device dev; /* the device structure */
int irq; /* irq issued by device */
//struct list_head detected;
};

驱动

1
2
3
4
5
6
7
8
9
struct i2c_driver {
//探测控制器上挂载了哪些i2c设备
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
struct device_driver driver;
//存储了该驱动中支持的i2c芯片的id裂变
const struct i2c_device_id *id_table;
};
//安装
i2c_del_driver(struct i2c_driver * driver)

image-20230221102914138

实验步骤

1234