UIO用户空间的简单程序经典案例——熟悉UIO程序工作原理以及编译运行过程

1、UIO背景
第一,硬件设备可以根据功能分为网络设备,块设备,字符设备,或者根据与CPU相连的方式分为PCI设备,USB设备等。它们被不同的内核子系统支持。这些标准的设备的驱动编写较为容易而且容易维护。很容易加入主内核源码树。但是,又有很多设备难以划分到这些子系统中,比如I/O卡,现场总线接口或者定制的FPGA。通常这些非标准设备的驱动被实现为字符驱动。这些驱动使用了很多内核内部函数和宏。而这些内部函数和宏是变化的。这样驱动的编写者必须编写一个完全的内核驱动,而且一直维护这些代码。像这种设备如果把驱动放入Linux内核,不但增大了内核的负担而且还很少使用,更没有人愿意免费的花费大量精力去维护这样的驱动,使用UIO使驱动的开发可以利用所有的用户空间的应用程序开发工具和库,而且当系统内核发生变化时,只要更改UIO框架与其他内核程序交互的接口即可,不需要更改UIO框架下面的driver。

第二,内核驱动的开发必须使用C语言加上少量汇编代码。uio可以使用C++,Java …等高级语言极大的方便了开发。我们可以发现,有很多的卡的驱动在内核空间已经有实现,这样,我们可以参考已经存在的代码,极大的提高的开发的速度,和降低了开发成本。而且内核驱动的调试会比用户空间程序调试复杂很多。我们经常遇到死机,涉及到多个子系统,棘手。放在用户空间的话如果驱动程序死了,并不影响系统的正常运行并且方便了我们的开发。

2、UIO工作原理
通过UIO的运行原理图可以看出,用户空间下的驱动程序比运行在内核空间的驱动要多得多,UIO框架下运行在内核空间的驱动程序所做的工作很简单,常做的只有两个:分配和记录设备需要的资源和注册uio设备和必须在内核空间实现的小部分中断应答函数,经过实践表明后面的工作也是可以省略的!我们认为uio内核空间的程序所做的越少越好,在用户空间能完成的我们就不需要放在内核空间做(比如说响应中断),这样假如内核有变化,uio框架中的驱动维护也是比较简单的!对于用户空间的驱动程序,我们还可以集成到某款应用软件中,这样也是可行的,上面已经说过了,因为使用uio的设备一般比较少见,所以可以作出这类的驱动也可以针对某款或者一类设备作出应用程序集成了驱动即可!

3、uio内核态功能
UIO的少量运行在内核空间的驱动所做的工作有哪些呢? 
1.分配和记录设备需要的资源和注册uio设备 
在设备的探测函数中: 
1. - 使能设备 
2. - 申请资源 
3. - 读取并记录配置信息 
4. - 注册uio设备// uio_register_device() 
2.必须*在内核空间实现的小部分中断应答函数

4、用户态功能
用户空间的关键操作 
5. 判断是否产生了硬件中断 
6. 响应硬件中断

int32_t irq_count;
  int fd = open("/dev/uio0", O_RDWR);
  /* Map the register regions to proccess's virtual memspace */
  void * access = mmap(NULL, 4096,
  // 寄存器的读写操作,可用过普通内存读写的方式完成
   PROT_READ | PROT_WRITE,MAP_SHARED, fd, 0);// [1]
   while (read(fd, &irq_count, 4) == 4) {[2]
   printf("Interrupt number %d\n", irq_count);
 }

 5、测试程序

为了用最简单的例子说明问题,我们在我们uio驱动的内核部分只映射了一块1024字节的
逻辑内存。没有申请中断。
这样加载上我们的驱动后,将会在/sys/class/uio/uio0/maps/map0中看到
 addr name offset size。他们分别是映射内存的起始地址, 映射内存的名字,起始地址的页内偏移,
 映射内存 的大小。 在uio驱动的用户空间部分,我们将打开addr, size以便使用映射好的内存。
 

 ---------- ksimple.c -------------

/**  
*  This is a simple demo of uio driver.    
*  
* Compile:    
*   Save this file name it ksimple.c  
*   # echo "obj-m := ksimple.o" > Makefile  
*   # make -Wall -C /lib/modules/`uname -r`/build M=`pwd` modules  
* Load the module:  
*   #modprobe uio  
*   #insmod ksimple.ko  
*/  
  
#include <linux/module.h>  
#include <linux/platform_device.h>  
#include <linux/uio_driver.h>  
#include <linux/slab.h> /* kmalloc, kfree */  
struct uio_info kpart_info = {  
        .name = "kpart",  
        .version = "0.1",  
        .irq = UIO_IRQ_NONE,  
};  
  
static int drv_kpart_probe(struct device *dev);  
static int drv_kpart_remove(struct device *dev);  
static struct device_driver uio_dummy_driver = {  
        .name = "kpart",  
        .bus = &platform_bus_type,  
        .probe = drv_kpart_probe,  
        .remove = drv_kpart_remove,  
};  
  
static int drv_kpart_probe(struct device *dev)  
{  
        printk("drv_kpart_probe( %p)\n", dev);  
        kpart_info.mem[0].addr = (unsigned long)kmalloc(1024,GFP_KERNEL);  
  
        if(kpart_info.mem[0].addr == 0)  
                return -ENOMEM;  
        kpart_info.mem[0].memtype = UIO_MEM_LOGICAL;  
        kpart_info.mem[0].size = 1024;  
  
        if( uio_register_device(dev, &kpart_info))  
                return -ENODEV;  
        return 0;  
}  
  
static int drv_kpart_remove(struct device *dev)  
{  
    uio_unregister_device(&kpart_info);  
  
    return 0;  
}  
  
static struct platform_device * uio_dummy_device;  
  
static int __init uio_kpart_init(void)  
{  
        uio_dummy_device = platform_device_register_simple("kpart", -1,  
                        NULL, 0);  
  
        return driver_register(&uio_dummy_driver);  
}  
  
static void __exit uio_kpart_exit(void)  
{  
        platform_device_unregister(uio_dummy_device);  
        driver_unregister(&uio_dummy_driver);  
}  
  
module_init(uio_kpart_init);  
module_exit(uio_kpart_exit);  
  
MODULE_LICENSE("GPL");  
MODULE_AUTHOR("Benedikt Spranger");  
MODULE_DESCRIPTION("UIO dummy driver");  

这个文件是我们uio驱动的内核部分。下面做下简要解释。
 一个uio驱动的注册过程简单点说有两个步骤:
   1. 初始化设备相关的 uio_info结构。
   2. 调用uio_register_device 分配并注册一个uio设备。
    
 uio驱动必须要提供并初始化的结构 uio_info, 它包含了您要注册的uio_device的重要特性。
 struct uio_info kpart_info = { 
        .name = "kpart",
        .version = "0.1",
        .irq = UIO_IRQ_NONE,//我们没有使用中断,所以初始为UIO_IRQ_NONE
};
当你没有实际的硬件设备,但是,还想产生中断的话,就可以把irq设置为UIO_IRQ_CUSTOM,
并初始化uio_info的handler字段,那么在产生中断时,你注册的中断处理函数将会被调用。
如果有实际的硬件设备,那么irq应该是您的硬件设备实际使用的中断号。

然后,在drv_kpart_probe函数(先不要管为什么在这个函数中进行这些操作)中,完成了
kpart_info.mem[0].addr, kpart_info.mem[0].memtype,
  kpart_info.mem[0].size字段的初始化。这是内存映射必须要设置的。
  其中,  kpart_info.mem[0].memtype可以是 UIO_MEM_PHYS,那么被映射到用户空间的是你
  设备的物理内存。也可以是UIO_MEM_LOGICAL,那么被映射到用户空间的是逻辑内存
  (比如使用 kmalloc分配的内存)。还可以是UIO_MEM_VIRTUAL,那么被映射到用户空间的是
  虚拟内存(比如用vmalloc分配的内存).这样就完成了uio_info结构的初始化。
  下一步,还是在drv_kpart_probe中,调用uio_register_device完成了uio设备向uio core的注册。
  
  下面讲一下,为什么我们的设备跟platform之类的东东扯上了关系?
  我们的驱动不存在真正的物理设备与之对应。而 Platform  驱动“自动探测“,这个特性是
  我们在没有实际硬件的情况下需要的。
  
  先从uio_kpart_init开始分析。
  platform_device_register_simple的调用创建了一个简单的platform设备。
  注册device_driver类型的uio_dummy_driver变量到bus。
  这里需要注意一个问题,就是 device_driver结构中的name为“kpart", 我们创建的platform设备
  名称也是"kpart"。而且先创建platform设备,后注册驱动。这是因为,创建好设备后,注册驱动时,
  驱动依靠name来匹配设备。
  之后 drv_kpart_probe就完成了uio_info的初始化和uio设备的注册。

--------------- simple.c ----------------------
这个是uio驱动的用户空间部分。 

#include <stdio.h>  
#include <fcntl.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <sys/mman.h>  
#include <errno.h>  
  
#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(void)  
{  
  int uio_fd, addr_fd, size_fd;  
  int uio_size;  
  void* uio_addr, *access_address;  
   
  uio_fd = open(UIO_DEV, /*O_RDONLY*/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);  
 //读写操作  
/*  
 access_address = (void*)mremap(access_address, getpagesize(), uio_size + getpagesize() + 11111, MAP_SHARED);  
  
  if ( access_address == (void*) -1) {  
      fprintf(stderr, "mremmap: %s\n", strerror(errno));  
      exit(-1);  
  }   
  printf(">>>AFTER REMAP:"  
         "logical address %p\n", access_address);  
*/  
  return 0;  
}  

把内核c文件和用户态c文件都放在一个空文件夹里,先编译内核c文件 ,在编译用户态c文件,具体过程如下:

1、touch ksimple.c 
2、gedit ksimple.c
3、touch simple.c
4、gedit simple.c
5、echo "obj-m := simple.o" > Makefile
6、make -Wall -C /lib/modules/'uname -r'/build M='pwd' modules
7、modprobe uio
8、sudo insmod ksimple. ko
9、gcc simple.c
10、./a.out

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值