Linux内核 -- UIO (User-space I/O) 简介与使用笔记
UIO (User-space I/O) 简介
UIO (User-space I/O) 是 Linux 内核提供的一种机制,用于简化设备驱动的开发。它将设备的硬件资源(如内存映射、中断等)通过简单的接口暴露给用户空间程序,从而使用户可以在用户空间编写复杂的设备逻辑,而内核只需完成基础的资源管理工作。
内核态作用与实现
UIO 内核态的作用
- 硬件资源管理:
- 提供设备内存映射支持,将设备寄存器或内存映射到用户空间。
- 注册和处理硬件中断(可选)。
- 中断通知:
- 将设备硬件中断转发给用户空间程序。
UIO 内核态的实现步骤
-
定义设备信息结构
使用struct uio_info
定义设备信息,包括中断号、内存地址等。static struct uio_info my_uio_info = { .name = "my_device", .version = "1.0", .irq = 42, // 中断号(若不需要中断,可设置为 UIO_IRQ_NONE) .irq_flags = IRQF_SHARED, // 中断标志 .handler = my_irq_handler, // 中断处理函数(可选) .mem[0].addr = 0x40000000, // 物理地址 .mem[0].size = 0x1000, // 内存大小 .mem[0].memtype = UIO_MEM_PHYS, // 内存类型 };
-
注册设备
使用uio_register_device
注册设备信息:static int __init my_uio_init(void) { return uio_register_device(NULL, &my_uio_info); } static void __exit my_uio_exit(void) { uio_unregister_device(&my_uio_info); } module_init(my_uio_init); module_exit(my_uio_exit); MODULE_LICENSE("GPL");
-
实现中断处理函数(可选)
如果需要支持硬件中断,需要实现中断处理逻辑:static irqreturn_t my_irq_handler(int irq, struct uio_info *dev_info) { return IRQ_HANDLED; // 通知内核中断已处理 }
注意事项
- 如果设备不需要中断功能,可以将
irq
设置为UIO_IRQ_NONE
,并省略handler
。 - 内核态代码只需要完成资源管理工作,具体设备逻辑由用户空间程序完成。
用户态作用与实现
UIO 用户态的作用
- 设备访问:通过
/dev/uioX
文件与设备交互。 - 内存映射:使用
mmap
将设备内存映射到用户空间,直接操作设备寄存器。 - 中断处理:通过
read
调用等待中断事件。
用户态的实现步骤
-
打开设备文件
int fd = open("/dev/uio0", O_RDWR); if (fd < 0) { perror("Failed to open /dev/uio0"); return -1; }
-
内存映射
使用mmap
将设备内存映射到用户空间:void *reg_base = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (reg_base == MAP_FAILED) { perror("Failed to mmap"); close(fd); return -1; }
-
处理中断
通过read
等待中断事件:unsigned int irq_count; while (1) { read(fd, &irq_count, sizeof(irq_count)); printf("Interrupt received! Count: %d\n", irq_count); }
-
释放资源
在程序结束时,释放映射的内存并关闭设备:munmap(reg_base, 0x1000); close(fd);
示例代码
内核态代码
以下是一个完整的内核态代码示例:
#include <linux/module.h>
#include <linux/uio_driver.h>
static irqreturn_t my_irq_handler(int irq, struct uio_info *dev_info) {
return IRQ_HANDLED;
}
static struct uio_info my_uio_info = {
.name = "my_device",
.version = "1.0",
.irq = 42,
.irq_flags = IRQF_SHARED,
.handler = my_irq_handler,
.mem[0].addr = 0x40000000,
.mem[0].size = 0x1000,
.mem[0].memtype = UIO_MEM_PHYS,
};
static int __init my_uio_init(void) {
return uio_register_device(NULL, &my_uio_info);
}
static void __exit my_uio_exit(void) {
uio_unregister_device(&my_uio_info);
}
module_init(my_uio_init);
module_exit(my_uio_exit);
MODULE_LICENSE("GPL");
用户态代码
以下是对应的用户态代码示例:
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
int main() {
int fd = open("/dev/uio0", O_RDWR);
if (fd < 0) {
perror("Failed to open /dev/uio0");
return -1;
}
void *reg_base = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (reg_base == MAP_FAILED) {
perror("Failed to mmap");
close(fd);
return -1;
}
unsigned int irq_count;
while (1) {
read(fd, &irq_count, sizeof(irq_count));
printf("Interrupt received! Count: %d\n", irq_count);
}
munmap(reg_base, 0x1000);
close(fd);
return 0;
}
注意事项
- 中断功能:
- 如果不需要中断功能,可以省略中断处理函数,并将
irq
设置为UIO_IRQ_NONE
。
- 如果不需要中断功能,可以省略中断处理函数,并将
- 性能问题:
- 使用 UIO 时,中断和用户空间之间的切换会增加延迟,不适合高实时性场景。
- 安全性:
- UIO 将设备的硬件资源直接映射到用户空间,需确保用户空间程序的安全性,以防止资源滥用。
总结
UIO 是一种简化设备驱动开发的高效工具,适合处理简单设备或快速原型开发。在内核态中,开发者只需完成资源注册和中断处理(可选);在用户态中,程序通过 /dev/uioX
文件与设备交互,完成具体逻辑。