linux驱动-I/O

本文详细介绍了Linux系统中I/O操作的基本概念,包括I/O端口和I/O内存的使用方法,以及如何进行端口分配、释放和读写操作。同时,还探讨了内存屏障的应用场景及其函数。

简述:

I/O是CPU和外部设备通信一种方式。linux将I/O分成两种,一种是I/O端口,另一种是I/O内存。

I/O端口:

I/O端口,是指对外部设备的访问,不能通过寻址方式访问,是一种特殊的,依赖CPU体系结构,操作I/O端口,是需要用CPU体系实现的接口。
linux对I/O的端口,一般是要经过分配,然后操作端口(即读写端口):

  • 1、I/O端口分配
  • (1)包含头文件
#include <linux/ioport.h>
  • (2)分配I/O端口的接口
    分配用宏request_region:
#define request_region(start,n,name)        __request_region(&ioport_resource, 
                                                            (start), (n), (name), 0)
extern struct resource * __request_region(struct resource *,
                    resource_size_t start,
                    resource_size_t n,
                    const char *name, int flags);

功能:分配端口,分配之后,在没有释放前,有其他地方再对I/O端口再次分配,将会失败。
第一个参数:start起始端口号
第二个参数:分配以start开始在内的,n个端口
第三个参数:此次申请分配端口,取名字name(字符串),以便查询。
返回值:返回struct resource结构体指针。

  • 2、释放I/O端口
  • (1)包含头文件
#include <linux/ioport.h>
  • (2)释放I/O端口的接口
    释放接口宏release_region:
#define release_region(start,n) __release_region(&ioport_resource, (start), (n))
extern void __release_region(struct resource *, resource_size_t,
                resource_size_t);

功能:释放分配的I/O端口
第一个参数:start起始端口
第二个参数:释放以start起始n个连续的端口

  • 3、操作I/O端口(读写端口)
  • (1)包含头文件
#include <asm/io.h>
  • (2)操作I/O端口函数
u8 inb(unsigned long addr);//读8位数据(一个字节),addr端口号,返回值是读回的值
u16 inw(unsigned long addr);//读16位数据,addr是端口号,返回值是读回的值
u32 inl(unsigned long addr);//读32位数据,addr是端口号,返回值是读回的值
void outb(u8 b, unsigned long addr);//向端口写8位数据,b是数据,addr是端口号
void outw(u16 b, unsigned long addr);//向端口写16位数据,b是数据,addr是端口号
void outl(u32 b, unsigned long addr);//向端口写32位数据,b是值数据,addr是端口号

void insb(unsigned long addr, void *buffer, int count);
//向端口addr,读count个8位数据(字节),数据读出来储存在buffer里面。

void insw(unsigned long addr, void *buffer, int count);
//向端口addr,读count个16位数据,数据读出来储存在buffer里面。

void insl(unsigned long addr, void *buffer, int count);
//向端口addr,读count个32位数据,数据读出来储存在buffer里面。

void outsb(unsigned long addr, const void *buffer, int count);
//向端口addr,写count个8位数据(字节),要写的数据储存在buffer。

void outsw(unsigned long addr, const void *buffer, int count);
//向端口addr,写count个16位数据,要写的数据储存在buffer。

void outsl(unsigned long addr, const void *buffer, int count);
//向端口addr,写count个32位数据,要写的数据储存在buffer。

addr就是上面request_region申请端口范围,其中一个端口。

I/O内存

I/O内存,是指对外部设备访问,是通过寄存器实现,而寄存器在CPU可寻址空间范围,即寄存器读写,就是对某个地址的读写。

  • 1、I/O内存分配
  • (1)包含头文件:
#include <linux/ioport.h>
  • (2)I/O内存映射:
    I/O内存,在申请前,需要将I/O内存地址(是物理地址)映射成内核虚拟地址。
    不在ioport.h里面,要包含下面的头文件:
#include <asm/io.h>

void *ioremap(unsigned long phys_addr, unsigned long size);
phys_addr:I/O起始物理地址
size:起始物理地址开始,空间大小size,这段物理空间
返回值:就是映射后的内核虚拟起始地址。

void iounmap(void * addr);
取消映射,addr是映射的起始内核虚地址。

  • (3)申请分配I/O内存的接口:
    接口request_mem_region
#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start),
                                                            (n), (name), 0)
extern struct resource * __request_region(struct resource *,
                    resource_size_t start,
                    resource_size_t n,
                    const char *name, int flags);

功能:分配I/O内存后,在没有释放前,有其他地方再对I/O内存再次分配,将会失败。
第一个参数:start是ioremap返回的内核虚拟地址
第二个参数:分配以start开始在内的,n个端口
第三个参数:申请分配端口,取名字name(字符串),以便查询。
返回值:返回struct resource结构体指针。

  • 2、释放I/O内存的接口
    接口release_mem_region:
#define release_mem_region(start,n) __release_region(&iomem_resource, (start), (n))
extern struct resource * __request_region(struct resource *,
                    resource_size_t start,
                    resource_size_t n,
                    const char *name, int flags);

功能:释放分配的I/O内存
第一个参数:start是ioremap返回的内核虚拟地址
第二个参数:释放以start起始n个连续的端口

  • 3、操作I/O端口(读写端口)

  • (1)包含头文件:

#include <asm/io.h>
  • (2)操作I/O内存函数
unsigned int ioread8(void *addr);//读取地址addr,8位数据,返回值是读取的值
unsigned int ioread16(void *addr);//读取地址addr,16位数据,返回值是读取的值
unsigned int ioread32(void *addr);//读取地址addr,32位数据,返回值是读取的值

void iowrite8(u8 value, void *addr);//向地址addr写,8位数据,value是要写的值
void iowrite16(u16 value, void *addr);//向地址addr写,16位数据,value是要写的值
void iowrite32(u32 value, void *addr);//向地址addr写,32位数据,value是要写的值

void ioread8_rep(void *addr, void *buf, unsigned long count);
//向地址addr读取,count个8位数据,读取的值储存在buf里面

void ioread16_rep(void *addr, void *buf, unsigned long count);
//向地址addr读取,count个16位数据,读取的值储存在buf里面

void ioread32_rep(void *addr, void *buf, unsigned long count);
//向地址addr读取,count个32位数据,读取的值储存在buf里面

void iowrite8_rep(void *addr, const void *buf, unsigned long count);
//向地址addr写,count个8位数据,要写的值储存在buf里面

void iowrite16_rep(void *addr, const void *buf, unsigned long count);
//向地址addr写,count个16位数据,要写的值储存在buf里面

void iowrite32_rep(void *addr, const void *buf, unsigned long count);
//向地址addr写,count个32位数据,要写的值储存在buf里面

addr是request_mem_region申请的地址范围(start到start+n-1范围里)
以上函数,其实在io.h里面定义的是宏,但是,宏的形参和返回值,类型正如上面函数参数和返回值。

现在,很多I/O口的访问,都是通过读写寄存器地址,而寄存器地址都是在CPU寻址范围内,所以用I/O内存的更多,在这种情况下I/O端口和I/O内存是通用的。但更建议用I/O内存。

内存屏障

由于编译器优化,会出现部分指令执行顺序,不是按照设计的顺序执行,这种改变如果会严重影响结果,此时就需要用内存屏障。

如:

writel(dev->registers.addr, io_destination_address);
writel(dev->registers.size, io_size);
writel(dev->registers.operation, DEV_READ);
wmb();
writel(dev->registers.control, DEV_GO);

wmb()是写内存屏障,如果不加wmb(),那么有可能writel(dev->registers.control, DEV_GO)
比writel(dev->registers.operation, DEV_READ)先运行。加上wmb(),就保证了不会出现这种情况,就能保证wmb()上面的,一定比wmb()下面的先调用。

内存屏障函数定义在include/asm-generic/barrier.h

void barrier(void);//基本内存屏障,通用的
void rmb(void);//读I/O时,用的内存屏障
void wmb(void);//写I/O时,用的内存屏障
void mb(void);//可以代替rmb(),wmb()

读写I/O,建议用对应读写内存屏障,除I/O读写的,建议用barrier()。

arm-sigmastar-linux-uclibcgnueabihf-gcc -Os -pipe -march=armv7-a -I/home/jasper/nvr_2025/torchlight/staging_dir/target-arm-openwrt-linux-uclibcgnueabihf/usr/include -I/home/jasper/nvr_2025/torchlight/staging_dir/target-arm-openwrt-linux-uclibcgnueabihf/include -I/home/jasper/nvr_2025/torchlight/../sstar931/toolchain//arm-sigmastar-linux-uclibcgnueabihf/sysroot/usr/include -Wall -Werror -g -Wno-nonnull-compare -fpic -I./ -Wall -Werror -g -Wno-nonnull-compare -fpic -I./ -c -o alloc_item.o alloc_item.c arm-sigmastar-linux-uclibcgnueabihf-gcc -Os -pipe -march=armv7-a -I/home/jasper/nvr_2025/torchlight/staging_dir/target-arm-openwrt-linux-uclibcgnueabihf/usr/include -I/home/jasper/nvr_2025/torchlight/staging_dir/target-arm-openwrt-linux-uclibcgnueabihf/include -I/home/jasper/nvr_2025/torchlight/../sstar931/toolchain//arm-sigmastar-linux-uclibcgnueabihf/sysroot/usr/include -Wall -Werror -g -Wno-nonnull-compare -fpic -I./ -Wall -Werror -g -Wno-nonnull-compare -fpic -I./ -c -o ta_override_c.o ta_override_c.c arm-sigmastar-linux-uclibcgnueabihf-g++ -Os -pipe -march=armv7-a -I/home/jasper/nvr_2025/torchlight/staging_dir/target-arm-openwrt-linux-uclibcgnueabihf/usr/include -I/home/jasper/nvr_2025/torchlight/staging_dir/target-arm-openwrt-linux-uclibcgnueabihf/include -I/home/jasper/nvr_2025/torchlight/../sstar931/toolchain//arm-sigmastar-linux-uclibcgnueabihf/sysroot/usr/include -Wall -Werror -g -Wno-nonnull-compare -fpic -I./ -c -o ta_override_cplus.o ta_override_cplus.cpp arm-sigmastar-linux-uclibcgnueabihf-gcc -shared -o libtrace_alloc.so trace_mm.o rbtree.o alloc_item.o ta_override_c.o ta_override_cplus.o trace_mm.o: file not recognized: file format not recognized collect2: error: ld returned 1 exit status Makefile:16: recipe for target 'libtrace_alloc.so' failed make[4]: *** [libtrace_alloc.so] Error 1 make[4]: Leaving directory '/home/jasper/nvr_2025/torchlight/build_dir/target-arm-openwrt-linux-uclibcgnueabihf/trace_alloc' Makefile:42: recipe for target '/home/jasper/nvr_2025/torchlight/build_dir/target-arm-openwrt-linux-uclibcgnueabihf/trace_alloc/.built' failed make[3]: *** [/home/jasper/nvr_2025/torchlight/build_dir/target-arm-openwrt-linux-uclibcgnueabihf/trace_alloc/.built] Error 2 make[3]: Leaving directory '/home/jasper/nvr_2025/tp_package/u_packages/trace_alloc' package/Makefile:105: recipe for target 'package/tp_package/u_packages/trace_alloc/compile' failed make[2]: *** [package/tp_package/u_packages/trace_alloc/compile] Error 2 make[2]: Leaving directory '/home/jasper/nvr_2025/torchlight' package/Makefile:101: recipe for target '/home/jasper/nvr_2025/torchlight/staging_dir/target-arm-openwrt-linux-uclibcgnueabihf/stamp/.package_compile' failed make[1]: *** [/home/jasper/nvr_2025/torchlight/staging_dir/target-arm-openwrt-linux-uclibcgnueabihf/stamp/.package_compile] Error 2 make[1]: Leaving directory '/home/jasper/nvr_2025/torchlight' /home/jasper/nvr_2025/torchlight/include/toplevel.mk:172: recipe for target 'world' failed make: *** [world] Error 2
最新发布
09-05
<think> 我们正在解决使用arm-sigmastar-linux-uclibcgnueabihf-gcc和g++编译生成libtrace_alloc.so时出现的'trace_mm.o: file not recognized: file format not recognized'错误。 这个错误通常意味着链接器(ld)无法识别目标文件(trace_mm.o)的格式。可能的原因包括: 1. 目标文件格式不正确(例如,使用了错误的工具链编译) 2. 目标文件损坏 3. 工具链配置问题 解决步骤: 1. 确认编译工具链的一致性: 确保你使用相同的工具链(arm-sigmastar-linux-uclibcgnueabihf-)编译所有源文件,包括生成trace_mm.o的源文件。 检查Makefile(或构建脚本)中是否明确指定了交叉编译器,并且没有混用主机编译器(如gcc)和交叉编译器。 2. 检查trace_mm.o的生成命令: 查看编译trace_mm.o的具体命令,确认使用的是arm-sigmastar-linux-uclibcgnueabihf-gcc(或g++,如果是C++源文件)。 例如,trace_mm.c应该使用: arm-sigmastar-linux-uclibcgnueabihf-gcc -c trace_mm.c -o trace_mm.o 3. 检查文件格式: 使用file命令检查trace_mm.o的文件格式,确认是否为ARM平台的目标文件。 file trace_mm.o 期望的输出应该包含类似“ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV)”的信息。 4. 如果文件格式不正确,重新编译生成: 如果发现trace_mm.o不是ARM目标文件,则可能是编译命令错误。清理后重新编译: make clean make 5. 检查链接命令: 在生成libtrace_alloc.so时,确保链接器使用的是交叉编译工具链中的ld,而不是宿主系统的ld。 通常,链接共享库的命令应该使用交叉编译器来驱动链接,例如: arm-sigmastar-linux-uclibcgnueabihf-gcc -shared -o libtrace_alloc.so trace_mm.o ... 6. 如果问题仍然存在,检查工具链的完整性: 尝试编译一个简单的测试程序,确认工具链正常工作。 例如,创建一个简单的hello.c,然后使用: arm-sigmastar-linux-uclibcgnueabihf-gcc hello.c -o hello 然后使用`file hello`检查输出文件格式。 7. 检查依赖项: 确保trace_mm.o没有依赖其他异常的目标文件或库。 8. 注意C和C++混合编译: 如果trace_mm.cpp是C++文件,必须使用g++来编译和链接。在链接共享库时,如果包含C++代码,最好使用g++来链接,因为它会自动链接C++标准库。 9. 检查文件损坏: 如果重新编译后问题依旧,可能是文件损坏。尝试删除trace_mm.o并重新编译。 10. 检查工具链的ABI和架构兼容性: 确认你的工具链是为正确的ARM架构(如armv7-a, armv5te等)和ABI(如EABI)配置的,并且与目标平台匹配。 11. 如果使用make,确保没有旧的依赖问题: 有时候make的依赖关系不正确,导致某些文件没有被重新编译。可以尝试先清理再构建。 12. 查看详细错误信息: 在链接命令前加上`VERBOSE=1`(如果使用make)或直接运行命令并添加`-v`选项,以获取更详细的输出。 由于你之前提到了一系列编译错误,建议先解决第一个错误(文件格式不被识别),因为后续错误可能是由第一个错误引发的。 如果按照以上步骤仍然无法解决,请提供更多信息,例如: - 编译trace_mm.o的具体命令(从构建日志中获取) - 生成libtrace_alloc.so的链接命令 - `file trace_mm.o`的输出 - 工具链的版本信息(通过arm-sigmastar-linux-uclibcgnueabihf-gcc -v获取) </think> ### 解决`trace_mm.o: file not recognized`编译错误的分析与方案 该错误表明链接器无法识别`trace_mm.o`的文件格式,通常由交叉编译工具链不一致或文件损坏导致。以下是系统排查方案: #### 核心原因分析 1. **工具链不一致**(最常见) - 用非ARM工具链编译了`trace_mm.o`(如误用x86编译器) - 混合使用不同版本工具链(如编译用gcc4,链接用gcc8) 2. **文件损坏** - 编译过程被中断导致.o文件不完整 - 磁盘坏块或传输错误 3. **ABI不匹配** - uclibc与glibc混用(常见于嵌入式开发) - 硬件浮点(hf)与软浮点(soft-float)混淆[^1] #### 逐步解决方案 1. **验证编译一致性** ```bash # 检查trace_mm.o的编译命令 make -n | grep trace_mm # 确保使用完整工具链路径(示例) /opt/toolchain/bin/arm-sigmastar-linux-uclibcgnueabihf-gcc -c trace_mm.c -o trace_mm.o ``` 2. **检查文件格式** ```bash # 查看.o文件格式信息 file trace_mm.o # 期望输出(ARM目标文件): ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV) ``` 3. **重建工具链环境** ```bash # 清理并重建 make clean rm -f trace_mm.o # 设置工具链变量 export CC="/path/to/arm-sigmastar-linux-uclibcgnueabihf-gcc" export CXX="/path/to/arm-sigmastar-linux-uclibcgnueabihf-g++" # 重新编译 make VERBOSE=1 ``` 4. **ABI兼容性检查** ```bash # 确认工具链浮点配置 arm-sigmastar-linux-uclibcgnueabihf-gcc -v 2>&1 | grep -- --with-float # 正确输出应包含:--with-float=hard ``` 5. **手动验证编译流程** ```bash # 分步执行(避免Makefile干扰) arm-sigmastar-linux-uclibcgnueabihf-gcc -c -fPIC trace_mm.c -o trace_mm.o arm-sigmastar-linux-uclibcgnueabihf-gcc -shared -o libtrace_alloc.so trace_mm.o ``` #### 高频问题场景 - **场景1**:在Makefile中混用`gcc`和交叉编译器 **修复**:全局替换`gcc`为`$(CC)`变量 - **场景2**:Docker环境未挂载工具链 **修复**:检查`docker run -v /opt/toolchain:/toolchain` - **场景3**:IDE配置错误(如VSCode) **修复**:检查`.vscode/tasks.json`中的`"command"`路径 > 若以上步骤无效,建议使用`hexdump -C trace_mm.o | head`检查文件头。合法的ELF文件应以`7f 45 4c 46`开头[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值