内核实验(九):添加IO驱动的阻塞读写功能
创始人
2025-05-30 18:16:26

一、篇头

继续使用qemu调试内核的实验。本章复习阻塞与非阻塞IO的概念和机制,然后对之前实验(八)的代码做少许修改,添加阻塞的IO读写。

二、系列文章

略……

三、实验环境

  • 编译服务器+NFS:ubuntu 22.04
  • Qemu 虚拟机:Linux version 5.15.102 + Buysbox 1.3.36 + ARM_32bit

Qemu 启动命令:qemu-system-arm -nographic -M vexpress-a9 -m 1024M -kernel arch/arm/boot/zImage -initrd …/busybox/rootfs.ext4.img.gz -dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb

四、源码

4.1 概念及接口介绍

非阻塞IO

进程发起I/O系统调用后,如果设备驱动的缓冲区没有数据,那么进程返回一个错误而不会被阻塞。如果驱动缓冲区中有数据,那么设备驱动把数据直接返回给用户进程。

阻塞

进程发起I/O系统调用后,如果设备的缓冲区没有数据,那么需要到硬件I/O中重新获取新数据,进程会被阻塞,也就是睡眠等待。直到数据准备好,进程才会被唤醒,并重新把数据返回给用户空间。

睡眠等待

Linux内核提供了简单的睡眠方式,并封装成wait_event()的宏以及其他几个扩展宏,主要功能是在让进程睡眠时也检查进程的唤醒条件。

wait_event(wq, condition)
wait_event_interruptible(wq, condition)
wait_event_timeout(wq, condition, timeout)
wait_event_interruptible_timeout(wq, condition, timeout)

wq表示等待队列头。condition是一个布尔表达式,在condition变为真之前,进程会保持睡眠状态。timeout表示当timeout时间到达之后,进程会被唤醒,因此它只会等待限定的时间。当给定的时间到了之后,wait_event_timeout()和wait_event_interruptible_timeout()这两个宏无论condition是否为真,都会返回0。
wait_event_interruptible()会让进程进入可中断睡眠状态,而wait_event()会让进程进入不可中断睡眠态,也就是说不受干扰,对信号不做任何反应,不可能发送 SIGKILL 信号使它停止,因为它们不响应信号。因此,一般驱动程序不会采用这个睡眠模式。

唤醒

wake_up(x)
wake_up_interruptible(x)wake_up()会唤醒等待队列中所有的进程;
wake_up()应该和 wait_event()或者wait_event_timeout()配对使用;
wake_up_interruptible()应该和 wait_event_interruptible()和wait_event_interruptible_timeout()配对使用。

4.2 驱动源码

  • 文件名:linux-stable\my_kmodules\test_9.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  #define MY_DEV_NAME "my_dev"struct my_device_st {const char *name;struct device *dev;struct miscdevice *miscdev;wait_queue_head_t read_queue;wait_queue_head_t write_queue;	
};static struct my_device_st *my_device; DEFINE_KFIFO(my_kfifo, char, 128);static int test_9_open(struct inode *inode, struct file *file)
{int major = MAJOR(inode->i_rdev);int minor = MINOR(inode->i_rdev);pr_info("%s: major=%d, minor=%d\n", __func__, major, minor);file->private_data = my_device;pr_info("%s: file->private_data=0x%x \n", __func__, (unsigned int)file->private_data);	return 0;
}static int test_9_release(struct inode *inode, struct file *file)
{	struct my_device_st *data = file->private_data;pr_info("%s: file->private_data=0x%x \n", __func__, (unsigned int)file->private_data);	kfree(data);file->private_data = NULL;return 0;
}static ssize_t test_9_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{int ret;unsigned int copied_count=0;struct my_device_st *my_device = file->private_data;pr_info("%s \n", __func__);/** (1)add for O_NONBLOCK reading*/if(kfifo_is_empty(&my_kfifo)){if(file->f_flags & O_NONBLOCK)return  -EAGAIN;// (2)若用户发起的是非BLOCK读,则因为空间为空,调度进程到等等队列,进程睡眠pr_info("%s: pid=%d, going to sleep\n", __func__, current->pid);ret = wait_event_interruptible(my_device->read_queue,!kfifo_is_empty(&my_kfifo));if (ret)return ret;}ret = kfifo_to_user(&my_kfifo, buf,  count, &copied_count);if(ret != 0) return -EFAULT;// (3)如果FIFI有空余,唤醒等待写入的其他进程if (!kfifo_is_full(&my_kfifo))wake_up_interruptible(&my_device->write_queue);pr_info("%s, pid=%d, actual_readed=%d, pos=%lld\n",__func__,current->pid, copied_count, *ppos);return copied_count;
}static ssize_t test_9_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
{int ret;unsigned int copied_count=0;struct my_device_st *data = file->private_data;pr_info("%s \n", __func__);/** (1)add for O_NONBLOCK writing*/if(kfifo_is_full(&my_kfifo)){if(file->f_flags & O_NONBLOCK){return  -EAGAIN;}// (2)若用户发起的是非BLOCK写,则因为空间已满,调度进程到等等队列,进程睡眠pr_info("%s: pid=%d, going to sleep\n", __func__, current->pid);ret = wait_event_interruptible(data->write_queue,!kfifo_is_empty(&my_kfifo));if (ret)return ret;}ret = kfifo_from_user(&my_kfifo, buf, count, &copied_count);if(ret != 0) return -EFAULT;// (3)如果数据非空,唤醒等待读取的其他进程if (!kfifo_is_empty(&my_kfifo))wake_up_interruptible(&data->read_queue);pr_info("%s, pid=%d, actual_readed=%d, pos=%lld\n",__func__,current->pid, copied_count, *f_pos);return copied_count;}static const struct file_operations test_fops = {.owner = THIS_MODULE,.open = test_9_open,.release = test_9_release,.read = test_9_read,.write = test_9_write
};static struct miscdevice test_9_misc_device ={.minor = MISC_DYNAMIC_MINOR,.name = MY_DEV_NAME,.fops = &test_fops,
};static int __init test_9_init(void)
{int ret;pr_info("test_9_init\n");//(1) 创建共享的数据结构,包含IO读和写的等待队列my_device = kmalloc(sizeof(struct my_device_st), GFP_KERNEL);if (!my_device)return -ENOMEM;my_device->miscdev = &test_9_misc_device;init_waitqueue_head(&my_device->read_queue);init_waitqueue_head(&my_device->write_queue);ret = misc_register(&test_9_misc_device);if (ret != 0 ) {pr_err("failed to misc_register");return ret;}pr_err("Minor number = %d\n", test_9_misc_device.minor);return 0;
}static void __exit test_9_exit(void)
{pr_info("test_9_exit\n");if(my_device)kfree(my_device);misc_deregister(&test_9_misc_device);
}module_init(test_9_init);
module_exit(test_9_exit);MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("szhou <66176468@qq.com>");
MODULE_DESCRIPTION("test_9, 使用misc、KFIFO开发设备驱动非阻塞+阻塞的设备驱动。");

4.3 APP源码

  • 简单起见,用echo和cat分别启2个进程进行读写。

4.4 Makefile

  • 文件名:linux-stable\my_kmodules\Makefile
  • 如下,继续追加 test_9.o 即可编译出test_9.ko
KDIR := /home/szhou/works/qemu_linux/linux-stableobj-m := test_1.o test_2.o test_3.o test_4.o  test_5.o test_6.o test_7.o test_9.o
all :$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) cleanrm -f *.ko

五、编译及部署

(1)执行驱动KO编译
szhou@bc01:~/works/qemu_linux/linux-stable/my_kmodules$ make
make -C /home/szhou/works/qemu_linux/linux-stable M=/home/szhou/works/qemu_linux/linux-stable/my_kmodules modules
make[1]: Entering directory '/home/szhou/works/qemu_linux/linux-stable'CC [M]  /home/szhou/works/qemu_linux/linux-stable/my_kmodules/test_9.oMODPOST /home/szhou/works/qemu_linux/linux-stable/my_kmodules/Module.symversCC [M]  /home/szhou/works/qemu_linux/linux-stable/my_kmodules/test_9.mod.oLD [M]  /home/szhou/works/qemu_linux/linux-stable/my_kmodules/test_9.ko
make[1]: Leaving directory '/home/szhou/works/qemu_linux/linux-stable'(2)执行app编译
szhou@bc01:~/works/qemu_linux/linux-stable/my_kmodules$ arm-linux-gnueabi-gcc app_test_9.c -o app_test_9 --static(3)将KO和APP存放到NFS共享目录
szhou@bc01:~/works/qemu_linux/linux-stable/my_kmodules$ cp test_9.ko app_test_9  ~/works/nfs_share/
szhou@bc01:~/works/qemu_linux/linux-stable/my_kmodules$ 

六、运行及测试

(1)启动之前编译组建的QEMU虚拟机
----------------------------------------
Welcome to szhou's tiny Linux
----------------------------------------Please press Enter to activate this console. 
~
(2)挂载NFS共享目录
~ #  mount -t nfs -o nolock 192.168.3.67:/home/szhou/works/nfs_share /mnt(3) 加载ko
~ # cd /mnt/
/mnt # 
/mnt # insmod test_9.ko 
test_9: loading out-of-tree module taints kernel.
test_9_init
Minor number = 125
/mnt # 
/mnt # mdev -s(4) 运行app测试
/mnt # cat /dev/my_dev  &
/mnt # test_9_open: major=10, minor=125
test_9_open: file->private_data=0x81b8bb00 
test_9_read 
test_9_read: pid=489, going to sleep/mnt # echo "Hellow, do block reading ."  >  /dev/my_dev 
test_9_open: major=10, minor=125
test_9_open: file->private_data=0x81b8bb00 
test_9_write 
test_9_write, pid=457, actual_readed=27, pos=0
test_9_read, pid=489, actual_readed=27, pos=0
Hellow, do block reading .
test_9_read 
test_9_read: pid=489, going to sleep
test_9_release: file->private_data=0x81b8bb00 
/mnt # 

操作画面如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uIt4gZi9-1679224390081)(images/bqvq4Cgsci5XldBx8E95-2S8ar-zuZSyHRBQxdGgUyw.png)]

相关内容

热门资讯

一文读懂强化学习! 一.了解强化学习1.1基本概念强化学习是考虑智能体(Agent)与环境&...
Spring Cloud之一:... 目录 环境 Eureka工程的创建步骤 系列目录(持续更新。。。) S...
golang实现守护进程(2) 前言golang实现守护进程,包含功能:1. 守护进程只创建一次2. 平...
url 格式详解 统一资源定位系统(uniform resource locator; url ...
elasticsearch7.... elasticsearch版本:7.17.3 目标:实现对类型为text...
SpringBoot 加载系统... 开发环境: IDEA 2022.1.4+ MyBatis         代码参考:spri...
交换机概念和知识和命令 目录 一、华为交换机基础学习的一些重要概念和知识 二、交换机常用命令大全 三、不常用的交换机命令 ...
什么是 JavaScript ... 本文首发自「慕课网」,想了解更多IT干货内容,程序员圈内热闻࿰...
【C++】C++11——lam... 文章目录一、Lambda表达式引入二、Lambda表达式语法三、Lambda表达式交换两个值四、La...
Java分布式事务(十) 文章目录🔥分布式架构的理论知识_BASE理论🔥分布式事务解决方案_最...
vmware中centos7实... 前言 在开发收银系统SAAS版本时,采用的是centos服务器,经常需要...
计算机图形学 | 可编程渲染管... 计算机图形学 | 可编程渲染管线计算机图形学 | 可编程渲染管线3.1 从固定到可编程图形编程的发展...
linux下安装两个或多个to... 安装jdk,tomcat编辑环境变量profilevi /etc/profile加入以...
selenium的显示等待、隐... 关于selenium有三种等待方式,分别为显示等待、隐式等待、强制等待 1、强制等待 ...
测牛学堂:软件测试接口自动化之... requests库 用postman进行接口测试有一定的限制,我们测试更应该掌握的是用...
day36_jdbc 今日内容 上课同步视频:CuteN饕餮的个人空间_哔哩哔哩_bilibili 同步笔记沐沐霸的博客...
【java基础】Stream流... 文章目录基本介绍流的创建流的各种常见操作forEach方法filter方法map方法peek方法fl...
幂等性通用组件 一、什么是幂等性幂等是一个数学与计算机学概念,在数学中某一元运算为幂等时,...
Nacos服务注册 又是美好的一天呀~ 个人博客地址: huanghong.top 本文预估阅读时长为3...
令人惊艳的ChatGPT项目,... 自从 ChatGPT、Stable Diffusion 发布以来,各种相关开源项目百花...
舆情监测系统有哪些优势,TOO... 舆情监测系统是一种基于大数据技术的舆情分析工具,可以帮助企业、政府等机构实时监控公众对...
【Linux】基础IO流(上) 文章目录1. 预备知识2. 回忆C接口fopenfputsfprintfsnprintf追加方式——...
设计模式(二十七)----行为... 1 概述 如上图,设计一个软件用来进行加减计算。我们第一想法就是使用工具类ÿ...
精心整理前端主流框架学习路径 版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog....
typescript声明 前言 “d.ts”文件用于为 TypeScript 提供有关用 JavaScript 编写的 API...
HashMap源码分析 Java源码系列:下方连接 http://t.csdn.cn/Nwzed 文章目录...
一、基础算法3:二分 模板题+... 文章目录算法模板整数二分算法模板浮点数二分算法模板模板题数的范围原题链接题目题解数的三次方根原题链接...
Essential C++复习... 好久没写代码了,很多东西都忘记了。复盘一下C++编写基础头文件 与 输...
三十五、DRF中的过滤、Dja... 一、DRF中的过滤 REST framework 的通用列表视图的默认行为是返回模型管理器的整个查询...
基于RZ/G2UL Corte... 以太网接口是一种广泛应用的网络接口,它可以在不同的场合实现不同的功能。例如࿰...