实现linux系统内存的分配 读写 释放功能

刚刚我们实现了linux系统内存的分配,读写,释放功能,下面,我们一鼓作气将io端口映射及io内存映射搞定,加油!
(一)地址的概念 1)物理地址:cpu地址总线传来的地址,由硬件电路控制其具体含义。物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存、bios等)。在程序指令中的虚拟地址经过段映射和页面映射后,就生成了物理地址,这个物理地址被放到cpu的地址线上。
物理地址空间,一部分给物理ram(内存)用,一部分给总线用,这是由硬件设计来决定的,因此在32 bits地址线的x86处理器中,物理地址空间是2的32次方,即4gb,但物理ram一般不能上到4gb,因为还有一部分要给总线用(总线上还挂着别的许多设备)。在pc机中,一般是把低端物理地址给ram用,高端物理地址给总线用。
2)总线地址:总线的地址线或在地址周期上产生的信号。外设使用的是总线地址,cpu使用的是物理地址。
物理地址与总线地址之间的关系由系统的设计决定的。在x86平台上,物理地址就是总线地址,这是因为它们共享相同的地址空间——这句话有点难理解,详见下面的独立编址。在其他平台上,可能需要转换/映射。比如:cpu需要访问物理地址是0xfa的单元,那么在x86平台上,会产生一个pci总线上对0xfa地址的访问。因为物理地址和总线地址相同,所以凭眼睛看是不能确定这个地址是用在哪儿的,它或者在内存中,或者是某个卡上的存储单元,甚至可能这个地址上没有对应的存储器。
3)虚拟地址:现代操作系统普遍采用虚拟内存管理(virtual memory management)机制,这需要mmu(memory management unit)的支持。mmu通常是cpu的一部分,如果处理器没有mmu,或者有mmu但没有启用,cpu执行单元发出的内存地址将直接传到芯片引脚上,被内存芯片(物理内存)接收,这称为物理地址(physical address),如果处理器启用了mmu,cpu执行单元发出的内存地址将被mmu截获,从cpu到mmu的地址称为虚拟地址(virtual address),而mmu将这个地址翻译成另一个地址发到cpu芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址。
linux中,进程的4gb(虚拟)内存分为用户空间、内核空间。用户空间分布为0~3gb(即page_offset,在0x86中它等于0xc0),剩下的1g为内核空间。程序员只能使用虚拟地址。系统中每个进程有各自的私有用户空间(0~3g),这个空间对系统中的其他进程是不可见的。
cpu发出取指令请求时的地址是当前上下文的虚拟地址,mmu再从页表中找到这个虚拟地址的物理地址,完成取指。同样读取数据的也是虚拟地址,比如mov ax, var.编译时var就是一个虚拟地址,也是通过mmu从也表中来找到物理地址,再产生总线时序,完成取数据的。
(二)编址方式 1)外设都是通过读写设备上的寄存器来进行的,外设寄存器也称为i/o端口,而io端口有两种编址方式:独立编址和统一编制。
统一编址:外设接口中的io寄存器(即io端口)与主存单元一样看待,每个端口占用一个存储单元的地址,将主存的一部分划出来用作io地址空间,如,在pdp-11中,把最高的4k主存作为io设备寄存器地址。端口占用了存储器的地址空间,使存储量容量减小。
统一编址也称为i/o内存方式,外设寄存器位于内存空间(很多外设有自己的内存、缓冲区,外设的寄存器和内存统称i/o空间)。
如,samsung的s3c2440,是32位arm处理器,它的4gb地址空间被外设、ram等瓜分:
0x8 1 led 8*8点阵的地址
0x4800 0 ~ 0x6 0 sfr(特殊暂存器)地址空间
0x3800 1002键盘地址
0x3 0 ~ 0x3400 0 sdram空间
0x2 0020 ~ 0x2 002e ide
0x1900 0300 cs8900
独立编址(单独编址):io地址与存储地址分开独立编址,i/0端口地址不占用存储空间的地址范围,这样,在系统中就存在了另一种与存储地址无关的io地址,cpu也必须具有专用与输入输出操作的io指令(in、out等)和控制逻辑。独立编址下,地址总线上过来一个地址,设备不知道是给io端口的、还是给存储器的,于是处理器通过memr/memw和ior/iow两组控制信号来实现对i/o端口和存储器的不同寻址。如,intel 80x86就采用单独编址,cpu内存和i/o是一起编址的,就是说内存一部分的地址和i/o地址是重叠的。
独立编址也称为i/o端口方式,外设寄存器位于i/o(地址)空间。
对于x86架构来说,通过in/out指令访问。pc架构一共有65536个8bit的i/o端口,组成64k个i/o地址空间,编号从0~0xffff,有16位,80x86用低16位地址线a0-a15来寻址。连续两个8bit的端口可以组成一个16bit的端口,连续4个组成一个32bit的端口。i/o地址空间和cpu的物理地址空间是两个不同的概念,例如i/o地址空间为64k,一个32bit的cpu物理地址空间是4g。如,在intel 8086+redhat9.0下用more /proc/ioports可看到:
0-001f : dma1
0020-003f : pic1
0040-005f : timer
0060-006f : keyboard
0070-007f : rtc
0080-008f : dma page reg
00a0-00bf : pic2
00c0-00df : dma2
00f0-00ff : fpu
0170-0177 : ide1
不过intel x86平台普通使用了名为内存映射(mmio)的技术,该技术是pci规范的一部分,io设备端口被映射到内存空间,映射后,cpu访问io端口就如同访问内存一样。看intel ta 719文档给出的x86/x64系统典型内存地址分配表:
bios 1m
本地apic 4k
芯片组保留 2m
io apic 4k
pci设备 256m
pci express设备 256m
pci设备(可选) 256m
显示帧缓存 16m
tseg 1m
对于某一既定的系统,它要么是独立编址、要么是统一编址,具体采用哪一种则取决于cpu的体系结构。 如,powerpc、m68k等采用统一编址,而x86等则采用独立编址,存在io空间的概念。目前,大多数嵌入式微控制器如arm、powerpc等并 不提供i/o空间,仅有内存空间,可直接用地址、指针访问。但对于linux内核而言,它可能用于不同的cpu,所以它必须都要考虑这两种方式,于是它采 用一种新的方法,将基于i/o映射方式的或内存映射方式的i/o端口通称为i/o区域(i/o region),不论你采用哪种方式,都要先申请io区域:request_resource(),结束时释放 它:release_resource()。
2)对外设的访问
1、访问i/o内存的流程是:request_mem_region()->ioremap()->ioread8()/iowrite8()->iounmap()->release_mem_region()。
前面说过,io内存是统一编址下的概念,对于统一编址,io地址空间是物理主存的一部分,对于编程而言,我们只能操作虚拟内存,所以,访问的第一步就是要把设备所处的物理地址映射到虚拟地址,linux2.6下用ioremap():
void *ioremap(unsigned long offset, unsigned long size);
然后,我们可以直接通过指针来访问这些地址,但是也可以用linux内核的一组函数来读写:
ioread8(), iowrite16(), ioread8_rep(), iowrite8_rep()......
2、访问i/o端口
访问io端口有2种途径:i/o映射方式(i/o-mapped)、内存映射方式(memory-mapped)。前一种途径不映射到内存空间,直接使用intb()/outb()之类的函数来读写io端口;后一种mmio是先把io端口映射到io内存(内存空间),再使用访问io内存的函数来访问io端口。
void ioport_map(unsigned long port, unsigned int count);
通过这个函数,可以把port开始的count个连续的io端口映射为一段内存空间,然后就可以在其返回的地址是像访问io内存一样访问这些io端口。
(三)linux下的io端口和io内存 cpu对外设端口物理地址的编址方式有两种:一种是io映射方式,另一种是内存映射方式。
linux将基于io映射方式的和内存映射方式的io端口统称为io区域(io region)。
io region仍然是一种io资源,因此它仍然可以用resource结构类型来描述。
linux管理io region:
1) request_region()
把一个给定区间的io端口分配给一个io设备。
2) check_region()
检查一个给定区间的io端口是否空闲,或者其中一些是否已经分配给某个io设备。
3) release_region()
释放以前分配给一个io设备的给定区间的io端口。
linux中可以通过以下辅助函数来访问io端口:
inb(),inw(),inl(),outb(),outw(),outl()
bwl分别代表8位,16位,32位。
对io内存资源的访问
1) request_mem_region()
请求分配指定的io内存资源。
2) check_mem_region()
检查指定的io内存资源是否已被占用。
3) release_mem_region()
释放指定的io内存资源。
其中传给函数的start address参数是内存区的物理地址(以上函数参数表已省略)。
驱动开发人员可以将内存映射方式的io端口和外设内存统一看作是io内存资源。
ioremap()用来将io资源的物理地址映射到内核虚地址空间(3gb - 4gb)中,参数addr是指向内核虚地址的指针。
linux中可以通过以下辅助函数来访问io内存资源:
readb(),readw(),readl(),writeb(),writew(),writel()。
linux在kernel/resource.c文件中定义了全局变量ioport_resource和iomem_resource,来分别描述基于io映射方式的整个io端口空间和基于内存映射方式的io内存资源空间(包括io端口和外设内存)。
1)关于io与内存空间:
在x86处理器中存在着i/o空间的概念,i/o空间是相对于内存空间而言的,它通过特定的指令in、out来访问。端口号标识了外设的寄存器地址。intel语法的in、out指令格式为:
in累加器, {端口号│dx}
out {端口号│dx},累加器
目前,大多数嵌入式微控制器如arm、powerpc等中并不提供i/o空间,而仅存在内存空间。内存空间可以直接通过地址、指针来访问,程序和程序运行中使用的变量和其他数据都存在于内存空间中。
即便是在x86处理器中,虽然提供了i/o空间,如果由我们自己设计电路板,外设仍然可以只挂接在内存空间。此时,cpu可以像访问一个内存单元那样访问外设i/o端口,而不需要设立专门的i/o指令。因此,内存空间是必须的,而i/o空间是可选的。
(2)inb和outb:
在linux设备驱动中,宜使用linux内核提供的函数来访问定位于i/o空间的端口,这些函数包括:
·读写字节端口(8位宽)
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
·读写字端口(16位宽)
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
·读写长字端口(32位宽)
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
·读写一串字节
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
· insb()从端口port开始读count个字节端口,并将读取结果写入addr指向的内存;outsb()将addr指向的内存的count个字节连续地写入port开始的端口。
·读写一串字
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
·读写一串长字
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);
上述各函数中i/o端口号port的类型高度依赖于具体的硬件平台,因此,只是写出了unsigned。
以上知识点摘自:
这儿我不献丑了,因为上面的已经写的很好了。
(四)linux下的io端口和io内存 由于我本人对imx257的io内存分配不太了解,曾经询问过很多周立功公司那些所谓的技术人员,每个人回答的答案都是,你好,我们提供的资料就只是官网上的光盘资料了哦,每次碰到这种话,我真的很无语,我只能说,好吧,只能说是我智商太低了,那些资料太高深了。
好了,废话也不多说了,这次,我就针对s3c24xx的gpio驱动程序来讲解io端口的分配。
1. 定义一些寄存器(数据寄存器,配置寄存器)
上图中,gpio_va就是我们gpio的基址,是用来确定gpfcon配置寄存器和gpfdat数据寄存器的指针地址的
,其初始化我们在init函数中会讲解。
2.在init函数中确定gpio基址
前面我们寄存器的正常作用的前提就是gpio_va这个基址,所以我们在init函数中分配io端口
如图所示,将gpio的地址0x56这个io端口分配为gpio_va,这样也就确定了,前面的配置寄存器和数据寄存器。
3.在open函数中配置gpio
和单片机程序一样,使用gpio都要先配置gpio,原理一样:
给gpio引脚赋初值,以后对gpio引脚的操作,直接用这个寄存器赋值就可以了
4.在exit函数中释放io端口
使用完gpio引脚后,要将我们申请的io端口释放掉。
总结一下,很简单,申请,配置,释放
申请io端口à配置gpio端口为输出(入)à给数据寄存器赋值(读取)à释放io端口

开关电源变压器的结构和组成
TI eSMO库Fsmopos和Gsmopos参数解析
MAX9382在锁相环中的应用
民用航空发动机润滑油
诺基亚近日宣布,60年来首次更改其标志Logo,使用全新标志
实现linux系统内存的分配 读写 释放功能
特斯拉汽车因安全问题面临强制召回危机
连云港东海县利用RFID电子车牌管理渣土车
特斯拉开始生产机器人?普渡机器人入驻防疫酒店
海外cdn加速有用吗?
太平鸟和阿里巴巴达成了A100战略合作
KeyStone II架构应对异构网络挑战
儿童车必选 竹制平衡自行车能够自然地教会孩子骑车技巧
依旧不将就:一加手机5曝光,骁龙835+8GB,性能强大毋庸置疑
阿里巴巴2020校园招聘公告分享 工程师来了解一下
什么是电池中活性物质
京东方屏幕怎么样_好不好
“下半场”来临,中国彩电行业亟待凤凰涅槃
长治供电公司正在全力推进1000千伏送出配套工程
PCIe 5.0 SSD首发测试