Linux内存管理
一、基本概念
(1) Linux内核管理内存的最小单位是内存页,通常一个物理内存页的大小为4KB字节(颗粒度为物理内存页);内核会为每个物理内存页创建一个struct page结构体;
struct page{ //mm_types.h
}
(2)内核中管理内存并不是一视同仁,分低端内存和高端内存;
低端内存:小于896M的物理地址称为低端内存;--->静态映射,固定映射,虚拟地址=0xc0000000+偏移地址;
高端内存:大于896M的物理地址称为高端内存;--->对应于虚拟地址空间为128M的内存,采用动态映射的方式(开销更大),使用时建立映射关系,使用完毕立即解除映射关系;为解决虚拟地址不够用的问题。
高端内存与低端内存是对于内核而言,在实际内存中并没有高端内存和低端内存之分;简单来说,内核设置了一条线,896M内存,一旦超过了这个内存,就是称为高端内存,使用动态映射;
对于用户态也可以访问到这些内存,用户进程页表可以映射任何物理页(低端或高端),不依赖内核的动态映射区;用户态虚拟地址空间不能映射那些被内核保留为自身使用(或固件保留)的物理内存页,因为操作系统不会为这些物理页在用户页表中建立映射,或者在页表项中禁止用户态访问。而对于普通物理内存(包括所有高端内存和绝大多数低端内存),用户态都可以正常映射使用;
用户态和内核态都使用虚拟地址,物理地址是MMU和内核页表的内部细节;
二、动态分配内存的API的函数
这里插入一个问题,驱动层的物理内存申请与用户层的物理内存申请有什么区别呢?用户层的内存申请,使用malloc函数,分配一个内存,这里函数返回一个地址,虚拟地址,一个进程的虚拟空间分配4GB,有1GB是给内核态的,我认为内核态是整个linux的文件系统、镜像、设备树?那貌似这1GB肯定不够用了呀,那内核态驱动层的内存申请是干什么的呢?(回答在文章末尾)
(1)按页分配
//第一套函数
//申请2^order次幂个物理内存页(4KB)
struct page * alloc_pages(gfp_t gfp_mask, unsigned int order)
{
return alloc_pages_current(gfp_mask, order);
}
// 将物理内存页映射为虚拟地址
void *page_address(const struct page *page);
(2)按字节分配
//kmalloc/kfree
void *kmalloc(size_t size ,gfp_t flags);
flags,常用取值
GFP_KERNEL,有可能引起睡眠、不能用于中断上下文
GFP_ATOMIC,申请不成功,直接返回错误,不会引起睡眠 可以在中断上下文环境中使用
void kfree(const void *objp);
//vmalloc/vfree
void *vmalloc(unsigned long size);
void vfree(const void *addr);
区别:kmalloc申请得到的物理内存一定是连续的
vmalloc申请得到的物理内存不一定连续,有可能睡眠 不能用于中断,分配的虚拟地址位于内核的动态映射去,该区域用于间接访问高端物理内存,其物理页面可以是不连续的。
//建立号映射关系的物理地址和虚拟地址的互转
virt_to_phys(a) //虚拟地址--->物理地址
phys_to_virt(a) //物理地址--->虚拟地址
这两个宏仅适用于低端内存的线性映射地址
在驱动代码中,我们常常需要定义一种类型的变量,这种类型变量有一个特点是,这类变量往往是阶段性使用,或者当申请成功时使用,申请失败时不使用;这些变量需要得到控制,防止这些变量不断地占用内存,所以我们可以使用kmalloc/kfree进行分配变量的地址,不使用的时候释放;
三、内核中的错误处理
内核虚拟地址空间的末尾(最后4KB左右)被保留用作”错误指针区域“。当函数返回的指针落在这个区域时,表示出错,需要通过PTR_ERR提取错误码。简单来说,有一部分函数的返回值是地址,内核中设置了一片区域,如果函数返回的是这片区域的地址,就代表出现错误。错误类型可以根据Linux源码中的宏进行判断;
有两个常用的宏:
static inline long __must_check IS_ERR(const void *ptr)
{
return IS_ERR_VALUE((unsigned long)ptr);--+
} |
|
V
#define MAX_ERRNO 4095
#define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)
-4095的二进制表示为0xfffff001
这里是判断返回地地址是否是在0xfffff001~0xffffffff之间,也就是内核中对应的错误地址区域
static inline long __must_check PTR_ERR(const void *ptr)
{
return (long) ptr;
}
//用于返回错误码;
常见用法是:
if (IS_ERR(regmap)) {
ret = PTR_ERR(regmap);
dev_err(&i2c->dev, "regmap init failed: %d\n", ret);
return ret;
}
我们可以下表来对照错误码,然后判断错误类型,可以选择打印;
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
Q1:这里插入一个问题,驱动层的内存申请与用户层的内存申请有什么区别呢?用户层的内存申请,使用malloc函数,分配一个内存,这里函数返回一个地址,虚拟地址,一个进程的虚拟空间分配4GB,有1GB是给内核态的,我认为内核态是整个linux的文件系统、镜像、设备树?那貌似这1GB肯定不够用了呀,那内核态驱动层的内存申请是干什么的呢?
R1:我们将上面的问题拆分成小问题;
(1)1GB的内核空间装了啥?
a. 内核自身:操作系统核心的代码段和数据段,永久占用一小部分;
b. 设备树与模块:开发板上描述信息的设备树会被解析加载到这里,动态加载的驱动模 块也存放在这里;
c. 核心数据结构:内存页表、进程管理结构等;
d. 文件系统的”窗口“:文件系统的元数据和缓存通过页缓存机制;
注意:这1GB的内核空间是所有进程共享的1GB内核空间,也就是说,这1GB的内核空间是整个操作系统的大将军指挥者。
内核代码中有调度器、内存管理器、文件系统、驱动等核心逻辑、核心数据结构,比如所有的进程的进程描述符(task_struct)、页表(内核部分)、文件系统缓存、设备状态等。
动态映射窗口:用于临时访问高端物理内存或设备I/O内存。
(2)驱动层的内存申请与用户层的内存申请有什么区别?
驱动层的内存申请是在内核态的1GB虚拟内存中,而用户态的是在那3GB中;
(3)那1GB够不够使用?
1GB是虚拟地址范围,实际上物理内存通常远大于1GB。所以即使内核虚拟地址空间只有1GB,它可以通过动态映射(高端内存机制)访问超过1GB的物理内存;
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)