5-5 Linux内存、IO与实例

来源:互联网 发布:快递软件是什么 编辑:程序博客网 时间:2024/05/18 22:55

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 FF0-3G

 内核空间:0xC—0xFF FF FF FF3G-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_IRQ0KM_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.1vmalloc一样,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()          //在设备驱动模块加载或open( )函数中进行

     (3) ioread8()ioread16()ioread32()iowrite8()//在设备驱动中初始化、read()write()ioctl()等函数中进行

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

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

22、将设备地址映射到用户空间

   22.1用户空间不能直接访问设备

   22.2设备驱动程序中可实现mmap()函数,可使得用户空间能直接访问设备的物理地址

         mmap()将用户空间的一段内存与设备内存关联

         当用户访问用户空间的这段地址范围时,会转化为对设备的访问

   22.3 mmap()必须以PAGE_SIZE为单位进行映射

         内存只能以页为单位进行映射,并且进行页对齐,PAGE_SIZE的倍数大小进行映射。

23、将设备地址映射到用户空间

   mmap函数的原型

   int(*mmap)(struct file* flie,struct vm_area_strct* vma);

    

 24、实例

       24.1驱动程序

        (1)驱动程序代码\beep_ioremap\drive\beep_ioremap.c

#include <linux/errno.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/input.h>#include <linux/init.h>#include <linux/serio.h>#include <linux/delay.h>#include <linux/clk.h>#include <linux/miscdevice.h>#include <linux/gpio.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/uaccess.h>#include <mach/regs-clock.h>#include <plat/regs-timer.h> #include <mach/regs-gpio.h>//linux-2.6.32.2/arch/arm/mach-s3c2410/include/mach/regs-gpio.h#include <linux/cdev.h>#define DEVICE_NAME"beep"//Port GPBx  Register address declaration#define GPBCON (unsigned long)ioremap(0x56000010,4)  //IO映射物理地址0x56000010映射为一个虚拟地址并返回 GPB控制寄存器虚拟地址#define GPBDAT (unsigned long)ioremap(0x56000014,4)#define GPBUP  (unsigned long)ioremap(0x56000018,4)MODULE_AUTHOR("Hanson He");MODULE_LICENSE("Dual BSD/GPL");#define BEEP_MAGIC 'k'#define BEEP_START_CMD _IO (BEEP_MAGIC, 1)#define BEEP_STOP_CMD _IO (BEEP_MAGIC, 2)static unsigned int port_status =0;/* * Open the device; in fact, there's nothing to do here. */int beep_open (struct inode *inode, struct file *filp){return 0;}ssize_t beep_read(struct file *file, char __user *buff, size_t count, loff_t *offp){return 0;}ssize_t beep_write(struct file *file, const char __user *buff, size_t count, loff_t *offp){return 0;}/*void beep_stop( void ){//add your src HERE!!!//set GPB0 as outputs3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT);s3c2410_gpio_setpin(S3C2410_GPB(0),0);}void beep_start( void ){//add your src HERE!!!//set GPB0 as outputs3c2410_gpio_pullup(S3C2410_GPB(0),1);s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT);s3c2410_gpio_setpin(S3C2410_GPB(0),1);}*/void beep_start( void ){/*config GPBCON, set GPB0 as output port*/ port_status = readl(GPBCON); //读port_status &= ~0x03; /*最低两位清零*/port_status |= 0x01;writel(port_status,GPBCON); //写port_status = readl(GPBDAT);port_status |= 0x01; // set 1 to GPB0writel(port_status,GPBDAT);}void beep_stop( void ){/*config GPBCON, set GPB0 as output port*/ port_status = readl(GPBCON);port_status &= ~0x03; port_status |= 0x01;writel(port_status,GPBCON);port_status = readl(GPBDAT);port_status &= ~0x01;// set 0 to GPB0writel(port_status,GPBDAT);}static int beep_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){//add your src HERE!!!switch ( cmd ) {case BEEP_START_CMD: {beep_start(); break;}case BEEP_STOP_CMD: {beep_stop(); break;}default: {break;}}return 0;}static int beep_release(struct inode *node, struct file *file){return 0;}/* * Our various sub-devices. *//* Device 0 uses remap_pfn_range */static struct file_operations beep_remap_ops = {.owner   = THIS_MODULE,.open    = beep_open,.release = beep_release,.read    = beep_read,.write   = beep_write,.ioctl   = beep_ioctl,};/* * There's no need for us to maintain any * special housekeeping info, so we just deal with raw cdevs. */static struct miscdevice misc = {.minor = MISC_DYNAMIC_MINOR, //动态设备号.name = DEVICE_NAME,.fops = &beep_remap_ops,};/* * Module housekeeping. */static int beep_init(void){int ret;ret = misc_register(&misc);printk("The device name is: %s\n", DEVICE_NAME);return 0;}static void beep_cleanup(void){misc_deregister(&misc);printk("beep device uninstalled\n");}module_init(beep_init);module_exit(beep_cleanup);

        (2)Makefie文件

ifeq ($(KERNELRELEASE),)#KERNELDIR ?= /your/target/source/directory/KERNELDIR ?=/home/student/linux-2.6.32.2PWD := $(shell pwd)modules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesmodules_install:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_installclean:rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions.PHONY: modules modules_install cleanelse    obj-m := beep_ioremap.oendif

      24.2用户程序

        (1)用户代码\beep_ioremap\user\main.c

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>#include <linux/ioctl.h>#define BEEP_MAGIC 'k'#define BEEP_START_CMD _IO (BEEP_MAGIC, 1)#define BEEP_STOP_CMD _IO (BEEP_MAGIC, 2)int main(){int i = 0;int dev_fd;dev_fd = open("/dev/beep",O_RDWR | O_NONBLOCK);if ( dev_fd == -1 ) {printf("Cann't open file /dev/beep\n");exit(1);}printf("Start beep\n");ioctl (dev_fd, BEEP_START_CMD,0);getchar();ioctl (dev_fd, BEEP_STOP_CMD,0);printf("Stop beep and Close device\n");close(dev_fd);return 0;}

        (2)Makefie文件

KERNELDIR ?=/home/student/linux-2.6.32.2all: beep_test beep_test : main.c#arm-linux-gcc -I$(KERNELDIR) -s -Wl,-warn-common --static -o $@ $^arm-linux-gcc -I$(KERNELDIR) -o $@ $^clean :rm beep_test

声明:本文非原创,整理自申嵌。

 

 

原创粉丝点击