Linux设备驱动中的阻塞和非阻塞I/O

【基本概念】

1、阻塞

  阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件后再进行操作。被挂起的进程进入休眠状态(不占用cpu资源),从调度器的运行队列转移到等待队列,直到条件满足。

2、非阻塞

  非阻塞操作是指在进行设备操作是,若操作条件不满足并不会挂起,而是直接返回或重新查询(一直占用CPU资源)直到操作条件满足为止。

  当用户空间的应用程序调用read(),write()等方法时,若设备的资源不能被获取,而用户又希望以阻塞的方式来访问设备,驱动程序应当在设备驱动层的对应 read(),write()操作中,将该进程阻塞直到资源可以获取为止;若用户是以非阻塞方式获取资源,当资源不能获取时设备驱动的read()、write()应当立即返回,用户空间的read()、write()也相应的立即返回。

  阻塞从字面上听起来似乎意味着效率低,其实不是这样。如果以非阻塞方式,用户想获取某一资源只能不停地查询,这样会占用CPU大量资源。而阻塞访问,若不能获取资源就会进入休眠从而节省CPU资源给其他进程使用。

  很显然阻塞的进程会进入到休眠状态,因此必须保证有一个地方能够唤醒休眠的进程。唤醒进程的地方一般都在中断里面,意味硬件资源的获得同时往往伴随着一个中断。

  在设备驱动中,阻塞的实现通常是通过等待队列。

一、阻塞IO实现(等待队列)

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

  在Linux设备驱动中等待队列实现阻塞一般方式是:

  1. 定义一个等待队列头 wait_queue_head_t  wq_h;
  2. 初始化等待队列头
  3. 当有操作要以阻塞方式访问资源时,调用 wait_event()加入到等待队列中即可
  4. 在条件满足时调用wake_up()唤醒等待队列 

涉及到两个比较重要的数据结构:__wait_queue_head,该结构描述了等待队列的链头,其包含一个链表和一个原子锁,结构定义如下:

struct __wait_queue_head {
  spinlock_t    lock;  /* 保护等待队列资源的一个自旋锁 */
  struct list_head    task_list;   /* 等待队列 */
};
typedef struct __wait_queue_head wait_queue_head_t;

__wait_queue,该结构是对一个等待任务的抽象。每个等待任务都会抽象成一个wait_queue,并且挂载到wait_queue_head上。该结构定义如下:

struct __wait_queue
{
    unsigned int flags;
    void *private;                       /* 通常指向当前任务控制块 */

    /* 任务唤醒操作方法,该方法在内核中提供,通常为autoremove_wake_function */
    wait_queue_func_t func;
    struct list_head task_list;              /* 挂入wait_queue_head的挂载点 */
};

Linux中等待队列的实现思想如下图所示,当一个任务需要在某个wait_queue_head上睡眠时,将自己的进程控制块信息封装到wait_queue中,然后挂载到wait_queue的链表中,执行调度睡眠。当某些事件发生后,另一个任务(进程)会唤醒wait_queue_head上的某个或者所有任务,唤醒工作也就是将等待队列中的任务设置为可调度的状态,并且从队列中删除。

使用等待队列时首先需要定义一个wait_queue_head,这可以通过DECLARE_WAIT_QUEUE_HEAD宏来完成,这是静态定义的方法。该宏会定义一个wait_queue_head,并且初始化结构中的锁以及等待队列。当然,动态初始化的方法也很简单,初始化一下锁及队列就可以了。

一个任务需要等待某一事件的发生时,通常调用wait_event,该函数会定义一个wait_queue,描述等待任务,并且用当前的进程描述块初始化wait_queue,然后将wait_queue加入到wait_queue_head中。

函数实现流程说明如下:

a -- 用当前的进程描述块(PCB)初始化一个wait_queue描述的等待任务。

b -- 在等待队列锁资源的保护下,将等待任务加入等待队列。

c -- 判断等待条件是否满足,如果满足,那么将等待任务从队列中移出,退出函数。

d --  如果条件不满足,那么任务调度,将CPU资源交与其它任务。

e -- 当睡眠任务被唤醒之后,需要重复b、c 步骤,如果确认条件满足,退出等待事件函数。

等待队列的操作接口函数

1、定义和初始化“等待队列头”

wait_queue_head_t my_queue;  /* 定义等待队列头 */
init_waitqueue_head(&my_queue);  /* 初始化等待队列头 */

也可以使用Linux内核中的宏来同时完成“等待队列头”的定义和初始化

DECLARE_WAIT_QUEUE_HEAD(my_queue);

2、定义等待队列:

DECLARE_WAITQUEUE(name,tsk);  /* 定义并初始化一个名为name的等待队列。*/

3、(从等待队列头中)添加/移出等待队列:

/* add_wait_queue()函数,设置等待的进程为非互斥进程,并将其添加进等待队列头(q)的队头中*/
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
/* 该函数也和add_wait_queue()函数功能基本一样,只不过它是将等待的进程(wait)设置为互斥进程。*/
void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);

4、等待事件:

(1)wait_event(queue, condition)宏:

/**
 * wait_event - sleep until a condition gets true
 * @wq: the waitqueue to wait on
 * @condition: a C expression for the event to wait for
 *
 * The process is put to sleep (TASK_UNINTERRUPTIBLE) until the
 * @condition evaluates to true. The @condition is checked each time
 * the waitqueue @wq is woken up.
 *
 * wake_up() has to be called after changing any variable that could
 * change the result of the wait condition.
 */
#define wait_event(wq, condition)                   \
do {                                    \
  if (condition)                          \
  break;                          \
  __wait_event(wq, condition);                    \
} while (0)  

在等待会列中睡眠直到condition为真。在等待的期间,进程会被置为TASK_UNINTERRUPTIBLE进入睡眠,直到condition变量变为真。每次进程被唤醒的时候都会检查condition的值.

(2)wait_event_interruptible(queue, condition)函数:

和wait_event()的区别是调用该宏在等待的过程中当前进程会被设置为TASK_INTERRUPTIBLE状态.在每次被唤醒的时候,首先检查condition是否为真,如果为真则返回,否则检查如果进程是被信号唤醒,会返回-ERESTARTSYS错误码.如果是condition为真,则返回0.

(3)wait_event_timeout(queue, condition, timeout)宏:

也与wait_event()类似.不过如果所给的睡眠时间为负数则立即返回.如果在睡眠期间被唤醒,且condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回0
(4)wait_event_interruptible_timeout(wq, condition, timeout)宏:
   与wait_event_timeout()类似,不过如果在睡眠期间被信号打断则返回ERESTARTSYS错误码.
(5) wait_event_interruptible_exclusive(wq, condition)宏

5、唤醒队列

(1)wake_up(x)函数

#define wake_up(x)          __wake_up(x, TASK_NORMAL, 1, NULL)
/**
 * __wake_up - wake up threads blocked on a waitqueue.
 * @q: the waitqueue
 * @mode: which threads
 * @nr_exclusive: how many wake-one or wake-many threads to wake up
 * @key: is directly passed to the wakeup function
 */
void __wake_up(wait_queue_head_t *q, unsigned int mode,
            int nr_exclusive, void *key)
{
    unsigned long flags;  

    spin_lock_irqsave(&q->lock, flags);
    __wake_up_common(q, mode, nr_exclusive, 0, key);
    spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(__wake_up);  
唤醒等待队列.可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERUPTIBLE状态的进程,和wait_event/wait_event_timeout成对使用.(2)wake_up_interruptible()函数:
#define wake_up_interruptible(x)    __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

和wake_up()唯一的区别是它只能唤醒TASK_INTERRUPTIBLE状态的进程.,与wait_event_interruptible/wait_event_interruptible_timeout/ wait_event_interruptible_exclusive成对使用。

下面看一个实例:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/errno.h>

#include <asm/current.h>
#include <linux/sched.h>

#include <linux/uaccess.h>

#include <asm/atomic.h>
#include <linux/mutex.h>

#include <linux/wait.h>

#include <linux/device.h>
static struct class *cls = NULL;

static int major = 0;
static int minor = 0;
const  int count = 6;

#define DEVNAME    "demo"

static struct cdev *demop = NULL;
static atomic_t tv;
//等待队列头
static wait_queue_head_t wq;

static int counter = 0;

//打开设备
static int demo_open(struct inode *inode, struct file *filp)
{
    //get major and minor from inode
    printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
        imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);

    if(!atomic_dec_and_test(&tv)){
        atomic_inc(&tv);
        return -EBUSY;
    }

    return 0;
}

//关闭设备
static int demo_release(struct inode *inode, struct file *filp)
{
    //get major and minor from inode
    printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
        imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);

    atomic_inc(&tv);
    return 0;
}

//读设备
//ssize_t read(int fd, void *buf, size_t count)
static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    int err = 0;
    struct inode *inode = filp->f_path.dentry->d_inode;
    //get major and minor from inode
    printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
        imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);

    if(!counter){
        if(filp->f_flags & O_NONBLOCK){
            return -EAGAIN;
        }

        err = wait_event_interruptible(wq, (0 != counter));
        if(err){
            return err;
        }
    }

    //...

    counter = 0;
    return 0;
}

//写设备
static ssize_t demo_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
    struct inode *inode = filp->f_path.dentry->d_inode;
    //get major and minor from inode
    printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
        imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);

    counter = 10;
    wake_up_interruptible(&wq);

    return 0;
}

static struct file_operations fops = {
    .owner    = THIS_MODULE,
    .open    = demo_open,
    .release= demo_release,
    .read    = demo_read,
    .write    = demo_write,
};

static int __init demo_init(void)
{
    dev_t devnum;
    int ret, i;

    struct device *devp = NULL;

    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n",
        current->comm, current->pid, __FILE__, __func__, __LINE__);

    //1. alloc cdev obj    动态分配 字符设备结构体空间
    demop = cdev_alloc();
    if(NULL == demop){
        return -ENOMEM;
    }

    //2. init cdev obj    初始化字符设备,并注册操作方法集
    cdev_init(demop, &fops);

    //动态分配设备号
    ret = alloc_chrdev_region(&devnum, minor, count, DEVNAME);
    if(ret){
        goto ERR_STEP;
    }
    major = MAJOR(devnum);

    //3. register cdev obj
    ret = cdev_add(demop, devnum, count);
    if(ret){
        goto ERR_STEP1;
    }

    cls = class_create(THIS_MODULE, DEVNAME);
    if(IS_ERR(cls)){
        ret = PTR_ERR(cls);
        goto ERR_STEP1;
    }

    for(i = minor; i < (count+minor); i++){
        devp = device_create(cls, NULL, MKDEV(major, i), NULL, "%s%d", DEVNAME, i);
        if(IS_ERR(devp)){
            ret = PTR_ERR(devp);
            goto ERR_STEP2;
        }
    }

    // init atomic_t    初始化原子量
    atomic_set(&tv, 1);

    init_waitqueue_head(&wq);        //初始化等待队列

    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - ok.\n",
        current->comm, current->pid, __FILE__, __func__, __LINE__);
    return 0;

ERR_STEP2:
    for(--i; i >= minor; i--){
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);

ERR_STEP1:
    unregister_chrdev_region(devnum, count);

ERR_STEP:
    cdev_del(demop);

    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - fail.\n",
        current->comm, current->pid, __FILE__, __func__, __LINE__);
    return ret;
}

static void __exit demo_exit(void)
{
    int i;
    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - leave.\n",
        current->comm, current->pid, __FILE__, __func__, __LINE__);

    for(i=minor; i < (count+minor); i++){
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);

    unregister_chrdev_region(MKDEV(major, minor), count);
    cdev_del(demop);
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Farsight");
MODULE_DESCRIPTION("Demo for kernel module");

工作队列使用范例

注意两个概念:

a --  疯狂兽群

wake_up的时候,所有阻塞在队列的进程都会被唤醒,但是因为condition的限制,只有一个进程得到资源,其他进程又会再次休眠,如果数量很大,称为 疯狂兽群

b -- 独占等待

等待队列的入口设置一个WQ_FLAG_EXCLUSIVE标志,就会添加到等待队列的尾部,没有设置设置的添加到头部,wake up的时候遇到第一个具有WQ_FLAG_EXCLUSIVE这个标志的进程就停止唤醒其他进程。

二、非阻塞I/O实现方式 —— 多路复用

1、轮询的概念和作用

      在用户程序中,select() 和 poll() 也是设备阻塞和非阻塞访问息息相关的论题。使用非阻塞I/O的应用程序通常会使用select() 和 poll() 系统调用查询是否可对设备进行无阻塞的访问。select() 和 poll() 系统调用最终会引发设备驱动中的 poll()函数被执行。

2、应用程序中的轮询编程

在用户程序中,select()和poll()本质上是一样的, 不同只是引入的方式不同,前者是在BSD UNIX中引入的,后者是在System V中引入的。用的比较广泛的是select系统调用。原型如下

int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptionfds, struct timeval *timeout);

    其中readfs,writefds,exceptfds分别是select()监视的读,写和异常处理的文件描述符集合,numfds的值是需要检查的号码最高的文件描述符加1,timeout则是一个时间上限值,超过该值后,即使仍没有描述符准备好也会返回。

 struct timeval
{
    int tv_sec;    //秒
    int tv_usec;   //微秒
}

涉及到文件描述符集合的操作主要有以下几种:

1)清除一个文件描述符集   FD_ZERO(fd_set *set);

2)将一个文件描述符加入文件描述符集中    FD_SET(int fd,fd_set *set);

3)将一个文件描述符从文件描述符集中清除  FD_CLR(int fd,fd_set *set);

4)判断文件描述符是否被置位    FD_ISSET(int fd,fd_set *set);

最后我们利用上面的文件描述符集的相关来写个验证添加了设备轮询的驱动,把上边两块联系起来

3、设备驱动中的轮询编程

设备驱动中的poll() 函数原型如下

unsigned int(*poll)(struct file *filp, struct poll_table * wait);

第一个参数是file结构体指针,第二个参数是轮询表指针,poll设备方法完成两件事:

a -- 对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列头添加到poll_table,如果没有文件描述符可用来执行 I/O, 则内核使进程在传递到该系统调用的所有文件描述符对应的等待队列上等待。

b -- 返回表示是否能对设备进行无阻塞读、写访问的掩码。

位掩码:POLLRDNORM, POLLIN,POLLOUT,POLLWRNORM

设备可读,通常返回:(POLLIN | POLLRDNORM)

设备可写,通常返回:(POLLOUT | POLLWRNORM)       

poll_wait()函数:用于向 poll_table注册等待队列

 void poll_wait(struct file *filp, wait_queue_head_t *queue,poll_table *wait)  

poll_wait()函数不会引起阻塞,它所做的工作是把当前进程添加到wait 参数指定的等待列表(poll_table)中。

真正的阻塞动作是上层的select/poll函数中完成的。select/poll会在一个循环中对每个需要监听的设备调用它们自己的poll支持函数以使得当前进程被加入各个设备的等待列表。若当前没有任何被监听的设备就绪,则内核进行调度(调用schedule)让出cpu进入阻塞状态,schedule返回时将再次循环检测是否有操作可以进行,如此反复;否则,若有任意一个设备就绪,select/poll都立即返回。

具体过程如下:

a -- 用户程序第一次调用select或者poll,驱动调用poll_wait并使两条队列都加入poll_table结构中作为下次调用驱动函数poll的条件,一个mask返回值指示设备是否可操作,0为未准备状态,如果文件描述符未准备好可读或可写,用户进程被会加入到写或读等待队列中进入睡眠状态。

b -- 当驱动执行了某些操作,例如,写缓冲或读缓冲,写缓冲使读队列被唤醒,读缓冲使写队列被唤醒,于是select或者poll系统调用在将要返回给用户进程时再次调用驱动函数poll,驱动依然调用poll_wait 并使两条队列都加入poll_table结构中,并判断可写或可读条件是否满足,如果mask返回POLLIN | POLLRDNORM或POLLOUT | POLLWRNORM则指示可读或可写,这时select或poll真正返回给用户进程,如果mask还是返回0,则系统调用select或poll继续不返回

下面是一个典型模板:

static unsigned int XXX_poll(struct file *filp, poll_table *wait)
{
    unsigned int mask = 0;
    struct XXX_dev *dev = filp->private_data;     //获得设备结构指针
    ...
    poll_wait(filp, &dev->r_wait, wait);    //加读等待对列头
    poll_wait(filp ,&dev->w_wait, wait);    //加写等待队列头
    if(...)//可读
    {
       POLLIN | POLLRDNORM;    //标识数据可获得
    }
    if(...)//可写
    {
          mask |= POLLOUT | POLLWRNORM;    //标识数据可写入
    }
    ..
    return mask;
}          

4、调用过程:

Linux下select调用的过程:

1、用户层应用程序调用select(),底层调用poll())
2、核心层调用sys_select() ------> do_select()

  最终调用文件描述符fd对应的struct file类型变量的struct file_operations *f_op的poll函数。
  poll指向的函数返回当前可否读写的信息。
  1)如果当前可读写,返回读写信息。
  2)如果当前不可读写,则阻塞进程,并等待驱动程序唤醒,重新调用poll函数,或超时返回。

3、驱动需要实现poll函数
当驱动发现有数据可以读写时,通知核心层,核心层重新调用poll指向的函数查询信息。

poll_wait(filp,&wait_q,wait) // 此处将当前进程加入到等待队列中,但并不阻塞

在中断中使用wake_up_interruptible(&wait_q)唤醒等待队列。
4、实例分析

1、memdev.h

/*mem设备描述结构体*/
struct mem_dev
{
  char *data;
  unsigned long size;
  wait_queue_head_t inq;
};

#endif /* _MEMDEV_H_ */

2、驱动程序 memdev.c

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>  

#include <linux/poll.h>
#include "memdev.h"  

static mem_major = MEMDEV_MAJOR;
bool have_data = false; /*表明设备有足够数据可供读*/  

module_param(mem_major, int, S_IRUGO);  

struct mem_dev *mem_devp; /*设备结构体指针*/  

struct cdev cdev;   

/*文件打开函数*/
int mem_open(struct inode *inode, struct file *filp)
{
    struct mem_dev *dev;  

    /*获取次设备号*/
    int num = MINOR(inode->i_rdev);  

    if (num >= MEMDEV_NR_DEVS)
            return -ENODEV;
    dev = &mem_devp[num];  

    /*将设备描述结构指针赋值给文件私有数据指针*/
    filp->private_data = dev;  

    return 0;
}  

/*文件释放函数*/
int mem_release(struct inode *inode, struct file *filp)
{
  return 0;
}  

/*读函数*/
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 */
  {
        if (filp->f_flags & O_NONBLOCK)
            return -EAGAIN;  

    wait_event_interruptible(dev->inq,have_data);
  }  

  /*读数据到用户空间*/
  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 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;
}  

/* seek文件定位函数 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{
    loff_t newpos;  

    switch(whence) {
      case 0: /* SEEK_SET */
        newpos = offset;
        break;  

      case 1: /* SEEK_CUR */
        newpos = filp->f_pos + offset;
        break;  

      case 2: /* SEEK_END */
        newpos = MEMDEV_SIZE -1 + offset;
        break;  

      default: /* can‘t happen */
        return -EINVAL;
    }
    if ((newpos<0) || (newpos>MEMDEV_SIZE))
        return -EINVAL;  

    filp->f_pos = newpos;
    return newpos;  

}
unsigned int mem_poll(struct file *filp, poll_table *wait)
{
    struct mem_dev  *dev = filp->private_data;
    unsigned int mask = 0;  

   /*将等待队列添加到poll_table */
    poll_wait(filp, &dev->inq,  wait);  

    if (have_data)         mask |= POLLIN | POLLRDNORM;  /* readable */  

    return mask;
}  

/*文件操作结构体*/
static const struct file_operations mem_fops =
{
  .owner = THIS_MODULE,
  .llseek = mem_llseek,
  .read = mem_read,
  .write = mem_write,
  .open = mem_open,
  .release = mem_release,
  .poll = mem_poll,
};  

/*设备驱动模块加载函数*/
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));
     //init_waitqueue_head(&(mem_devp[i].outq));
  }  

  return 0;  

  fail_malloc:
  unregister_chrdev_region(devno, 1);  

  return result;
}  

/*模块卸载函数*/
static void memdev_exit(void)
{
  cdev_del(&cdev);   /*注销设备*/
  kfree(mem_devp);     /*释放设备结构体内存*/
  unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
}  

MODULE_AUTHOR("David Xie");
MODULE_LICENSE("GPL");  

module_init(memdev_init);
module_exit(memdev_exit);  

3、应用程序 app-write.c

#include <stdio.h>  

int main()
{
    FILE *fp = NULL;
    char Buf[128];  

    /*打开设备文件*/
    fp = fopen("/dev/memdev0","r+");
    if (fp == NULL)
    {
        printf("Open Dev memdev Error!\n");
        return -1;
    }  

    /*写入设备*/
    strcpy(Buf,"memdev is char dev!");
    printf("Write BUF: %s\n",Buf);
    fwrite(Buf, sizeof(Buf), 1, fp);  

    sleep(5);
    fclose(fp);  

    return 0;      

}  

4、应用程序 app-read.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>  

int main()
{
    int fd;
    fd_set rds;
    int ret;
    char Buf[128];  

    /*初始化Buf*/
    strcpy(Buf,"memdev is char dev!");
    printf("BUF: %s\n",Buf);  

    /*打开设备文件*/
    fd = open("/dev/memdev0",O_RDWR);  

    FD_ZERO(&rds);
    FD_SET(fd, &rds);  

    /*清除Buf*/
    strcpy(Buf,"Buf is NULL!");
    printf("Read BUF1: %s\n",Buf);  

    ret = select(fd + 1, &rds, NULL, NULL, NULL);
    if (ret < 0)
    {
        printf("select error!\n");
        exit(1);
    }
    if (FD_ISSET(fd, &rds))
        read(fd, Buf, sizeof(Buf));              

    /*检测结果*/
    printf("Read BUF2: %s\n",Buf);  

    close(fd);  

    return 0;
}  
				
时间: 2024-08-01 10:46:50

Linux设备驱动中的阻塞和非阻塞I/O的相关文章

深入浅出~Linux设备驱动中的阻塞和非阻塞I/O

今天意外收到一个消息,真是惊呆我了,博客轩给我发了信息,说是俺的博客文章有特色可以出本书,,这简直让我受宠若惊,俺只是个大三的技术宅,写的博客也是自己所学的一些见解和在网上看到我一些博文以及帖子里综合起来写的,,总之这又给了额外的动力,让自己继续前进,,希望和大家能够分享一些自己的经验,,在最需要奋斗的年级以及在技术的领域踽踽独行的过程中有共同的伙伴继续前进~ 今天写的是Linux设备驱动中的阻塞和非阻塞I/0,何谓阻塞与非阻塞I/O?简单来说就是对I/O操作的两种不同的方式,驱动程序可以灵活的

Linux设备驱动中的阻塞与非阻塞IO与并发控制

Linux设备驱动中的阻塞与非阻塞IO: 1.Linux设备驱动中的阻塞与非阻塞总结:http://m.blog.csdn.net/blog/dongteen/17264501 2.Linux设备驱动中的阻塞与非阻塞IO:http://m.blog.csdn.net/blog/dongteen/17264501 3.Linux设备驱动中的阻塞与非阻塞I/O实例:http://blog.csdn.net/wenhui_/article/details/6817659 linux内核中等待队列: 1

20150518 Linux设备驱动中的并发控制

20150518 Linux设备驱动中的并发控制 2015-05-18 Lover雪儿 总结一下并发控制的相关知识: 本文参考:华清远见<Linux 设备驱动开发详解>—第7章 Linux 设备驱动中的并发控制,更多详细内容请看原书 一.并发与竞态 并发(concurrency)指的是多个执行单元同时.并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量.静态变量等)的访问则很容易导致竞态(race conditions). 在 Linux 内核中,主要的竞态发生于如下几种情况:

Hasen的linux设备驱动开发学习之旅--linux设备驱动中的并发与竞态

/** * Author:hasen * 参考 :<linux设备驱动开发详解> * 简介:android小菜鸟的linux * 设备驱动开发学习之旅 * 主题:linux设备驱动中的并发与竞态 * Date:2014-11-04 */ 1.并发与竞态 并发(concurrency)指的是多个执行单元同时.并行被执行,而并发的执行单元对共享资源(软件上的全 局变量,静态变量等)的访问则很容易导致竞态(race conditions). 主要的竞态发生在以下几种情况: (1)对称多处理(SMP)

linux设备驱动中的并发控制

并发指的是多个执行单元同时.并行被执行,而并发的执行单元对共享资源的访问则很容易导致竞态 linux内核中主要竞态1.多对称处理器的多个CPU  2.单CPU内进程与抢占它的进程 3.中断(硬中断.软中断.Tasklet.下半部)与进程之间访问共享内存资源的代码区称为“临界区”,临界区需要被以某种互斥机制加以保护,中断屏蔽.原子操作.自旋锁和信号量等是linux设备驱动中可采用的互斥途径. 这几个互斥的介绍: 1.中断屏蔽,这个主要用于单CPU,中断屏蔽将使得中断和进程之间的并发不再发生.使用方

Linux设备驱动中的IO模型---阻塞和非阻塞IO【转】

在前面学习网络编程时,曾经学过I/O模型 Linux 系统应用编程——网络编程(I/O模型),下面学习一下I/O模型在设备驱动中的应用. 回顾一下在Unix/Linux下共有五种I/O模型,分别是: a -- 阻塞I/Ob -- 非阻塞I/Oc -- I/O复用(select和poll)d -- 信号驱动I/O(SIGIO)e -- 异步I/O(Posix.1的aio_系列函数) 下面我们先学习阻塞I/O.非阻塞I/O .I/O复用(select和poll),先学习一下基础概念 a -- 阻塞 

《Linux4.0设备驱动开发详解》笔记--第九章:Linux设备驱动中的异步通知与同步I/O

在设备驱动中使用异步通知可以使得对设备的访问可进行时,由驱动主动通知应用程序进行访问.因此,使用无阻塞I/O的应用程序无需轮询设备是否可访问,而阻塞访问也可以被类似"中断"的异步通知所取代.异步通知类似于硬件上的"中断"概念,比较准确的称谓是"信号驱动的异步I/O". 9.1 异步通知的概念和作用 异步通知:一旦设备就绪,则主动通知应用程序,该应用程序无需查询设备状态 几种通知方式比较: 阻塞I/O :一直等待设备可访问后开始访问 非阻塞I/O:

Linux 设备驱动中的file_operations

Linxu驱动中的设备文件注册的操作方法结构体,也是向用户层提供操作接口的方法体,我的版本为3.1.10 撰写不易,转载需注明出处:http://blog.csdn.net/jscese/article/details/43408625 原型在 内核源码 /include/linux/fs.h中定义: struct file_operations { struct module *owner; //第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指

深入浅出~Linux设备驱动中的并发控制

并发和竞争发生在两类体系中: 对称多处理器(SMP)的多个CPU 内核可抢占的单CPU系统 访问共享资源的代码区域称为临界区(critical sections),临界区需要以某种互斥机制加以保护.在驱动程序中,当多个线程同时访问相同的资源(critical sections)时(驱动程序中的全局变量是一种典型的共享资源),可能会引发"竞态",因此我们必须对共享资源进行并发控制.Linux内核中解决并发控制的方法又中断屏蔽.原子操作.自旋锁.信号量.(后面为主要方式) 中断屏蔽: 使用