非阻塞 IO(NIO)

文章目录

非阻塞 IO(NIO)

上一节中 https://blog.csdn.net/tyustli/article/details/135140523,使用等待队列头实现了阻塞 IO

程序使用时,阻塞 IO 和非阻塞 IO 的区别在于文件打开的时候是否使用了
O_NONBLOCK 标志位。

  • O_RDWR 默认以阻塞的方式打开
  • O_NONBLOCK 以非阻塞的方式打开

模型

驱动程序

文件打卡时,文件打开标志存放在文件结构体 struct file f_flags 字段

文件结构体原型在 linux/fs.h 文件中

c 复制代码
struct file {
	union {
		struct llist_node	f_llist;
		struct rcu_head 	f_rcuhead;
		unsigned int 		f_iocb_flags;
	};

	/*
	 * Protects f_ep, f_flags.
	 * Must not be taken from IRQ context.
	 */
	spinlock_t		f_lock;
	fmode_t			f_mode;
	atomic_long_t		f_count;
	struct mutex		f_pos_lock;
	loff_t			f_pos;
	unsigned int		f_flags;
	struct fown_struct	f_owner;
	const struct cred	*f_cred;
	struct file_ra_state	f_ra;
	struct path		f_path;
	struct inode		*f_inode;	/* cached value */
	const struct file_operations	*f_op;

	u64			f_version;
#ifdef CONFIG_SECURITY
	void			*f_security;
#endif
	/* needed for tty driver, and maybe others */
	void			*private_data;

#ifdef CONFIG_EPOLL
	/* Used by fs/eventpoll.c to link all the hooks to this file */
	struct hlist_head	*f_ep;
#endif /* #ifdef CONFIG_EPOLL */
	struct address_space	*f_mapping;
	errseq_t		f_wb_err;
	errseq_t		f_sb_err; /* for syncfs */
} __randomize_layout
  __attribute__((aligned(4)));	/* lest something weird decides that 2 is OK */

所以驱动中需要判断文件打开标志是否支持非阻塞方式

c 复制代码
    new_chrdev_t *dev = (new_chrdev_t *)file->private_data;

    if (file->f_flags & O_NONBLOCK) {
        if (!dev->flag) {
            return -EAGAIN;
        }
    }

驱动程序源码

c 复制代码
#include "asm-generic/errno-base.h"
#include "linux/device/class.h"
#include "linux/export.h"
#include "linux/uaccess.h"
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/wait.h>

#define CHRDEVBASE_NAME "chrdevbase" /* 设备名 */
#define CHRDEVBASE_NUM 1             /* 设备数目 */

static char *string_test = "kernel data this tyustli test";

typedef struct {
    dev_t dev_id;          /* 设备号 */
    struct cdev c_dev;     /* cdev */
    struct class *class;   /* 类 */
    struct device *device; /* 设备 */
    int major;             /* 主设备号 */
    int minor;             /* 次设备号 */
    int flag;              /* read flag */
    char write_buf[100];
    char read_buf[100];
    wait_queue_head_t read_wait; /* 读等待队列头 */
} new_chrdev_t;

static new_chrdev_t new_chrdev1;

static int chrdevbase_open(struct inode *inode, struct file *file)
{
    file->private_data =
            container_of(inode->i_cdev, new_chrdev_t, c_dev); /* 设置私有数据 */

    printk("k: chrdevbase open\r\n");

    return 0;
}

static ssize_t chrdevbase_read(struct file *file, char __user *buf,
                               size_t count, loff_t *ppos)
{
    unsigned long ret = 0;
    new_chrdev_t *dev = (new_chrdev_t *)file->private_data;

    if (file->f_flags & O_NONBLOCK) {
        if (!dev->flag) {
            return -EAGAIN;
        }
    }

    wait_event_interruptible(dev->read_wait, dev->flag);
    dev->flag = 0;

    memcpy(dev->read_buf, string_test, strlen(string_test));

    ret = copy_to_user(buf, dev->read_buf, count);
    if (ret == 0) {
        printk("k: read data success\r\n");
    } else {
        printk("k: read data failed ret = %ld\r\n", ret);
    }

    return ret;
}

static ssize_t chrdevbase_write(struct file *file, const char __user *buf,
                                size_t count, loff_t *ppos)
{
    unsigned long ret = 0;

    new_chrdev_t *dev = (new_chrdev_t *)file->private_data;

    ret = copy_from_user(dev->write_buf, buf, count);
    if (ret == 0) {
        printk("k: write data success write data is: %s\r\n", dev->write_buf);
    } else {
        printk("k: write data failed ret = %ld\r\n", ret);
    }

    dev->flag = 1;
    wake_up_interruptible(&dev->read_wait);

    return count;
}

static int chrdevbase_release(struct inode *inode, struct file *file)
{
    printk("k: chrdevbase release\r\n");

    return 0;
}

static struct file_operations chrdevbase_fops = {
    .owner = THIS_MODULE,
    .open = chrdevbase_open,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
    .release = chrdevbase_release,
};

static int __init chrdevbase_init(void)
{
    int err = 0;

    err = alloc_chrdev_region(&new_chrdev1.dev_id, 0, CHRDEVBASE_NUM,
                              CHRDEVBASE_NAME);
    if (err < 0) {
        printk("k: alloc chrdev region failed err = %d\r\n", err);
        goto err_chrdev;
    }

    /* get major 1 and minor 1 */
    new_chrdev1.major = MAJOR(new_chrdev1.dev_id);
    new_chrdev1.minor = MINOR(new_chrdev1.dev_id);
    printk("k: newcheled major=%d,minor=%d\r\n", new_chrdev1.major,
           new_chrdev1.minor);

    new_chrdev1.c_dev.owner = THIS_MODULE;
    cdev_init(&new_chrdev1.c_dev, &chrdevbase_fops);
    err = cdev_add(&new_chrdev1.c_dev, new_chrdev1.dev_id, 1);
    if (err < 0) {
        printk("k: cdev add failed err = %d\r\n", err);
        goto err_cdev_add;
    }
    new_chrdev1.class = class_create("chr_test1");
    if (IS_ERR(new_chrdev1.class)) {
        err = PTR_ERR(new_chrdev1.class);
        goto err_class_create;
    }

    new_chrdev1.device = device_create(new_chrdev1.class, NULL,
                                       new_chrdev1.dev_id, NULL, "chr_test1");
    if (IS_ERR(new_chrdev1.device)) {
        err = PTR_ERR(new_chrdev1.device);
        goto err_device_create;
    }

    /* 初始化等待队列头 */
    init_waitqueue_head(&new_chrdev1.read_wait);

    new_chrdev1.flag = 0;

    printk("k: base module init\r\n");

    return 0;

err_device_create:
    class_destroy(new_chrdev1.class);
err_class_create:
    cdev_del(&new_chrdev1.c_dev);
err_cdev_add:
    unregister_chrdev_region(new_chrdev1.dev_id, CHRDEVBASE_NUM);
err_chrdev:
    return err;
}

static void __exit chrdevbase_exit(void)
{
    device_destroy(new_chrdev1.class, new_chrdev1.dev_id);
    class_destroy(new_chrdev1.class);
    cdev_del(&new_chrdev1.c_dev);

    unregister_chrdev_region(new_chrdev1.dev_id, CHRDEVBASE_NUM);

    printk("k: base module exit!\r\n");
}

module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("tyustli");
MODULE_INFO(intree, "Y"); /* loading out-of-tree module taints kernel */

应用程序

打开文件时使用 O_NONBLOCK 非阻塞方式打开文件

c 复制代码
fd = open(filename, O_RDWR | O_NONBLOCK);

如果文件读取失败,循环读取

c 复制代码
        retvalue = -1;
        while (retvalue < 0) {
            retvalue = read(fd, readbuf, 50);
            if (retvalue < 0) {
                printf("u: read file %s failed!\r\n", filename);
                sleep(3);
            } else {
                /*  读取成功,打印出读取成功的数据 */
                printf("u: read data:%s\r\n", readbuf);
            }
        }

完整应用程序代码

c 复制代码
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

static char usrdata[] = { "user data!" };

int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    char readbuf[100], writebuf[100];

    if (argc != 3) {
        printf("u: error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    /* 打开驱动文件 */
    fd = open(filename, O_RDWR | O_NONBLOCK);
    if (fd < 0) {
        printf("u: can't open file %s\r\n", filename);
        return -1;
    }

    /* 从驱动 文件读取数据 */
    if (atoi(argv[2]) == 1) {
        retvalue = -1;
        while (retvalue < 0) {
            retvalue = read(fd, readbuf, 50);
            if (retvalue < 0) {
                printf("u: read file %s failed!\r\n", filename);
                sleep(3);
            } else {
                /*  读取成功,打印出读取成功的数据 */
                printf("u: read data:%s\r\n", readbuf);
            }
        }
    }

    /* 向设备驱动写数据 */
    if (atoi(argv[2]) == 2) {
        memcpy(writebuf, usrdata, sizeof(usrdata));
        retvalue = write(fd, writebuf, 50);
        if (retvalue < 0) {
            printf("u: write file %s failed!\r\n", filename);
        }
    }

    /* 关闭设备 */
    retvalue = close(fd);
    if (retvalue < 0) {
        printf("u: can't close file %s\r\n", filename);
        return -1;
    }

    return 0;
}

模块使用

模块安装

shell 复制代码
modprobe my_module

查看设备节点

shell 复制代码
ls /dev
shell 复制代码
~ # ls /dev/
chr_test1        ptypc            tty32            tty7
console          ptypd            tty33            tty8
cpu_dma_latency  ptype            tty34            tty9
full             ptypf            tty35            ttyAMA0
gpiochip0        random           tty36            ttyAMA1
gpiochip1        root             tty37            ttyAMA2
gpiochip2        rtc0             tty38            ttyAMA3
gpiochip3        snd              tty39            ttyp0
hwrng            tty              tty4             ttyp1

模块使用

shell 复制代码
~ # /lib/modules/6.5.7+/my_app /dev/chr_test1 1 &
~ # k: chrdevbase open
u: read file /dev/chr_test1 failed!
u: read file /dev/chr_test1 failed!
~ # /lib/modules/6.5.7+/my_app /dev/chr_test1 2
k: chrdevbase open
k: write data success write data is: user data!
k: chrdevbase release
~ # k: read data success
k: chrdevbase release
u: read data:kernel data this tyustli test

[1]+  Done                       /lib/modules/6.5.7+/my_app /dev/chr_test1 1
~ # 
相关推荐
自律的kkk5 天前
网络编程中的黏包和半包问题
java·开发语言·网络·网络编程·tcp·nio
power-辰南6 天前
Netty 常见面试题原理解析
java·开发语言·netty·nio
生活百般滋味,人生需要笑对。 --佚名9 天前
NIO 三大组件
java·开发语言·nio
鹏码纵横14 天前
如何解决 java.nio.charset.CoderMalfunctionError: 编码器故障错误问题?亲测有效的解决方法!
java·python·nio
智商低情商凑14 天前
NIO - selector简单介绍
java·开发语言·nio
智商低情商凑17 天前
Netty - NIO基础学习
nio
小猫猫猫◍˃ᵕ˂◍18 天前
NIO和零拷贝
linux·服务器·nio
程序猿进阶18 天前
ChannelInboundHandlerAdapter 与 SimpleChannelInboundHandler 的区别
java·开发语言·后端·面试·性能优化·netty·nio
材料苦逼不会梦到计算机白富美19 天前
多人聊天室 NIO模型实现
运维·服务器·nio
jupiter_88819 天前
Java NIO channel
java·服务器·nio