一、篇头
继续使用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 --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 #
操作画面如下图: