现在的位置: 首页 > 综合 > 正文

5-5 Linux内存、IO与实例

2013年03月11日 ⁄ 综合 ⁄ 共 6331字 ⁄ 字号 评论关闭

5-5 Linux内存、IO与实例讲解

什么是物理地址

什么是虚拟地址

物理地址与虚拟地址的关系

 

Linux内存分配的常用方法及区别

I/O端口和I/O内存访问流程

 

地址类型

物理地址和页

内存映射和页结构

页表

 

1、用户虚拟地址(2^32

  1.1用户空间程序所有能看到的常规地址

  1.2每个进程都有自己的虚拟空间

2、物理地址:该地址在处理器和系统内存之间使用

3、总线地址

  3.1该地址在外围总线和内存之间使用

  3.2它实现总线和主内存之间的重新映射

  3.3通常他们与处理器使用的物理地主相同。

4、内核逻辑地址

  4.1内核逻辑地址组成了内核的常规地址

  4.2该地址映射了部分(或全部)内存,并经常被视为物理地址

  4.3与物理地址是线性映射(一一映射的)(且是连续的)

  4.4例如:kmalloc返回的是逻辑地址

5、内核虚拟地址

  5.1内核虚拟地址和逻辑地址的相同之处在于,他们都将内核空间的地址映射到物理地址上。

  5.2与物理地址不必是线性映射关系(不一定是连续的)

  5.3例如:vmallockmap都是返回内核虚拟地址

6、地址的转化

  6.1 __pa(logical-addr)//逻辑地址–>物理地址

  6.2 __va(vphysical-addr) //物理地址->逻辑地址

7、虚拟地址

7.1Linux操作系统采用虚拟内存管理技术,使得每个进程都有独立的进程地址空间,该空间的大小为3G,用户看到和接触到的都是虚拟地址,无法看到时间的物理地址。利用这种虚拟的地址不但能起到保护操作系统的作用,而且更重要的是用户程序可使用比实际物理内存更大的地址空间。

7.2Linux4G的虚拟地址空间划分为两个部分:用户空间和内核空间。

 用户空间:0x0—0xBF
FF FF FF
0-3G

 内核空间:0xC—0xFF
FF FF FF
3G-4G

7.3用户进程通常情况下能访问到用户空间的虚拟地址,不能访问内核空间。例外情况是用户进程通过系统调用间接访问内核空间。

8、物理地址和页

 8.1物理地址被分成离散的大小相等单元,称为页

 8.2Linux系统内部许多对内存的操作都是基于页的

 8.3每个页的大小通常为4096个字节,集体的大小在<asm/page.h?中使用PAGE_SIZE定义。

 8.4内存地址,物理是虚拟的还是物理的,他们都为分为页号和一个页内的偏移量。

    31-12:页号,11-0PAGE_SHIFT页内偏移量。

 8.5页帧号:忽略地址偏移量,并将除去偏移量的剩余位移到右端,称该结果为页帧号。

9、内存映射和页结构

  9.1 page的数据结构:struct
page{
};

     <linux\mm.h>

     Atomic_t count; //对该页的访问计数。当计数值为0时,该页将放回给空闲链表。

     Void* virtual;//如果页面被映射,则指向页的内核虚拟地址;如果未被映射为NULL

     Unsigned long flags;

        描述页状态的一系列标志

        PG_locked表示内存中的页已经被锁住

        PG_reserved
表示禁止内存管理系统访问该页。

   9.2内核维护了一个或者多个page结构数组,用用来跟踪系统中的物理内存。

   9.3 page结构指针与虚拟地址之间进行转换

      Struct page* virt_to_page(void* kaddr);//内核逻辑地址
->page
结构指针

      Struct page* 
pfn_to_page(int pfn);//
页帧号->page结构指针

    

      Void* kmap(struct page *page);//page结构指针->内核虚拟地址

      Void kunmap(struct 
page*  page);//
释放映射

 

      //基于原子操作的kmap

      Void* kmap_atomic(struct page *page,enum km_type type);//type参数指定使用哪个槽(专用页表入口)

      Void kunmap_atomic(void* addr,enum km_type type);//KM_USER0KM_USER1(针对在用户空间中直接运行的代码)
KM_IRQ0
KM_IRQ1(针对中端处理程序)

10、页表

10.1通常处理器必修使用某种机制,将虚拟地址转换为相应的物理地址,这个机制被称为页表。

10.2页表是一个多层树形结构,结构化的数组中包含了虚拟地址到物理地址的映射和相关的标志位。

10.3幸运的是,对驱动程序开发者来说,在2.6版内核中删除了对页表直接操作的需求。

11、分配内存

11.1用户空间内存动态申请:malloc/free

11.2内核空间内存动态申请:

    Kmalloc()

    __get_free_page()和相关函数

    Vmalloc()极其辅助函数

12 kmalloc()函数

   Kmalloc申请的内存位于物理内核映射区域,而且在物理上也是连续的,与真实物理地址至于一个固定的偏移。

   #include<linux/slab.h>

   void* kmalloc(size_t size,int flags);

    size:分配的内存大小(字节数)

    flags: GRP_KERNEL:由运行在内核态的进程调用,分配内存,可能引起睡眠。说明该内存分配是有运行在内核态的进程调用。也就是说,调用它的函数是属于某个进程的,当空闲内存太少时,kmalloc函数会使当前进程进入睡眠,等待空闲页的出现。

         GRP_ATOMIC:在中断处理函数、tasklet、内核定时器和持有自旋锁的时候申请内核内存,必修使用GFP_ATOMIC分配标志。

13、后备高速缓存:设备驱动程序常常会反复地分配很多同一个大小的内存块。为了满足这样的应用,内核实现了这种形式的内存池,通常称为后备高速缓存(lookaside cache

14_get_free_page和相关函数

14.1如果模块需要分配打开的内存,那么使用面向页的分配技术会更好

14.2分配页面函数或宏

   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);//
分配页面数为2^order ,分配若干个连续的页面,返回指向该内存区域的指针,但也不清零这些内存区域

     14.3释放页面函数

         void free_page(unsigned long addr);

         void free_pages(unsigned long addr,unsigned long order); //分配页面数为2^order

15vmalloc及其辅助函数

15.1 vmalloc分配虚拟地址空间的连续区域,但这段区域在物理上可能是不连续的。

15.2 vamlloc不能用在原子上下文。因为它的内部实现使用了标志位GFP_KERNELkmalloc

#include<linux/vmalloc.h>

void* vmalloc(unsigned long size);

void vfree(void* addr);

   15.3 vmalloc
分配得到的地址是不能再微处理器之外使用的,当驱动程序需要真正的物理地址时(像外设用以驱动系统总线的DMA地址),就不能使用vmalloc;

   15.4
使用vmalloc函数的正确场合是在分配一大块连续的、只在软件中存在的、用于缓冲的内存区域的时候。

   15.5因为vmalloc不但获取内存,还要建立页表,它的开销比__get_free_pages大,因此,用vmalloc函数分配仅仅一夜的内存空间时不值得。

   15.6通过vmalloc获得的内存使用起来效率不高,在大多数情况下不鼓励使用。尽可能直接与单个的页面打交道。

16vmalloc及其辅助函数

   void* ioreamap(unsigned long offset,unsigned long size);

   void 
iounmap(void* addr);

   16.1
vmalloc一样,ioremap也建立新的页表,但和vmalloc不同的是,ioremap并不实际分配内存。

   16.2
使用ioremap()函数将设备所处的物理地址映射到虚拟地址。

   16.3
为了保持可移植性,不应把ioremap返回的地址当做指向内存的指针而直接访问。相反应使用readb或其他I/O函数(完成设备内存映射的虚拟地址的读写)。

   16.4vmallockmalloc的区别

       kmalloc使用的(虚拟)地址范围与物理内存是一一对应的;vmalloc使用的地址范围完全是虚拟的,每次分配都要通过对页表的设置来建立(虚拟)内存区域。vmalloc申请的内存不一定是连续的

 

      Vmalloc分配得到的地址是不能再微处理器之外使用的,因为他们只在处理器的内存单元上才有意义。当驱动程序需要真正的物理地址时(像外设用以驱动系统总线的DMA地址),就不能使用vmalloc

 

      通常,kmalloc分配小于128KB的内存,vmalloc可以分配更大的内存;

       

      vmalloc不能再原子上下文中使用,因为它的内部实际调用了kmalloc(size,GFP_KERNEL);

17、虚拟地址与物理地址关系

 17.1内核虚拟地址转化为物理地址

     #define __pa(x) 
((unsigned long)(x)-PAGE_OFFSET)//
物理地址=虚拟地址-偏移地址(通常为3GB

     extern inline unsigned long virt_to_phys(volatile void* address){

         return __pa(address);

     }

  17.2物理地址转化为内核虚拟地址

      #define 
__va(x) ((void*)((unsigned long)(x)+PAGE_OFFSET)) //
虚拟地址=物理地址+偏移量(通常为3GB

      extern inline void* 
phys_to_virt(unsigned long address){

          return __va(address);

      }

18I/O端口和I/O内存访问接口

   18.1 I/O端口的操作(asm-generic/io.h

      unsigned inb(unsigned 
port);//
读字节端口(8位宽)

      void outb(unsigned char byte,unsigned port);//写字节端口(8位宽)

 

      unsigned inw(unsigned 
port);//
读字端口(16位宽)

      void outw(unsigned char byte,unsigned port);//写字端口(16位宽)

 

      unsigned inl(unsigned 
port);//
读长字端口(32位宽)

      void outl(unsigned char byte,unsigned port);//写长字端口(32位宽)

 

      unsigned insb(unsigned port,void* addr, unsigned long count);//读一串字节

      void outsb(unsigned port,void* addr, unsigned long count);//写一串字节

 

      unsigned insw(unsigned port,void* addr, unsigned long count);//读一串字

      void outsw(unsigned port,void* addr, unsigned long count);//写一串字

 

      unsigned insl(unsigned port,void* addr, unsigned long count);//读一串长字

      void outsl(unsigned port,void* addr, unsigned long count);//写一串长字

      

 

   18.2 I/O内存(asm-generic/io.h

       在内核中访问I/O内存之前,需要先使用ioremap()函数将设备所处的物理地址映射到虚拟地址,ioremap()的原型如下(asm-generic/io.h):

       Void* ioremap(unsigned long offset,unsigned long size);

       

       访问函数:

       unsigned int ioread8(void* addr); 
//addr
为通过ioremap获取的地址

       unsigned int ioread16(void* addr);

       unsigned int ioread32(void* addr);

 

       unsigned readb(address);

       unsigned readw(address);

       unsigned readl(address);

 

       void iowrite8(u8 value,void* addr);

       void iowrite16(u16 value,void* addr);

       void iowrite32(u32 value,void* addr);

 

       void writeb(unsigned value,address);

       void writeb(unsigned value,address);

       void
writeb(unsigned value,address);

       

   18.3 I/O端口申请与释放

       

   18.4 I/O内存的申请与释放

       

   18.5
上述request_region()request_men_region()都不是必须的,建议使用。其任务是检查申请的资源是否可用,如果申请可用则申请成功,并标志为已经使用,启动驱动想再次申请该资源时就会失败。

 19I/O端口和I/O内存操作函数代码分析

      

20I/O端口的访问流程

   (1)request_region()  
//
在设备驱动模块加载或open( )函数中进行

    (2)inb()outb() //在设备驱动中初始化、read()write()ioctl()等函数中进行

   3release_region()
//
在设备驱动模块卸载或release()函数中进行

21I/O内存的访问流程

    1request_mem_region()//在设备驱动模块加载或open(
)
函数中进行

     (2) 
ioremap()          //

抱歉!评论已关闭.