io_1_基本概念

1 篇文章 0 订阅
本文详细解释了Linux系统中io操作的基本概念,包括io寄存器与内存的区别,io端口和io内存的申请、访问和释放方法,以及它们在不同场景下的适用性。还介绍了如何在Linux内核中管理和操作io资源,以支持外设通信。
摘要由CSDN通过智能技术生成

io

参考 https://www.cnblogs.com/geneil/archive/2011/12/08/2281367.html

input/output device。 外设是基于cpu视角出发看到的东西。本节用于理解基本概念,理清一些名词。

0221补充:
当大家说到IO这个概念时候,到底在指什么?
应用层:5种io模型,如何读写文件,处理并发问题;编写应用层程序时候,经常有这样的逻辑“功能运行-->需要io-->得到io资源-->功能继续运行”;
csapp里面的io章节:介绍的也是应用层的编程;
内核:block子系统。做数据库内核的开发接触的比较多。
驱动:外设,gpio,总线,dma,中断。
虚拟化章节:iommu,GIC的虚拟化功能

1. 基本拓扑图:

cpu出来有3条总线:数据总线、地址总线、控制总线。
总线上有很多个接口:存储器接口、中断控制接口、dma接口、并行、串接口等等…
外设通过需要连接在接口上。
在这里插入图片描述

2. 地址的概念:

物理地址:CPU地址总线传来的地址,由硬件电路控制其具体含义,是实际硬件的唯一标识。物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存、BIOS等)。在程序指令中的虚拟地址经过段映射和页面映射后,就生成了物理地址,这个物理地址被放到CPU的地址线上。
例如32bits地址线:2^32 = 4GB, 寻址范围:[0, 4GB] = RAM内存地址 + 外设;

总线地址:物理地址从cpu引脚出来,喂给总线,总线转一手,就变成总线地址,用于在总线上传输数据,得到的地址给外设使用。arm中支持多种总线接口,例如AMBA。总线地址与物理地址通过总线协议和系统配置定义的。

虚拟地址:有关虚拟内存管理机制。每个应用程序使用抽象的地址空间,应用认为独占内存空间。虚拟内存通过MMU模块映射到物理地址。

外设地址:设备本身的地址。

例子:当有外设A连接在接口上,A自身有4个寄存器[0,0x4](外设地址)。一个32位的cpu,cpu要访问该外设的状态,需要分配一段地址用来访问A(物理地址:[0,2^32] 中拿4个地址出来)。当应用程序B需要操作外设的「0x01」地址,会使用一个虚拟地址传递给mmu, mmu映射后转换成物理地址,物理地址传递到cpu引脚,向总线发送物理地址, 物理地址在总线上根据协议或硬件设计转换成总线地址。

在这里插入图片描述

3. 编址概念

统一编址: io寄存器和主存一样。统一编址也称为「I/O内存」的方式。

独立编址: io地址和存储地址是分开的。比如0x0100,可以认为io地址或存储地址。cpu有专用的io指令(IN、OUT)和控制逻辑,使用的时候称为「I/O端口」的方式。

这种区别来自于设计理念的不同,外设是否和内存是否分离的决定。

linux软件的处理:对于Linux内核而言,它可能用于不同的CPU,所以它必须都要考虑这两种方式,于是它采用一种新的方法,将基于I/O映射方式的或内存映射方式的I/O端口通称为“I/O区域”(I/O region),不论你采用哪种方式,都要先申请IO区域:request_resource(),结束时释放它:release_resource()。

4. 操作io

梳理一下io寄存器的特征,以及io端口和io内存的区别。

4.1 io寄存器和常规内存的区别

I/O操作有边际效应(side effect):访问I/O寄存器时,不仅仅会像访问普通内存一样影响存储单元的值,更重要的是它可能改变CPU的I/O端口电平、输出时序或CPU对I/O端口电平的反应等等,从而实现CPU的控制功能。CPU在电路中的意义就是实现其side effect 。举个例子,有些设备的中断状态寄存器只要一读取,便自动清零。

内存读写:cpu为了效率会优化代码,使用高速缓存或重排指令。
硬件缓冲:将底层硬件配置为当存取I/O区时,禁止任何硬件缓冲(不管是I/O 内存还是I/O 端口);
重排指令:增加内存屏障。

4.2 linux 中操作io端口

在Linux中,IO内存和IO端口是用于与外部设备进行通信的两种不同的机制。它们分别使用物理内存映射和特殊的IO端口指令进行访问

在Linux系统编程中,IO端口的访问涉及到申请资源和释放资源,通常是通过以下步骤完成的:

4.2.1 申请IO端口资源:

在Linux内核中,可以使用 request_region() 函数来申请IO端口的资源。这个函数会检查请求的IO端口范围是否被其他驱动程序占用,如果没有被占用,则分配给当前驱动程序使用。

#include <linux/ioport.h>

unsigned long io_port_start = 0x3F8;  // 例子:串口COM1的基地址
unsigned long io_port_size = 8;       // 串口COM1使用的IO端口数目

struct resource *port_res = request_region(io_port_start, io_port_size, "my_device");
if (!port_res) {
    // 请求失败,IO端口被占用或其他错误
    printk(KERN_ERR "Failed to request IO port region\n");
    return -EBUSY; // 或其他错误码
}
4.2.2 IO端口访问:

访问IO端口通常使用 inb()outb() 等函数,它们用于从IO端口读取一个字节或向IO端口写入一个字节。

#include <asm/io.h>

unsigned long value;
outb(0x55, io_port_start);   // 向IO端口写入数据
value = inb(io_port_start);  // 从IO端口读取数据
4.2.3 释放IO端口资源:

在设备不再需要使用IO端口时,需要释放已经分配的资源,使用 release_region() 函数。

release_region(io_port_start, io_port_size);
4.3 linux 中操作io内存
4.3.1 申请IO内存区域:

使用request_mem_region()函数来请求IO内存区域的使用权。这个函数会检查所请求的区域是否与其他设备冲突。

 struct resource *io_mem = request_mem_region(0xDEADBEEF, size, "my_device");
 if (!io_mem) {
     // 请求失败
     printk(KERN_ERR "Failed to request IO memory region\n");
     return -EBUSY; // 或者其他错误码
 }
4.3.2 映射IO内存:
  • 使用ioremap()或者devm_ioremap_resource()函数将IO内存映射到内核虚拟地址空间。
 void __iomem *io_mem_ptr = ioremap(io_mem->start, size);
 if (!io_mem_ptr) {
     // 映射失败
     release_mem_region(io_mem->start, size); // 释放之前申请的资源
     printk(KERN_ERR "Failed to remap IO memory\n");
     return -ENOMEM; // 或者其他错误码
 }
4.3.3 使用IO内存:
  • 现在,io_mem_ptr指向了映射后的IO内存区域,可以使用它进行IO操作。
 // 在 io_mem_ptr 上进行读写操作
 writeb(value, io_mem_ptr);
 value = readb(io_mem_ptr);


void memset_io(void *addr, u8 value, unsigned int count);
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);
4.3.4 释放IO内存区域:
  • 当设备不再需要IO内存区域时,使用release_mem_region()函数释放已经分配的资源。
void iounmap(void * addr); /* iounmap用于释放不再需要的映射 */
void release_mem_region(unsigned long start, unsigned long len); /* iounmap用于释放不再需要的映射 */
4.4 io内存和io端口的区分
4.4.1 适用场景
  • IO内存: 通常适用于需要大块连续内存的设备,如显卡、网络设备等。它通过物理内存映射提供了更灵活的访问方式。
  • IO端口: 通常适用于需要简单、离散的输入输出的设备,如串口、并口等。使用IO端口可以更直接地控制设备。
4.4.2 权限
  • IO内存: IO内存映射通常需要特权权限,因为直接访问物理内存可能对系统安全性造成影响。
  • IO端口: IO端口的访问权限通常受到I/O权限位的控制,一般需要在内核模块中使用request_region()来获取端口权限。
4.4.3 区分如何使用io

A. 检查设备文档:设备制造商通常会在设备的技术文档或规格书中明确指定应该使用哪种方式进行IO操作。查阅设备的相关文档是最直接的方法。

B. 代码查看设备资源:

  • 设备资源在Linux中通常由struct resource表示,可以通过内核函数platform_get_resource()pci_resource_start()等来获取。
  • 如果设备使用IO内存,资源类型为IORESOURCE_MEM,可以使用ioremap()来映射内存。如果设备使用IO端口,资源类型为IORESOURCE_IO,可以使用inb()outb()等IO端口指令。
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

if (res) {
    // IO内存方式
    void __iomem *io_mem_ptr = ioremap(res->start, resource_size(res));
} else {
    // IO端口方式
    unsigned long io_port = platform_get_resource(pdev, IORESOURCE_IO, 0)->start;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值