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

[复制链接]
发表于 2025-10-10 00:02:12 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
异步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 利用的完成关照和数据处置惩罚逻辑。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. #include <string.h>
  7. #define BUFFER_SIZE 1024
  8. int mouse_fd = -1;
  9. // 统一的信号处理函数
  10. void signal_handler(int signum) {
  11.     if (signum == SIGIO) {
  12.         char buffer[BUFFER_SIZE];
  13.         ssize_t bytes_read;
  14.         // 读取鼠标输入
  15.         memset(buffer, 0, sizeof(buffer));
  16.         bytes_read = read(mouse_fd, buffer, BUFFER_SIZE);
  17.         if (bytes_read > 0) {
  18.             printf("鼠标输入: 读取到 %zd 字节\n", bytes_read);
  19.         }
  20.         // 读取键盘输入
  21.         memset(buffer, 0, sizeof(buffer));
  22.         bytes_read = read(STDIN_FILENO, buffer, BUFFER_SIZE);
  23.         if (bytes_read > 0) {
  24.             buffer[bytes_read] = '\0';
  25.             printf("键盘输入: %s", buffer);
  26.         }
  27.     }
  28. }
  29. int main() {
  30.     int flags;
  31.     struct sigaction sa;
  32.     // 初始化 sigaction 结构体
  33.     memset(&sa, 0, sizeof(sa));
  34.     sa.sa_handler = signal_handler;
  35.     sigemptyset(&sa.sa_mask);
  36.     sa.sa_flags = SA_RESTART;
  37.     // 为 SIGIO 信号设置信号处理函数
  38.     if (sigaction(SIGIO, &sa, NULL) == -1) {
  39.         perror("sigaction");
  40.         return 1;
  41.     }
  42.     // 设置当前进程为标准输入的拥有者
  43.     fcntl(STDIN_FILENO, F_SETOWN, getpid());
  44.     // 获取标准输入的当前文件状态标志
  45.     flags = fcntl(STDIN_FILENO, F_GETFL);
  46.     if (flags == -1) {
  47.         perror("fcntl F_GETFL");
  48.         return 1;
  49.     }
  50.     // 设置标准输入为异步 I/O 模式
  51.     flags |= O_ASYNC;
  52.     if (fcntl(STDIN_FILENO, F_SETFL, flags) == -1) {
  53.         perror("fcntl F_SETFL");
  54.         return 1;
  55.     }
  56.     // 对鼠标设备文件进行相同的操作
  57.     mouse_fd = open("/dev/input/mouse0", O_RDONLY);
  58.     if (mouse_fd == -1) {
  59.         perror("无法打开鼠标设备文件");
  60.         return 1;
  61.     }
  62.     fcntl(mouse_fd, F_SETOWN, getpid());
  63.     flags = fcntl(mouse_fd, F_GETFL);
  64.     if (flags == -1) {
  65.         perror("fcntl F_GETFL (鼠标)");
  66.         close(mouse_fd);
  67.         return 1;
  68.     }
  69.     flags |= O_ASYNC;
  70.     if (fcntl(mouse_fd, F_SETFL, flags) == -1) {
  71.         perror("fcntl F_SETFL (鼠标)");
  72.         close(mouse_fd);
  73.         return 1;
  74.     }
  75.     printf("开始异步 I/O 监听...\n");
  76.     // 进入无限循环,等待信号
  77.     while (1) {
  78.         pause();
  79.     }
  80.     return 0;
  81. }
复制代码

存在bug , 多次利用键盘后,没有进入回调函数,接着移动鼠标,才会触发!!!
存储映射IO

存储映射 I/O(Memory - Mapped I/O)是一种将文件或装备的内容映射到历程地点空间的技能。通过这种方式,应用步伐可以像访问内存一样访问文件或装备的数据,而不必要利用传统的read和write体系调用。在 Linux 体系中,可以利用mmap函数来实现存储映射 I/O。
  1. #include <sys/mman.h>
  2. #include <fcntl.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <unistd.h>
  6. #include <sys/types.h>
  7. #include <sys/wait.h>
  8. int main() {
  9.     int fd = open("test.txt", O_RDWR);
  10.     if (fd == -1) {
  11.         perror("open");
  12.         exit(EXIT_FAILURE);
  13.     }
  14.     off_t file_size = lseek(fd, 0, SEEK_END);
  15.     lseek(fd, 0, SEEK_SET);
  16.     // 直接传递 fd,并且进行显式类型转换
  17.     char *map = static_cast<char*>(mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
  18.     if (map == MAP_FAILED) {
  19.         perror("mmap");
  20.         close(fd);
  21.         exit(EXIT_FAILURE);
  22.     }
  23.     pid_t pid = fork();
  24.     if (pid == -1) {
  25.         perror("fork");
  26.         munmap(map, file_size);
  27.         close(fd);
  28.         exit(EXIT_FAILURE);
  29.     } else if (pid == 0) {
  30.         // 子进程
  31.         printf("子进程读取文件内容:\n%s", map);
  32.         // 修改映射内存中的内容
  33.         map[0] = 'Y';
  34.     } else {
  35.         // 父进程
  36.         wait(NULL);
  37.         printf("父进程读取修改后的文件内容:\n%s", map);
  38.     }
  39.     // 解除映射
  40.     if (munmap(map, file_size) == -1) {
  41.         perror("munmap");
  42.     }
  43.     close(fd);
  44.     return 0;
  45. }
复制代码

通过mmap函数,将文件test.txt映射到历程的地点空间,map指向映射后的内存地区。对map的读写利用就相称于对文件的读写利用,而且对映射内存的修改会主动同步到文件中。存储映射 I/O 进步了 I/O 利用的服从,特殊是对于大文件的读写利用,淘汰了数据在用户空间和内核空间之间的复制次数。同时,多个历程可以共享同一个文件的映射,实现历程间通讯和数据共享。
LCD 显存的内存映射

根本概念

LCD 显存:LCD(Liquid Crystal Display,液晶体现器)显存是专门用于存储要体如今屏幕上的图像数据的内存地区。体现器会按照肯定的革新频率从显存中读取数据,并将其转换为对应的像素点体如今屏幕上。
内存映射:内存映射是一种将物理内存地点(如 LCD 显存的物理地点)映射到历程的捏造地点空间的技能。通过内存映射,应用步伐可以像访问平常内存一样访问物理装备的内存,从而实现对装备的直接读写利用。
实现步调


  • 打开装备文件
    在 Linux 体系中,LCD 装备通常以文件的情势存在于/dev目次下。应用步伐必要利用open()函数打开对应的装备文件,以获取装备文件形貌符。
  1. #include <fcntl.h>
  2. #include <stdio.h>
  3. int fd = open("/dev/fb0", O_RDWR);
  4. if (fd == -1) {
  5.     perror("open");
  6.     return -1;
  7. }
复制代码

  • 获取 LCD 显存的物理地点和巨细
    通过ioctl()函数向装备驱动步伐发送特定的下令,获取 LCD 显存的物理地点和巨细等信息。
  1. #include <sys/ioctl.h>
  2. #include <linux/fb.h>
  3. struct fb_fix_screeninfo finfo;
  4. struct fb_var_screeninfo vinfo;
  5. if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1) {
  6.     perror("ioctl FBIOGET_FSCREENINFO");
  7.     close(fd);
  8.     return -1;
  9. }
  10. if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) == -1) {
  11.     perror("ioctl FBIOGET_VSCREENINFO");
  12.     close(fd);
  13.     return -1;
  14. }
  15. 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 变量中。

  • 举行内存映射
    利用mmap()函数将 LCD 显存的物理地点映射到历程的捏造地点空间。
  1. #include <sys/mman.h>
  2. char *fbp = (char *)mmap(0, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  3. if (fbp == MAP_FAILED) {
  4.     perror("mmap");
  5.     close(fd);
  6.     return -1;
  7. }
复制代码

  • 访问 LCD 显存
    应用步伐可以像访问平常内存一样访问映射后的捏造地点,从而实现对 LCD 屏幕的控制。
  1. // 在屏幕左上角绘制一个红色像素点
  2. int x = 0;
  3. int y = 0;
  4. long location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) +
  5.                 (y+vinfo.yoffset) * finfo.line_length;
  6. *(fbp + location) = 255;        // 蓝色通道
  7. *(fbp + location + 1) = 0;      // 绿色通道
  8. *(fbp + location + 2) = 0;      // 红色通道
  9. *(fbp + location + 3) = 0;      // 透明度通道
复制代码

  • 清除内存映射并关闭装备文件
    在利用完 LCD 显存后,必要利用munmap()函数清除内存映射,并利用close()函数关闭装备文件。
  1. if (munmap(fbp, screen_size) == -1) {
  2.     perror("munmap");
  3. }
  4. close(fd);
复制代码
优点

高效性:内存映射克制了数据在用户空间和内核空间之间的频仍拷贝,进步了数据传输的服从。
简化编程:应用步伐可以利用平常的内存访问指令来访问 LCD 显存,无需利用复杂的装备驱动接口,低落了编程难度。
革新显存

全部革新


  • 原理
    全部革新指的是每次革新时把整个显存地区的数据都更新一遍。也就是不管屏幕上的图像有没有厘革,都会将新的一帧完备图像数据写入显存。这种方式简单直接,不外大概会斲丧较多的带宽和盘算资源,尤其是在画面厘革较小时。
  • 实现步调
    天生新的图像数据:在应用步伐里,按照需求天生一帧完备的图像数据。这大概涉及到图形渲染、图像处置惩罚等利用。
    写入显存:把天生的新图像数据写入到显存对应的地点空间。在差异的体系中,写入方式会有所差异,常见的有内存映射、直接内存访问(DMA)等。
    期待体现器革新:体现器会按照肯定的革新频率从显存读取数据并体现出来。在完成数据写入后,必要期待体现器举行下一次革新,如许新的图像才华体如今屏幕上。
  1. #include <fcntl.h>
  2. #include <sys/mman.h>
  3. #include <sys/ioctl.h>
  4. #include <linux/fb.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. int main() {
  8.     int fd = open("/dev/fb0", O_RDWR);
  9.     if (fd == -1) {
  10.         perror("open");
  11.         return -1;
  12.     }
  13.     struct fb_fix_screeninfo finfo;
  14.     struct fb_var_screeninfo vinfo;
  15.     if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1) {
  16.         perror("ioctl FBIOGET_FSCREENINFO");
  17.         close(fd);
  18.         return -1;
  19.     }
  20.     if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) == -1) {
  21.         perror("ioctl FBIOGET_VSCREENINFO");
  22.         close(fd);
  23.         return -1;
  24.     }
  25.     size_t screen_size = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
  26.     char *fbp = (char *)mmap(0, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  27.     if (fbp == MAP_FAILED) {
  28.         perror("mmap");
  29.         close(fd);
  30.         return -1;
  31.     }
  32.     // 生成新的图像数据(这里简单地将整个显存清零)
  33.     for (size_t i = 0; i < screen_size; i++) {
  34.         fbp[i] = 0;
  35.     }
  36.     // 解除映射并关闭文件
  37.     if (munmap(fbp, screen_size) == -1) {
  38.         perror("munmap");
  39.     }
  40.     close(fd);
  41.     return 0;
  42. }
复制代码
更新革新


  • 原理
    更新革新只更新显存中发生厘革的部分,而不是整个显存地区。如许可以淘汰数据传输量和处置惩罚时间,进步服从,特殊是在画面只有部分地区厘革的情况下。
  • 实现步调
    检测厘革地区:**应用步伐必要检测出屏幕上哪些地区的图像发生了厘革。**这可以通过比力前后两帧图像的差异来实现,也可以根据应用步伐的逻辑直接确定厘革的地区。
    天生厘革地区的新数据:针对检测到的厘革地区,天生新的图像数据。
    更新显存:把厘革地区的新数据写入到显存对应的位置。
    期待体现器革新:和全部革新一样,期待体现器举行下一次革新,让厘革的部分体如今屏幕上。
  1. # 假设 old_frame 和 new_frame 是前后两帧图像数据
  2. old_frame = get_old_frame()
  3. new_frame = get_new_frame()
  4. # 检测变化区域
  5. changed_rects = detect_changed_rects(old_frame, new_frame)
  6. # 遍历变化区域并更新显存
  7. for rect in changed_rects:
  8.     x, y, width, height = rect
  9.     for i in range(y, y + height):
  10.         for j in range(x, x + width):
  11.             # 计算显存中的位置
  12.             index = calculate_index(i, j)
  13.             # 更新显存中的数据
  14.             update_framebuffer(index, new_frame[i][j])
复制代码
总结

全部革新实现简单,但服从较低;更新革新能进步服从,但实现复杂度较高,必要额外的处置惩罚来检测厘革地区。在现实应用中,要根据具体的需求和场景选择符合的革新方式。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表