snifer

[原创]基于blackfin 平台的设备的阻塞与非阻塞操作

0
阅读(2379)

假期很快过去,春天来了,我呆的这个地方是个神奇的地方,整个冬天保持在20°以上,最近几天都30多°了,可惜没有时间去看风景,最近在做外设的安装,调试,针对blackfin 平台的设备的阻塞与非阻塞操作 ,谈谈我的看法,当然前提是ulinux 系统(内核需要支持 unix socket 选项 )且支持 frambuffer Blackfin 平台环境 , 并且有了 blackfin 平台编译工具链。

阻塞操作是指,在执行设备操作时,若不能获得资源,则进程挂起直到满足可操作的条件再进行操作。非阻塞操作的进程在不能进行设备操作时,并不挂起。被挂起的进程进入 sleep 状态,被从调度器的运行队列移走,直到等待的条件被满足。

ulinux驱动程序中,我们可以使用等待队列(wait queue)来实现阻塞操作。wait queue 很早就作为一个基本的功能单位出现在ulinux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。等待队列可以用来同步对系统资源的访问,信号量在内核中也是由等待队列来实现的。

下面我们重新定义设备“globalvar”,它可以被多个进程打开,但是每次只有当一个进程写入了一个数据之后本
进程或其它进程才可以读取该数据,否则一直阻塞。
#include <ulinux/module.h>
#include <ulinux/init.h>
#include <ulinux/fs.h>
#include <asm/uaccess.h>
#include <ulinux/wait.h>
#include <asm/semaphore.h>
 
MODULE_LICENSE("GPL"); #define MAJOR_NUM 254
 
static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);
static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);
 
struct file_operations globalvar_fops =
{
 read: globalvar_read, write: globalvar_write,
};
 
static int global_var = 0;
static struct semaphore sem;
static wait_queue_head_t outq;
static int flag = 0;
 
static int __init globalvar_init(void)
{
 int ret;
 ret = register_chrdev(MAJOR_NUM, "globalvar", &globalvar_fops);
 if (ret)
 {
    printk("globalvar register failure");
 }  else
 {
    printk("globalvar register success");
     init_MUTEX(&sem);
     init_waitqueue_head(&outq);
 }
 return ret;
}
 
static void __exit globalvar_exit(void) {
 int ret;
 ret = unregister_chrdev(MAJOR_NUM, "globalvar");
 if (ret)
 {
    printk("globalvar unregister failure");
 }
 else
 {
    printk("globalvar unregister success");
 }
}
 
 
static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t
 *off)
{
 //等待数据可获得
 if (wait_event_interruptible(outq, flag != 0))
 {
     return - ERESTARTSYS;
 }  if (down_interruptible(&sem))
 {
     return - ERESTARTSYS;
 }
 
 flag = 0;
 if (copy_to_user(buf, &global_var, sizeof(int)))
 {
    up(&sem);
     return - EFAULT;
 }
 
 up(&sem);
 
 return sizeof(int);
}
 
static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len,
 loff_t *off)
{
 if (down_interruptible(&sem))  {
     return - ERESTARTSYS;
 }
 if (copy_from_user(&global_var, buf, sizeof(int)))
 {
    up(&sem);
     return - EFAULT;
 }
 up(&sem);
 flag = 1;
 //通知数据可获得
 wake_up_interruptible(&outq);
 
 return sizeof(int);
}
 
module_init(globalvar_init);
module_exit(globalvar_exit); 编写两个用户态的程序来测试,第一个用于阻塞地读/dev/globalvar,另一个用于写/dev/globalvar。只有当
后一个对/dev/globalvar 进行了输入之后,前者的 read 才能返回。
读的程序为:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
main()
{
 int fd, num;
 
 fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR);
 if (fd != - 1)
 {
    while (1)
    {
       read(fd, &num, sizeof(int)); //程序将阻塞在此语句,除非有针对 globalvar 的输入
      printf("The globalvar is %d\n", num);
 
       //如果输入是 0,则退出
       if (num == 0)
      {
         close(fd);         break;
      }
    }  }
 else
 {
    printf("device open failure\n");
 }
}
写的程序为:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
main()
{
 int fd, num;
 
 fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR);
 if (fd != - 1)
 {
    while (1)
    {
      printf("Please input the globalvar:\n");
       scanf("%d", &num);
      write(fd, &num, sizeof(int));
         //如果输入 0,退出
       if (num == 0)
      {
         close(fd);
        break;
      }
    }
 }
 else
 {
    printf("device open failure\n");
 }
}
打开两个终端,分别运行上述两个应用程序,发现当在第二个终端中没有输入数据时,第一个终端没有输出(阻
塞),每当我们在第二个终端中给 globalvar 输入一个值,第一个终端就会输出这个值,如下图:
关于上述例程,我们补充说一点,如果将驱动程序中的 read 函数改为:
static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t
 *off)
{
 //获取信号量:可能阻塞  
 //释放信号量
 up(&sem);
 
 return sizeof(int);
}
即交换 wait_event_interruptible(outq, flag != 0)和 down_interruptible(&sem)的顺序,这个驱动程序
将变得不可运行。实际上,当两个可能要阻塞的事件同时出现时,即两个 wait_event 或 down 摆在一起的时
候,将变得非常危险,死锁的可能性很大,这个时候我们要特别留意它们的出现顺序。当然,我们应该尽可能
地避免这种情况的发生!
+还有一个与设备阻塞与非阻塞访问息息相关的论题,即 select 和 poll,select 和 poll 的本质一样,前者在
BSD Unix 中引入,后者在 System V 中引入。poll 和 select 用于查询设备的状态,以便用户程序获知是否能
对设备进行非阻塞的访问,它们都需要设备驱动程序中的 poll 函数支持。

深入下去有很多东西可以写,暂时就写这么多了, 希望大家能提出意见,谢谢