【内存管理子系统】
1. 四大地址:物理地址、虚拟地址(线性地址)和逻辑地址
物理地址:出现在CPU地址总线上 的寻址物理内存的地址信号,是地址变换的最终结果。
虚拟地址:又叫线性地址,,在32位 CPU架构下,可以表示4G的地址空间,用16进制
表示就是0x00000000到0xffffffff。
逻辑地址:程序代码经过编译后 在汇编程序中使用的地址。
地址转换:
CPU要将一个逻辑地址转换为物理地址,需要两步:首先CPU利用段式内存管理单元,将
逻辑地址转换成线程地址,再利用页式内存管理单元,把线性地址最终转换为物理地址。
2. 段式管理
逻辑地址 = 段基地址 + 段内偏移量
PA = 段寄存器的值 * 16 + 逻辑地址
3. 页式管理
线性地址 被分为固定长度的组,称为页(page),每个组为一页。
物理页,也叫页框、页桢。分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与线性地址页 是相同的。
页的大小由页的偏移决定
4. linux内存管理
所有段的基地址均为0 由此可以得出,每个段的逻辑地址空间范围为0-4GB。因为每个段
的基地址为0,因此,逻辑地址与线性地址保持一致(即逻辑地址的偏移量字段的值与线性
地址的值总是相同的),在Linux中所提到的逻辑地址和线性地址(虚拟地址),可以认为
是一致的。看来,Linux巧妙地把段机制给绕过去了,而完全利用了分页机制。
5. linux进程地址空间
Linux操作系统采用虚拟内存管理技术,使得每个进程都有独立的进程地址空间,该空间是大小为3G,用户看到和接触的都是虚拟地址,无法看到实际的物理地址。利用这种虚拟地址不但能起到保护操作系统的作用,而且更重要的是用户程序可使用比实际物理内存更大的地址空间。
Linux将4G的虚拟地址空间划分为两个 部分——用户空间与内核空间。用户空间从0到0xbfffffff,内核空间从3G到4G。用户进程通常情况下只能访问用户 空间的虚拟地址,不能访问内核空间。例外情况是用户进程通过系统调用访问内核空间。
实际的物理内存只有当进程真的去访问新 获取的虚拟地址时,才会由“请页机制”产生“缺页”异常,从而进入分配实际页框的程序。该异常是虚拟内存机制赖以存在的基本保证——它会告诉内核去为进程分配物理页,并建立对应的页表,这之后虚拟地址才实实在在地映射到了物理地址上。
【内核地址空间分布】
直接映射区: 线性空间中从3G开始最大896M的区间,为直接内存映射区,该区域的线性地址和物理地址存在
线性转换关系:线性地址=3G+物理地址。
动态内存映射区: 该区域由内核函数vmalloc来分配,特点是:线性空间连续,但是对应的物理空间不一定连续。
vmalloc分配的线性地址所对应的物理页可能处于低端内存,也可能处于高端内存。
永久内存映射区:该区域可访问高端内存。访问方法是使用alloc_page(_GFP_HIGHMEM)分配高端内存页或者
使用kmap函数将分配到的高端内存映射到该区域。
固定映射区: 该区域和4G的顶端只有4k的隔离带,其每个地址项都服务于特定的用途,如ACPI_BASE等。
物理内存管理是控制其空闲的内存(get_free_page),虚拟内存管理是控制其已经用了的内存。
【进程的地址空间】
linux采用虚拟内存管理技术,每一个进程都有一个3G大小的独立的进程地址空间,这个地址空间就是用户空间。每个进程的用户空间都是完全独立、互补相干的。进程访问内核空间的方式:系统调用和中断。
创建进程等进程相关操作都需要分配内存给进程。这时进程申请和获得的不是物理地址,仅仅是虚拟地址。
实 际的物理内存只有当进程真的去访问新获取的虚拟地址时,才会由“请页机制”产生“缺页”异常,从而进入分配实际叶框的程序。该异常是虚拟内存机制赖以存在 的基本保证---它会告诉内核去为进程分配物理页,并建立对应的页表,这之后虚拟地址才实实在在的映射到了物理地址上。
vmalloc和kmalloc区别
1. kmalloc对应于kfree,分配的内存处于3GB~high_memory之间,这段内核空间与物理内存的映射一一对应,可以分配连续的物理内存;
vmalloc对应于vfree,分配的内存在VMALLOC_START~4GB之间,分配连续的虚拟内存,但是物理上不一定连续。
2. vmalloc() 分配的物理地址无需连续,而kmalloc() 确保页在物理上是连续的
3. kmalloc分配内存是基于slab,因此slab的一些特性包括着色,对齐等都具备,性能较好。物理地址和逻辑地址都是连续的。
4. 最主要的区别是分配大小的问题,比如你需要28个字节,那一定用KMALLOC,如果用VMALLOC,分配不多次机器就罢工了。
尽管仅仅在某些情况下才需要物理上连续的内存块,但是,很多内核代码都调用kmalloc(),而不是用vmalloc()获得内存。
这主要是出于性能的考虑。vmalloc()函数为了把物理上不连续的页面转换为虚拟地址空间上连续的页,必须专门建立页表项。
还有,通过vmalloc()获得的页必须一个一个的进行映射(因为它们物理上不是连续的),这就会导致比直接内存映射大得多
的缓冲区刷新。因为这些原因,vmalloc()仅在绝对必要时才会使用——典型的就是为了获得大块内存时,例如,当模块被动态
插入到内核中时,就把模块装载到由vmalloc()分配的内存上。
/*****************************分配内存*********************************************/
#include <linux/slab.h>
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 <linux/slab.h>
//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 <linux/mempool.h>
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 <linux/vmalloc.h>
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 <linux/ioport.h>
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 <asm/io.h>
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 <asm/io.h>
viod *ioremap(unsigned long phy_addr, unsigned long size);
//为I/O内存区域分配虚拟地址,一旦调用这个函数后,设备驱动程序即可访问任意的I/O内存地址了
viod *ioremap_nocache(unsigned long phy_addr, unsigned long size);
unsigned int ioread8(void *addr); //从I/O内存中读取数据
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
void iowrite8(u8 value, void *addr);//向I/O内存写入数据
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
void ioread8_rep(void *addr, void *buf, unsigned long count); //从给定的buf向addr读取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); //从给定的buf向addr写入count个数据
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);
void memset_io(void *addr, u8 value, unsigned int count);//将addr开始count个I/O内存设置值value
void memcpy_fromio(void *dest, void *source, unsigned int count); //从I/O内存拷贝数据
void memcpy_toio(void *dest, void *source, unsigned int count); //向I/O内存拷贝数据
下面是老的内存读写函数,不推荐使用:
unsigned readb(address); //读8位数据
unsigned readw(address); //读16位数据
unsigned readl(address); //读32位数据
void writeb(unsigned value, address); //写8位数据
void writew(unsigned value, address); //写16位数据
void writel(unsigned value, address); //写32位数据
void *ioport_map(unsigned long port, unsigned int count);
//该函数重新映射count个I/O端口,使其看起来像I/O内存,此后驱动程序可在该函数返回的地址上使用ioread8及其同类函数了,
//需要注意的是在重映射之前,必须通过request_region分配这些端口
void *ioport_unmap(void *addr); //撤销上面的映射
/***********************end****************************/
/*************设备驱动架构的分层与分离思想*****************/
/*
*设备驱动的分层思想:在设备驱动方便,往往为同类的设备设计一个框架,而在框架中的核心层则实现了该设备驱动通用的一些功能,同样的,如果在具体的
*设备驱动中不想使用或者由于硬件设计的关系不使用核心层的函数,它可以重载之,例子如下:
*/
return_type core_func(xxx_device *xxx_dev,param1,param2)
{
if(xxx_dev->funca)
//判断底层设备是否重载了funca(底层是否定义了该函数),如果重载了(底层定义了该函数),就调用底层提供的funca函数,否则直接使用通用层的代码
return xxx_dev->funca(.......);
/* 核心层通用的funca代码 */
.......
}
/*
*主机驱动与外设驱动分离思想:
* 该思想是将主机控制器驱动和外设驱动进行分离,外设的驱动与主机控制器的驱动不相关,主机控制器的驱动不关心外设,而外设驱动不关心主机控制器。
*这样做了以后,那么要将它们联系起来就需要设计一个核心层,外设只是通过核心层的通用API进行数据传输(核心层通常需要定义主机和设备使用的数据结构、
*将主机控制器和设备联系起来的代码,用于在两者之间传输数据的代码等等)。linux的SPI、I2C、USB、ASoC等子系统都典型地利用了这种分离的设计思想
*/
/*******************************end************************************/
/********************platform设备驱动************************************/
/*
* 在2.6内核的设备驱动模型中,总是关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。
* 在系统每注册一个设备的时候,会寻找与之匹配的驱动,在系统每注册一个驱动的时候,
* 会寻找与之相匹配的设备,而匹配由总线完成。
*
* linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,
* 而驱动称为platform_driver,所谓的platform_device并不是与字符设备、块设备和
* 网络设备并列的概念,而是linux系统提供的一种附加手段
*
*相关结构和接口函数在platform_device.h、ioport.h、platform.c
*/
/*=================平台设备相关的数据结构==================================*/
#include <linux/platform_device.h>
-----------------struct platform_device--------------------------------
platform_device结构体定义如下:
struct platform_device { //表示挂在platform总线的设备
const char * name;
//设备名,一定要与struct platform_driver->struct device_driver->name相同
int id; //支持的设备,通常取-1支持所有匹配的设备
struct device dev;
u32 num_resources; //设备所使用的各类资源数量
struct resource * resource; //资源
struct platform_device_id *id_entry;
};
---------------------------------------------------------------------
/*-------------------struct device---------------------------*/
//device结构:
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
struct device_type *type;
struct semaphore sem; /* semaphore to synchronize calls to
* its driver.
*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *driver_data; /* data private to the driver */
void *platform_data; /* Platform specific data, device
//可用于存放自己定义的与开发板相关的设备资源数据platform_data
core doesn‘t touch it */
struct dev_pm_info power;
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma‘able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma‘ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
/* arch specific additions */
struct dev_archdata archdata;
dev_t devt; /* dev_t, creates the sysfs "dev" */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
};
----------------------------------------------------------------------
/*--------------------struct resource--------------------------------*/
//resource(资源)结构:
struct resource { //表示设备使用的资源
resource_size_t start; //资源的开始值
resource_size_t end; //资源的结束值
const char *name; //
unsigned long flags;
//资源类型,在linux目录下ioport.h中定义,以IORESOURCE_开头,start、end会随着flags变化而变更
struct resource *parent, *sibling, *child;
};
//对resource的定义通常在BSP的板文件中实现,而在具体的设备中通过下面这个函数来获取资源:
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);
//type=flags,num为资源引索(从0开始,其最大值为同类资源的个数-1)
int platform_get_irq(struct platform_device *dev, unsigned int num); //获取中断号资源
----------------------------------------------------------------------------------------------
/*
*设备的硬件描述除了中断、内存等以外,可能还会有一些配置信息,这些配置信息也依赖于板所以不适合直接放置在设备驱动本身,
因此,platform也提供了platform_data的支持, platform_data
*的形式是自己定义的,如DM9000的platform_data为一个dm9000_plat_data结构体,我们就可以将MAC地址,总线宽度等放到
自己定义的platform_data_xxx中,然后保存到struct platform_device-> struct device dev.platform_data中,
使用的时候可以这样: struct platform_device *pdev=函数传进来的struct platform_device指针;
struct platform_data_xxx *pdatax=pdev->dev.platform_data
*
*对platform_device的定义通常在BSP的板文件中实现,在板文件中,将platform_device归纳为一个数组,最终通过
*platform_add_devices()函数统一注册,platform_add_devices()函数可以将平台设备添加到系统中,这个函数的原型为:
*/
int platform_add_devices(struct platform_device **devs, int num);
//devs为平台设备数组指针,num为平台设备数目,它的内部调用platform_device_register()函数注册单个的平台设备
int platform_device_register(struct platform_device *dev); //注册platform_device
void platform_device_unregister(struct platform_device *dev); //卸载platform_device
=================================================================================================
/*================================平台驱动相关数据结构=============================================*/
----------------------------------struct platform_driver--------------------------------------
//platform_driver结构体包含probe()、remove()、shutdown()、suspend()、resume()函数,通常需要由驱动实现:
struct platform_driver { //挂在platform总线上的设备的对应的驱动
int (*probe)(struct platform_device *); //具体的硬件相关的操作,注册等等具体的操作都可以在这个函数里面实现
int (*remove)(struct platform_device *); //与上面相反的操作
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state); //挂起函数,用于电源管理
int (*suspend_late)(struct platform_device *, pm_message_t state);
//也是挂起函数,不过它与suspend的区别是工作于中断都被禁止的情况下,而且仅有一个CPU是活跃的,绝大部分驱动不实现该函数
int (*resume_early)(struct platform_device *);
//该函数也是恢复函数,它与resume区别是工作于中断都被禁止的情况下,绝大部分驱动不实现该函数
int (*resume)(struct platform_device *); //恢复函数,用于电源管理
struct device_driver driver;
//driver->name一定要与与platform_device结构中设备名platform_device->name相同
struct platform_device_id *id_table;
};
---------------------------------------------------------------------------------------------
/*-------------------------------struct device_driver-------------------------------------*/
struct device_driver {
const char *name; //设备名,一定要与platform_device结构中设备名platform_device->name匹配
struct bus_type *bus;
struct module *owner; //THIS_MODULE
const char *mod_name; /* used for built-in modules */
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
struct attribute_group **groups;
struct dev_pm_ops *pm;
struct driver_private *p;
};
--------------------------------------------------------------------------------------------
//通常在suspend函数里会停止设备,并关闭给它提供的时钟,所以在suspend函数里经常看到下面的语句:
clk_disable(xxx->clk);
//而在resume函数中进行相反的操作:
clk_enable();
struct clk *clk_get(struct device *dev, const char *id); //获取时钟
void clk_put(struct clk *clk); //释放时钟
int clk_enable(struct clk *clk); //使能时钟
int clk_disable(struct clk *clk); //禁止时钟
unsigned long clk_get_rate(struct clk *clk); //获取时钟频率
long clk_round_rate(struct clk *clk,unsigned long rate); //试探时钟频率
int clk_set_rate(struct clk *clk,unsigned long rate); //设置时钟频率
//device_driver结构体:
#include <linux/device.h>
//系统中为platform总线定义了一个bus_type的实例platform_bus_type:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
//该成员函数确定了platform_device和platform_driver之间如何匹配,通过比较name字段是否相同,
//如果相同则驱动的probe函数来进一步进行设备的具体的初始化和注册
.uevent = platform_uevent,
.pm = PLATFORM_PM_OPS_PTR,
};
int platform_driver_register(struct platform_driver *drv); //注册platform_driver
void platform_driver_unregister(struct platform_driver *drv); //卸载platform_driver
//一般的模板:
static struct resource xxx_resource[] = {
[0] = {
.start = xxxx,
.end = xxxx,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = xxx,
.end = xxx,
.flags = IORESOURCE_IRQ,
},
............
............
};
static struct platform_device xxx_device={
.name = "xxx",
.id = -1,
.num_resources = ARRAY_SIZE(xxx_resource),
.resource = xxx_resource,
.dev = {
.release = xxx_release,
},
};
static int xxx_init(void)
{
platform_device_register(&xxx_device);
return 0;
}
static void xxx_exit(void)
{
platform_device_unregister(&xxx_device);
}
module_init(xxx_init);
module_exit(xxx_exit);
以上是device:
//下面是具体的设备驱动程序实现:
static struct platform_driver xxx_device_driver={
.probe=xxx_prob,
.remove=__devexit_p(xxx_remove),
.....
.driver={
.name="xxx",
.owner=THIS_MODULE,
.......
}
};
static int __devinit xxx_probe(struct platform_device *pdev)
{
申请设备号;
为设备设备描述结构体分配内存;
注册设备;
。。。。。
}
static int __devexit xxx_remove(struct platform_device *pdev)
{
卸载设备号;
为设备设备描述结构体释放内存;
卸载设备;
。。。。。
}
static int xxx_init(void)
{
platform_driver_registe(&xxx_device_driver);
return 0;
}
static void xxx_exit(void)
{
platform_driver_unregister(&xxx_device_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
//备注:
1:在devs.c中定义了各种平台的platform_device和资源,可在该文件下添加相应的设备和资源
2:在arch/arm/mach-2410/mach-smdk2410.c中的
static struct platform_device *smdk2410_devices[] __initdata = {
&xxx_device,
};
//以完成设备的注册。这样的platform_device_register(&xxx_device)和platform_device_unregister(&xxx_device)就可以省略不用自己做了
/*************************end***************************************************/
/**********************输入子系统*********************************************/
/*
* 输入设备(如按键、键盘、触摸屏、鼠标等)是典型的字符设备,其一般的工作机理是底层在按键、
* 触摸屏等动作发生时产生一个中断(或驱动通过timer定时查询),然后CPU通过SPI、IIC或外部
* 存储器总线读取键值、坐标等数据,放入一个缓冲区,字符设备驱动管理缓冲区,而驱动的read接口让用户可以读取键值、坐标等数据。
* 显然,在这些工作中,只是中断、读键值或坐标值是设备相关的,而输入事件的缓冲区管理以及字
* 符设备驱动的file_operations接口则对输入设备是通用的,基于此,内核设计了输入子系统。
*
*相关结构和接口函数在input.h、input.c
*/
#include <linux/input.h>
//输入设备结构:
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //能产生哪类事件事件
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //能产生哪些按键事件
unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //能产生哪些相对位移事件
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; //能产生哪些绝对位移事件
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;
int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);
int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);KEY_L
struct ff_device *ff;
unsigned int repeat_key;
struct timer_list timer;
int sync;
int abs[ABS_MAX + 1];
int rep[REP_MAX + 1];
unsigned long key[BITS_TO_LONGS(KEY_CNT)];
unsigned long led[BITS_TO_LONGS(LED_CNT)];
unsigned long snd[BITS_TO_LONGS(SND_CNT)];
unsigned long sw[BITS_TO_LONGS(SW_CNT)];
int absmax[ABS_MAX + 1];
int absmin[ABS_MAX + 1];
int absfuzz[ABS_MAX + 1];
int absflat[ABS_MAX + 1];
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
struct input_handle *grab;
spinlock_t event_lock;
struct mutex mutex;
unsigned int users;
int going_away;
struct device dev;
struct list_head h_list;
struct list_head node;
};
//所有的输入事件,内部都用统一的input_event结构来描述:
struct input_event {
struct timeval time; /* 事件发生的事件 */
__u16 type; //事件的类型(按键类事件、位移类事件等等)
__u16 code; //事件的值
__s32 value; //1表示事件发生,0表示事件未发生
};
//分配与释放:
struct input_dev *xxx_dev;
struct input_dev *input_allocate_device(void); //分配一个输入设备即分配input_dev 结构体,表征一个输入设备
void input_free_device(struct input_dev *dev); //释放一个输入设备即释放input_dev 结构体,
1:注意释放设备必须要在其他资源释放了并且在设备注销了之后,要不然会产生段错误
//注册与注销:
int __must_check input_register_device(struct input_dev *dev); //注册一个输入设备
void input_unregister_device(struct input_dev *dev); //注销一个输入设备
//设置能产生的事件:
set_bit(nr,p); //设置能产生哪类事件及该类事件中能产生哪些事件
//nr为事件类型或哪些事件(比如:EV_KEY、KEY_L等)
//p为事件类型,在上面的struct input_dev结构中定义(比如evbit)。驱动程序在模块加载函数中告知input子系统它支持哪些事件
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code); //设置此输入设备可告知的事件
//报告输入事件接口:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
//报告指定type、code的输入事件,type在input.h中定义
void input_report_key(struct input_dev *dev, unsigned int code, int value); //报告键值
void input_report_rel(struct input_dev *dev, unsigned int code, int value); //报告相对坐标
void input_report_abs(struct input_dev *dev, unsigned int code, int value); //报告绝对坐标
void input_sync(struct input_dev *dev); //报告同步事件,说明以上的事件是一组事件
//一般的输入子系统驱动程序流程:
1:分配一个input_dev结构体
2:设置结构体成员(能产生哪类事件以及产生该类事件中的哪些事件等)
3:注册
4:硬件相关的代码,在其中上报事件
/*****************************************end*******************************************/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
MODULE_LICENSE("Dual BSD/GPL");
static int driver_probe(struct platform_device *dev)
{
printk("platform: match ok!\n");
return 0;
}
static int driver_remove(struct platform_device *dev)
{
printk("platform: driver remove\n");
return 0;
}
static void device_release(struct device *dev)
{
printk("platform: device remove\n");
}
struct platform_device test_device = {
.id = -1,
.name = "test_device",
.dev.release = device_release,
};
struct platform_driver test_driver = {
.probe = driver_probe,
.remove = __devexit_p(driver_remove),
.driver = {
.name = "test_device",
},
};
static int __init s5pc100_platform_init(void)
{
platform_device_register(&test_device);
platform_driver_register(&test_driver);
printk("platform: driver installed\n");
return 0;
}
static void __exit s5pc100_platform_exit(void)
{
platform_driver_unregister(&test_driver);
platform_device_unregister(&test_device);
printk("platform: driver uninstalled!\n");
}
module_init(s5pc100_platform_init);
module_exit(s5pc100_platform_exit);
【头文件led.h】
【主程序led_drivers.c】
/*
* 自动创建设备文件结点(udevfs/mdev)
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
struct led_t {
// 设备类型(字符设备、块设备、网络设备)
struct cdev led_cdev;
// 设备号
dev_t devno;
// 1.
// 设备类
struct class *class;
// 1.
// 设备
struct device *dev;
// 设备本身特征
// 操作设备的寄存器的虚拟地址
};
int led_open(struct inode *inode, struct file *filp)
{
printk("char open\n");
return 0;
}
long led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
long ret = 0;
switch (cmd) {
default:
ret = -ENOTTY;
break;
}
return ret;
}
int led_release(struct inode *inode, struct file *filp)
{
printk("char release\n");
return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.unlocked_ioctl = led_ioctl,
.release = led_release,
};
int led_probe(struct platform_device *dev)
{
int ret = 0;
struct led_t *led;
printk("platform: match ok!\n");
led = (struct led_t*)kmalloc(sizeof(struct led_t), GFP_KERNEL);
if (NULL == led) {
ret = -ENOMEM; // 返回内存操作错误给内核
goto exit;
}
memset(led, 0, sizeof(struct led_t));
platform_set_drvdata(dev, led);
cdev_init(&led->led_cdev.ops, &fops);
led->led_cdev.owner = THIS_MODULE;
ret = alloc_chrdev_region(&led->devno, 0, 1, "led device");
if (ret) {
goto alloc_chrdev_region_err;
}
ret = cdev_add(&led->led_cdev, led->devno, 1);
if (ret) {
goto cdev_add_err;
}
// 1. 创建设备类
/*
* @brief 创建设备类(/sys/cloass/类名)
* @param[in] owner 类的所有者(指模块)
* @param[in] name 类名
* @return 类结构指针
*/
// struct class *class_create(struct module *owner, const char *name)
led->class = class_create(THIS_MODULE, "led");
if (IS_ERR(led->class)) {
ret = PTR_ERR(led->class);
goto class_create_err;
}
// 2. 导出设备信息到应用空间(类的目录下)
/*
* @brief 导出设备信息到sysfs
* @param[in] class 设备导出到的类
* @param[in] parent 设备的父设备
* @param[in] devno 设备号
* @param[in] drvdata 驱动数据
* @param[in] fmt 设备名("led%d", minor)
* @notes 看/dev/led文件是否存在或/sys/class/类名/设备名/
*/
// struct device *device_create(struct class *class, struct device *parent, dev_t devno, void *drvdata, const char *fmt, ...);
led->dev = device_create(led->class, NULL, led->devno, NULL, "led");
if (IS_ERR(led->dev)) {
ret = PTR_ERR(led->dev);
goto device_create_err;
}
goto exit;
device_create_err:
class_destroy(led->class);
class_create_err:
cdev_del(&led->led_cdev);
cdev_add_err:
unregister_chrdev_region(led->devno, 1);
alloc_chrdev_region_err:
kfree(led);
exit:
return ret;
}
int led_remove(struct platform_device *dev)
{
struct led_t *led = (struct led_t *)platform_get_drvdata(dev);
printk("platform: driver remove\n");
//2.
/*
* @brief 销毁设备
* @param[in] class 设备所属的类
* @param[in] device 设备数据结构
*/
// void device_destroy(struct class *class, dev_t devno);
device_destroy(led->class, led->devno);
// 1.
/*
* @brief 销毁类
* @param[in] cls 需要销毁的类
*/
// void class_destroy(struct class *cls);
class_destroy(led->class);
cdev_del(&led->led_cdev);
unregister_chrdev_region(led->devno, 1);
kfree(led);
return 0;
}
struct platform_driver led_driver = {
.probe = led_probe,
.remove = __devexit_p(led_remove),
.driver = {
.name = "led_device",
},
};
int __init led_init(void)
{
platform_driver_register(&led_driver);
return 0;
}
void __exit led_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
注:以上总结部分选自韦东山群答疑助手:沈朝平《Linux驱动程序学习笔记》!非常感谢!