一段摘自《Linux设备驱动程序》的话:

    每种外设都通过读写寄存器进行控制。大部分外设都有多个寄存器,不管是内存地址空间还是I/O地址空间,这些寄存器的访问地址都是连续的。

    在硬件层,内存区域和I/O区域没有概念上的区别:它们都通过向地址总线和控制总线发送电平信号进行访问,在通过数据总线读写数据。一些CPU制造厂商在它们的芯片中使用单一的地址空间,而另一些则为外设保留了独立的地址空间,以便和内存区分开来。一些处理器(主要是X86家族的)还为I/O端口的读写提供了单独的线路,并且使用特殊的CPU指令访问端口。

  I/O端口和I/O内存是两种概念上的方法,用以支持设备驱动程序和设备之间的通信。为使得各种不同的驱动程序彼此互不干扰,有必要事先为驱动程序分配端口和I/O内存范围。这确保几种设备驱动程序不会试图访问同样的资源。

6.6.1 资源管理

  我们首先看看管理资源的数据结构和函数。

  1. 树数据结构

  Linux提供了一个通用框架,用于在内存中构建数据结构。这些结构描述了系统可用的资源,使得内核代码能够管理和分配资源。注意,其中关键的数据结构式resource,定义如下:

/* ./include/linux/ioport.h */
struct resource {
resource_size_t start;
resource_size_t end; // start 和 end 通常表示某个地址空间中的一个区域
const char *name; // 资源名称,实际与内核无关,只是在以可读形式输出资源列表(在proc文件系统中)时比较有用
unsigned long flags; // 更准确的描述资源及其当前状态
struct resource *parent, *sibling, *child; // 这3个指向resource的指针建立了一个树形层次的结构
};

  

        树形结构中的资源管理

从上图可以发现,resource的parent、child、sibling成员的规则如下:

  • 每个子结点只有一个父结点
  • 一个父结点可以有人以数目的子结点
  • 同一个父结点的所有子结点,会连接到兄弟结点连表上

在内存中,表示数据结构时,必须注意以下问题:

  • 尽管每个子结点都有一个指针指向父结点,但父结点只有一个指针指向第一个子结点。所有其他子结点都通过兄弟结点访问链表。
  • 指向父结点的指针同样可以为NULL,在这种情况下,说明已经没有更高层次的结点了。

  如何将层次结构一年关于设备驱动程序呢?我们来考察一个系统总线的例子,其附接了一块网卡。网卡支持两个输出,每个都分配一块特定的内存区域,用于数据的输入和输出。总线自身也有一个I/O内存区域,其中一些部分由网卡使用。

  该方案可以完美地融入到树形层次结构中。总线的内存区域理论上占用了0和1000之间的内存范围,充当根结点(最高的父结点)。网卡要求使用100和199之间的内存区域,这是根结点的一个子结点。网卡的子结点表示各个网络输出,分配I/O内存区分别为100到149和150到199.原来较大的资源区域被划分为了较小的部分,每次细分都表示了抽象模型中的一个层次。因此,子结点可用于将内存区划分为越来越小、功能越来越具体的部分。

  2. 请求和释放资源

  为确保可靠地配置资源,内核必须提供一种机制来分配和释放资源。一旦资源已经被分配,则不能由任何其他驱动程序使用。

  请求和释放资源,无非是从资源树中添加和删除而已。

  • 请求资源

  内核提供了一个__request_resource函数,用于请求一个资源区域。这函数需要一系列参数,包括一个指向父结点的指针,资源区域的起始地址和结束地址,表示该区域名称的字符串。

/* ./kernel/resource.c */

/* Return the conflict entry if you can't request it */
static struct resource * __request_resource(struct resource *root, struct resource *new)
{
resource_size_t start = new->start;
resource_size_t end = new->end;
struct resource *tmp, **p; if (end < start)
return root;
if (start < root->start)
return root;
if (end > root->end)
return root; // 判断要request source是否在root source的范围内
p = &root->child;
for (;;) { // 连续地扫描现存资源,将新资源添加到正确的位置,或者发现与已分配区域的冲突
tmp = *p;
if (!tmp || tmp->start > end) { // 在合适的位置插入
new->sibling = tmp;
*p = new;
new->parent = root;
return NULL;
}
p = &tmp->sibling; // 仅仅遍历同一层次的兄弟结点,不会扫描更底层子结点的链表
if (tmp->end < start)
continue;
return tmp;
}
} /**
* request_resource_conflict - request and reserve an I/O or memory resource
* @root: root resource descriptor
* @new: resource descriptor desired by caller
*
* Returns 0 for success, conflict resource on error.
*/
struct resource *request_resource_conflict(struct resource *root, struct resource *new)
{
struct resource *conflict; write_lock(&resource_lock);
conflict = __request_resource(root, new);
write_unlock(&resource_lock);
return conflict;
} /**
* request_resource - request and reserve an I/O or memory resource
* @root: root resource descriptor
* @new: resource descriptor desired by caller
*
* Returns 0 for success, negative error code on error.
*/
int request_resource(struct resource *root, struct resource *new)
{
struct resource *conflict; conflict = request_resource_conflict(root, new);
return conflict ? -EBUSY : ;
} EXPORT_SYMBOL(request_resource);
  • 释放资源
static int __release_resource(struct resource *old)
{
struct resource *tmp, **p; p = &old->parent->child;
for (;;) { // 遍历与old同一层的所有结点,发现与old相同的结点即old结点,删除该结点并链接链表
tmp = *p;
if (!tmp)
break;
if (tmp == old) {
*p = tmp->sibling;
old->parent = NULL;
return ;
}
p = &tmp->sibling;
}
return -EINVAL;
} /**
* release_resource - release a previously reserved resource
* @old: resource pointer
*/
int release_resource(struct resource *old)
{
int retval; write_lock(&resource_lock);
retval = __release_resource(old);
write_unlock(&resource_lock);
return retval;
}

6.6.2 I/O内存

  资源管理还有一个很重要的方面是I/O内存的分配方式,因为在所有平台上这都是与外设通信的主要方法(IA-32除外,其中I/O端口更为主要)。

  I/O内存不仅包括与扩展设备通信直接使用的内存区域,还包括系统中可用的物理内存和ROM存储器,以及包含在资源列表中的内存。

  看下kernel 3.08的iomem信息:

root@paramount:/ # cat /proc/iomem

00000000-0fefffff : System RAM

00010000-005c7e73 : Kernel code

005c7e74-00770d87 : Kernel data

10003000-100030ff : jz-rtc.0

10003000-100030ff : jz-rtc

10020000-1002006f : dsp.0

10020000-1002006f : dsp

10030000-10030fff : jz-uart.0

10033000-10033fff : jz-uart.3

10050000-10050fff : jz-i2c.0

10051000-10051fff : jz-i2c.1

10052000-10052fff : jz-i2c.2

10071000-10071000 : dsp.1

10071000-10071000 : dsp

13040000-130407ff : galcore register region

13050000-130517ff : jz-fb.0

13050000-130517ff : jz-fb

13080000-13087fff : jz-ipu.0

13080000-13087fff : jz-ipu

13200000-132effff : jz-vpu.0

13300000-13350000 : ovisp-camera

13300000-13350000 : ovisp-camera

13420000-1342ffff : jz-dma

13420000-1342ffff : jz-dma

13450000-13450fff : jzmmc_v1.2.0

13460000-13460fff : jzmmc_v1.2.1

13500000-1353ffff : jz-dwc2

13500000-1353ffff : jz-dwc2

13500000-1353ffff : dwc2

  arm或mips架构,对kernel而言,其实所有的系统资源就是memory,或者所有的系统资源就是iomem。

  比如,以我们使用的mips32架构为例:

/* kernel/arch/mips/xburst/soc-m200/common/setup.c */

void __init plat_mem_setup(void)
{
/* jz mips cpu special */
__asm__ (
"li $2, 0xa9000000 \n\t"
"mtc0 $2, $5, 4 \n\t"
"nop \n\t"
::"r"()); /* use IO_BASE, so that we can use phy addr on hard manual
* directly with in(bwlq)/out(bwlq) in io.h.
*/
set_io_port_base(IO_BASE);
ioport_resource.start = 0x00000000;
ioport_resource.end = 0xffffffff;
iomem_resource.start = 0x00000000;
iomem_resource.end = 0xffffffff; // iomem_resource是表示整个系统的memory空间,所有其他资源都是从属于该子资源的child或Grandson
setup_init();
init_all_clk(); #ifdef CONFIG_ANDROID_PMEM
/* reserve memory for pmem. */
board_pmem_setup();
#endif
return;
}

  在使用I/O内存时,分配内存区域并不是所需的唯一操作。取决于总线系统和处理器类型,可能必须将扩展设备的地址空间映射到内核地址空间中,才能访问该设备(陈志伟软件I/O映射)。这是通过使用ioremap内核函数适当的设置系统页表而实现的,内核源代码中有若干不同地方使用了该函数,其定义与体系结构相关。同样地,提供了特定于体系结构的iounmap函数来解除映射。

  在某种程度上,实现对进城页表的操作冗长而复杂。特别地,不同系统的实现有很大的差别,而且它对理解设备驱动程序并不重要,所以此处不再讨论其实现。一般地说,更重要的是:将一个物理地址映射到处理器的虚拟地址空间中,使得内核可以使用该地址。就设备驱动而言,这意味着扩展总线的地址空间映射到处理器的虚拟地址空间中,使得能够用普通内存访问函数操作总线/设备。

  1. request_source分配内存区域 2. ioremap建立扩展总线地址空间到内核虚拟地址空间的映射。

  通过上述两步,是否意味着在驱动程序中需要访问io时,可以直接对ioremap的指针进行反引用的?答案是否定的。

6.3.3 I/O端口

  I/O端口是一种与设备和总线通信的流行方法,特别是在IA-32平台上。

  可以使用 cat /proc/ioports 查看I/O端口。

【深入理解Linux内核架构】6.6 资源分配的更多相关文章

  1. 【深入理解Linux内核架构】3.3 页表

    页表:用于建立用户进程空间的虚拟地址空间和系统物理内存(内存.页帧)之间的关联. 向每个进程提供一致的虚拟地址空间. 将虚拟内存页映射到物理内存,因而支持共享内存的实现. 可以在不增加物理内存的情况下 ...

  2. 【深入理解Linux内核架构】第3章:内存管理

    3.1 概述 内存管理涵盖了许多领域: 内存中物理内存页的管理: 分配大块内存的伙伴系统: 分配小块内存的slab.slub.slob分配器: 分配非连续内存块的vmalloc机制: 进程的地址空间. ...

  3. 【深入理解Linux内核架构】3.2 (N)UMA模型中的内存组织

    内核对一致和非一致内存访问系统使用相同的数据结构.在UMA系统上,只使用一个NUMA结点来管理整个系统内存.而内存管理的其他部分则相信他们是在处理一个伪NUMA系统. 3.2.1 概述 内存划分为结点 ...

  4. 《深入理解linux内核架构》第二章 进程管理和调度

    2.1进程优先级 进程优先级 硬实时进程 软实时进程 抢占式多任务处理 2.2进程生命周期 用户太切换到核心态的办法 系统调用 中断 抢占调度模型优先级普通进程<系统调用<中断 普通进程可 ...

  5. 读书笔记之Linux系统编程与深入理解Linux内核

    前言 本人再看深入理解Linux内核的时候发现比较难懂,看了Linux系统编程一说后,觉得Linux系统编程还是简单易懂些,并且两本书都是讲Linux比较底层的东西,只不过侧重点不同,本文就以Linu ...

  6. 搭建《深入Linux内核架构》的Linux环境

    作者 彭东林 pengdonglin137@163.com 软件 Host: Ubuntu14.04 64 Qemu 2.8.0 Linux 2.6.24 busybox 1.24.2 gcc 4.4 ...

  7. 【读书笔记::深入理解linux内核】内存寻址【转】

    转自:http://www.cnblogs.com/likeyiyy/p/3837272.html 我对linux高端内存的错误理解都是从这篇文章得来的,这篇文章里讲的 物理地址 = 逻辑地址 – 0 ...

  8. 【读书笔记::深入理解linux内核】内存寻址

    我对linux高端内存的错误理解都是从这篇文章得来的,这篇文章里讲的 物理地址 = 逻辑地址 – 0xC0000000:这是内核地址空间的地址转换关系. 这句话瞬间让我惊呆了,根据我的CPU的知识,开 ...

  9. 《深入理解Linux内核》 读书笔记

    深入理解Linux内核 读书笔记 一.概论 操作系统基本概念 多用户系统 允许多个用户登录系统,不同用户之间的有私有的空间 用户和组 每个用于属于一个组,组的权限和其他人的权限,和拥有者的权限不一样. ...

随机推荐

  1. 几个递进的make file

    春节在家写的几个递进的make file,部分有点问题.接下来 有空我要把GNU make的手册看完.不然这方面太菜了. GNU make手册 都需要make先设置环境变量BUILD_MODE为run ...

  2. jmeter性能测试入门使用参数化

    我经常使用jmeter进行接口测试,这个工具还是很好用的.昨天收到一个需求,需要压测一下接口,jmeter进行接口测试,使用cvs文件进行多个数据参数化. 临时准备了一下发现忘记怎么做参数化了,自己百 ...

  3. Wireshark中遇到的epoch time

    使用Wireshark分析DNS时遇到的Epoch time 首先看一下Wireshark分析DNS的情况(如下图): 这是协议树的第一项,第一项中的第五行出现了Epoch Time,查阅资料之后才知 ...

  4. MySQL数据库练习题

    表结构 DROP DATABASE IF EXISTS test1; CREATE DATABASE test1; USE test1; ##部门表 #DROP IF EXISTS TABLE DEP ...

  5. ceph osd跟cpu进行绑定

    通过cgroup将ceph-osd进程与某一个 CPU core 绑定脚本: mkdir -p /sys/fs/cgroup/cpuset/ceph # cup number : ,,, = - ec ...

  6. Reinforcement Learning, Fast and Slow

    郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布! 1 DeepMind, London, UK2 University College London, London, UK3 Prince ...

  7. Spring boot程序的部署及运行

    将 spring boot 应用程序打包成 jar 包 我们使用 spring boot 的 maven 插件来构建管理整个应用程序,使用 mvn package 将应用程序打包成一个 jar 包 将 ...

  8. 5G边缘计算:开源架起5G MEC生态发展新通路

    摘要:‍‍本文尝试从‍‍边缘计算的角度来阐述了‍‍为什么‍‍要把边缘计算当做一种新的生产关系来构建,‍‍以及如何用开源来构建这种新的生产关系. 5G推动新一轮工业革命 过去‍‍人类经历了三次工业革命, ...

  9. 性能提升40%: 腾讯 TKE 用 eBPF 绕过 conntrack 优化 K8s Service

    Kubernetes Service 用于实现集群中业务之间的互相调用和负载均衡,目前社区的实现主要有userspace,iptables和IPVS三种模式.IPVS模式的性能最好,但依然有优化的空间 ...

  10. C# 压缩、解压文件夹或文件(带密码)

    今天梳理一下项目中用到的压缩.解压文件夹或文件的方法,发现因为需求不同,已经用了好几个不同组件.今天就好好整理记录下,别下次遇到需求又重头开始了. DotNetZip DotNetZip是一个开源的免 ...