字符设备驱动(6)-阻塞I/O

        当进程以阻塞的方式打开设备文件时(默认方式),如果资源不可用,那么进程阻塞,也可以说进程休眠,其的具体过程就是主动将自己的状态设置为TASK_UNINTERRUPTIBLE或者TASK_INTERRUNTIBLE,然后将自己加入一个等待队列中,调用schedule主动放弃CPU,操作系统将之从运行队列上移除,并调度就绪队列上的第一个进程运行。

        对比非阻塞I/O,当资源不可用时,它不占用CPU时间,但它也有缺点,就是在阻塞(休眠)期间,再也不能做其他事情了。

        既然有休眠,就应该有对应的唤醒操作,否则进程就会一直休眠下去。驱动程序应该在资源可用时进行唤醒操作,比如我们在《字符设备驱动(5)-非阻塞I/O》里面的例子,若读数据时内核缓存里面没有数据,则阻塞等待,直到有进程或者线程往内核缓存写数据,唤醒设备,但在真实设备中,可能收到了数据,产生中断,然后在中断处理程序中读出数据,唤醒设备。另外,我们也可以指定进程的最长休眠时间,超时后进程自动苏醒,判断资源是否可用,若可用就往后进行操作,若不可用,就继续休眠。

        

        最简单的驱动程序实现如下:

static char kbuf[MAX_LEN] = {0};
static int kbuf_len = 0;
 
ssize_t my_read(struct file *pf, char __user *ubuf, size_t len, loff_t *pl)
{
	int ret = -1;
    if (MAX_LEN < len)
    {
        printk("len is large than %d.\n", MAX_LEN);
		return -1;
	}
	
    //判断设备缓存是否为空
    while (0 == kbuf_len)
	{
        //如果是非阻塞操作,返回-EAGAIN,用户态轮询
		if (pf->f_flags& O_NONBLOCK)
			return -EAGAIN;
        else
        //如果是阻塞操作,休眠1s,再判断
            sleep(1);
	}
 
	ret =  copy_to_user(ubuf, kbuf, len);
    if (0 != ret)
    {   
        printk("Copy to user failed.\n");
        return -1;
    }
    
    //读出后,把对应的设备数据清空,保留未读出数据
    memcpy(kbuf, kbuf + len, MAX_LEN - len);
    memset(kbuf + MAX_LEN - len, 0, len);
 
    //计算设备缓存长度
    if (kbuf_len > len)
    	kbuf_len -= len;
    else
        kbuf_len = 0; 
	return len;
}
 
 
ssize_t my_write(struct file *pf, const char __user *ubuf, size_t len, loff_t *pl)
{
	int ret = -1;
 
    //判断是否会写超过
    if (MAX_LEN < len + kbuf_len)
	{
        printk("len is large than %d.\n", MAX_LEN);
		return -1;
	}
	
	while (MAX_LEN == kbuf_len)
	{
        //如果是非阻塞操作,返回-EAGAIN,用户态轮询
		if (pf->f_flags& O_NONBLOCK)
			return -EAGAIN;
        else
        //如果是阻塞操作,休眠1s,再判断
            sleep(1)
	}
    
    ret = copy_from_user(kbuf + kbuf_len, ubuf, len);
    if (0 != ret)
    {   
        printk("Copy from user failed.\n");
        return -1;
    }
    kbuf_len += len;
    return len;
}

        不过linux内核中,提供了一个等待队列,等待队列通过一个等待队列头进行管理,其数据类型是wait_queue_head_t,队列节点为wait_queue_t,定义在<linux/wait.h>中。我们可以通过静态方法定义并初始化队列:

DECLARE_WAIT_QUEUE_HEAD(name)

        或使用动态方法定义并初始化队列:

wait_queue_head_t name;
init_wait_queue_head(&name);

        相关宏如下:

wait_event(wq_head, condition)	
wait_event_timeout(wq_head, condition, timeout)	
wake_up(x)


wait_event_interruptible(wq_head, condition)	
wait_event_interruptible_timeout(wq_head, condition, timeout)
wait_event_interruptible_exclusive(wq, condition)
wake_up_interruptible(x)

        wait_event是在条件condition不成立情况下将当前进行放入到等待队列并休眠的基本操作。加上time_out表示有超时时间限制,interruptible表示进程在休眠时可以通过信号来唤醒,exclusive表示进程有排他性,在默认情况下,唤醒操作将唤醒等待队列中的所有进程,但是如果一个进程以排他的方式休眠,那么唤醒操作在唤醒这个进程后,不会继续唤醒其他进程。

        等待宏如果不带timeout,返回0表示被成功唤醒,返回-ERESTARTSYS表示被信号唤醒;如果带timeout,返回0表示超时,返回大于0的值表示被成功唤醒,离超时还剩余的时间。对唤醒宏来说,有对应关系。

        上面的例子改成:

static char kbuf[MAX_LEN] = {0};
static int kbuf_len = 0;
DECLARE_WAIT_QUEUE_HEAD(my_read_queue);
DECLARE_WAIT_QUEUE_HEAD(my_write_queue);
 
ssize_t my_read(struct file *pf, char __user *ubuf, size_t len, loff_t *pl)
{
	int ret = -1;
    if (MAX_LEN < len)
    {
        printk("len is large than %d.\n", MAX_LEN);
		return -1;
	}
	
    //判断设备缓存是否为空
    if (0 == kbuf_len)
	{
        //如果是非阻塞操作,返回-EAGAIN,用户态轮询
		if (pf->f_flags& O_NONBLOCK)
			return -EAGAIN;
        else
        {
            //如果是阻塞操作,等待
            if (wait_event_interruptible(my_read_queue, 0!= kbuf_len))
                return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
        }
	}
 
	ret =  copy_to_user(ubuf, kbuf, len);
    if (0 != ret)
    {   
        printk("Copy to user failed.\n");
        return -1;
    }
    
    //读出后,把对应的设备数据清空,保留未读出数据
    memcpy(kbuf, kbuf + len, MAX_LEN - len);
    memset(kbuf + MAX_LEN - len, 0, len);
 
    //计算设备缓存长度
    if (kbuf_len > len)
    	kbuf_len -= len;
    else
        kbuf_len = 0; 
    
    //当缓存长度不满时,唤醒写队列
    if (MAX_LEN != kbuf_len)
        wake_up_interruptible(&my_write_queue);
	return len;
}
 
 
ssize_t my_write(struct file *pf, const char __user *ubuf, size_t len, loff_t *pl)
{
	int ret = -1;
 
    //判断是否会写超过
    if (MAX_LEN < len + kbuf_len)
	{
        printk("len is large than %d.\n", MAX_LEN);
		return -1;
	}
	
	if (MAX_LEN == kbuf_len)
	{
        //如果是非阻塞操作,返回-EAGAIN,用户态轮询
		if (pf->f_flags& O_NONBLOCK)
			return -EAGAIN;
        else
        {
            //如果是阻塞操作,等待
            if (wait_event_interruptible(my_write_queue, MAX_LEN != kbuf_len))
                return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
        }
	}
    
    ret = copy_from_user(kbuf + kbuf_len, ubuf, len);
    if (0 != ret)
    {   
        printk("Copy from user failed.\n");
        return -1;
    }
    kbuf_len += len;
    
    //当缓存长度不为空时,唤醒读队列
    if (0 != kbuf_len)
        wake_up_interruptible(&my_read_queue);
    return len;
}

        注意:

        1、等待函数的第一个参数是非指针变量,wake_up的参数是指针变量。

        2、调用唤醒操作时,需要在condition变化后再调用,不要在之前调用,否则会错过唤醒机会,比如上面的例子中,写操作调用wake_up_interruptible(&my_read_queue);要在kbuf_len+=len语句之后再调用,不能在其之前调用。

用户态程序

app.c

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
 
#define FILE_NAME "/dev/mydev"
#define MAX_LEN   64
 
int fd;
 
void *func(void *arg)
{
    char buf[MAX_LEN] = {0};
    int rlen;
   
    rlen = read(fd, buf, MAX_LEN - 1);
    if (rlen < 0)
    {
        perror("Read");
     	return NULL;
    }

    printf("Read buf %s len %d success.\n", buf, rlen);
}
 
int main(void)
{
     int wlen; 
     char buf[MAX_LEN] = "abcdefg";
     pthread_t td;
 
     //注意以非阻塞方式打开文件
     fd = open(FILE_NAME, O_RDWR);
     if (0 > fd) 
     {   
        printf("Open failed.\n");
        return -1; 
     }   
     
     pthread_create(&td, NULL, func, NULL);
 
     sleep(5);
 
     wlen = write(fd, buf, strlen(buf));
     if (wlen < 0)
     {
     	perror("Write error");
     	return wlen;
     }
     
     printf("Write buf len %d success.\n", wlen);
 
     while(1)
     {
     }
     return 0;
}

完整驱动程序

cdev.c

//head
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/wait.h>

#define MAJOR_CHAR 100
#define MINOR_CHAR 0
#define MAX_LEN    64 
 
static int my_open(struct inode *pnode, struct file *pfile)
{
    printk("Open cdev.\n");
    return 0;
}
 
static int my_close(struct inode *pnode, struct file *pfile)
{
    printk("Close cdev.\n");
    return 0;
}

static char kbuf[MAX_LEN] = {0};
static int kbuf_len = 0;
DECLARE_WAIT_QUEUE_HEAD(my_read_queue);
DECLARE_WAIT_QUEUE_HEAD(my_write_queue);
 
ssize_t my_read(struct file *pf, char __user *ubuf, size_t len, loff_t *pl)
{
	int ret = -1;
    if (MAX_LEN < len)
    {
        printk("len is large than %d.\n", MAX_LEN);
		return -1;
	}
	
    //判断设备缓存是否为空
    if (0 == kbuf_len)
	{
        //如果是非阻塞操作,返回-EAGAIN,用户态轮询
		if (pf->f_flags& O_NONBLOCK)
			return -EAGAIN;
        else
        {
            //如果是阻塞操作,等待
            if (wait_event_interruptible(my_read_queue, 0!= kbuf_len))
                return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
        }
	}
 
	ret =  copy_to_user(ubuf, kbuf, len);
    if (0 != ret)
    {   
        printk("Copy to user failed.\n");
        return -1;
    }
    
    //读出后,把对应的设备数据清空,保留未读出数据
    memcpy(kbuf, kbuf + len, MAX_LEN - len);
    memset(kbuf + MAX_LEN - len, 0, len);
 
    //计算设备缓存长度
    if (kbuf_len > len)
    	kbuf_len -= len;
    else
        kbuf_len = 0; 
    
    //当缓存长度不满时,唤醒写队列
    if (MAX_LEN != kbuf_len)
        wake_up_interruptible(&my_write_queue);
	return len;
}
 
 
ssize_t my_write(struct file *pf, const char __user *ubuf, size_t len, loff_t *pl)
{
	int ret = -1;
 
    //判断是否会写超过
    if (MAX_LEN < len + kbuf_len)
	{
        printk("len is large than %d.\n", MAX_LEN);
		return -1;
	}
	
	if (MAX_LEN == kbuf_len)
	{
        //如果是非阻塞操作,返回-EAGAIN,用户态轮询
		if (pf->f_flags& O_NONBLOCK)
			return -EAGAIN;
        else
        {
            //如果是阻塞操作,等待
            if (wait_event_interruptible(my_write_queue, MAX_LEN != kbuf_len))
                return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
        }
	}
    
    ret = copy_from_user(kbuf + kbuf_len, ubuf, len);
    if (0 != ret)
    {   
        printk("Copy from user failed.\n");
        return -1;
    }
    kbuf_len += len;
    
    //当缓存长度不为空时,唤醒读队列
    if (0 != kbuf_len)
        wake_up_interruptible(&my_read_queue);
    return len;
}
 
struct cdev cdevice;
 
struct file_operations cdev_ops = {
    .open    = my_open,
    .release = my_close,
    .read    = my_read,
    .write   = my_write,
};
 
//加载
static int hello_init(void)
{ 
    dev_t devno = MKDEV(MAJOR_CHAR,MINOR_CHAR);
    int ret = -1;
	printk(KERN_ALERT "Hello World.\n");
    //up kernel
        //1、注册设备号
        ret = register_chrdev_region(devno, 1, "hello");
        if (0 != ret)
        {
            printk("Register char device failed.\n");
            return ret;
        }
    
        //2、初始化字符设备结构体
        cdev_init(&cdevice, &cdev_ops);
    
        cdevice.owner = THIS_MODULE;
	
        //3、添加字符设备结构体给内核
        ret = cdev_add(&cdevice,devno , 1);
        if (0 != ret)
        {   
            //注意释放设备号
            unregister_chrdev_region(devno,1);
            printk("Unregister char device.\n");
            return ret;
        }
 
        printk("Register char device success.\n");
    //down hardware
 
    return 0;
} 
 
//卸载函数(必须)
static void hello_exit(void)//返回值是void类型,函数名自定义,参数是void
{
    dev_t devno = MKDEV(MAJOR_CHAR, MINOR_CHAR);
 
    printk(KERN_ALERT "Goodbye World.\n");
    // down hardware
 
    // up kernel
        //1、从内核中删除字符设备结构体
        cdev_del(&cdevice);
 
        //2、注销设备号
        unregister_chrdev_region(devno, 1);
}
 
//注册(必须)
module_init(hello_init);
module_exit(hello_exit);
 
//license(必须)
MODULE_LICENSE("GPL");
 
//作者与描述(可选)
MODULE_AUTHOR("Ono Zhang");
MODULE_DESCRIPTION("A simple Hello World Module");

运行结果


$ sudo mknod /dev/mydev c 100 0
$ sudo insmod cdev.ko
$ sudo ./a.out
Write buf len 7 success.
Read buf abcdefg len 63 success.

  • 19
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值