由于一些原因,需要了解一下linux管理I/O资源的机制。这里也解释一下什么是I/O资源,如果做过嵌入式开发相关的工作,应该对arm cpu访问串口寄存器的方式有所了解。我们在实现串口的设备驱动时,其实是根据spec实现一个串口设备的数据结构,然后将数据结构的指针指向串口设备的基址。这一片区域只能由串口驱动的代码访问。cpu通过这种方式访问串口设备与cpu访问RAM类似,被称为内存映射方式(memory-mapped)。还有一种方式,例如在x86架构的cpu中,cpu具有一块独立的地址空间,这块区域只能通过cpu执行特殊的指令(inb/outb, etc)来访问,这种方式被称为I/O映射方式(I/O-mapped)。
在实时操作系统中,我们对于整个系统的地址空间的把控力度很足,并且一般情况下,整个硬件系统都是厂商定制且软件业务一旦确定,对于实时操作系统的代码修改不大,所以使用hardcode的方式既省时省力,效率还很高。但是,在linux中需要考虑兼容性和可移植性,所以针对不同的平台提供不同的接口会导致上一层的开发者非常苦恼。为管理I/O资源开发一套统一的接口就势在必行了。本着严谨的研究态度,我找到了linus在发现这个问题之后发送的邮件(Being more anal about iospace accesses…),从大师骂人的话语中也能了解到大师的思想,哈哈。
下面回归正题,我们从数据结构和接口两个层面了解一下linux中的I/O资源管理机制。作为内核接口的使用者,从接口先入手就再好不过了。
参考代码
linux/kernel/resource.c
include/linux/ioport.h
前段时间看了EOPL,养成了一个好习惯,即将接口和数据结构的实现分离开来。这样的好处当然就是可以随时修改数据结构的实现,而不会影响使用该数据结构的代码。linux中的设计也采用了这种方式,我只能说大佬的思路都是相似的。
现在我们明确了什么是I/O资源,所以我们需要操作的I/O资源其实就分两类:I/O-mapped的资源和memory-mapped的资源。所以我们需要用软件实现能描述这两种资源的数据结构,以及相关的api,通过操作数据结构去访问I/O资源
我将linux中与resource相关的api做了一个分类,如图1所示
按照EOPL中提供的思路,根据是否需要知道struct resource这个数据结构将api分成两类。作为I/O资源的使用者,我其实不关心resource的具体实现,只需要在想使用某个I/O资源时,我提供I/O资源的起始地址和长度,然后能够确保我是独占这个资源就好了。linux中提供了相应的api,如图1中所示。图中标红的rename_region
是个例外,这个api需要了解resource的数据结构。
devres api是带有引用计数自动回收内存机制的api,现代的驱动开发应该多使用devres api
api的功能
这里只简单的介绍一下常用的一些api,其他的请自行看源码。
普通api
request_region
:
request_mem_region
:
request_region
相同request_region
类似,不同点在新分配的资源节点会加入到iomem_resource资源树中。request_region
相同release_region
:
release_mem_region
:
release_region
类似。devres api
devm_request_region
:类似request_region
,多一个参数dev,一旦dev释放,使用该api申请的i/o资源自动释放。devm_request_mem_region
:类似request_mem_region
,多一个参数dev,一旦dev释放,使用该api申请的i/o资源自动释放。使用场景
在编写platform device driver的时候,我们使用如下api从dts中获取设备的物理基址
#include
void __iomem *devm_platform_ioremap_resource(struct platform_device *pdev, unsigned int index);
函数调用栈如下
devm_platform_ioremap_resource
-> devm_platform_get_and_ioremap_resource
-> platform_get_resource
-> devm_ioremap_resource
-> __devm_ioremap_resource
-> devm_request_mem_region /* 这里申请了资源 */
-> __devm_ioremap /* 这里对申请的资源做了重映射,返回的是resource覆盖物理地址对应的虚拟地址 */
所以,linux的i/o资源管理机制需要结合dts一起使用
devm_platform_ioremap_resource
,可以得到reg中物理地址对应的虚拟地址base。实现这些api实际上是实现了一种描述I/O资源的数据结构,linux中叫struct resource。这种实现的细节放在下一节中描述。其实这些api对于驱动开发者来说应该是透明的,这里我就简单的描述一下api的作用。
api的功能
request_resource
:
__request_resource
实现,该函数的功能是将需要使用new插入到root的子节点中,如果与资源树的子节点中已经申请的资源区域重合,则返回冲突的资源,如果没有冲突,则插入新的资源块后返回NULL。request_resource
函数插入成功返回0,反之返回-EBUSYrelease_resource
:
__release_resource
实现,该函数的功能是将资源块new从该资源树节点new所在的父节点的树中释放,如果第二个参数设置为true,那么该资源块节点的所有子节点都释放,反之,将该节点所有的子节点上升一级插入到原节点的父节点下。release_resource
是第二个参数设置为true的__release_resource
insert_resource
:
__insert_resource
实现,该函数的功能是将资源块new插入到资源树节点parent所在的资源树中。remove_resource
:
__release_resource
实现,传入的第二个参数为false。lookup_resource
:allocate_resource
:
linux中是以资源树的形式管理的I/O资源的,所以上一节中操作struct resource的api其实是对资源树操作的实现。这下就感受到接口和数据结构分离的好处。我完全可以使用其他类型的数据结构表示struct resource,重新实现相关的api,但是用户使用的api不会受到任何影响。当然linux对于资源树的实现也是相当精妙,下面我们就来着重研究一下struct resource的实现,当然仅包括对基本功能的分析,一些高级的属性有需要的话请自行分析哈。
首先在include/linux/ioport.h
中struct resource的定义如下:
struct resource {
resource_size_t start; /* I/O资源的起始物理地址 */
resource_size_t end; /* I/O资源的终止物理地址,闭区间 */
const char *name; /* 该I/O资源的名字 */
unsigned long flags; /* 描述此资源属性的标记 */
unsigned long desc;
struct resource *parent, *sibling, *child;
};
这个结构和b+树的实现类似,区别是资源树的每一层节点都用指针连起来了,而b+树只有最底一层的叶子节点用指针连接起来。
然后在linux/kernel/resource.c
中,实现了对I/O-mapped类型和memory-mapped类型的资源树,代码如下
struct resource ioport_resource = {
.name = "PCI IO",
.start = 0,
.end = IO_SPACE_LIMIT,
.flags = IORESOURCE_IO,
};
EXPORT_SYMBOL(ioport_resource);
struct resource iomem_resource = {
.name = "PCI mem",
.start = 0,
.end = -1,
.flags = IORESOURCE_MEM,
};
EXPORT_SYMBOL(iomem_resource);
i/o-mapped类型的资源的大小是[0, IO_SPACE_LIMIT],IO_SPACE_LIMIT的值由硬件决定,各个体系结构的core对应的值不一定相同,如图3所示。
memory-mapped类型的资源的大小期望是[0, 最大的物理地址],这里由于各个soc的内存大小各不相同所以将end设置为-1表示最大的物理地址。
这里结合上面的api,以PCI IO资源树为例,我画了图4,方便理解struct resource。
实现思路
图中,b和d,e节点是冲突的,但是b节点设置了特殊的属性,所以d,e节点变成了b节点的子节点,在pci 设备的driver中经常有这种情况。
在linux/kernel/resource.c
中,还有针对其他高级功能的代码,这里就不一一分析了,有兴趣的可以自己去分析,比如
static struct resource *bootmem_resource_free;
资源树去管理boot阶段内存热插拔的资源