linux 进城 io字节,(2)linux内核之内存分配与IO口操作

/***************************************************分配内存******************************************************************/

/*

*linux对内存的管理总体上可以分为两大类:一是物理内存的管理;二是对虚拟内存的管理,前者用于特定的平台架构上的实际物理内存空间的管理,后者用于特定的处理器体系架构上的虚拟地址空间的管理。

*

*linux的物理内存管理:其引入了内存节点(node)、内存区域(zone)和内存页(page)的概念,其对物理内存的管理总体上又可以分成两大部分:最底层实现的是页面级内存管理,然后是基于页面级管理之上的slab内存管理

*/

#include

void kmalloc(size_t size, int flags); //size为要分配的内存大小,flags为分配标志

//kmalloc分配的内存区域仍然保持原有的数据,并没有清零,它分配的内存区域在物理内存中也是连续的

//flags标志的取值常用的有以下几种:

GFP_ATOMIC   //用于在中断处理例程或其他运行于进程上下文之外的代码中分配内存,不会休眠

GFP_KERNEL   //内核内存的通常分配方法,可能引起休眠,最常用

GFP_USER     //用于用户空间页分配内存,可能会休眠

//上面的标志可以和下面常用标志“或”起来使用,下面这些标志控制如何进行分配

__GFP_DMA        //该标志请求分配发送在可进行DMA的内存区域

__GFP_COLD       //对于DMA读取的页面分配,可使用这个标志

__GFP_REPEAT     //如果分配不成功,使用这个标志时,它会重新尝试分配,但仍有可能失败

__GFP_NORETRY    //告诉分配器,如果所请求的内存不可获得,就立即返回

/*

*linux内核把内存分为3个区段:可用于DMA的内存、常规内存以及高端内存,通常的内存分配发生在常规内存区

*内核负责管理物理内存,物理内存只能按页面进行分配

*   linux处理内存分配的方法是,创建一系列的内存对象池,每个池中的内存块大小是固定一致,处理分配请求时,就直接在包含有足够大的内存块的池中传递一个整块给请求者,对kmalloc能够分配的内存块大小

*存在一个上限,如果希望代码具有完整的可移植性,则不应该分配大于128K的内存

*/

/*后备高速缓存(能够反复使用内存池(设备驱动程序通常不会涉及到它))*/:

//linux内核的高速缓存管理有时称为“slab分配器”。

#include //slab分配器实现的高速缓存具有kmem_cache_t类型,可通过调用kmem_cache_creat创建:

kmem_cache_t *xxx_cache;

kmem_cache_t *kmem_cache_create(const char *name, size_t size,size_t offset,unsigned long flags,void (*constructor)(void *, kmem_cache_t *,unsigned long flags),

void (*destructor)(void *, kmem_cache_t *,unsigned long flags));  //该函数创建一个新的高速缓存对象,其中可以容纳任意数目的内存区域,这些区域的大小都相同,由size指定,

//name 参数和这个缓存关联并且作为一个在追踪问题时有用的管理信息; 通常, 它被设置为被缓存的结构类型的名子. 这个缓存保留一个指向

//name 的指针, 而不是拷贝它, 因此驱动应当传递一个指向在静态存储中的名子的指针(常常这个名子只是一个文字字串). 这个名子不能包含空格

//offset 是页内的第一个对象的偏移; 它可被用来确保一个对被分配的对象的特殊对齐, 但是你最可能会使用 0 表示使用默认值

//flags 控制如何进行分配并且是下列标志的一个位掩码:1:SLAB_NO_REAP设置这个标志保护缓存在系统查找内存时不会被减少. 设置这个标志通常是

//个坏主意; 重要的是避免不必要地限制内存分配器的行动自由:2:SLAB_HWCACHE_ALIGN这个标志需要每个数据对象被对齐到一个缓存行

//SLAB_CACHE_DMA这个标志要求每个数据对象在 DMA 内存区分配.

//函数的 constructor 和 destructor 参数是可选函数( 但是不能只有destructor 而没有 constructor ); 前者可以用来初始化新分配的对象,

//后者可以用来"清理"对象在它们的内存被作为一个整体释放回给系统之前.

//一旦某个对象的高速缓存被创建,就可以调用kmem_cache_alloc从中分配内存对象了:

void *kmem_cache_alloc(kmem_cache_t *cache,int flags); //cache是前面创建的高速缓存,flags同kmalloc的flags相同

void *kmem_cache_free(kmem_cache_t *cache,const void *obj); //释放一个内存对象

int *kmem_cache_destory(kmem_cache_t *cache); //释放高速缓存,注意检查返回状态

/******内存池(在内存分配不允许失败的使用它)*********/:

#include mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn,  mempool_free_t *free_fn, void *pool_data); //创建内存池对象,min_nr表示内存池应始终保持的已分配对象的最少数目

//对象的实际分配和释放由alloc_fn和free_fn函数处理,pool_data为它们的参数,其原型如下:

typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data);  //一般为mempool_alloc_slab函数,是内核已经定义好的函数

typedef void (mempool_free_t)(void *element, void *pool_data);   //一般为mempool_free_slab函数,是内核已经定义好的函数

//在建立内存池后,可通过下面函数分配和释放对象:

void * mempool_alloc(mempool_t *pool, gfp_t gfp_mask);   //分配内存池对象

void mempool_free(void *element, mempool_t *pool);  //释放内存池对象

int mempool_resize(mempool_t *pool, int new_min_nr, int gfp_mask);//调整mempool的大小,如果调用成功,将把内存池的大小调整为至少有new_min_nr个预分配对象

void mempool_destroy(mempool_t *pool);  //如果不需要内存池了,可使用它将其返还给系统

//注意:应尽量避免在驱动程序中使用mempool

/*********如果需要分配大块的内存,使用下面分配页面函数************/

get_zeroed_page(unsigned int flags);  //返回一个指向新页的指针并且将页面清零

__get_free_page(unsigned int flags);  //类似于 get_zeroed_page, 但是没有清零该页

__get_free_pages(unsigned int flags, unsigned int order);  //分配若干(物理连续)页面,返回一个指向第一个内存区第一个字节的指针, 但不清零页面

//flags同kmalloc的flags相同

//order是要申请或释放的页面数的以2为底的对数即log2N,如果你要一个页 order 为 0, 如果你请求 8 页就是 3.

//如果 order 太大(没有那个大小的连续区可用), 页分配失败,get_order 函数, 它使用一个整数参数, 可以用来从一个 size 中提取

//order(它必须是 2 的幂)给主机平台. order 允许的最大值是 10 或者 11 (对应于 1024 或者 2048 页), 依赖于体系. 但是, 一个

//order=10 的分配在除了一个刚刚启动的有很多内存的系统中成功的机会是小的.

//当不再需要使用页面时,可以使用下面函数之一来释放:

void free_page(unsigned long addr);

void free_pages(unsigned long addr, unsigned long order);

//基于页面的分配策略的优点实际不是在速度上,而是在于更有效地使用内存

/*********vmalloc及其辅助函数**********/:

//它分配虚拟地址空间的连续内存,这段区域在物理上可能是不连续的,vmalloc在发生错误时返回NULL,成功时返回一个指针,大多数情况下不鼓励使用vmalloc,它获得的内存使用起来效率不高

#include void *vmalloc(unsigned long size);

void vfree(void * addr);

void *ioremap(unsigned long offset, unsigned long size);  //它和vmalloc一样也需要新建页表,但其并不实际分配内存,它的返回值是一个特殊的虚拟地址,可以用来访问指定的物理内存区域,它更多的用于映射物理地址到内核空间

void iounmap(void * addr);

/*

*kmalloc和__get_free_pages返回的内存地址也是虚拟地址,其实际仍然要由MMU处理才能转换物理地址

*   用vmalloc分配得到的地址是不能在处理器之外使用的,因为它们在处理器的内存管理单元上才有意义,当驱动程序需要真正的物理地址时就不能使用vmalloc了

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

*/

/****************************************************end**********************************************************************/

/****************************************************使用I/O端口***************************************************************/

#include struct resource *request_region(unsigned long first, unsigned long n,const char *name); //申请I/O端口,这个函数告诉内核,我们要使用起始first的n个端口,参数name应该是设备的名称,

//分配成功,返回非NULL值,返回NULL那我们就不能使用这些端口,所有的端口分配可从/proc/ioports中得到

void release_region(unsigned long first, unsigned long n);  //如果不在使用某组I/O端口,调用它将这些端口返回给系统

int check_region(unsigned long first, unsigned long n);  //检查给定的I/O端口是否可用,如果不可用,返回负的错误码

#include

unsigned inb(unsigned port);

void outb(unsigned char byte, unsigned port);  //读或写字节端口( 8 位宽 ). port 参数定义为 unsigned long 在某些平台以及 unsigned short 在一些的平台上. inb 的返回类型也是跨体系而不同的

unsigned inw(unsigned port);

void outw(unsigned short word, unsigned port); //这些函数访问16位端口

unsigned inl(unsigned port);

void outl(unsigned longword, unsigned port); //这些函数访问 32位 端口. longword 根据不同的平台被定义为 unsigned long 或者unsigned int

void insb(unsigned port, void *addr, unsigned long count);

void outsb(unsigned port, void *addr, unsigned long count);//从内存地址addr开始连续读或写count数目的字节. 数据读自或者写入单个port 端口

void insw(unsigned port, void *addr, unsigned long count);

void outsw(unsigned port, void *addr, unsigned long count); //对16位端口连续读写16位数据

void insl(unsigned port, void *addr, unsigned long count);

void outsl(unsigned port, void *addr, unsigned long count); //对32位端口连续读写32位数据,它们直接将字节流从端口中读取或写入

struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);   //分配I/O内存区域,该函数从start开始分配len字节长的内存区域,如果成功,返回非NULL指针

void release_mem_region(unsigned long start, unsigned long len);  //释放用上面函数分配的I/O内存区域

int check_mem_region(unsigned long start, unsigned long len);  //用来检查给定的I/O内存区域是否可用的老函数,这个函数不安全,应避免使用

#include #define ioremap(cookie,size)  __arm_ioremap(cookie, size, MT_DEVICE)  /* 为I/O内存区域分配虚拟地址,一旦调用这个函数后,设备驱动程序即可访问任意的I/O内存地址了 */

#define iounmap(cookie)   __iounmap(cookie)  /* 取消映射 */

#define ioread8(p) ({ unsigned int __v = __raw_readb(p); __v; })   /* 从I/O内存p读取8位数据 */

#define ioread16(p) ({ unsigned int __v = le16_to_cpu((__force __le16)__raw_readw(p)); __v; })/* 从I/O内存p读取16位数据 */

#define ioread32(p) ({ unsigned int __v = le32_to_cpu((__force __le32)__raw_readl(p)); __v; })/* 从I/O内存p读取32位数据 */

#define iowrite8(v,p) __raw_writeb(v, p)   /* 向I/O内存p写入8位数据v */

#define iowrite16(v,p) __raw_writew((__force __u16)cpu_to_le16(v), p) /* 向I/O内存p写入16位数据v */

#define iowrite32(v,p) __raw_writel((__force __u32)cpu_to_le32(v), p) /* 向I/O内存p写入32位数据v */

#define memset_io(c,v,l) _memset_io(__mem_pci(c),(v),(l))    /* 将c开始l个I/O内存设置值v*/

#define memcpy_fromio(a,c,l) _memcpy_fromio((a),__mem_pci(c),(l)) /*从I/O内存c拷贝l个数据到a*/

#define memcpy_toio(c,a,l) _memcpy_toio(__mem_pci(c),(a),(l)) /*从内存a拷贝l个数据到I/O内存c*/

下面是老的内存读写函数,不推荐使用:

#define readb(c) ({ __u8  __v = __raw_readb(__mem_pci(c)); __v; }) /* 写8位数据 */

#define readw(c) ({ __u16 __v = le16_to_cpu((__force __le16) \     /* 写16位数据 */

__raw_readw(__mem_pci(c))); __v; })

#define readl(c) ({ __u32 __v = le32_to_cpu((__force __le32) \       /* 写32位数据 */

__raw_readl(__mem_pci(c))); __v; })

#define writeb(v,c)  __raw_writeb(v,__mem_pci(c))  /* 写8位数据 v到I/O c */

#define writew(v,c)  __raw_writew((__force __u16) \   /* 写16位数据v到I/O c  */

cpu_to_le16(v),__mem_pci(c))

#define writel(v,c)  __raw_writel((__force __u32) \     /* 写32位数据v到I/O c */

cpu_to_le32(v),__mem_pci(c))

void *ioport_map(unsigned long port, unsigned int count); //该函数重新映射count个I/O端口,使其看起来像I/O内存,此后驱动程序可在该函数返回的地址上使用ioread8及其同类函数了,

//需要注意的是在重映射之前,必须通过request_region分配这些端口

void *ioport_unmap(void *addr); //撤销上面的映射

/****************************************************end**********************************************************************/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值