[ 驱动移植 ] [ 4 ] 五种 IO 模型

阻塞

驱动层实现方式:等待队列

wait_queue_head_t  // 等待队列头数据类型

init_waitqueue_head(wait_queue_head_t *pwd)  // 初始化等待队列头

wait_queue_t    // 等待队列项

等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可用的时候就要将这些进程对应等待队列项添加到等待队列中。如果设备可用,则将对应等待队列项从队列中移除。

根据等待队列项(进程)是否可以自动唤醒,可以将睡眠分为:浅度睡眠和深度睡眠

/*
功能:条件不成立则让任务进入浅度睡眠,直到条件成立醒来
  wq:等待队列头
  condition:C 语言表达式
返回:正常唤醒返回0,信号唤醒返回非0
*/
wait_event_interruptible(wq, condition)

wait_event(wq, condition)  // 深度睡眠

// 与之对应的唤醒函数如下
wake_up_interruptible(wait_queue_head_t *pwd)
wake_up(wait_queue_head_t *pwd)


非阻塞

实现方式:轮询

poll、epoll 和 select 都可以用于处理轮询。例如当应用程序调用 poll 函数时,设备驱动中 poll 函数就会被执行,因此需要在设备驱动中编写 poll 函数。

实现步骤{1}:

1. 调用 poll_wait 将包含当前文件描述符的 poll_table_entry 加入等待队列

2. 返回一个 mask 值,表明目前哪些操作可以无阻塞进行

void poll_wait(struct file* filp, wait_queue_head_t* wait_address, poll_table* p);
/*
  功能:将等待队列头加至 poll_table 表中
  参数:struct file:设备文件
  wait_queue_head_t:等待队列头
  poll_table:poll_table表
*/

testpoll_app.c

#include <poll.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define BUFSIZE 10

int main(void) {
    int ret;
    int fd;
    struct pollfd fds;

    fd = open("/dev/chardev", O_RDWR | O_NONBLOCK);   // 非阻塞
    fds.fd = fd;
    fds.events = POLLIN;
    ret = poll(&fds, 1, 500);
    if (ret) {
        // 新连接

        // 客户端关闭

        // 读取数据
        if (fds.revents & POLLIN) {
            char buf[BUFSIZE] = {0};

            int len = read(fds.fd, buf, BUFSIZE - 1, 0);
            if (len > 0) {
                printf("read: buf = %s\n", buf);
            }

        }
        // 写入数据
        if (fds.revents & POLLOUT) {

        }
    } else if (ret == 0) {
        // 超时
        printf("poll time out\n");
    } else if (ret < 0) {
        // 错误
        printf("poll error\n");
    }

    return 0;
}

mychar_poll.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/uaccess.h>

// 定义3个全局变量,分别用来表示:
// 主设备号、次设备号、设备数
int major = 11;
int minor = 0;
int mychar_num = 1;

// 定义一个 struct cdev 的全局变量
// 来表示本设备
struct cdev mydev;

char kernelbuff[1024];

int mychar_open(struct inode *pnode, struct file *pfile) {
    printk("mychar_open is called\n");
    return 0;
}

int mychar_close(struct inode *pnode, struct file *pfile) {
    printk("mychar_close is called\n");
    return 0;
}

ssize_t mychar_read(struct file *filp, char __user *pbuf, size_t count, loff_t *ppos) {
    if (*ppos > 1024 || count > 1024) {
        return -EFBIG;
    }
    if ((*ppos + count) > 1024) {
        count = 1024 - *ppos;
    }
    copy_to_user(pbuf, kernelbuff, count);
    *ppos += count;
    return count;
}

ssize_t mychar_write(struct file *filp, const char __user *pbuf, size_t count, loff_t *ppos) {
    printk("mychar_write called\n");
    if (((*ppos) > sizeof(kernelbuff)) || (count > sizeof(kernelbuff))) {
        printk("Error: Allocating more than kernel buffer size");
        return -EFBIG;
    }
    if ((*ppos + count) > sizeof(kernelbuff)) {
        printk("Error: Allocating more than kernel buffer size");
        return -EFBIG;
    }
    copy_from_user(kernelbuff, pbuf, 50);
    return 1024;
}

// 定义一个 struct file_operations 结构体变量,
// 其 owner 成员设置成 THIS_MODULE
struct file_operations myops = {
    .owner = THIS_MODULE,
    .open = mychar_open,
    .release = mychar_close,
    .read = mychar_read,
    .write = mychar_write,
};

int __init mychar_init(void) {
    int ret = 0;
    dev_t devno = MKDEV(major, minor);

    // 申请设备号
    ret = register_chrdev_region(devno, mychar_num, "mychar");
    if (ret) {
        ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
        if (ret) {
            printk("get devno failed\n");
            return -1;
        }
        major = MAJOR(devno);
    }

    // 给struct cdev对象指定操作函数集
    cdev_init(&mydev, &myops);

    // 将struct cdev对象添加到内核对应的数据结构中
    mydev.owner = THIS_MODULE;
    cdev_add(&mydev, devno, mychar_num);
    return 0;
}

void __exit mychar_exit(void) {
    dev_t devno = MKDEV(major, minor);
    // 从内核中移除 struct cdev
    cdev_del(&mydev);
    // 注销设备号
    unregister_chrdev_region(devno, mychar_num);
}

MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);

运行测试程序,输出如下:





多路复用


信号驱动


异步IO


驱动程序实现外设和Linux内核进行数据交互,在交互数据时,可以采用以上五种方式,来对数据进行处理。


参考:

1. https://blog.csdn.net/m0_47696151/article/details/118654433