文章目录
一、IO模型
(一)种类
非阻塞IO
阻塞IO
IO多路复用
异步通知(信号驱动IO)
(二)非阻塞IO模型
当应用程序使用非阻塞IO模型读取数据时,不管数据是否就绪,函数都要立即返回。如果数据就绪,返回的就是读取到的数据,如果没有数据就返回错误。
非阻塞IO模型会增加系统调用的次数,让程序的执行效率变低
1. 实现机制
US:
fd = open("/dev/mycdev0",O_RDWR|O_NONBLOCK); //非阻塞方式打开
/*或者使用如下方式实现非阻塞打开
fd = open("/dev/mycdev0",O_RDWR);
unsigned int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
*/
read(fd,buf,sizeof(buf));
----------------------------------------------------
KS:
mycdev_read(struct file* file,char __user* ubuf, size_t size, loff_t* offs)
{
if(file->f_flags & O_NONBLOCK){
//非阻塞方式打开的
//1.如果数据就绪返回数据
//2.如果数据没有就绪返回错误码
}
return size;
}
2. 驱动实现非阻塞方式读取数据
ssize_t myled_read(struct file* file,
char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
//对参数进行检查
if(size>sizeof(kbuf)){
size=sizeof(kbuf);
}
//使用了非阻塞方式打开
if(file->f_flags & O_NONBLOCK){
//如果数据准备好就返回数据,否则返回错误
if(strlen(kbuf)){
ret = copy_to_user(ubuf,kbuf,size);
if(ret){
pr_err("copy_to_user error\n");
return -EIO;
}
return size;
}
return -ENODATA;//返回错误码
}
return size;
}
(三)阻塞IO模型
当应用程序通过阻塞方式打开设备文件的时候,调用read函数读取数据。如果数据没有就绪,read阻塞,进程进入休眠状态;如果数据就绪了,进程需要从休眠态变为运行状态,并将就绪的数据返回给用户空间的read即可。
1. 实现机制
US:
fd = open("/dev/mycdev0",O_RDWR); //阻塞方式打开
read(fd,buf,sizeof(buf)); //读取数据
-------------------------------------------------------------
KS:
mycdev_read(struct file* file,char __user* ubuf, size_t size, loff_t* offs)
{
if(file->f_flags & O_NONBLOCK){
//非阻塞
return -EINVAL; //无效参数
}else{
//阻塞,数据没有就绪进程休眠,数据就绪了进程不休眠
}
//将数据拷贝到用户空间
//返回拷贝的字节数
}
mycdev_write()
{
//唤醒休眠
}
- 注:进程处于休眠状态,数据就绪时,进程如何知道?
方法1:当底层硬件数据就绪时会产生硬件中断,在中断处理函数中唤醒休眠的进程。
方法2:当A进程数据没有就绪而休眠时,可以再启动一个B进程去唤醒A进程。
此示例采用方法2
2. 阻塞IO模型的API
1. 定义等待队列头
wait_queue_head_t wq_head;
2. 初始化等待队列头
init_waitqueue_head(&wq_head);
3. 阻塞等待
wait_event(wq_head, condition);
wait_event_interruptible(wq_head, condition);
4. 唤醒
condition = 1;
wake_up(&wq_head);
wake_up_interruption(&wq_head);
- 注:实现机制:
- 将进程加入到等待队列中,当调用wake_up函数时,就会唤醒队列头,然后检查condition是否满足条件,如果满足就退出休眠,否则继续休眠
3. 驱动实现阻塞IO模型
#include <linux/module.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include "mycdev.h"
int major=0;//主设备号
int minor=0;//次设备号
dev_t mydev;//设备号
struct cdev* cdev;
char kbuf[128];
struct class *mycls;
struct device* mydevice;
dev_t currt_dev;
/***1. 定义队列头,初始化等待条件***/
wait_queue_head_t my_wait_head;//定义队列头
int condition=0;
int mycdev_open(struct inode* inode, struct file* file)
{
pr_info("%s:%d\n",__func__,__LINE__);
return 0;
}
ssize_t mycdev_read(struct file* file,
char __user* ubuf, size_t size, loff_t* offs)
{
int currt_index = (int)file->private_data;
int ret;
//对参数进行检查
if(size>sizeof(kbuf)){
size=sizeof(kbuf);
}
//使用了非阻塞方式打开
if(file->f_flags & O_NONBLOCK){
//如果数据准备好就返回数据,否则返回错误
if(strlen(kbuf)){
ret = copy_to_user(ubuf,kbuf,size);
if(ret){
pr_err("copy_to_user error\n");
return -EIO;
}
return size;
}
return -ENODATA;//返回错误码
}else{
/***3. 阻塞等待条件满足,进程进入休眠***/
ret = wait_event_interruptible(my_wait_head,condition);
printk("wake up %d\n",currt_index);
if(ret){//如果不为0说明是信号中断唤醒的
pr_err("wake_up by signal...\n");
return ret;
}
//如果是数据就绪唤醒,就将数据拷贝给用户
ret = copy_to_user(ubuf,kbuf,size);
if(ret){
pr_err("copy_to_user error\n");
return -EIO;
}
//拷贝数据完成后,要将condition置0,为下次阻塞做准备
condition= 0;
return size;
}
}
ssize_t mycdev_write(struct file* file,
const char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
int currt_index = (int)file->private_data;
memset(kbuf, 0, sizeof(kbuf));
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if (ret) {
pr_err("copy_from_user error\n");
return -EIO;
}
/***4. 获取到了用户空间的数据,将满足条件置1***/
condition = 1; //将条件置1
wake_up_interruptible(&my_wait_head);
return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
return 0;
}
const struct file_operations myfops={
.open=mycdev_open,
.release=mycdev_close,
.write=mycdev_write,
.read=mycdev_read,
};
static int __init mycdev_init(void){
int i;
//分配对象
cdev = cdev_alloc();
if(NULL == cdev){ //成功返回结构体指针,失败返回NULL
pr_err("cdv_err error");
return -ENOMEM;
}
//初始化对象:部分成员初始化
cdev_init(cdev,&myfops);
//申请设备号:如果major为0,则动态申请,否则就静态指定
if(major > 0){
register_chrdev_region(MKDEV(major,minor),1,"mycdev");
}else if(major == 0){
alloc_chrdev_region(&mydev,0,1,"mycdev"); //此处的name是cat /proc/devices
major=MAJOR(mydev);
minor=MINOR(mydev);
}
printk("major = %d",major);
//注册
cdev_add(cdev,MKDEV(major,minor),1);
//自动创建设备节点
mycls=class_create(THIS_MODULE,"mycdev");
mydevice = device_create(mycls,NULL,MKDEV(major,minor+i),NULL,"mycdev");
/***2. 初始化等待队列头***/
init_waitqueue_head(&my_wait_head);
return 0;
}
static void __exit mycdev_exit(void){
device_destroy(mycls, MKDEV(major, minor));
class_destroy(mycls);
cdev_del(cdev);
unregister_chrdev_region(MKDEV(major, minor), 1);
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
4. 进阶版:实现多个阻塞IO的驱动进程同时运行时,指定某个进程唤醒
#include <linux/module.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include "mycdev.h"
int major=0;//主设备号
int minor=0;//次设备号
dev_t mydev;//设备号
struct cdev* cdev;
char kbuf[128];
struct class *mycls;
struct device* mydevice;
dev_t currt_dev;
wait_queue_head_t my_wait_head;//定义队列头
int condition[128] = {0}; //每进入一个进程就为其分配一个唯一的标识号
int my_index=0;
int mycdev_open(struct inode* inode, struct file* file)
{
file->private_data = (void *)my_index++;
if(my_index == 128)
my_index=0;
pr_info("%s:%d\n",__func__,__LINE__);
return 0;
}
ssize_t mycdev_read(struct file* file,
char __user* ubuf, size_t size, loff_t* offs)
{
int currt_index = (int)file->private_data;
int ret;
//对参数进行检查
if(size>sizeof(kbuf)){
size=sizeof(kbuf);
}
//使用了非阻塞方式打开
if(file->f_flags & O_NONBLOCK){
//如果数据准备好就返回数据,否则返回错误
if(strlen(kbuf)){
ret = copy_to_user(ubuf,kbuf,size);
if(ret){
pr_err("copy_to_user error\n");
return -EIO;
}
return size;
}
return -ENODATA;//返回错误码
}
//使用阻塞方式打开
ret = wait_event_interruptible(my_wait_head,condition[currt_index]);
printk("wake up %d\n",currt_index);
if(ret){//如果不为0说明是信号中断唤醒的
pr_err("wake_up by signal...\n");
return ret;
}
//如果是数据就绪唤醒,就将数据拷贝给用户
kbuf[0]='0'+currt_index;
ret = copy_to_user(ubuf,kbuf,size);
if(ret){
pr_err("copy_to_user error\n");
return -EIO;
}
//拷贝数据完成后,要将condition置0,为下次阻塞做准备
condition[currt_index] = 0;
return size;
}
ssize_t mycdev_write(struct file* file,
const char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
memset(kbuf, 0, sizeof(kbuf));
if (size > sizeof(kbuf))
size = sizeof(kbuf);
//从用户空间读取到要唤醒的进程的下标
ret = copy_from_user(kbuf, ubuf, size);
if (ret) {
pr_err("copy_from_user error\n");
return -EIO;
}
condition[kbuf[0]-'0'] = 1; //将条件置1
printk("cond%c is ok\n",kbuf[0]);
wake_up_interruptible(&my_wait_head);
return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
return 0;
}
const struct file_operations myfops={
.open=mycdev_open,
.release=mycdev_close,
.write=mycdev_write,
.read=mycdev_read,
};
static int __init mycdev_init(void){
int i;
//分配对象
cdev = cdev_alloc();
if(NULL == cdev){ //成功返回结构体指针,失败返回NULL
pr_err("cdv_err error");
return -ENOMEM;
}
//初始化对象:部分成员初始化
cdev_init(cdev,&myfops);
//申请设备号:如果major为0,则动态申请,否则就静态指定
if(major > 0){
register_chrdev_region(MKDEV(major,minor),1,"mycdev");
}else if(major == 0){
alloc_chrdev_region(&mydev,0,1,"mycdev"); //此处的name是cat /proc/devices
major=MAJOR(mydev);
minor=MINOR(mydev);
}
printk("major = %d",major);
//注册
cdev_add(cdev,MKDEV(major,minor),1);
//自动创建设备节点
mycls=class_create(THIS_MODULE,"mycdev");
mydevice = device_create(mycls,NULL,MKDEV(major,minor+i),NULL,"mycdev");
/***阻塞IO:初始化等待队列头***/
init_waitqueue_head(&my_wait_head);
return 0;
}
static void __exit mycdev_exit(void){
device_destroy(mycls, MKDEV(major, minor));
class_destroy(mycls);
cdev_del(cdev);
unregister_chrdev_region(MKDEV(major, minor), 1);
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");