文件映射
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写数据到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。
mmap
函数名 | mmap |
---|---|
头文件 | #include <sys/mman.h> |
函数原型 | void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);int munmap(void *addr, size_t length); |
功能 | 映射/解除映射 文件存储空间到进程的虚拟地址空间 |
参数说明 | addr:映射的地址空间首地址,NULL 表示让系统决定;length:地址空间大小3. prot:映射的地址空间访问方式,必须和文件打开方式匹配4. flags: 映射的地址空间的访问标记5. fd: 需映射的文件描述符6. offset: 文件存储空间的偏移量 注:prot,flags参数相见手册 |
返回值 | 成功:munmap返回 0 ,mmap 返回映射后的地址;出错:返回 MMAP_FAILED,并将错误码存入 errno 中 |
stat
函数名 | stat |
---|---|
头文件 | #include <fcntl.h> #include <unistd.h>#include <sys/types.h> #include <sys/stat.h> |
函数原型 | int stat(const char * path, struct stat * buf)int fstat(int fd, struct stat *buf); |
功能 | 获取文件状态, |
参数说明 | path:要操作的文件名或路径;buf:指向stat 结构体的指针,用来获取文件状态信息:3. fd:文件描述符 注:struct stat 见下页 stat 函数用来获取未打开的文件状态信息,fstat 函数用来获取打开的文件状态信息 |
返回值 | 成功:返回 0出错:返回 -1,并将错误码存入 errno 中 |
文件状态结构体 stat
struct stat
{
dev_t st_dev; //文件设备编号
ino_t st_ino; //文件inode节点号
mode_t st_mode; //文件类型,访问权限等
nlink_t st_nlink; //文件的连接数
uid_t st_uid; //文件所有者的用户ID
gid_t st_gid; //文件所有者对应的组ID
dev_t st_rdev; //若文件为设备文件,则表示设备编号
off_t st_size; //文件大小,对应的文件字节数
blksize_t st_blksize; //文件系统的 I/O 缓冲区大小
blkcnt_t st_blocks; //占用文件区块数量,每一区块512
}
常用属性 st_mode
st_mode 常见取值: (八进制,文件类型主要取决第3字节)
S_IFSOCK 0140000 socket 套接字文件
S_IFLNK 0120000 链接文件
S_IFREG 0100000 一般文件
S_IFBLK 0060000 块设备文件
S_IFDIR 0040000 目录
S_IFCHR 0020000 字符设备文件
S_IFIFO 0010000 管道文件
上述的文件类型在POSIX中定义了检查这些类型的宏定义:
S_ISLNK (st_mode) 判断是否为链接文件
S_ISREG (st_mode) 是否为一般文件
S_ISDIR (st_mode) 是否为目录
S_ISCHR (st_mode) 是否为字符设备文件
S_ISBLK (st_mode) 是否为块设备文件
S_ISSOCK (st_mode) 是否为socket套接字文件
S_ISFIFO (st_mode) 是否为管道文件
图片显示(S5P6818)
#include <stdio.h>
#include "header.h"
//属性mmap,munmap的使用,可以不通过read,和write操作文件
int main(int argc, char const *argv[])
{
if (argc < 2)
{
puts("缺少参数");
return -1;
}
//打开文件
int fd = open(argv[1], O_RDWR);
if (fd == -1)
{
perror("open failed");
return 0;
}
//使用lseek获取文件大小
off_t size = lseek(fd, 0, SEEK_END);
//调用mmap函数,在进程中申请共享内存
void* p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
{
perror("mmap failed");
close(fd);
return -1;
}
//操作内存就可以读写文件中的数据
char* pt = (char*)p;
printf("%c--%c\n", pt[0], pt[1]);
printf("%c==%c\n", *(pt+0), *(pt+1));
//写数据
pt[2] = 'A';
//解除内存映射
munmap(p, size);
return 0;
}
首先,代码中检查了命令行参数数量,如果参数数量不足,则输出错误提示信息并返回-1。
然后,代码使用open函数打开了一个文件,并检查了返回值,如果返回值为-1,则输出错误提示信息,并返回0。
接下来,代码使用lseek函数获取了文件的大小。
然后,代码调用了mmap函数,在进程中申请了共享内存。mmap函数的参数依次为:NULL(表示让系统选择共享内存的地址)、size(表示共享内存大小)、PROT_READ | PROT_WRITE(表示内存区域可读可写)、MAP_SHARED(表示共享内存区域可写)、fd(表示文件描述符)、0(表示从文件的开始位置开始映射内存)。
如果mmap函数返回值为MAP_FAILED,则输出错误提示信息,并关闭文件,然后返回-1。
接着,代码通过将p转换为char类型指针pt,可以直接操作内存区域,读写文件的数据。代码中打印了内存中的前两个字符。
接下来,代码将内存中第三个字节修改为字符’A’。
最后,代码调用munmap函数解除内存映射,然后返回0。
定义结构体–bmp文件头
typedef struct
{
int8_t types[2]; // 图片文件类型 BM
int32_t size; // 图片文件大小(字节) 1152054
int16_t bf1;
int16_t bf2;
int32_t offset; // 图片头部到像素色值之间的字节偏移量---54
} __attribute__((packed)) BmpHeader;
定义结构体–位图信息
typedef struct
{
int32_t biSize;
int32_t width;
int32_t height;
int16_t planes;
int16_t bitCount;
int32_t compression;
int32_t sizeImage;
int32_t xpels;
int32_t ypeis;
int32_t clrUsed;
int32_t clrImportant;
} __attribute__((packed)) BmpInfo;
字段解析:
biSize: 表示该结构体的大小
width: 图像的宽度
height: 图像的高度
planes: 图像的平面数
bitCount: 每个像素的位数
compression: 图像的压缩类型
sizeImage: 图像数据的大小
xpels: 每米的像素数,水平方向
ypels: 每米的像素数,垂直方向
clrUsed: 使用的颜色表中的颜色数
clrImportant: 对于显示有影响的颜色数
注解:
attribute((packed)): 使用它可以确保该结构体的内存布局是紧凑的,没有填充字节。
定义宏
#define LCD_W 800
#define LCD_H 480
#define LCD_DEV "/dev/fb0"
通过指定的图片路径读取图片的数据
存在问题:
1.图片倒置 :图片像素的坐标原点在左下角,屏幕像素坐标的原点在左上角
2.图片颜色不对 :图片存储颜色色值是三个字节为一个像素点的颜色值,是小端存储导致顺序是b g r
3.雪花点
void drawbmp(char const *bmp_path)
{
// 1.打开文件
int bmp_fd = open(bmp_path, O_RDONLY);
if (bmp_fd == -1)
{
perror("open bmp failed");
return;
}
// 2.操作文件
// lseek()
BmpHeader bmp_header = {0};
printf("bmp_header=%lu\n", sizeof(bmp_header));
read(bmp_fd, &bmp_header, sizeof(bmp_header));
BmpInfo bmp_info = {0};
printf("bmp_info=%lu\n", sizeof(bmp_info));
read(bmp_fd, &bmp_info, sizeof(bmp_info));
// 文件信息
printf("文件格式:%c%c\n", bmp_header.types[0], bmp_header.types[1]);
printf("文件大小:%d\n", bmp_header.size);
printf("偏移量:%d\n", bmp_header.offset);
// 图片信息
printf("宽:%d\n", bmp_info.width);
printf("高:%d\n", bmp_info.height);
printf("位深:%d\n", bmp_info.bitCount);
// 读取颜色色值(1152000字节--24位(3个字节RGB))
int size = bmp_header.size - bmp_header.offset;
// 在堆中申请内存
char *bmpData = (char *)calloc(size, sizeof(char));
read(bmp_fd, bmpData, size);
// 打开屏幕的设备文件
int lcd_fd = open(LCD_DEV, O_WRONLY);
if (lcd_fd == -1)
{
perror("open lcd failed");
goto flag;
}
/*
FF 08 00
00 08 FF
char r = 0xFF; //bmpData[0]
char g = 0x08; //bmpData[1]
char b = 0x00; //bmpData[2]
int32_t color = 0x00ff0800;
0xFF << 16
0x08 << 8
0x00
|
-----------------
ff0800
00000000 00000000 00000000 11111111 0xFF
00000000 11111111 00000000 00000000 0xFF<<16
00000000 00000000 00000000 00001000 0x08
00000000 00000000 00001000 00000000 0x08<<8
00000000 11111111 00000000 00000000 0xFF<<16
| 00000000 00000000 00001000 00000000 0x08<<8
-------------------------------------------------------
00000000 11111111 00001000 00000000 0x00FF0800
*/
// 循环将图片的颜色写到屏幕上
int h, w;
int i = 0; // 累计变量
char r, g, b;
// 图片倒置处理:将所有像素点的颜色(ARGB)安装正确的顺序存储到一个数组中,在循环外一次将数组中的所有颜色值写到屏幕上
int32_t lcd_colors[800 * 480] = {0};
for (h = 0; h < bmp_info.height; h++)
{
int32_t color;
for (w = 0; w < bmp_info.width; w++)
{
color = 0;
b = bmpData[i++];
g = bmpData[i++];
r = bmpData[i++];
// 将红,绿,蓝三种三组合成屏幕上的一个像素点:透明度 红 绿 蓝
color = r << 16 | g << 8 | b;
//通过算法一次将倒数第一整行的颜色值存储到第一行上
//将倒数第二正行的颜色值存储到第二行上,以此类推,就能将图片倒置问题解决
lcd_colors[(479 - h) * 800 + w] = color;
/*
h 0 1
w 0 1 ~ 799 ---最后一行的颜色值存储到数组中的最后一行上
*/
}
}
//一次将数组中的所有颜色值写到屏幕上
write(lcd_fd, lcd_colors, sizeof(lcd_colors));
close(lcd_fd);
flag:
// 回收堆内存
free(bmpData);
// 3.关闭文件
close(bmp_fd);
}
这段代码是一个用于显示位图文件的函数。它的基本流程如下:
打开位图文件,获取文件描述符。
读取位图文件头和位图信息头,打印文件信息和图片信息。
读取位图像素数据。
打开LCD设备文件,获取文件描述符。
循环将图片的颜色写到LCD屏幕上,首先对图片进行倒置处理,然后将颜色值存储到一个数组中。
一次将数组中的所有颜色值写到LCD屏幕上。
关闭LCD设备文件。
回收内存。
关闭位图文件。
其中,步骤5中的颜色值处理使用了位运算和移位操作,将RGB三个颜色分量合并为一个32位整数的颜色值。
需要注意的是,代码中使用了一些系统调用和文件操作函数(例如open、read、write、close等),以及一些C语言的内存操作函数(例如calloc和free)。此外,代码中还使用了自定义的BmpHeader和BmpInfo结构体来存储位图文件头和位图信息头的数据。
主程序
int main(int argc, char const *argv[])
{
if (argc < 2)
{
puts("缺少参数,请指明图片的路径");
return -1;
}
puts(argv[1]);
// 调用函数显示图片
drawbmp(argv[1]);
return 0;
}
代码首先判断命令行参数的个数,如果小于2个则输出提示信息并返回-1。一般情况下,argc参数至少为1,表示程序本身的路径,所以当只有一个参数时,说明没有传入图片的路径。
如果命令行参数个数大于等于2个,则通过puts函数将第二个参数(即图片的路径)输出到控制台。
然后调用drawbmp函数,传入图片的路径作为参数,该函数用于显示指定路径的图片。
最后返回0,表示程序正常结束。