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

       前面字符设备驱动读写时《字符设备驱动(3)-read、write》,我们用户态程序是先往设备写数据,然后再读出来,数据是有效的,但是如果先读数据,这时设备里面没有数据,那么对读来说就是资源不可用,同理,如果设备的缓存(内核中的缓存)如果满了,我们往设备写数据,写也不会成功。当资源不可用时,用户态程序和驱动一起的各种配合就组成了多种I/O模型。这些I/O模型分为非阻塞I/O,阻塞I/O,多路复用,信号驱动和异步I/O。

基本概念

为了理解这几种模型,我们来举个等吃饭的例子:

        非阻塞I/O-----一会儿就进厨房,看饭做好没有。

        阻塞I/O--------在厨房,盯着做饭。

        多路复用------厨房有100个灶台同时做饭,有个好了,就可以吃了。

        信号驱动------厨房里,饭做好了,通知你吃饭。

        异步I/O------把在厨房盯着做饭的活交给李四,做好后,李四再通知你。

在这个故事中,厨房是操作系统,饭是数据,做好饭是数据就绪条件。

        那非阻塞I/O就是用户态程序轮询过程,当资源不可用时,驱动就立即返回,并用一个错误码EAGAIN来通知资源不可用,然后用户态程序稍后再尝试。

用户态程序

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;
    while (1)
    {
        rlen = read(fd, buf, MAX_LEN - 1);
        if (rlen > 0)
        {
     	    printf("Read buf %s len %d success.\n", buf, rlen);
     	    return NULL;
        }
        else if (errno == EAGAIN)
        {
            sleep(1);
            printf("Device is not ready please wait.\n");
        }
        else
        {
            perror("Read error");
            return NULL;
        }
    } 
    
}
 
int main(void)
{
     int wlen; 
     char buf[MAX_LEN] = "abcdefg";
     pthread_t td;
 
     //注意以非阻塞方式打开文件
     fd = open(FILE_NAME, O_RDWR|O_NONBLOCK);
     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;
}

注意编译时,需要加上库pthread

$gcc app.c -l pthread

驱动程序关键点

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;
	}
	
    //判断设备缓存是否为空
    if (0 == kbuf_len)
	{
		if (pf->f_flags& O_NONBLOCK)
			return -EAGAIN;
	}

	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;
	}
	
	if (MAX_LEN == kbuf_len)
	{
		if (pf->f_flags& O_NONBLOCK)
			return -EAGAIN;
	}
    
    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;
}

完整驱动程序

//head
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/string.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;

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)
	{
		if (pf->f_flags& O_NONBLOCK)
            return -EAGAIN;
        
	}

	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;
	}
	
	if (MAX_LEN == kbuf_len)
	{
		if (pf->f_flags& O_NONBLOCK)
			return -EAGAIN;
	}
    
    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;
}
 
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 
Device is not ready please wait.
Device is not ready please wait.
Device is not ready please wait.
Device is not ready please wait.
Write buf len 7 success.
Device is not ready please wait.
Read buf abcdefg len 63 success.

tobecontinue

​ 每周三、周六更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值