内存浅谈:
器件物理地址由对应CPU地址线确定的。内核通过内存机制,创造4G虚拟地址,驱动编程用的地址都是虚拟地址,没有实际意义,
使用这些地址之前必须对应物理地址(页表),否则出现缺页。避免缺页,第一种方法:用kmalloc申请物理地址(返回物理存储器地址,sdram等地址)。
第二种方法:用ioremap指定具体物理空间里面的地址,任何器件物理地址。不局限于存储器地址,而且可以是寄存器地址等。
srmap_pfn_range浅谈:
内核吧4G的空间分成了3G user和1G kernel空间。首先用kmalloc申请SDRAM的内存空间。在通过remap_pfn_range,在3G user建立页表。所以用户程序才可以直接操作SDRAM。
一.概念
内存
1.物理地址:该地址在处理器和系统内存之间使用
2.总线地址:该地址在外围总线和内存之间使用,它实现总线和主内存之间的重新映射,通常它们与处理器使用的物理地址相同
3.用户虚拟地址:用户空间程序所能看到的常规地址 ,每个进程都有自己的虚拟地址空间.
4.内核逻辑地址:内核逻辑地址组成了内核的常规地址空间。该地址映射了部分(或者全部)内存, 并经常被视为物理地址。与物理地址是线性映射的。例如,kmalloc返回的是逻辑地址,小于128K
5.内核虚拟地址:内核虚拟地址和逻辑地址的相同之处在于,它们都将内核空间的地址映射到物理地址上。与物理地址不必是线性映射关系。例如,vmalloc与kmap都是返回内核虚拟地址,大于128K
6.页表:通常处理器必须使用某种机制,将虚拟地址转换为相应的物理地址。这种机制被称为页表。对驱动程序作者来说,在2.6版内核中删除了对页表直接操作的需求
7.内存管理结构体:每个进程(除了内核空间的一些辅助线程外)都拥有一个struct mm_struct结构(在<linux/sched.h >中定义)。
8.内存分配:内核为设备驱动程序提供了一致的内存管理接口。
IO端口和IO内存
9.设备通常会提供一组寄存,如控制寄存器、数据寄存器和状态寄存器等。这些寄存器可能位于I/O空间,也可能位于内存空间。通常, 除X86外,嵌入式处理器(比如ARM,PowerPC等)一般只存在内存空间
10.IO端口: 这些寄存器位于I/O空间,通过特定的指令访问(in,out)这里不做过多的介绍
11.I/O内存:这些寄存器位于内存空间
二.常见问题
1.内存管理结构体包括了什么?如何得到该结构体?
mm_struct结构包含了:a.虚拟内存区域链表、页表以及其他大量内存管理信息.b.一个信号灯(mmap_sem)和一个自旋锁(page_table_lock)
驱动程序可直接通过current->mm访问mm_struct结构。多个进程可以共享内存管理结构,Linux就是用这种方法实现线程的。
2.有哪些内存分配的方法?
a.kmalloc函数。b.后备高速缓存。c.get_free_page和相关函数。d.vmalloc及其辅助函数。e.获取大的缓冲区
3.如何把上述内核态分配的内存在用户态也能用呢?
struct page *virt_to_page(void *kaddr);
struct page *page_to_pfn(int pfn);
int remap_pfn_range(struct vm_area_struct *vma,unsigned long virt_addr,unsigned long pfn,unsigned long size,pgprot_t prot);//通过页帧号来建立页表, 并映射到用户空间!
4.简述物理地址与虚拟地址的关系?
内核虚拟地址转化为物理地址:_pa(logical-addr)。
物理地址转化为内核虚拟地址:_va(physical-addr).
5.mmap函数的作用?mmap映射的过程?具体用在哪里?
一般情况下,用户空间不能也不应该直接访问设别,但是设备驱动可以实现mmap函数,这个函数使得用户空间可以直接访问设备的物理地址。
他将用户空间的一段内存和设备内存关联,当用户访问用户空间的这段地址范围时,实际上会转化为访问设备内存。
这种能力最多用在显示适配器一类的设备非常有意义。如果用户空间可直接通过内存映射访问显存的话,屏幕帧的各点的像素将不再需要一个从用户空间到内核空间的复制过程了。
三.分配获取内核虚拟内存
1.分配小于128K #include<linux/slab.h>.
void *kmalloc(size_t size,int flags);//kmalloc特殊之处在于它分配的内存是物理上连续的(虚拟内存上也是连续的),这对于要进行DMA的设备十分重要.
void *vmalloc(unsigned long size);//vmalloc它分配的内存在虚拟内存上是连续的。
2.分配大于128K
a.分配页面函数或宏
unsigned long get_zeroed_page(unsigned int flags);
unsigned long __get_free_page(unsigned int flags);
unsigned long __get_free_pages(unsigned int flags,unsigned int order);
b.释放页面函数
void free_page(unsigned long addr);
void free_pages(unsigned long addr,unsigned long order);
四.页和内存管理信息
1.struct page;<linux/mm_types.h>{
struct page作用:函数操作一般都以页进行操作。1页对应1M。物理和虚拟内存地址空间分页。
atomic_t count;//对该页的访问计数。当计数值为0时,该页将返回给空闲链表。
void *virtual;//如果页面被映射,则指向页的内核虚拟地址;如果未被映射则为NULL。
unsigned long flags;//描述页状态的一系列标志.PG_locked表示内存中的页已经被锁住,PG_reserved表示禁止内存管理系统访问该页。
}
常用函数:
struct page *virt_to_page(void *kaddr);
struct page *pfn_to_page(int pfn);
2.struct mm_struct:{
虚拟内存区域链表、页表以及其他大量内存管理信息
一个信号灯(mmap_sem)和一个自旋锁(page_table_lock)
}
struct mm_struct作用:多个进程可以共享内存管理结构,Linux就是用这种方法实现线程的
功能函数:
current->mm访问mm_struct结构
五.设备地址映射到用户空间
int (*mmap)(struct file *filp,struct vm_area_struct *vma);
int remap_pfn_range(struct vm_area_struct *vma,unsigned long virt_addr,unsigned long pfn,unsigned long size,pgprot_t prot);//通过你的页帧号来建立页表, 并映射到用户空间!
六.IO端口
1.IO端口申请与释放
#include <linux/ioport.h>
struct resource *request_region(unsigned long first, unsigned long n,const char *name);
void release_region(unsigned long start, unsigned long n);
2.访问函数
a.读
unsigned inb(unsigned port);
unsigned inw(unsigned port);
unsigned inl(unsigned port);
void insb(unsigned port, void *addr, unsigned long count);
void insw(unsigned port, void *addr, unsigned long count);
void insl(unsigned port, void *addr, unsigned long count);
b.写
void outb(unsigned char byte, unsigned port);
void outw(unsigned short word, unsigned port);
void outl(unsigned longword, unsigned port);
void outsb(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);
七.IO 内存:Linux中访问内存空间的流程“申请资源->映射->访问->去映射->释放资源”
1.IO内存的申请与释放:申请和释放并不是必须的,但建议使用。其任务是检查申请的资源是否可用,如果可用在申请可用,并且标记为已经使用,其他驱动想再次申请资源就会失败。
struct resource *request_mem_region(unsigned long start,unsigned long len,char *name);
void release_mem_region(unsigned long start, unsigned long len);
2.获取映射和释放映射函数
void *ioremap(unsigned long offset,unsigned long size);//在内核中访问I/0内存之前,需首先使用ioremap()函数将设备所处的物理地址映射到虚拟地址.
void iounmap(void *addr);
3.访问函数
a.读
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
b.写
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
c.其他
void ioread8_rep(void *addr, void *buf, unsigned long count);
void ioread16_rep(void *addr, void *buf, unsigned long count);
void ioread32_rep(void *addr, void *buf, unsigned long count);
void iowrite8_rep(void *addr, const void *buf, unsigned long count);
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);
void memset_io(void *addr, u8 value, unsigned int count);