第8章 Linux设备驱动中的阻塞与非阻塞I/O-支持轮询操作的globalfifo驱动

8.3 支持轮询操作的globalfifo驱动

8.3.1 在globalfifo驱动中增加轮询操作

1、驱动头文件

#ifndef __GLOBAL_MEM_H
#define __GLOBAL_MEM_H

#include <linux/cdev.h>
#include <linux/mutex.h>
#include <linux/wait.h>

#define DRIVER_AUTHOR "[email protected]"
#define DRIVER_DESC   "A sample driver" 

#define GLOBALFIFO_SIZE 0x1000
#define MEM_CLEAR 0x1
#define DEV_NAME "global_fifo"
#define GLOBALFIFO_MAJOR 230 /* 主设备号 */

/**
*
* 定义全局内存字符设备的结构体:
* 借用面向对象程序设计中“封装”的思想,体现良好的编程习惯。
*
*/
struct globalfifo_dev {
struct cdev cdev;
unsigned int current_len; // FIFO中有效数据的长度
unsigned char mem[GLOBALFIFO_SIZE];
struct mutex mutex; // 互斥锁
wait_queue_head_t r_wait;// 读等待队列头部
wait_queue_head_t w_wait;// 写等待队列头部
};


#endif /* __GLOBAL_MEM_H */

2、驱动源代码文件

#include <linux/init.h>  
#include <linux/module.h> 
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/poll.h>

/* include local head files */
#include "global_fifo.h"

static int globalfifo_major = GLOBALFIFO_MAJOR;
module_param(globalfifo_major, int, S_IRUGO); /* mode:S_IRUGO */

struct globalfifo_dev *globalfifo_devp = NULL;


/**
*
* 读写函数
* 让设备结构体globalfifo_dev的mem[]数组与用户空间交互数据,
* 并随着访问的字节数变更更新文件读写偏移位置。
*/
// globalfifo设备驱动的读函数,从设备中读取数据
static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
int ret;
struct globalfifo_dev *dev = filp->private_data; // 获取私有数据指针

DECLARE_WAITQUEUE(wait, current);// 定义并初始化一个名为wait的等待队列元素
mutex_lock(&dev->mutex); // 获取互斥锁
add_wait_queue(&dev->r_wait, &wait); // 把等待队列元素wait添加到读等待队列头部r_wait指向的双向链表中
while (dev->current_len == 0) { // FIFO为空
if (filp->f_flags & O_NONBLOCK) { // 非阻塞
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);// 改变进程状态TASK_INTERRUPTIBLE,浅度睡眠标记,并没有真正睡眠.
mutex_unlock(&dev->mutex); // 释放互斥锁
schedule();// 让读进程进入睡眠,调度其他进程执行
if (signal_pending(current)) { // 判断是否被信号唤醒
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}

if (count > dev->current_len)
count = dev->current_len;

if (copy_to_user(buf, dev->mem, count)) { // 把内核空间中的数据拷贝到用户空间,该函数可能引起阻塞
ret = -EFAULT;
goto out;
} else {
memcpy(dev->mem, dev->mem + count, dev->current_len - count);
dev->current_len -= count;
printk(KERN_INFO "read %d bytes(s),current_len:%d\n", count, dev->current_len);
wake_up_interruptible(&dev->w_wait);// 唤醒可能阻塞的写进程
ret = count;
}


out:
mutex_unlock(&dev->mutex); // 释放互斥锁
out2:
remove_wait_queue(&dev->w_wait, &wait); // 把等待队列元素wait从读等待队列头部r_wait指向的双向链表中移除
set_current_state(TASK_RUNNING); // 设置进程状态为TASK_RUNNING
return ret;
}


// globalfifo设备驱动的写函数,向设备发送数据
static ssize_t globalfifo_write(struct file *filp, const char __user * buf, size_t count, loff_t * ppos)
{
struct globalfifo_dev *dev = filp->private_data;
int ret;
DECLARE_WAITQUEUE(wait, current);// 定义并初始化一个名为wait的等待队列元素

mutex_lock(&dev->mutex);// 获取互斥锁
add_wait_queue(&dev->w_wait, &wait);// 把等待队列元素wait添加到写等待队列头部w_wait指向的双向链表中
 
while (dev->current_len == GLOBALFIFO_SIZE) { // FIFO为满
if (filp->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);// 改变进程状态TASK_INTERRUPTIBLE,浅度睡眠标记,并没有真正睡眠.
mutex_unlock(&dev->mutex);
schedule();// 让写进程进入睡眠,调度其他进程执行
if (signal_pending(current)) {// 判断是否被信号唤醒
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}
 
if (count > (GLOBALFIFO_SIZE - dev->current_len))
count = GLOBALFIFO_SIZE - dev->current_len;
 
if (copy_from_user(dev->mem + dev->current_len, buf, count)) { // 把用户空间中的数据写到内核空间,该函数可能引起阻塞
ret = -EFAULT;
goto out;
} else {
dev->current_len += count;
printk(KERN_INFO "written %d bytes(s),current_len:%d\n", count, dev->current_len);
wake_up_interruptible(&dev->r_wait);// 唤醒可能阻塞的读进程
ret = count;
}
 
out:
mutex_unlock(&dev->mutex); // 释放互斥锁
out2:
remove_wait_queue(&dev->w_wait, &wait);// 把等待队列元素wait从写等待队列头部w_wait指向的双向链表中移除
set_current_state(TASK_RUNNING);// 设置进程状态为TASK_RUNNING
return ret;
}

// globalfifo设备驱动的seek函数
// seek函数对文件定位的起始地址可以是文件开头(SEEK_SET,0)、当前位置(SEEK_CUR,1)和文件尾(SEEK_END,2),假设globalfifo支持从文件开头和当前位置的相对偏移。
// 在定位的时候,应该检查用户请求的合法性,若不合法,函数返回-EINVAL,合法时更新文件的当前位置并返回该位置。
static loff_t globalfifo_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig) {
case 0: /* 从文件开头位置 seek */
if (offset < 0) {
ret = -EINVAL;
break;
}
if ((unsigned int)offset > GLOBALFIFO_SIZE) {
ret = -EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1: /* 从文件当前位置开始 seek */
if ((filp->f_pos + offset) > GLOBALFIFO_SIZE) {
ret = -EINVAL;
break;
}
if ((filp->f_pos + offset) < 0) {
ret = -EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = -EINVAL;
break;
}

return ret;
}


// ioctl函数
// globalfifo设备驱动的ioctl函数接受MEM_CLEAR命令,这个命令会将全局内存的有效数据长度清0,对于设备不支持的命令,ioctl函数应该返回-EINVAL。
static long globalfifo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct globalfifo_dev *dev = filp->private_data;// 使用文件的私有数据访问设备结构体

switch (cmd) {
case MEM_CLEAR:
memset(dev->mem, 0, GLOBALFIFO_SIZE); // 清空内存
printk(KERN_INFO "globalfifo is set to zero\n");
break;
default:
return -EINVAL;
}

return 0;
}


static int globalfifo_open(struct inode *inode, struct file *filep)

filep->private_data = globalfifo_devp;// 设置私有数据,将文件的私有数据private_data指向设备结构体的实例,体现Linux的面向对象的设计思想
printk(KERN_INFO "globalfifo_open\n");
return 0; /* 成功 */
}

static int globalfifo_release(struct inode *inode, struct file *filep)
{
printk(KERN_INFO "globalfifo_release\n");
return 0;
}

static unsigned int globalfifo_poll (struct file *filep, struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct globalfifo_dev *dev = filep->private_data;// 使用文件的私有数据访问设备结构体

mutex_lock(&dev->mutex);
poll_wait(filep, &dev->r_wait, wait); // 加入读等待队列
    poll_wait(filep, &dev->w_wait, wait); // 加入写等待队列

if (dev->current_len != 0) { // 可读(FIFO非空)
mask |= POLLIN | POLLRDNORM; // 设置位掩码
}

if (dev->current_len != GLOBALFIFO_SIZE) { // 可写(FIFO非满)
mask |= POLLOUT | POLLWRNORM; // 设置位掩码
}

mutex_unlock(&dev->mutex);
return mask;
}

// globalfifo设备驱动的文件操作结构体
static const struct file_operations globalfifo_fops = {
.owner = THIS_MODULE,
.llseek = globalfifo_llseek,
.read = globalfifo_read,
.write = globalfifo_write,
.unlocked_ioctl = globalfifo_ioctl,
.open = globalfifo_open,
.release = globalfifo_release,
.poll = globalfifo_poll,
};
 
static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int minor)
{
int err, devno = MKDEV(globalfifo_major, minor);
cdev_init(&dev->cdev, &globalfifo_fops); // 初始化字符设备
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1); // 注册字符设备到内核
if (err)
printk(KERN_NOTICE "Error %d adding globalfifo [%d]", err, minor);
}

static int __init global_fifo_init(void)
{
int ret;

dev_t devno = MKDEV(globalfifo_major, 0); /* 主设备号、次设备号合并为设备号 */
if (globalfifo_major)
ret = register_chrdev_region(devno, 1, DEV_NAME); // 静态分配设备号
else {
ret = alloc_chrdev_region(&devno, 0, 1, DEV_NAME); // 动态分配设备号
globalfifo_major = MAJOR(devno); // 提取主设备号
}
if (ret < 0)
return ret;

globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev), GFP_KERNEL); /* 分配内存并且清空内存 */
if (!globalfifo_devp) {
ret = -ENOMEM;
goto fail_kmalloc;
}

globalfifo_setup_cdev(globalfifo_devp, 0); // 注册字符设备

mutex_init(&globalfifo_devp->mutex);  // 初始化互斥锁
init_waitqueue_head(&globalfifo_devp->r_wait);// 初始化读等待队列头部
init_waitqueue_head(&globalfifo_devp->w_wait);// 初始化写等待队列头部

    printk(KERN_INFO "----global_fifo_init----\n");
    return 0; // 表示成功

fail_kmalloc:
unregister_chrdev_region(devno, 1); // 注销分配设备号
return ret;
}


static void __exit global_fifo_exit(void)
{
cdev_del(&globalfifo_devp->cdev); // 注销字符设备
kfree(globalfifo_devp); // 释放分配的内存空间
unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);// 注销分配设备号
    printk(KERN_INFO "----global_fifo_exit----\n");
}

module_init(global_fifo_init);
module_exit(global_fifo_exit);

MODULE_VERSION("v1.0");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);

MODULE_ALIAS(DRIVER_DESC);

3、Makefile文件

ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:                               
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:                                             
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
else
    obj-m := global_fifo.o

endif

4、驱动测试文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>  
  
#define DEV_NAME "/dev/globalfifo"
#define FIFO_CLEAR 0x1
#define BUFFER_LEN 20


void main(void)  
{  
    int fd, num;
char rd_ch[BUFFER_LEN];
fd_set rfds, wfds; /* 读/写文件描述符集 */
 
   /* 以非阻塞、只读方式打开设备文件:/dev/globalfifo */
fd = open(DEV_NAME, O_RDONLY | O_NONBLOCK);
if (fd != -1) {
/* FIFO清0 */
if (ioctl(fd, FIFO_CLEAR, 0) < 0)
            printf("ioctl command failed\n");


while (1) {
            FD_ZERO(&rfds);//清除一个读文件描述符集合
            FD_ZERO(&wfds);
            FD_SET(fd, &rfds);//将一个文件描述符fd加入读文件描述符集合中
            FD_SET(fd, &wfds);


            select(fd + 1, &rfds, &wfds, NULL, NULL);// 多路复用IO select(应用程序)->poll(驱动程序)
            /* 数据可获得 */
            if (FD_ISSET(fd, &rfds))// 判断文件描述符是否被置位:可读
                printf("Poll monitor:can be read\n");
            /* 数据可写入 */
            if (FD_ISSET(fd, &wfds))// 判断文件描述符是否被置位:可写
                printf("Poll monitor:can be written\n");
       }
} else {
printf("Device open failure\n");
}
    
close(fd);

}      

8.3.2 在用户空间中验证globalfifo设备的轮询

1、sudo insmod global_fifo.ko

2、创建设备文件 sudo mknod -m 0666 /dev/globalfifo c 230 0

3、./test

结论:当没有任何输入,即FIFO为空时,程序不断地输出Poll monitor:can be written,通过echo向/dev/globalfifo写入一些数据后,将输出Poll monitor:can be read和Poll monitor:can be written,如果不断地通过echo向/dev/globalfifo写入数据直至写满FIFO,则发现pollmonitor程序将只输出Poll monitor:can be read。对于globalfifo而言,不会出现既不能读,又不能写的情况。

猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/80397008