linux驱动使用的相关内核内存管理

一、系统内存布局

1. 内核地址空间

默认情况下,在32位系统中,系统的地址空间按照3/1G进行划分,其中高于0xC000000的1G空间属于内核地址空间,而用户空间使用低3G字节的空间。

内存一般按照PAGE_SIZE进行划分管理,通常把物理内存称为帧,页面帧号(PFN);

页面表是虚拟内存到物理地址的映射。

linux的内核地址空间只占有高1G的地址空间,把这1G的地址空间分为低896M和高128M空间。

        其中低896M空间是和物理内存页帧连续的一一映射的,也是固定映射的(称为低端内存,映射产生的地址称为逻辑地址),映射产生的逻辑地址可以减去一个offset值获取物理页帧;

        其中内核高128M地址空间映射的物理内存页帧不是连续的,是可以随时映射和取消的。

2. 用户地址空间

        每个进程在内核中都表示为struct task_struct的实例,它表征并描述进程。每个进程都被赋予一个内核映射表struct mm_struct{}结构,内核全局变量current指向当前进程,字段*mm指向内存映射表mm_struct{}。

struct mm_struct {
    struct vm_area_struct *mmap;
    struct rb_root mm_rb;
    unsigned long mmap_base;
    unsigned long task_size;
    unsigned long highest_vm_end;
    pgd_t *pgd;  // 指向进程的一个一级页表
    atomic_t mm_users;
    atomic_t mm_count;
    atomic_long_t nr_ptes;
    int map_count;
    ulong hiwater_rss;
    ulong hiwater_vm;
    ulong total_vm;
    ulong locked_vm;
    ulong pinned_vm;
    ulong data_vm;
    ulong exec_vm;
    ulong stack_vm;
    ulong def_flags;
    ulong start_code, end_code, start_data, end_data; // 进程各个虚拟内存区域
    ulong start_brk, end_brk, start_stack;
    ulong arg_start, arg_end, env_start, env_end;
    
    struct task_struct *owner;
    struct user_namespace *user_ns;
    struc file __rcu *exe_file;
    /*省略一些不关注的成员变量*/
};

进程各个虚拟内存区域:

堆栈空间内存区域
空洞
内存映射表
空洞
堆空间内存区域
空洞
BSS段内存区域 未初始化的变量
数据段内存区域 初始化了的变量
文本段(text)内存区域 指令、常量

3. 内存区域

        内核使用虚拟内存区域struct vm_area_struct{}跟踪进程内存映射。VMA是独立于处理器的结构,具有权限和访问标志。每个VMA区域有base、length,其在虚拟空间是连续的,但是不会映射到连续的物理页面帧。查看/proc/<pid>/maps可获取进程<pid>所使用的虚拟内存区域。

二、内存映射表和MMU

        对于进程,需要访问的所有页面必须存在于该进程任意一个VMA中。每个PTE对应于页面和帧之间的映射。

        linux使用四级分页模式,页面全局目录PGD、页面上部目录PUD、页面中部目录PMD、页面标PTE。几乎所有的32位cpu都只支持PGD和PTE两级目录。

        在MMU执行之前,TLB加速了虚拟地址到物理地址的映射。

三、内存分配机制

 

linux不同分配器的依赖关系

1. 页面分配器

页面在内核中表示为struct page

//返回page
struct page *alloc_pages(gfp_t mask, unsigned int order);
void __free_pages(struct page* page, unsigned int order);

// 直接返回虚拟地址的分配方式
unsigned long __get_free_pages(gfp_t mask, unsigned int order);
unsigned long get_zeroed_page(gfp_t mask);

void free_pages(unsigned long addr, gfp_t mask);

//虚拟地址和page*的互相转换
struct page* virt_to_page(void *kaddr);
void *page_to_virt(struct page* pg);

2.  SLAB分配器

使用伙伴算法进行分配,分配的大小必须为2的幂;

3. kmalloc分配系列

kmalloc依赖于slab分配器,它返回的内存在物理内存和虚拟内存中都是连续的。在arm和x86体系结构中,kmalloc每次分配的最大内存为4M,总可分配的内存量为128MB。

/*其中flag可为:GFP_KERNEL, GFP_ATOMIC, GFP_DMA;
*/
void *kmalloc(size_t size, gfp_t flag);
void kfre(void *ptr);

/*类似的函数簇:*/
void *kzalloc(size_t size, gfp_t flag);
void kzfree(void *p);
void *kzalloc(size_t n, size_t size, gfp_t flag);
void *krealloc(void *p, size_t newsize, gfp_t flag);

4. vmalloc分配系列

        vmalloc分配的内存始终来自HIGH_MEM区域,返回的地址不能转换为总线地址或物理地址(因为其内存区域与物理内存不是一一映射),不能被用作DMA。vmalloc比kmalloc慢,因为每次分配必须检索内存、分配页面表PTE。在linux中可以查看/proc/vmallocinfo查看vmalloc分配的内存。

void *vmalloc(unsigned long size);
void *vzalloc(unsigned long size);
void vfree(void *vaddr);

三、I/O内存访问硬件

        访问设备寄存器时,内核根据系统架构不同提供两种可能的操作。通过IO端口(PIO)和通过内存映射输入输出(MMIO),前者PIO适用于x86架构,因为x86提供了in/out汇编指令,io和内存是不同的地址空间;后者MMIO适用于ARM架构,IO和内存是同一个地址空间。

1 PIO设备访问方式

        在PIO系统中,用于I/O的端口地址空间,只有有限的65536个地址。内核导出一些函数来处理I/O端口,在访问任何IO端口区域之前,必须使用request_region()把要使用的IO端口通知内核。在/proc/ioports中可以查看IO端口的占用情况。

/*申请IO端口区间*/
struct resource *request_region(ulong start, ulong len, char *name);
void release_region(ulong start, ulong len);

/*访问IO端口*/
u8 inb(ulong addr);
u16 inw(ulong addr);
u32 inl(ulong addr);

void outb(u8 b, ulong addr);
void outw(u16 w, ulong addr);
void outl(u32 l, ulong addr);

2 MMIO设备访问方式

        内核通常使用HIGH_MEM地址空间来映射设备寄存器。内核提供了函数request_mem_region用于注册空间,注册后可在/proc/iomem查看空间使用情况。在注册内存空间后,同样需要io_remap()把物理地址映射为虚拟地址。

// 注册MMIO内存地址空间,此处占用的是物理地址空间
struct resource *request_mem_region(ulong start, ulong len, char *name);
void release_mem_region(ulong start, ulong len);

// 映射物理地址
void __iomem *ioremap(ulong phyaddr, ulong size);
void iounmap(void __iomem *virt_addr);

unsigned int ioread8(void __iomem *addr);
unsigned int ioread16(void __iomem *addr);
unsigned int ioread32(void __iomem *addr);
void iowrite8(u8 val, void __iomem *addr);
void iowrite16(u16 val, void __iomem *addr);
void iowrite32(u32 val, void __iomem *addr);

四、内存重映射

        内核有时需要重新映射内存,常常会出现把内存映射到用户空间的需求。

1. 单Page映射kmap

        kmap用于把指定内存页面映射到内核虚拟地址空间。内核HIGHMEM中的128M地址空间,被用于映射物理地址空间除低896MB外的任一内存页帧。

void *kmap(struct page *page);
void kunmap(struct page *page);

2 映射内核内存到用户空间

内核提供了函数remap_pfn_range/io_remap_pfn_range将物理内存页帧PFN或IO地址空间映射到用户空间进程,它主要用于实现mmap系统调用。

int remap_pfn_range(struct vm_area_struct *vma, ulong addr, ulong pfn,
                    ulong size, pgprot_t flags);
/*
其中 vma是要映射的vma虚拟内存区域
addr: 是vma->vm_start
pfn:是要映射的物理页面帧号pfn,可以通过pfn=virt_to_phys(buffer+offset) >> PAGE_SHIFT获得,或者通过pfn = page_to_pfn(virt_to_page(buffer+offset));获得
flags: 代表VMA所要求的保护,
*/

// 类似的,对于io内存,使用io_remap_pfn_range方式,这个函数是ioremap的对位函数
int remap_pfn_range(struct vm_area_struct *vma, ulong virt_addr, ulong phy_addr,
                    ulong size, pgprot_t flags);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值