linux kernel 学习手记3 I/O

探索Linux Kernel:早期I/O内存映射的奥秘 在计算机的世界里,Linux 系统就像一个庞大而精密的工厂,各个组件协同运作,而内存管理则是这个工厂至关重要的 “物流调度中心”。它掌控着内存资源的分配、回收与调度,确保系统的每一个环节都能顺畅运行。当我们聚焦于 Linux Kernel 内存管理的一个关键环节 —— 早期 I/O 内存映射(early ioremap),就如同揭开了这个工厂中一个神秘而高效的 “小车间” 的面纱。 阅读详情

 9  与硬件通讯

在硬件级别上, 内存区和 I/O 区域没有概念上的区别: 它们都是通过在地址总线和控制总线上发出电信号来存取(, 读写信号)[32]并且读自或者写到数据总线. 也有例外,有些cpu在芯片上一个单个地址空间, 如:x86有人开的读和写电线给I/O端口和特殊的cpu指令存取端口,也有的在芯片组或cpu中附加额外的电路,后一种方法在很多嵌入式cpu中出现较多。

硬件缓冲的问题是最易面对的:底层的硬件已经配置(或者自动地或者通过 Linux 初始化代码)成禁止任何硬件缓冲, 当存取 I/O 区时(不管它们是内存还是端口区域).

对编译器优化和硬件重编排的解决方法是安放一个内存屏障。

 

#include <linux/kernel.h>

void barrier(void)

这个函数告知编译器插入一个内存屏障但是对硬件没有影响. 编译的代码将所有的当前改变的并且驻留在 CPU 寄存器的值存储到内存, 并且后来重新读取它们当需要时. 对屏障的调用阻止编译器跨越屏障的优化, 而留给硬件自由做它的重编排.

#include <asm/system.h>

void rmb(void);

void read_barrier_depends(void);

void wmb(void);

void mb(void);

void smp_rmb(void);

void smp_read_barrier_depends(void);

void smp_wmb(void);

void smp_mb(void);

屏障的这些版本仅当内核为 SMP 系统编译时插入硬件屏障; 否则, 它们都扩展为一个简单的屏障调用.

I/O 端口分配

#include <linux/ioport.h>

struct resource *request_region(unsigned long first, unsigned long n, const char *name);

这个函数告诉内核, 你要使用 n 个端口, first 开始. name 参数应当是你的设备的名子. 如果分配成功返回值是非 NULL. 如果你从 request_region 得到 NULL, 你将无法使用需要的端口.

所有的的端口分配显示在 /proc/ioports . 如果你不能分配一个需要的端口组, 这是地方来看看谁先到那里了.

当你用完一组 I/O 端口(在模块卸载时, 也许), 应当返回它们给系统, 使用:

void release_region(unsigned long start, unsigned long n); 

还有一个函数以允许你的驱动来检查是否一个给定的 I/O 端口组可用:

int check_region(unsigned long first, unsigned long n); 

但是一般不去使用这个函数,因为无法保证能获取端口,这是由于非原子性造成。

I/O 端口操作

Linux 内核头文件(特别地, 体系依赖的头文件 <asm/io.h>) 定义了下列内联函数来存取 I/O 端口:

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-位 端口( 一个字宽 ); 在为 S390 平台编译时它们不可用, 它只支持字节 I/O.

unsigned inl(unsigned port);

void outl(unsigned longword, unsigned port);

这些函数存取 32-位 端口. longword 声明为或者 unsigned long 或者 unsigned int, 根据平台. 如同字 I/O, "Long" I/O S390 上不可用.

从用户空间访问I/O端口

下列条件应当应用来对于 inb 及其友在用户空间代码中使用:

·         程序必须使用 -O 选项编译来强制扩展内联函数.

·         ioperm iopl 系统调用必须用来获得权限来进行对端口的 I/O 操作. ioperm 为单独端口获取许可, iopl 为整个 I/O 空间获取许可. 2 个函数都是 x86 特有的.

·         程序必须作为 root 来调用 ioperm 或者 iopl.[34] 可选地, 一个它的祖先必须已赢得作为 root 运行的端口权限.

如果没有 ioperm iopl系统调用,则用户空间可以通过/dev/port设备文件访问I/O端口

可以尝试在程序中调用setuid root这样就不用显式获取特权了。

技术上来说,必须有CAP_SYS_RAWIO权限

字符串操作

直接的串操作比c下的循环效率要高很多,

字串函数的原型是:

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-位 端口.

有件事要记住, 当使用字串函数时: 它们移动一个整齐的字节流到或自端口. 当端口和主系统有不同的字节对齐规则, 结果可能是令人惊讶的. 使用 inw 读取一个端口交换这些字节, 如果需要, 来使读取的值匹配主机字节序. 字串函数, 相反, 不进行这个交换.

 

暂停式I/O

一些平台 - 最有名的 i386 - 可能有问题当处理器试图太快传送数据到或自总线. 当处理器对于外设总线被过度锁定时可能引起问题( 想一下 ISA )并且可能当设备单板太慢时表现出来. 解决方法是插入一个小的延时在每个 I/O 指令后面, 如果跟随着另一个指令. x86 , 这个暂停是通过进行一个 outb 指令到端口 0x80 ( 正常地不是常常用到 )实现的, 或者通过忙等待. 细节见你的平台的 asm 子目录的 io.h 文件.

 

平台依赖性

IA-32 (x86)

x86_64

这个体系支持所有的本章描述的函数. 端口号是 unsigned short 类型.

IA-64 (Itanium)

支持所有函数; 端口是 unsigned long(以及内存映射的)). 字串函数用 C 实现.

Alpha

支持所有函数, 并且端口是内存映射的. 端口 I/O 的实现在不同 Alpha 平台上是不同的, 根据它们使用的芯片组. 字串函数用 C 实现并且定义在 arch/alpha/lib/io.c 中定义. 端口是 unsigned long.

ARM

端口是内存映射的, 并且支持所有函数; 字串函数用 C 实现. 端口是 unsigned int 类型.

Cris

这个体系不支持 I/O 端口抽象, 甚至在一个模拟模式; 各种端口操作定义成什么不做.

M68k

M68k

端口是内存映射的. 支持字串函数, 并且端口类型是 unsigned char.

MIPS

MIPS64

MIPS 端口支持所有的函数. 字串操作使用紧凑汇编循环来实现, 因为处理器缺乏机器级别的字串 I/O. 端口是内存映射的; 它们是 unsigned long.

PA

支持所有函数; 端口是 int 在基于 PCI 的系统上以及 unsigned short EISA 系统, 除了字串操作, 它们使用 unsigned long 端口号.

PowerPC

PowerPC64

支持所有函数; 端口有 unsigned char * 类型在 32-位 系统上并且 unsigned long 64-位 系统上.

S390

类似于 M68k, 这个平台的头文件只支持字节宽的端口 I/O, 而没有字串操作. 端口是 char 指针并且是内存映射的.

Super

端口是 unsigned int ( 内存映射的 ), 并且支持所有函数.

SPARC SPARC64

再一次, I/O 空间是内存映射的. 端口函数的版本定义来使用 unsigned long 端口.

好奇的读者能够从 io.h 文件中获得更多信息, 这个文件有时定义几个结构特定的函数, 加上我们在本章中描述的那些. 但是, 警告有些这些文件是相当难读的.

使用I/O内存

I/O 端口在 x86 世界中流行, 用来和设备通讯的主要机制是通过内存映射的寄存器和设备内存. 2 者都称为 I/O 内存, 因为寄存器和内存之间的区别对软件是透明的.

I/O内存访问的方法和计算机体系架构有关,不过原理相同,I/O 内存可以或者不可以通过页表来存取. 当通过页表存取, 内核必须首先安排从你的驱动可见的物理地址, 并且这常常意味着你必须调用 ioremap 在做任何 I/O 之前. 如果不需要页表, I/O 内存位置看来很象 I/O 端口, 并且你只可以使用正确的包装函数读和写它们.不管如何都不鼓励使用I/O内存指针。

I/O内存的分配和映射

I/O 内存区必须在使用前分配. 分配内存区的接口是( <linux/ioport.h> 定义):

struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);

这个函数分配一个 len 字节的内存区, start 开始. 如果一切顺利, 一个非NULL 指针返回; 否则返回值是 NULL. 所有的 I/O 内存分配来 /proc/iomem 中列出.

内存区在不再需要时应当释放:

void release_mem_region(unsigned long start, unsigned long len); 

还有一个旧的检查 I/O 内存区可用性的函数:

int check_mem_region(unsigned long start, unsigned long len); //尽量少用,如果需要分配用第一个较好

 

I/O 内存读, 使用下列之一:

unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);

这里, addr 应当是从 ioremap 获得的地址(也许与一个整型偏移); 返回值是从给定 I/O 内存读取的.

有类似的一系列函数来写 I/O 内存:

void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);

如果你必须读和写一系列值到一个给定的 I/O 内存地址, 你可以使用这些函数的重复版本:

void ioread8_rep(void *addr, void *buf, unsigned long 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);
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);

这些函数读或写 count 值从给定的 buf 给定的 addr. 注意 count 表达为在被写入的数据大小; ioread32_rep 读取 count 32-位值从 buf 开始.

上面描述的函数进行所有的 I/O 到给定的 addr. 如果, 相反, 你需要操作一块 I/O 地址, 你可使用下列之一:

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);

如果你通览内核源码, 你可看到许多调用旧的一套函数, 当使用 I/O 内存时. 这些函数仍然可以工作, 但是它们在新代码中的使用不鼓励. 除了别的外, 它们较少安全因为它们不进行同样的类型检查. 但是, 我们在这里描述它们:

unsigned readb(address);
unsigned readw(address);
unsigned readl(address); 

这些宏定义用来从 I/O 内存获取 8-, 16-, 32-位数据值.

void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address); 

如同前面的函数, 这些函数()用来写 8-, 16-, 32-位数据项.

一些 64-位平台也提供 readq writeq, PCI 总线上的 4-(8-字节)内存操作. 这个 4-字 的命名是一个从所有的真实处理器有 16-位 字的时候的历史遗留. 实际上, 用作 32-位 值的 L 命名也已变得不正确, 但是命名任何东西可能使事情更混淆.

 
Linux Kernel - The Block I/O Layer 固定大小的数据块称为块,常见块设备磁盘有硬盘,闪存等,随机访问,字符设备以序列数据流的方式访问,如键盘输入是一字节接一字节访问。由于块设备的复杂度,访问性能等原因,内核提供了一个子系统进行管理,即 Block I/O Layer。块设备最小可寻址单元称为扇区,扇区大小是一个设备的物理属性,大部分块设备为 512 B,CD-ROM 磁盘为 2KB。 ... 阅读详情

相关推荐

kernel笔记——块I/O

Linux下,I/O处理的层次可分为4层:   1. 系统调用层,应用程序使用系统调用指定读写哪个文件,文件偏移是多少   2. 文件系统层,写文件时将用户态中的buffer拷贝到内核态下,并由cache缓存该部分数据 3. 块层,管理块设备I/O队列,对I/O请求进行合并、排序 4. 设备层,通过DMA与内存直接交互,将数据写到磁盘 下图清晰地说明了Linu...

微信公众号:大数据从业者 246

kernel: Buffer I/O error on device sdb, logical block 3

http://blog.csdn.net/kinges/article/details/40425841转自:http://blog.csdn.net/kinges/article/details/40425841主机报错信息:Buffer I/O errors occurring on SAN devices presented to Linux host using Li...

weixin_34128839的博客 6994

Linux设备驱动程序学习(八)——内存与I/O访问

内存空间和I/O空间   在X86处理器中存在着I/O空间的概念,I/O空间是相对于内存空间而言的,它通过特定的指令in、out来访问。端口号标识了外设的寄存器地址。Intel语法中的in、out指令格式如下: IN 累加器, {端口号│DX} OUT {端口号│DX},累加器   目前,大多数嵌入式微控制器(如ARM、PowerPC等)中并不提供I/O空间,而仅存在内存空间。内存空间可以直接通...

Alex_wu的博客 3053

linux disk I/O tuning?

原贴地址:http://www.lupaworld.com/441/viewspace_2106.html linux disk I/O tuning?    

最实用的Linux博客 6321

kernel 内存 I/O

链接: https://www.jianshu.com/p/e0bcdfea3082 内存 I/O# 内存管理单元## MMU辅助操作系统进行内存管理,提供虚拟地址和物理地址的映射、内存访问权限保护和cache缓存控制等硬件支持 TLB(translation lookaside buffer):转换旁路缓存,TLB是MMU的核心部件,它缓存少量的虚拟地址与物理地址的转换关系,是转换表的cache...

runshui27的博客 159

Linux 内存与I/O访问

内存空间与I/O空间

Dylan的博客 854

Linux I/O模式

一、概念说明 1. 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核

ttdss12345678的博客 292

i o error dev oracle,Linux kernel: end_request: I/O error

Hi,I am intermitently getting the following error in Oracle-Linux ASM database /var/log/messages. This is a 2-node RAC database. But everything is working fine.We are using:RHEL4(U4)Oracle 10gR2 with ...

weixin_32515577的博客 707

Linux Kernel文件系统写I/O流程代码分析(二)bdi_writeback

Linux Kernel文件系统写I/O流程代码分析(二)bdi_writeback 上一篇# Linux Kernel文件系统写I/O流程代码分析(一),我们看到Buffered IO,写操作写入到page cache后就直接返回了,本文主要分析脏页是如何刷盘的。 概述 由于内核page cache的作用,写操作实际被延迟写入。当page cache里的数据被用户写入但是没有刷新到磁盘时,则该p...

weixin_34148456的博客 557

Linux设备驱动】中的内存管理与I/O访问

通过对Linux设备驱动中的内存管理和I/O访问的深入探讨,我们了解了这些技术的重要性及其背后的实现原理。合理的内存管理可以提升驱动程序的性能和系统的稳定性;高效的I/O访问则能够加速设备与内核之间的数据交换。通过使用内核提供的API和工具,我们可以构建出既高效又可靠的Linux设备驱动程序。希望本文能够帮助读者更好地理解和掌握Linux设备驱动开发的关键技术。

极客代码 1562

【iotop】在 Linux 中如何使用 iotop 和 iostat 监控磁盘 I/O 活动?

一、关于iotop Linux 下 IO 统计工具,比如 iostat, nmon 等只能统计到每个设备的读写情况,如果想要知道哪一个进程占用比较高的 IO 就要使用 iotop。 iotop是一个用来监控磁盘 I/O 的类似 top 的工具,iotop具有与top相似的UI,其中包括PID、用户、I/O、进程等相关信息。 iotop 使用 Python 语言编写,要求 Python >= 2.5,Linux Kernel >= 2.6.20. (使用python -V和unam...

michaelwoshi的博客 1804

Linux多路复用例子,I/O多路复用和异步I/O

一、I/O模式对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:等待数据准备 (Waiting for the data to be ready)将数据从内核拷贝到进程中 (Copying the data from the kernel to the proces...

weixin_34061241的博客 186

什么是linux kernel?有什么作用?

说到kernel就得先明白什么是操作系统,什么是kernel?什么是操作系统?他们是什么关系?貌似这个问题很大框,答案挺多,以前总以为自己是明白的,但当面试时被问到还真不知道怎么样才是正确的答案,现在总结一下: 操作系统是计算机资源的管理者,主要管理cpu的资源调度、存储器、I/O设备、文件四个部分。 对于cpu的资源调度,其实就是进程管理,进程有三种基本状态:就绪态、执行

科岩成果 3万+

linux进程同步和异步机制,理解Linux五种I/O模型、同步I/O与异步I/O、阻塞与非阻塞...

备到kernelbuffer再到appbuffer的两个阶段展开。其中数据复制到kernelbuffer的过程称为数据准备阶段,数据从kernelbuffer复制到userbuffer的过程称为数据复。所以,这三个函数仅仅只是处理了数据是否准备好以及如何通知进程的问题。可以将这几个函数结合阻塞和非阻塞IO模式使用,例如设置为非阻塞时,select()/poll()/epoll将不会阻塞在对应的描述...

weixin_32871457的博客 406

Linux系统磁盘I/O、内存管理及Swap空间优化

响应策略需根据实际负载持续验证调整,可采用SRE黄金指标(延迟、流量、错误、饱和度)评估优化效果。Page Frame回收。非活跃页Swap Out。触发OOM Killer。

完颜振江 1449

Linux下I/O资源管理(原理)

声明:该文件为本人原创,如转载修改及使用其中图片,请注明出处及原作者。 Author:lanbo(高兆成) E-mail:lanbo_g@126.com 如有任何疑问可留言或E-mail 下图为示意图,读者可以参考该图来阅读代码。 个人认为对linux下I/

高兆成的专栏 1498

Linux Kernel 3

LSM 的主要功能是:允许内核在敏感操作(如访问文件、创建进程)前后插入自定义的安全检查逻辑。允许将进程分层管理,并按照配置规则对系统资源(如CPU、内存、IO)进行分配。:按“块”进行读写的设备,如磁盘,不同于按字节流工作的字符设备(如串口)。用于对进程视图进行隔离(如文件系统、PID、网络栈等),配合控制组。,针对其代表的设备类型设计,以简化驱动编写并减少重复代码。,用于简化和统一各种实际文件系统驱动的开发,避免代码重复。,用于扩展默认的 Unix 权限模型,支持各种安全框架。

程子鱼 1108
上一篇: linux kernel 学习手记2 Alloc memory -kmalloc
下一篇: linux kernel 学习手记3 Interrupt
human3000
博客等级 码龄26年 22粉丝 18原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值