内核实验(八):实现O-NONBLOCK非阻塞读写

简介: 本文通过修改之前的内核模块代码,介绍了如何在Linux内核中实现O_NONBLOCK非阻塞读写机制,并通过在Qemu虚拟机上的测试验证了非阻塞读写操作的正确性。

一、篇头

继续使用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与阻塞IO

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

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

4.2 驱动源码

  • 文件名:linux-stable\my_kmodules\test_7.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>


#define MY_DEV_NAME "my_dev"

DEFINE_KFIFO(my_kfifo, char, 128);

static int test_6_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);
    return 0;
}

static int test_6_release(struct inode *inode, struct file *file)
{
   
   
    pr_info("%s \n", __func__);

    return 0;
}

static ssize_t test_6_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
   
   
    int ret;
    unsigned int copied_count=0;
    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;
        }
    }

    ret = kfifo_to_user(&my_kfifo, buf,  count, &copied_count);
    if(ret != 0) {
   
   
        return -EFAULT;
    }
    //pr_info("%s :%s, count=%d, copied_count=%d\n", __func__, my_kfifo.buf,  count, copied_count);

    return copied_count;
}

static ssize_t test_6_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
{
   
   
    int ret;
    unsigned int copied_count=0;
    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;
        }
    }

    ret = kfifo_from_user(&my_kfifo, buf, count, &copied_count);
    if(ret != 0) {
   
   
        return -EFAULT;
    }

    //pr_info("%s , my_kfifo.buf=%s\n", __func__, my_kfifo.buf);    
    return copied_count;


}

static const struct file_operations test_fops = {
   
   
    .owner = THIS_MODULE,
    .open = test_6_open,
    .release = test_6_release,
    .read = test_6_read,
    .write = test_6_write
};

static struct miscdevice test_6_misc_device ={
   
   

    .minor = MISC_DYNAMIC_MINOR,
    .name = MY_DEV_NAME,
    .fops = &test_fops,
};


static int __init test_6_init(void)
{
   
   
    int ret;

    pr_info("test_6_init\n");

    ret = misc_register(&test_6_misc_device);
    if (ret != 0 ) {
   
   
        pr_err("failed to misc_register");
        return ret;
    }

    pr_err("Minor number = %d\n", test_6_misc_device.minor);

    return 0;
}

static void __exit test_6_exit(void)
{
   
   
    pr_info("test_6_exit\n");
    misc_deregister(&test_6_misc_device);
}

module_init(test_6_init);
module_exit(test_6_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("szhou <66176468@qq.com>");
MODULE_DESCRIPTION("test_7, 使用misc、KFIFO开发设备驱动非阻塞设备驱动。");

4.3 APP源码

  • 文件名:linux-stable\my_kmodules\app_test_7.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h> //消除 warning: implicit declaration of function ‘malloc’ [-Wimplicit-function-declaration]
#include <string.h> //消除 warning: implicit declaration of function ‘memset’ [-Wimplicit-function-declaration]

#define MY_DEV_NAME "/dev/my_dev"

int main(int argc, void * argv[])
{
   
   

    int fd;
    int ret;
    size_t len;
    char message[80] = "To test reading and writing FIFO device with O_NONBLOCK.";
    char *read_buffer;

    len = sizeof(message);

    read_buffer = (char *)malloc((size_t)2*len);
    memset((void *)read_buffer, 0, 2*len);

    fd = open(MY_DEV_NAME, O_RDWR | O_NONBLOCK);
    if (fd < 0) {
   
   
        printf("open device %s failded\n", MY_DEV_NAME);
        return -1;
    }

    /*1: 先读一次,但这个时候KFIFO为空,所以会直接返回 */
    ret = read(fd, read_buffer, (size_t)2*len);
    printf("read %d bytes\n", ret);
    printf("read buffer=%s\n", read_buffer);

    /*2. 写一次数据 */
    ret = write(fd, message, len);
    if (ret != len)
        printf("have write %d bytes\n", ret);


    /*3. 再写一次数据 */
    ret = write(fd, message, len);
    if (ret != len)
        printf("have write %d bytes\n", ret);

    /*4. 读取数据,这次因为KFIFO非空,所以可以获得数据 */
    ret = read(fd, read_buffer, 2*len);
    printf("read %d bytes\n", ret);
    printf("read buffer=%s\n", read_buffer);

    close(fd);
}

4.4 Makefile

  • 文件名:linux-stable\my_kmodules\Makefile
  • 如下,继续追加 test_7.o 即可编译出test_7.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
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_7.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_7.mod.o
  LD [M]  /home/szhou/works/qemu_linux/linux-stable/my_kmodules/test_7.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_7.c -o app_test_7 --static

(3)将KO和APP存放到NFS共享目录
szhou@bc01:~/works/qemu_linux/linux-stable/my_kmodules$ cp test_7.ko app_test_7  ~/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 # insmod test_7.ko 
test_7: loading out-of-tree module taints kernel.
test_6_init
Minor number = 125
/mnt # mdev  -s

(4) 运行app测试
/mnt # ./app_test_7 
test_6_open: major=10, minor=125
test_6_read 
read -1 bytes
read buffer=
test_6_write 
test_6_write 
have write 48 bytes
test_6_read 
read 128 bytes
read buffer=To test reading and writing FIFO device with O_NONBLOCK.
test_6_release 
/mnt #

操作画面如下图:

在这里插入图片描述

相关文章
|
存储 监控 Linux
【Linux IO多路复用 】 Linux下select函数全解析:驾驭I-O复用的高效之道
【Linux IO多路复用 】 Linux下select函数全解析:驾驭I-O复用的高效之道
3007 0
|
搜索推荐 算法
经典 O(n²)比较类排序算法
经典 O(n²)比较类排序算法
249 0
|
数据可视化
在使用SVN的过程中,通过哪些执行查看某个文件的修改信息
在使用SVN的过程中,通过哪些执行查看某个文件的修改信息
2678 0
|
Rust 算法 Go
【密码学】一文读懂FNV Hash
FNV哈希全名为Fowler-Noll-Vo算法,是以三位发明人Glenn Fowler,Landon Curt Noll,Phong Vo的名字来命名的,最早在1991年提出。它可以快速hash大量的数据并保持较小的冲突概率,适合hash一些相近的字符串比如IP地址、URL、文件名等等。目前FNV算法有三个版本,分别是: FNV-0(已废弃)、FNV-1以及FNV-1a。这三个算法的结构非常相似,因此呢,在这里就一块说了。
5405 0
【密码学】一文读懂FNV Hash
|
人工智能 JavaScript API
零基础构建MCP服务器:TypeScript/Python双语言实战指南
作为一名深耕技术领域多年的博主摘星,我深刻感受到了MCP(Model Context Protocol)协议在AI生态系统中的革命性意义。MCP作为Anthropic推出的开放标准,正在重新定义AI应用与外部系统的交互方式,它不仅解决了传统API集成的复杂性问题,更为开发者提供了一个统一、安全、高效的连接框架。在过去几个月的实践中,我发现许多开发者对MCP的概念理解透彻,但在实际动手构建MCP服务器时却遇到了各种技术壁垒。从环境配置的细节问题到SDK API的深度理解,从第一个Hello World程序的调试到生产环境的部署优化,每一个环节都可能成为初学者的绊脚石。因此,我决定撰写这篇全面的实
2911 67
零基础构建MCP服务器:TypeScript/Python双语言实战指南
|
安全 Unix Linux
VMware Workstation 17.6.3 发布下载,现在完全免费无论个人还是商业用途
VMware Workstation 17.6.3 发布下载,现在完全免费无论个人还是商业用途
162908 65
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
1019 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
用户态和内核态是如何切换的?
【10月更文挑战第28天】用户态和内核态的切换是通过系统调用指令、异常和中断等机制来实现的。这些机制确保了应用程序能够在需要时请求内核提供的服务,同时也保证了内核能够对系统资源进行有效的管理和保护,维护系统的稳定性和安全性。通过准确地保存和恢复上下文信息,实现了用户态和内核态之间的无缝切换,为计算机系统的正常运行提供了有力保障。
|
存储 编译器 C语言
C语言:数组名作为类型、作为地址、对数组名取地址的区别
在C语言中,数组名可以作为类型、地址和取地址使用。数组名本身代表数组的首地址,作为地址时可以直接使用;作为类型时,用于声明指针或函数参数;取地址时,使用取地址符 (&),得到的是整个数组的地址,类型为指向该类型的指针。
1581 4
|
存储 Unix C语言
C语言进程(第三章,exec函数族,execl,execlp,execle,execv,execvp,execve)
C语言进程(第三章,exec函数族,execl,execlp,execle,execv,execvp,execve)
1255 0

热门文章

最新文章