内核实验(九):添加IO驱动的阻塞读写功能

一、篇头

继续使用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 <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/kfifo.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/slab.h> 



#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-stable

obj-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) clean
	rm -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.o
  MODPOST /home/szhou/works/qemu_linux/linux-stable/my_kmodules/Module.symvers
  CC [M]  /home/szhou/works/qemu_linux/linux-stable/my_kmodules/test_9.mod.o
  LD [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 --static3)将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)]

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿迷创客

感谢!您的支持是我写作的动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值