阻塞型字符设备

阻塞型字符设备是驱动中的一个重点。

一、问题:当一个设备无法立刻满足用户的读写请求时应当如何处理?

解答:应用程序通常不关心这种问题,应用程序只是调用 read 或 write 并得到返回值。驱动程序应当(缺省地)阻塞进程,使它进入睡眠,直到请求可以得到满足。

二、阻塞方式

在阻塞型驱动程序中,Read实现方式如下:如果进程调用read,但设备没有数据或数据不足,进程阻塞。当新数据到达后,唤醒被阻塞进程。

在阻塞型驱动程序中,Write实现方式如下:如果进程调用了write,但设备没有足够的空间供其写入数据,进程阻塞。当设备中的数据被读走后,缓冲区中空出部分空间,则唤醒进程。

三、非阻塞方式

阻塞方式是文件读写操作的默认方式,但应用程序员可通过使用O_NONBLOCK标志来人为的设置读写操作为非阻塞方式。

与阻塞方式的区别:

如果设置了O_NONBLOCK标志,read和write的行为是不同的。如果进程在没有数据就绪时调用了read,或者在缓冲区没有空间时调用了write,系统只是简单地返回-EAGAIN,而不会阻塞进程。

下面试阻塞设备的程序(只是在前面使用一块内存模仿字符设备的基础上进行的简单改造,说明原理就好):

备注:文件的打开、释放、定位以及文件的结构体和以前的程序基本相同。

文件的读函数(因为是生产者和消费者的问题,所以从读函数开始比较好)

/*读函数*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/

  /*判断读位置是否有效*/
  if (p >= MEMDEV_SIZE)
    return 0;
  if (count > MEMDEV_SIZE - p)
    count = MEMDEV_SIZE - p;
    
while (!have_data) /* 没有数据可读,考虑为什么不用if,而用while,中断信号唤醒 */
                   //have_data是一个布尔变量,
{
        if (filp->f_flags & O_NONBLOCK) //判断是否按照非阻塞的方式处理。
            return -EAGAIN; //处理方式,直接返回。
	
	wait_event_interruptible(dev->inq,have_data);  //进程阻塞咯~~~
	                      //驱动程序本身既不是进程也不是线程,应用程序才是一个进程。
	                      //实际上阻塞是调用驱动程序的应用程序的进程。
} //使用while的原因:
  //设备只有一个,加入数据再设备里面,
  //那么任何一个进程都可以读,
  //原因在于wait_event_interruptible,当有数据的时候会被唤醒,信号也可以唤醒它,
  //所以应该再进行检测所以使用while,实际是while和wait_event_interruptible配合。
  //唤醒的部分请到124行。这里我理解了好久,突然发现其实还是生产者和消费者的问题。


  /*读数据到用户空间*/
  if (copy_to_user(buf, (void*)(dev->data + p), count))
  {
    ret =  - EFAULT;
  }
  else
  {
    *ppos += count;
    ret = count;
   
    printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
  }
  
  have_data = false; /* 表明不再有数据可读 返回一个值告诉没有数据了*/
  return ret;
}

下面是设备驱动模块加载函数----这一部分中有等待队列的初始化。

/*设备驱动模块加载函数*/
static int memdev_init(void)
{
  int result;
  int i;

  dev_t devno = MKDEV(mem_major, 0);

  /* 静态申请设备号*/
  if (mem_major)
    result = register_chrdev_region(devno, 2, "memdev");
  else  /* 动态分配设备号 */
  {
    result = alloc_chrdev_region(&devno, 0, 2, "memdev");
    mem_major = MAJOR(devno);
  }  
  
  if (result < 0)
    return result;

  /*初始化cdev结构*/
  cdev_init(&cdev, &mem_fops);
  cdev.owner = THIS_MODULE;
  cdev.ops = &mem_fops;
  
  /* 注册字符设备 */
  cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
   
  /* 为设备描述结构分配内存*/
  mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
  if (!mem_devp)    /*申请失败*/
  {
    result =  - ENOMEM;
    goto fail_malloc;
  }
  memset(mem_devp, 0, sizeof(struct mem_dev));
  
  /*为设备分配内存*/
  for (i=0; i < MEMDEV_NR_DEVS; i++) 
  {
        mem_devp[i].size = MEMDEV_SIZE;
        mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
        memset(mem_devp[i].data, 0, MEMDEV_SIZE);
  
  	/*初始化等待队列*/
 	init_waitqueue_head(&(mem_devp[i].inq));
  }
   
  return 0;

  fail_malloc: 
  unregister_chrdev_region(devno, 1);
  
  return result;
}


下面是整个程序的头文件,里面包括等待队列的定义

#ifndef _MEMDEV_H_
#define _MEMDEV_H_

#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 0   /*预设的mem的主设备号*/
#endif

#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2    /*设备数*/
#endif

#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif

/*mem设备描述结构体*/
struct mem_dev                                     
{                                                        
  char *data;                      
  unsigned long size; 
  wait_queue_head_t inq;   //定义等待队列头   
};

#endif /* _MEMDEV_H_ */


下面是写函数的定义---这一部分包含读函数的等待的唤醒。

/*写函数*/
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/
  
  /*分析和获取有效的写长度*/
  if (p >= MEMDEV_SIZE)
    return 0;
  if (count > MEMDEV_SIZE - p)
    count = MEMDEV_SIZE - p;
    
  /*从用户空间写入数据*/
  if (copy_from_user(dev->data + p, buf, count))
    ret =  - EFAULT;
  else
  {
    *ppos += count;
    ret = count;
    
    printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
  }
  
  have_data = true; /* 有新的数据可读 */
    
    /* 唤醒读进程 */
    wake_up(&(dev->inq));

  return ret;
}


附上两个图来说明死锁:


 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值