一、基本概念

        (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的物理内存;

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐