您的位置:首页 > 职场人生

用户空间驱动

2016-04-07 14:56 295 查看
一个第一次涉及内核问题的 Unix 程序员, 可能会紧张写一个模块. 编写一个用户程序来

直接读写设备端口可能容易些.

确实, 有几个论据倾向于用户空间编程, 有时编写一个所谓的用户空间设备驱动对比钻研

内核是一个明智的选择. 在本节, 我们讨论几个理由, 为什么你可能在用户空间编写驱动.

本书是关于内核空间驱动的, 但是, 所以我们不超越这个介绍性的讨论.

用户空间驱动的好处在于:

• 完整的 C 库可以连接. 驱动可以进行许多奇怪的任务, 不用依靠外面的程序(实现

使用策略的工具程序, 常常随着驱动自身发布).

• 程序员可以在驱动代码上运行常用的调试器, 而不必走调试一个运行中的内核的弯

路.

• 如果一个用户空间驱动挂起了, 你可简单地杀掉它. 驱动的问题不可能挂起整个系

统, 除非被控制的硬件真的疯掉了.

• 用户内存是可交换的, 不象内核内存. 一个不常使用的却有很大一个驱动的设备不

会占据别的程序可以用到的 RAM, 除了在它实际在用时.

• 一个精心设计的驱动程序仍然可以, 如同内核空间驱动, 允许对设备的并行存取.

• 如果你必须编写一个封闭源码的驱动, 用户空间的选项使你容易避免不明朗的许可

的情况和改变的内核接口带来的问题.

例如, USB 驱动能够在用户空间编写; 看(仍然年幼) libusb 项目, 在

libusb.sourceforge.net 和 “gadgetfs” 在内核源码里. 另一个例子是 X 服务器: 它确

切地知道它能处理哪些硬件, 哪些不能, 并且它提供图形资源给所有的 X 客户. 注意, 然

而, 有一个缓慢但是固定的漂移向着基于 frame-buffer 的图形环境, X 服务器只是作为

一个服务器, 基于一个内核空间的真实的设备驱动, 这个驱动负责真正的图形操作.

常常, 用户空间驱动的编写者完成一个服务器进程, 从内核接管作为单个代理的负责硬件

控制的任务. 客户应用程序就可以连接到服务器来进行实际的操作; 因此, 一个聪明的驱

动经常可以允许对设备的并行存取. 这就是 X 服务器如何工作的.

但是用户空间的设备驱动的方法有几个缺点. 最重要的是:

• 中断在用户空间无法用. 在某些平台上有对这个限制的解决方法, 例如在 IA32 体

系上的 vm86 系统调用.

• 只可能通过内存映射 /dev/mem 来使用 DMA, 而且只有特权用户可以这样做.

• 存取 I/O 端口只能在调用 ioperm 或者 iopl 之后. 此外, 不是所有的平台支持这

些系统调用, 而存取/dev/port 可能太慢而无效率. 这些系统调用和设备文件都要

求特权用户.

• 响应时间慢, 因为需要上下文切换在客户和硬件之间传递信息或动作.

• 更不好的是, 如果驱动已被交换到硬盘, 响应时间会长到不可接受. 使用 mlock 系

统调用可能会有帮助, 但是常常的你将需要锁住许多内存页, 因为一个用户空间程

序依赖大量的库代码. mlock, 也, 限制在授权用户上.

• 最重要的设备不能在用户空间处理, 包括但不限于, 网络接口和块设备.

如你所见, 用户空间驱动不能做的事情毕竟太多. 感兴趣的应用程序还是存在: 例如, 对

SCSI 扫描器设备的支持( 由 SANE 包实现 )和 CD 刻录器 ( 由 cdrecord 和别的工具实

现 ). 在两种情况下, 用户级别的设备情况依赖 “SCSI gneric” 内核驱动, 它输出了低层

的 SCSI 功能给用户程序, 因此它们可以驱动它们自己的硬件.

一种在用户空间工作的情况可能是有意义的, 当你开始处理新的没有用过的硬件时. 这样

你可以学习去管理你的硬件, 不必担心挂起整个系统. 一旦你完成了, 在一个内核模块中

封装软件就会是一个简单操作了.

用户空间驱动的优缺点:

用户空间驱动的优点:

1 完整的 C 库可以连接. 驱动可以进行许多奇怪的任务, 不用依靠外面的程序(实现使用策略的工具程序, 常常随着驱动自身发布);

2 程序员可以在驱动代码上运行常用的调试器, 而不必走调试一个运行中的内核的弯路;

3 如果一个用户空间驱动挂起了, 你可简单地杀掉它. 驱动的问题不可能挂起整个系统, 除非被控制的硬件真的疯掉了;

4 用户内存是可交换的, 不象内核内存. 一个不常使用的却有很大一个驱动的设备不会占据别的程序可以用到的 RAM, 除了在它实际在用时;

5 一个精心设计的驱动程序仍然可以, 如同内核空间驱动, 允许对设备的并行存取;

6 如果你必须编写一个封闭源码的驱动, 用户空间的选项使你容易避免不明朗的许可的情况和改变的内核接口带来的问题;

7 一种在用户空间工作的情况可能是有意义的, 当你开始处理新的没有用过的硬件时. 这样你可以学习去管理你的硬件, 不必担心挂起整个系统. 一旦你完成了, 在一个内核模块中封装软件就会是一个简单操作了。

用户空间驱动的缺点:

1 中断在用户空间无法用. 在某些平台上有对这个限制的解决方法, 例如在 IA32体系上的 vm86 系统调用;

2 只可能通过内存映射 /dev/mem 来使用 DMA, 而且只有特权用户可以这样做;

3 存取 I/O 端口只能在调用 ioperm 或者 iopl 之后. 此外,不是所有的平台支持这些系统调用, 而存取/dev/port可能太慢而无效率. 这些系统调用和设备文件都要求特权用户;

4 响应时间慢, 因为需要上下文切换在客户和硬件之间传递信息或动作;

5 更不好的是,如果驱动已被交换到硬盘, 响应时间会长到不可接受。 使用 mlock 系统调用可能会有帮助,但是常常的你将需要锁住许多内存页, 因为一个用户空间程序依赖大量的库代码. mlock, 也, 限制在授权用户上。最重要的设备不能在用户空间处理, 包括但不限于, 网络接口和块设备。

下面来看在前面的基础上加上中断的情况,由于用户空间不能操作中断,所以只能使用内核驱动,下面贴出代码,具体解析我在《GPIO中断程序》一文中有讲述,此处不再赘述。

/*
* PB18_IRQTest.c
* This is  a test program for sam9260, using PB19(J5_18 pin) input a signal to PB18(J5_16 pin),
* PB18 receive this signal as IRQ and make the LED linking on PB17((J5_14 pin)) turn on or turn off
*
*           @Author: Cun Tian Rui
*           @Date :March.18.2011
*/

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <asm/arch/hardware.h>
#include <asm/arch/gpio.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <asm/arch/board.h>
#include <linux/cdev.h>
#include <asm/arch/gpio.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/arch/at91_pio.h>
#include <asm/arch/at91_aic.h>
#include <asm/arch/at91_pmc.h>

void led_on()
{
at91_set_gpio_output(AT91_PIN_PB17,1);
}

void led_off()
{
at91_set_gpio_output(AT91_PIN_PB17 ,0);
}

struct light_dev *light_devp;
int light_major = 200;

struct light_dev
{
struct cdev cdev;
unsigned char value;
};

MODULE_AUTHOR("Cun Tian Rui");
MODULE_LICENSE("Dual BSD/GPL");

static void io_init(void)
{
at91_set_gpio_input(AT91_PIN_PB18, 1);
at91_set_deglitch(AT91_PIN_PB18, 1);
at91_sys_write(1 + PIO_IDR,  1<<18);
at91_sys_write(1 + PIO_IER,  (~(1<<18)));
at91_sys_write(AT91_PMC_PCER, 1 << 3);
}

struct gpio_irq_desc
{
int irq;
unsigned long flags;
char *name;
};

static struct gpio_irq_desc PB18_IRQ={AT91_PIN_PB18,AT91_AIC_SRCTYPE_LOW,"PB18"};

static irqreturn_t PB18_intHandle(int irq, void *dev_id)
{
led_on();
return IRQ_RETVAL(IRQ_HANDLED);
}

int light_open(struct inode *inode,struct file *filp)
{
int err;
struct light_dev *dev;
dev = container_of(inode->i_cdev,struct light_dev,cdev);
filp->private_data = dev;
io_init();
err = request_irq(PB18_IRQ.irq,PB18_intHandle,PB18_IRQ.flags,PB18_IRQ.name,(void*)0);
if(err)
{
free_irq(PB18_IRQ.irq,(void*)0);
return  -EBUSY;
}

return 0;
}

int light_release(struct inode *inode,struct file *filp)
{
free_irq(PB18_IRQ.irq,(void*)0);
return 0;
}

// ioctl
int light_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,
unsigned long arg)
{
struct light_dev *dev = filp->private_data;

switch(cmd)
{
case 0:
at91_set_gpio_output(AT91_PIN_PB19,0);
break;

case 1:
at91_set_gpio_output(AT91_PIN_PB19,1);
led_off();
break;

default:

return -ENOTTY;
// break;
}

return 0;
}

struct file_operations light_fops =
{
.owner = THIS_MODULE,
.ioctl = light_ioctl,
.open  = light_open,
.release = light_release,
};

static void light_setup_cdev(struct light_dev *dev,int index)
{
int err,devno = MKDEV(light_major,index);

cdev_init(&dev->cdev,&light_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &light_fops;

err = cdev_add(&dev->cdev,devno,1);

if(err)
{
printk(KERN_NOTICE "Error %d adding LED%d",err,index);
}
}

int light_init(void)
{
int result;

dev_t dev = MKDEV(light_major,0);
if(light_major)
{

result = register_chrdev_region(dev,1,"PB18_IRQTest");
}

if(result < 0)
{
return result;
}

light_devp = kmalloc(sizeof(struct light_dev),GFP_KERNEL);
if(!light_devp)
{
result = - ENOMEM;
goto fail_malloc;
}

memset(light_devp,0,sizeof(struct light_dev));
light_setup_cdev(light_devp,0);

return 0;

fail_malloc:unregister_chrdev_region(dev,light_devp);
return result;

}

void light_cleanup(void)
{
cdev_del(&light_devp->cdev);
kfree(light_devp);
unregister_chrdev_region(MKDEV(light_major,0),1);
}

module_init(light_init);
module_exit(light_cleanup);


最后给出自己写的一个在用户层读写AT91sam9260SDRAM某一个位置的小程序结束本文。

#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SDRAM_PHY_Position 0x20800000
int main (int args, char* arg[])
{
int i;
int fd;
char* mem;
char *buff = "HELLO";
//open /dev/mem with read and write mode
if ((fd = open ("/dev/mem", O_RDWR)) < 0)
{
perror ("open error");
return -1;
}
//map physical memory 0-10 bytes
mem = mmap (0, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd, SDRAM_PHY_Position);
if (mem == MAP_FAILED) {
perror ("mmap error:");
return 1;
}
//Read old value
for (i = 0; i < 5; i++)
{
printf("\nold mem[%d]:%d", i, mem[i]);
}
//write memory
memcpy(mem, buff, 5);
//Read new value
for (i = 0; i<5 ; i++)
{
printf("\nnew mem[%d]:%c", i, mem[i]);
}
printf("\n");
munmap (mem, 10); //destroy map memory
close (fd);   //close file
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息