本文共 4183 字,大约阅读时间需要 13 分钟。
UIO(Userspace I/O)是运行在用户空间的I/O技术。UIO适合在编写大型驱动程序的时候使用,它免去了频繁的内核模块的切换与重启。另外某些情况下性能也比内核驱动好,比如典型的应用例子就是dpdk。除了实现用户态驱动以外UIO也适合在虚拟化的时候做设备透传,相较于VFIO也是一种不错的选择。
设备驱动的编写无非是两件事情:
1.设备内存的读写 2.中断的响应UIO实现了mmap,可以实现映射物理内存到虚拟内存供用户层读写。
中断的响应必须在内核进行,UIO在内核实现了很少一部分中断的处理,之后通知到用户空间。用户层只需要简单的阻塞在对 /dev/uioX的read()操作上。当设备产生中断时,read()操作立即返回。UIO 也实现了poll()系统调用,你可以使用 select()来等待中断的发生。select()有一个超时参数可以用来实现有限时间内等待中断。
对设备的控制还可以通过/sys/class/uio下的各个文件的读写来完成。你注册的uio设备将会出现在该目录下。 假如你的uio设备是uio0那么映射的设备内存文件出现在 /sys/class/uio/uio0/maps/mapX,对该文件的读写就是对设备内存的读写。想要运行一个 UIO的程序需要以下几个步骤:
1.编写内核UIO驱动 2.卸载真实设备驱动 3.绑定设备到UIO驱动UIO的使用需要先编写一个内核的驱动,之后通过sys下的接口将设备卸载后再挂载到UIO驱动上。下面先看一下一个简单的内核驱动的实现。
#include#include #include #include #include #include #include /* kmalloc, kfree */struct uio_info uio_virtual_device_info = { .name = "myuio", .version = "1.0", .irq = UIO_IRQ_NONE, // .irq = UIO_IRQ_CUSTOM,};static int uio_virtual_device_drv_probe(struct platform_device *pdev){ printk("uio_virtual_device_probe( %p)\n", pdev); uio_virtual_device_info.mem[0].addr = (unsigned long)kmalloc(1024,GFP_KERNEL); if(uio_virtual_device_info.mem[0].addr == 0) return -ENOMEM; uio_virtual_device_info.mem[0].memtype = UIO_MEM_LOGICAL; uio_virtual_device_info.mem[0].size = 1024; printk("[%s,%d] uio_virtual_device_info.mem[0].addr:0x%x,.size :%lu\n",\ __func__,__LINE__,uio_virtual_device_info.mem[0].addr,\ uio_virtual_device_info.mem[0].size); if(uio_register_device(&pdev->dev, &uio_virtual_device_info)) return -ENODEV; return 0;}static int uio_virtual_device_drv_remove(struct platform_device *pdev){ uio_unregister_device(&uio_virtual_device_info); return 0;}static struct platform_driver virtual_device_drv = { .probe = uio_virtual_device_drv_probe, // .remove = __devexit_p(uio_virtual_device_drv_remove), .remove = uio_virtual_device_drv_remove, .driver = { .name = "VIRTUAL_DEVICE", .owner = THIS_MODULE, }};static void virtual_device_remove(struct device *dev){ }static struct platform_device virtual_device = { .name = "VIRTUAL_DEVICE", .id = -1, .dev = { .release = virtual_device_remove, },};static int __init uio_virtual_device_init(void){ printk("virtual_device init ok!\n"); platform_device_register(&virtual_device); printk("virtual_device_drv init ok!\n"); return platform_driver_register(&virtual_device_drv);}static void __exit uio_virtual_device_exit(void){ printk("Virtual_device remove ok!\n"); platform_device_unregister(&virtual_device); printk("virtual_device_drv remove ok!\n"); platform_driver_unregister(&virtual_device_drv);}module_init(uio_virtual_device_init);module_exit(uio_virtual_device_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("ZP1015");MODULE_DESCRIPTION("Demon of UIO");
这里使用了一个虚拟的设备代替真实设备,所以模块一插入就能初始化好UIO驱动,用户太就能直接操作。
这里顺带提一下sys下pci设备的各种ID目录
/sys/bus/pci/devices/地址/vendor/sys/bus/pci/devices/地址/device/sys/bus/pci/devices/地址/subsystem_vendor/sys/bus/pci/devices/地址/subsystem_device
根据id可以匹配自己想匹配的设备,这里就暂时不用真实设备了,设备的解绑和绑定都不需要。
#include#include #include #include #include #include #include #define UIO_DEV "/dev/uio0"#define UIO_ADDR "/sys/class/uio/uio0/maps/map0/addr"#define UIO_SIZE "/sys/class/uio/uio0/maps/map0/size"static char uio_addr_buf[16], uio_size_buf[16];int main(){ int uio_fd, addr_fd, size_fd; int uio_size; void* uio_addr, *access_address; uio_fd = open(UIO_DEV, O_RDWR); addr_fd = open(UIO_ADDR, O_RDONLY); size_fd = open(UIO_SIZE, O_RDONLY); if( addr_fd < 0 || size_fd < 0 || uio_fd < 0) { fprintf(stderr, "mmap: %s\n", strerror(errno)); exit(-1); } read(addr_fd, uio_addr_buf, sizeof(uio_addr_buf)); read(size_fd, uio_size_buf, sizeof(uio_size_buf)); uio_addr = (void *)strtoul(uio_addr_buf, NULL, 0); uio_size = (int)strtol(uio_size_buf, NULL, 0); access_address = mmap(NULL, uio_size, PROT_READ | PROT_WRITE, MAP_SHARED, uio_fd, 0); if (access_address == (void*) -1) { fprintf(stderr, "mmap: %s\n", strerror(errno)); exit(-1); } printf("The device address %p (lenth %d)\n" "can be accessed over\n" "logical address %p\n", uio_addr, uio_size, access_address); return 0;}
转载地址:http://qvzhz.baihongyu.com/