文章目录
非阻塞 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
~ #