当前位置: 首页 > article >正文

Linux应用:异步IO、存储映射IO、显存的内存映射

异步IO

异步 I/O 允许应用程序在发起 I/O 操作后,继续执行其他任务,而不需要等待 I/O 操作完成。与非阻塞 I/O 不同的是,异步 I/O 不需要应用程序轮询检查 I/O 操作的状态,而是由操作系统在 I/O 操作完成后通过某种方式(如信号、回调函数等)通知应用程序。例如,在 Linux 系统中,可以使用aio_*系列函数(如aio_read、aio_write等)来实现异步 I/O。当调用aio_read函数发起一个异步读操作后,应用程序可以继续执行其他代码,当读取操作完成后,操作系统会向应用程序发送一个信号(默认是SIGIO),应用程序可以通过信号处理函数来处理读取到的数据。异步 I/O 大大提高了应用程序的并发性能和响应能力,但编程模型相对复杂,需要仔细处理 I/O 操作的完成通知和数据处理逻辑。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

#define BUFFER_SIZE 1024
int mouse_fd = -1;

// 统一的信号处理函数
void signal_handler(int signum) {
    if (signum == SIGIO) {
        char buffer[BUFFER_SIZE];
        ssize_t bytes_read;

        // 读取鼠标输入
        memset(buffer, 0, sizeof(buffer));
        bytes_read = read(mouse_fd, buffer, BUFFER_SIZE);
        if (bytes_read > 0) {
            printf("鼠标输入: 读取到 %zd 字节\n", bytes_read);
        }

        // 读取键盘输入
        memset(buffer, 0, sizeof(buffer));
        bytes_read = read(STDIN_FILENO, buffer, BUFFER_SIZE);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("键盘输入: %s", buffer);
        }
    }
}

int main() {
    int flags;
    struct sigaction sa;

    // 初始化 sigaction 结构体
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;

    // 为 SIGIO 信号设置信号处理函数
    if (sigaction(SIGIO, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    // 设置当前进程为标准输入的拥有者
    fcntl(STDIN_FILENO, F_SETOWN, getpid());

    // 获取标准输入的当前文件状态标志
    flags = fcntl(STDIN_FILENO, F_GETFL);
    if (flags == -1) {
        perror("fcntl F_GETFL");
        return 1;
    }

    // 设置标准输入为异步 I/O 模式
    flags |= O_ASYNC;
    if (fcntl(STDIN_FILENO, F_SETFL, flags) == -1) {
        perror("fcntl F_SETFL");
        return 1;
    }

    // 对鼠标设备文件进行相同的操作
    mouse_fd = open("/dev/input/mouse0", O_RDONLY);
    if (mouse_fd == -1) {
        perror("无法打开鼠标设备文件");
        return 1;
    }

    fcntl(mouse_fd, F_SETOWN, getpid());
    flags = fcntl(mouse_fd, F_GETFL);
    if (flags == -1) {
        perror("fcntl F_GETFL (鼠标)");
        close(mouse_fd);
        return 1;
    }

    flags |= O_ASYNC;
    if (fcntl(mouse_fd, F_SETFL, flags) == -1) {
        perror("fcntl F_SETFL (鼠标)");
        close(mouse_fd);
        return 1;
    }

    printf("开始异步 I/O 监听...\n");

    // 进入无限循环,等待信号
    while (1) {
        pause();
    }

    return 0;
}

在这里插入图片描述
存在bug , 多次操作键盘后,没有进入回调函数,接着移动鼠标,才会触发!!!

存储映射IO

存储映射 I/O(Memory - Mapped I/O)是一种将文件或设备的内容映射到进程地址空间的技术。通过这种方式,应用程序可以像访问内存一样访问文件或设备的数据,而不需要使用传统的read和write系统调用。在 Linux 系统中,可以使用mmap函数来实现存储映射 I/O。

#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    int fd = open("test.txt", O_RDWR);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    off_t file_size = lseek(fd, 0, SEEK_END);
    lseek(fd, 0, SEEK_SET);
    // 直接传递 fd,并且进行显式类型转换
    char *map = static_cast<char*>(mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
    if (map == MAP_FAILED) {
        perror("mmap");
        close(fd);
        exit(EXIT_FAILURE);
    }

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        munmap(map, file_size);
        close(fd);
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程
        printf("子进程读取文件内容:\n%s", map);
        // 修改映射内存中的内容
        map[0] = 'Y';
    } else {
        // 父进程
        wait(NULL);
        printf("父进程读取修改后的文件内容:\n%s", map);
    }

    // 解除映射
    if (munmap(map, file_size) == -1) {
        perror("munmap");
    }
    close(fd);
    return 0;
}

在这里插入图片描述

通过mmap函数,将文件test.txt映射到进程的地址空间,map指向映射后的内存区域。对map的读写操作就相当于对文件的读写操作,并且对映射内存的修改会自动同步到文件中。存储映射 I/O 提高了 I/O 操作的效率,特别是对于大文件的读写操作,减少了数据在用户空间和内核空间之间的复制次数。同时,多个进程可以共享同一个文件的映射,实现进程间通信和数据共享。

LCD 显存的内存映射

基本概念

LCD 显存:LCD(Liquid Crystal Display,液晶显示器)显存是专门用于存储要显示在屏幕上的图像数据的内存区域。显示器会按照一定的刷新频率从显存中读取数据,并将其转换为对应的像素点显示在屏幕上。
内存映射:内存映射是一种将物理内存地址(如 LCD 显存的物理地址)映射到进程的虚拟地址空间的技术。通过内存映射,应用程序可以像访问普通内存一样访问物理设备的内存,从而实现对设备的直接读写操作。

实现步骤

  1. 打开设备文件
    在 Linux 系统中,LCD 设备通常以文件的形式存在于/dev目录下。应用程序需要使用open()函数打开对应的设备文件,以获取设备文件描述符。
#include <fcntl.h>
#include <stdio.h>

int fd = open("/dev/fb0", O_RDWR);
if (fd == -1) {
    perror("open");
    return -1;
}
  1. 获取 LCD 显存的物理地址和大小
    通过ioctl()函数向设备驱动程序发送特定的命令,获取 LCD 显存的物理地址和大小等信息。
#include <sys/ioctl.h>
#include <linux/fb.h>

struct fb_fix_screeninfo finfo;
struct fb_var_screeninfo vinfo;

if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1) {
    perror("ioctl FBIOGET_FSCREENINFO");
    close(fd);
    return -1;
}

if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) == -1) {
    perror("ioctl FBIOGET_VSCREENINFO");
    close(fd);
    return -1;
}

size_t screen_size = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;

#include <sys/ioctl.h>:该头文件提供了 ioctl 函数的声明。ioctl(Input/Output Control)是一个用于设备输入输出控制操作的系统调用,它允许用户空间程序向设备驱动程序发送特定的控制命令,以实现对设备的各种操作。
#include <linux/fb.h>:此头文件定义了与 Linux 帧缓冲设备(Frame Buffer)相关的数据结构和常量。帧缓冲设备是 Linux 系统中用于管理图形显示的一种抽象设备,通过它可以直接访问显示器的显存。

struct fb_fix_screeninfo finfo;:定义了一个 fb_fix_screeninfo 类型的结构体变量 finfo。fb_fix_screeninfo 结构体用于存储帧缓冲设备的固定信息,这些信息在设备初始化后通常不会改变,例如帧缓冲设备的物理地址、一行像素数据的字节数等。
struct fb_var_screeninfo vinfo;:定义了一个 fb_var_screeninfo 类型的结构体变量 vinfo。fb_var_screeninfo 结构体用于存储帧缓冲设备的可变信息,这些信息可以通过 ioctl 命令进行修改,例如屏幕的分辨率、像素深度等。

ioctl(fd, FBIOGET_FSCREENINFO, &finfo):调用 ioctl 函数向帧缓冲设备发送 FBIOGET_FSCREENINFO 命令,该命令用于获取帧缓冲设备的固定屏幕信息。fd 是帧缓冲设备文件的文件描述符,&finfo 是存储固定屏幕信息的结构体变量的地址。
if (ioctl(…) == -1):检查 ioctl 函数的返回值。如果返回值为 -1,表示 ioctl 操作失败。
perror(“ioctl FBIOGET_FSCREENINFO”);:如果 ioctl 操作失败,使用 perror 函数输出错误信息,提示用户在获取固定屏幕信息时发生了错误。
close(fd);:关闭帧缓冲设备文件的文件描述符,释放相关资源。
return -1;:返回 -1 表示程序执行失败。

ioctl(fd, FBIOGET_VSCREENINFO, &vinfo):调用 ioctl 函数向帧缓冲设备发送 FBIOGET_VSCREENINFO 命令,该命令用于获取帧缓冲设备的可变屏幕信息。&vinfo 是存储可变屏幕信息的结构体变量的地址。
后续的错误处理逻辑与获取固定屏幕信息时类似,如果 ioctl 操作失败,输出错误信息,关闭文件描述符,并返回 -1 表示程序执行失败。

vinfo.xres:表示屏幕的水平分辨率,即屏幕一行的像素数量。
vinfo.yres:表示屏幕的垂直分辨率,即屏幕一列的像素数量。
vinfo.bits_per_pixel:表示每个像素占用的位数,例如 8 位、16 位、24 位等。
vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8:通过水平分辨率、垂直分辨率和每个像素占用的位数计算出屏幕显存的总字节数,并将结果存储在 screen_size 变量中。

  1. 进行内存映射
    使用mmap()函数将 LCD 显存的物理地址映射到进程的虚拟地址空间。
#include <sys/mman.h>

char *fbp = (char *)mmap(0, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (fbp == MAP_FAILED) {
    perror("mmap");
    close(fd);
    return -1;
}
  1. 访问 LCD 显存
    应用程序可以像访问普通内存一样访问映射后的虚拟地址,从而实现对 LCD 屏幕的控制。
// 在屏幕左上角绘制一个红色像素点
int x = 0;
int y = 0;
long location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) +
                (y+vinfo.yoffset) * finfo.line_length;

*(fbp + location) = 255;        // 蓝色通道
*(fbp + location + 1) = 0;      // 绿色通道
*(fbp + location + 2) = 0;      // 红色通道
*(fbp + location + 3) = 0;      // 透明度通道
  1. 解除内存映射并关闭设备文件
    在使用完 LCD 显存后,需要使用munmap()函数解除内存映射,并使用close()函数关闭设备文件。
if (munmap(fbp, screen_size) == -1) {
    perror("munmap");
}
close(fd);

优点

高效性:内存映射避免了数据在用户空间和内核空间之间的频繁拷贝,提高了数据传输的效率。
简化编程:应用程序可以使用普通的内存访问指令来访问 LCD 显存,无需使用复杂的设备驱动接口,降低了编程难度。

刷新显存

全部刷新

  • 原理
    全部刷新指的是每次刷新时把整个显存区域的数据都更新一遍。也就是不管屏幕上的图像有没有变化,都会将新的一帧完整图像数据写入显存。这种方式简单直接,不过可能会消耗较多的带宽和计算资源,尤其是在画面变化较小时。
  • 实现步骤
    生成新的图像数据:在应用程序里,按照需求生成一帧完整的图像数据。这可能涉及到图形渲染、图像处理等操作。
    写入显存:把生成的新图像数据写入到显存对应的地址空间。在不同的系统中,写入方式会有所不同,常见的有内存映射、直接内存访问(DMA)等。
    等待显示器刷新:显示器会按照一定的刷新频率从显存读取数据并显示出来。在完成数据写入后,需要等待显示器进行下一次刷新,这样新的图像才能显示在屏幕上。
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    int fd = open("/dev/fb0", O_RDWR);
    if (fd == -1) {
        perror("open");
        return -1;
    }

    struct fb_fix_screeninfo finfo;
    struct fb_var_screeninfo vinfo;

    if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1) {
        perror("ioctl FBIOGET_FSCREENINFO");
        close(fd);
        return -1;
    }

    if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) == -1) {
        perror("ioctl FBIOGET_VSCREENINFO");
        close(fd);
        return -1;
    }

    size_t screen_size = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
    char *fbp = (char *)mmap(0, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (fbp == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return -1;
    }

    // 生成新的图像数据(这里简单地将整个显存清零)
    for (size_t i = 0; i < screen_size; i++) {
        fbp[i] = 0;
    }

    // 解除映射并关闭文件
    if (munmap(fbp, screen_size) == -1) {
        perror("munmap");
    }
    close(fd);

    return 0;
}

更新刷新

  • 原理
    更新刷新只更新显存中发生变化的部分,而不是整个显存区域。这样可以减少数据传输量和处理时间,提高效率,特别是在画面只有部分区域变化的情况下。
  • 实现步骤
    检测变化区域:**应用程序需要检测出屏幕上哪些区域的图像发生了变化。**这可以通过比较前后两帧图像的差异来实现,也可以根据应用程序的逻辑直接确定变化的区域。
    生成变化区域的新数据:针对检测到的变化区域,生成新的图像数据。
    更新显存:把变化区域的新数据写入到显存对应的位置。
    等待显示器刷新:和全部刷新一样,等待显示器进行下一次刷新,让变化的部分显示在屏幕上。
# 假设 old_frame 和 new_frame 是前后两帧图像数据
old_frame = get_old_frame()
new_frame = get_new_frame()

# 检测变化区域
changed_rects = detect_changed_rects(old_frame, new_frame)

# 遍历变化区域并更新显存
for rect in changed_rects:
    x, y, width, height = rect
    for i in range(y, y + height):
        for j in range(x, x + width):
            # 计算显存中的位置
            index = calculate_index(i, j)
            # 更新显存中的数据
            update_framebuffer(index, new_frame[i][j])

总结

全部刷新实现简单,但效率较低;更新刷新能提高效率,但实现复杂度较高,需要额外的处理来检测变化区域。在实际应用中,要根据具体的需求和场景选择合适的刷新方式。


http://www.kler.cn/a/599227.html

相关文章:

  • 搜索引擎工作原理图解:抓取→索引→排名全链路拆解
  • clamav服务器杀毒(Linux服务器断网状态下如何进行clamav安装、查杀)
  • 04_Linux驱动_06_GPIO子系统总结
  • jangow-01-1.0.1靶机攻略
  • HTML应用指南:利用POST请求获取城市肯德基门店位置信息
  • 玩转python: 掌握Python常用库之数据分析pandas
  • Elasticsearch 文档
  • Python 正则表达式全攻略:re 库精讲与实战
  • 【python】OpenCV—Hand Detection
  • 内网(域)渗透测试流程和模拟测试day--1--信息收集阶段
  • 具身系列——NLP工程师切入机器人和具身智能方向
  • 如何让机器像人类一样感知声调颤抖与嘴角抽动的同步情感表达?
  • MySQL InnoDB行锁等待时间是怎么引起的?
  • 腾讯云HAI1元体验:DeepSeek-R1模型助力个人博客快速搭建
  • 使用python numpy计算并显示音频数据的频谱信息
  • 内核编程十:进程的虚拟地址空间
  • 机器学习之条件概率
  • 金牛区国际数字影像产业园:文创核心功能深度剖析
  • 【科研工具使用】latex如何插入图片、分段落、插入公式
  • Python实现MySQL数据库对象的血缘分析