fb应用程序
一.什么是Frame Buffer
所以在 Linux 系统中,显示设备被称为 Frame Buffer 设备(帧缓冲设备).
二.LCD 的基础知识
LCD Framebuffer 就是一块显存,在嵌入式系统中,显存是被包含在内存中。LCD Framebuffer里的若干字节(根据驱动程序对LCD控制器的配置而定)表示LCD屏幕中的一个像素点,一一对应整个LCD屏幕。举个例子,LCD屏幕是800*600的分辨率,即LCD屏幕存在480000个像素点,若每个像素点4个字节表示,那么LCD Framebuffer显存大小为480000 *4=960000字节,即1.92MB。因此我们的内存将会分割至少1.92MB的空间用作显存。具体地址在哪里,这个就是又驱动程序去定,应用程序只需直接使用即可,硬件相关操作已由驱动程序封装好。

如上图,我们只需要往Framebuffer中填入不同的值,驱动程序和硬件控制器就会把这些数据传输到对应LCD屏幕上的像素点,从而显示不同的颜色。由此可知,我们应用程序只需要针对Framebuffer操作即可,其他交给驱动程序和硬件。
三.LCD 应用编程介绍
在应用程序中,操作/dev/fbX 的一般步骤如下:
①、首先打开/dev/fbX 设备文件。
②、 使用 ioctl()函数获取到当前显示设备的参数信息,譬如屏幕的分辨率大小、像素格式,根据屏幕参数计算显示缓冲区的大小。
③、通过存储映射 I/O 方式将屏幕的显示缓冲区映射到用户空间(mmap)。
④、映射成功后就可以直接读写屏幕的显示缓冲区,进行绘图或图片显示等操作了。
⑤、完成显示后, 调用 munmap()取消映射、并调用 close()关闭设备文件。
使用 ioctl()获取屏幕参数信息
当打开 LCD 设备文件之后,需要先获取到 LCD 屏幕的参数信息,譬如 LCD 的 X 轴分辨率、 Y 轴分辨率以及像素格式等信息,通过这些参数计算出 LCD 显示缓冲区的大小。
通 过 ioctl() 函 数 来 获 取 屏 幕 参 数 信 息 , 对 于 Framebuffer 设 备 来 说 , 常 用 的 request 包 括FBIOGET_VSCREENINFO、 FBIOPUT_VSCREENINFO、 FBIOGET_FSCREENINFO。
-
FBIOGET_VSCREENINFO
获取 FrameBuffer 设备的可变参数信息,可变参数信息使用 struct fb_var_screeninfo 结 构 体 来 描 述 , 所 以 此 时 ioctl() 需 要 有第 三 个 参 数 , 它 是 一 个 struct fb_var_screeninfo *指针,指向 struct fb_var_screeninfo 类型对象, 调用 ioctl()会将 LCD 屏的可变参数信息保存在 struct fb_var_screeninfo 类型对象中,
struct fb_var_screeninfo fb_var; //描述可变参数信息 ioctl(fd, FBIOGET_VSCREENINFO, &fb_var); //将信息保存在fb_var对象中
-
FBIOPUT_VSCREENINFO
表示设置 FrameBuffer 设备的可变参数信息,既然是可变参数,那说明应用层可对其进行修改、重新配置,当然前提条件是底层驱动支持这些参数的动态调整,譬如在我们的 Windows 系统中,用户可以修改屏幕的显示分辨率,这就是一种动态调整。同样此时 ioctl()需要有第三个参数, 也是一个 struct fb_var_screeninfo *指针,指向 struct fb_var_screeninfo 类型对象, 表示用 struct fb_var_screeninfo 对象中填充的数据设置 LCD,
struct fb_var_screeninfo fb_var = {0}; //创建对象 /* 对 fb_var 进行数据填充 */ //初始化对象 ...... /* 设置可变参数信息 */ ioctl(fd, FBIOPUT_VSCREENINFO, &fb_var);
-
FBIOGET_FSCREENINFO
表示获取 FrameBuffer 设备的固定参数信息,既然是固定参数,那就意味着应用程序不可修改。固定参数信息使用struct fb_fix_screeninfo结构体来描述,所以此时ioctl()需要有第三个参数,它是一个 struct fb_fix_screeninfo *指针,指向 struct fb_fix_screeninfo 类型对象,调用 ioctl()会将 LCD 的固定参数信息保存在 struct fb_fix_screeninfo 对象中,如下所示:
struct fb_fix_screeninfo fb_fix; ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
相关宏
上面提到的三个宏和两个结构体都定义再<linux/fb.h>头文件中
#define FBIOGET_VSCREENINFO 0x4600 //获取可变参数
#define FBIOPUT_VSCREENINFO 0x4601 //设置可变参数
#define FBIOGET_FSCREENINFO 0x4602 //获取固定参数
相关结构体
struct fb_var_screeninfo 结构体
xres和yres可以获取分辨率 bpp表示像素深度:每一个像素点用多少个 bit 位来描述颜色 xres * yres * bits_per_pixel / 8 就是一块显存的大小
red、 green、 blue 描述了 RGB 颜色值中 R、 G、 B 三种颜色通道分别使用多少 bit 来表示以及它们各自的偏移量,通过 red、 green、 blue 变量可知道 LCD 的 RGB 像素格式,譬如是 RGB888 还是 RGB565,亦或者是 BGR888、 BGR565 等。
struct fb_var_screeninfo {
__u32 xres; /* 可视区域,一行有多少个像素点, X 分辨率 */
__u32 yres; /* 可视区域,一列有多少个像素点, Y 分辨率 */
__u32 xres_virtual; /* 虚拟区域,一行有多少个像素点 */
__u32 yres_virtual; /* 虚拟区域,一列有多少个像素点 */
__u32 xoffset; /* 虚拟到可见屏幕之间的行偏移 */
__u32 yoffset; /* 虚拟到可见屏幕之间的列偏移 */
__u32 bits_per_pixel; /* 每个像素点使用多少个 bit 来描述,也就是像素深度 bpp */
__u32 grayscale; /* =0 表示彩色, =1 表示灰度, >1 表示 FOURCC 颜色 */
/* 用于描述 R、 G、 B 三种颜色分量分别用多少位来表示以及它们各自的偏移量 */
struct fb_bitfield red; /* Red 颜色分量色域偏移 */
struct fb_bitfield green; /* Green 颜色分量色域偏移 */
struct fb_bitfield blue; /* Blue 颜色分量色域偏移 */
struct fb_bitfield transp; /* 透明度分量色域偏移 */
__u32 nonstd; /* nonstd 等于 0,表示标准像素格式;不等于 0 则表示非标准像素格式 */
__u32 activate;
__u32 height; /* 用来描述 LCD 屏显示图像的高度(以毫米为单位) */
__u32 width; /* 用来描述 LCD 屏显示图像的宽度(以毫米为单位) */
__u32 accel_flags;
/* 以下这些变量表示时序参数 */
__u32 pixclock; /* pixel clock in ps (pico seconds) */
__u32 left_margin; /* time from sync to picture */
__u32 right_margin; /* time from picture to sync */
__u32 upper_margin; /* time from sync to picture */
__u32 lower_margin;
__u32 hsync_len; /* length of horizontal sync */
__u32 vsync_len; /* length of vertical sync */
__u32 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 rotate; /* angle we rotate counter clockwise */
__u32 colorspace; /* colorspace for FOURCC-based modes */
__u32 reserved[4]; /* Reserved for future compatibility */
};
/***********************************************************/
struct fb_bitfield {
__u32 offset; /* 偏移量 */
__u32 length; /* 长度 */
__u32 msb_right; /* != 0 : Most significant bit is right */
};
struct fb_fix_screeninfo 结构体
smem_start 表示显存的起始地址,这是一个物理地址. smem_len 表示显存的长度,这个长度并一定等于 LCD 实际的显存大小。 line_length 表示屏幕的一行像素点有多少个字节,通常可以使用 line_length * yres 来得到屏幕显示缓冲区的大小。
struct fb_fix_screeninfo {
char id[16]; /* 字符串形式的标识符 */
unsigned long smem_start; /* 显存的起始地址(物理地址) */
__u32 smem_len; /* 显存的长度 */
__u32 type;
__u32 type_aux;
__u32 visual;
__u16 xpanstep;
__u16 ypanstep;
__u16 ywrapstep;
__u32 line_length; /* 一行的字节数 */
unsigned long mmio_start; /* Start of Memory Mapped I/O(physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O */
__u32 accel; /* Indicate to driver which specific chip/card we have */
__u16 capabilities;
__u16 reserved[2];
};
应用编程(获取屏幕信息)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
int main(int argc, char *argv[])
{
struct fb_fix_screeninfo fb_fix;
struct fb_var_screeninfo fb_var;
int fd;
/* 打开 framebuffer 设备 */
if (0 > (fd = open("/dev/fb0", O_WRONLY))) {
perror("open error");
exit(-1);
}
/* 获取参数信息 */
ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
printf("分辨率: %d*%d\n"
"像素深度 bpp: %d\n"
"一行的字节数: %d\n"
"像素格式: R<%d %d> G<%d %d> B<%d %d>\n",
fb_var.xres, fb_var.yres, fb_var.bits_per_pixel,
fb_fix.line_length,
fb_var.red.offset, fb_var.red.length,
fb_var.green.offset, fb_var.green.length,
fb_var.blue.offset, fb_var.blue.length);
/* 关闭设备文件退出程序 */
close(fd);
exit(0);
}
这里获取到的数据如下图 一共有480*854个像素点 每一个像素点用16个bit位来表示 像素格式为RGB565的
四.图片格式
4.1.BMP格式
4.1.1bmp图像介绍:
一般常用的图片格式有三种:JPEG(或 JPG)、 PNG、 BMP还有动图GIF。
BMP(全称 Bitmap)是 Window 操作系统中的标准图像文件格式,文件后缀名为“.bmp”,使用非常广。它采用位映射存储格式,除了图像深度可选以外,图像数据没有进行任何压缩,因此, BMP 图像占用的空间很大,但是没有失真、 并且解析 BMP 图像简单。
BMP 文件的图像深度可选 lbit、 4bit、 8bit、 16bit、 24bit 以及 32bit,典型的 BMP 图像文件由四部分组成:
- BMP 文件头(BMP file header),它包含 BMP 文件的格式、大小、 位图数据的偏移量等信息;
- 位图信息头(bitmap information) ,它包含位图信息头大小、 图像的尺寸、 图像大小、 位平面数、压缩方式以及颜色索引等信息;
- 调色板(color palette),这部分是可选的,如果使用索引来表示图像, 调色板就是索引与其对应颜色的映射表;
- 位图数据(bitmap data),也就是图像数据。
BMP 文件头、位图信息头、调色板和位图数据, 总结如下表所示:
数据段名称 | 大小(Byte) | 说明 |
---|---|---|
bmp 文件头 (bmp file header) | 14 | 包含 BMP 文件的格式、大小、到位图数据的 偏移量等信息 |
位图信息头 (bitmap information) | 通常为 40 或 56 字节 | 包含位图信息头大小、 图像的尺寸、图像大 小、位平面数、压缩方式以及颜色索引等信息; |
调色板 (color palette) | 由颜色索引数决定 | 可选,如果使用索引来表示图像的颜色, 则调 色板就是索引与其对应颜色的映射表; |
位图数据 (bitmap data) | 由图像尺寸决定 | 图像数据 |
一般常见的图像都是以 16 位(RGB 565)、 24 位(RGB 888) 色图像为主,我们称这样的图像为真彩色图像, 真彩色图像是不需要调色板的,即位图信息头后面紧跟的就是位图数据了。
①bmp头文件
Windows 下为 bmp 文件头定义了如下结构体:
typedef struct tagBITMAPFILEHEADER
{
UINT16 bfType;
DWORD bfSize;
UINT16 bfReserved1;
UINT16 bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
变量名 | 地址偏移 | 大小 | 作用 |
---|---|---|---|
bfType | 00H | 2 bytes | 说明 bmp 文件的类型,可取值为: ①BM – Windows ②BA – OS/2 Bitmap Array ③CI – OS/2 Color Icon ④CP – OS/2 Color Pointer ⑤IC – OS/2 Icon ⑥PT – OS/2 Pointer |
bfSize | 02H | 4 bytes | 说明该文件的大小,以字节为单位。 |
bfReserved1 | 06H | 2 bytes | 保留字段,必须设置为 0。 |
bfReserved2 | 08H | 2 bytes | 保留字段,必须设置为 0。 |
bfOffBits | 0AH | 4 bytes | 说明从文件起始位置到图像数据之间的字节偏移量。 这个参数非常有用,因为位图信息头和调色板的长度 会根据不同的情况而变化,所以我们可以用这个偏移 量迅速从文件中找到图像数据的偏移地址 |
②位图信息头
Windows 下为位图信息头定义了如下结构体:
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
变量名 | 地址偏移 | 大小 | 作用 |
---|---|---|---|
biSize | 0EH | 4 bytes | 位图信息头大小。 |
biWidth | 12H | 4 bytes | 图像的宽度,以像素为单位。 |
biHeight | 16H | 4 bytes | 图像的高度,以像素为单位。 注意,这个值除了用于描述图像的高度之外, 它还有另外一个用途,用于指明该图像是倒向 的位图、还是正向的位图。 如果该值是一个正数,说明是倒向的位图;如 果该值是一个负数,则说明是正向的位图。 一般情况下, BMP 图像都是倒向的位图,也就 是该值是一个正数。 |
biPlanes | 1AH | 2 bytes | 色彩平面数,该值总被设置为 1。 |
biBitCount | 1CH | 2 bytes | 像素深度,指明一个像素点需要多少个 bit 数据 来描述,其值可为 1、 4、 8、 16、 24、 32 |
biCompression | 1EH | 4 bytes | 说明图像数据的压缩类型,取值范围如下: ①0 – RGB 方式 ②1 – 8bpp 的 RLE 方式,只用于 8bit 位图 ③2 – 4bpp 的 RLE 方式, 只用于 4bit 位图 ④3 – Bit-fields 方式 ⑤4 – 仅用于打印机 ⑥5 – 仅用于打印机 |
biSizeImage | 22H | 4 bytes | 说明图像的大小,以字节为单位,当压缩类型 为 BI_RGB 时,可设置为 0。 |
biXPelsPerMeter | 26H | 4 bytes | 水平分辨率,用像素/米来表示,有符号整数。 |
biYPelsPerMeter | 2AH | 4 bytes | 垂直分辨率,用像素/米来表示,有符号整数。 |
biClrUsed | 2EH | 4 bytes | 说明位图实际使用的彩色表中的颜色索引数。 |
biClrImportant | 32H | 4 bytes | 说明对图像显示有重要影响的颜色索引的数 目,如果是 0,则表示都重要。 |
只有压缩方式选项被设置为 bit-fileds(0x3) 时, 位图信息头的大小才会等于 56 字节,否则,为 40 字节。 56 个字节相比于 40 个字节,多出了 16 个字节, 那么多出的 16 个字节数据描述了什么信息呢? 稍后再给大家介绍。
③调色板:
调色板是单色、 16 色、 256 色位图图像文件所持有的,如果是 16 位、 24 位以及 32 位位图文件,则 BMP文件组成部分中不包含调色板,关于调色板这里不过多介绍,有兴趣可以自己去了解。
④位图数据:
位图数据其实就是图像的数据,24位位图用三个字节数据来表示一个像素点的颜色,同理16位位图用两个字节32位位图用4个字节.
BMP 位图分为正向的位图和倒向的位图,主要区别在于图像数据存储的排列方式如下如所示(左边对应的是正向位图,右边对应的则是倒向位图) :
所以正向位图先存储图像的第一行数据,从左到右依次存放,接着存放第二行,依次这样;而倒向位图,则先存储图像的最后一行(倒数第一行)数据,也是从左到右依次存放,接着倒数二行,依次这样。
⑤RGB 和 Bit-Fields
当图像中引用的色彩超过 256 种时,就需要 16bpp 或更高 bpp 的位图(24 位、 32 位)。调色板不适合bpp 较大的位图,因此 16bpp 及以上的位图都不使用调色板,不使用调色板的位图图像有两种编码格式:RGB 和 Bit-Fields(下称 BF) 。
RGB 编码格式是一种均分的思想, 使 Red、 Green、 Blue 三种颜色信息容量一样大:
- 24bpp-RGB:在 24bits 中,低 8 位表示 Blue 分量;中 8 为表示 Green 分量;高 8 位表示 Red分量。
- 32bpp-RGB:低 24 位的编码方式与 24bpp 位图相同,最高 8 位用来表示透明度 Alpha 分量。对于需要半透过效果的图像,更好的选择是 PNG 格式。
BF 编码格式与 RGB 不同,它利用位域操作,人为地确定 RGB 三分量所包含的信息容量。
- 位图信息头中:当压缩方式选项置为 BF 时, 位图信息头大小比平时多出 16 字节, 这 16 个字节实际上是 4个 32bit 的位域掩码, 按照先后顺序,它们分别是 R、 G、 B、 A 四个分量的位域掩码, 当然如果没有 Alpha分量,则 Alpha 掩码没有实际意义。
- 位域掩码的作用是指出 R、 G、 B 三种颜色信息容量的大小,分别使用多少个 bit 数据来表示,以及三种颜色分量的位置偏移量。譬如对于 16 位色的 RGB565 图像,通常使用 BF 编码格式,同样这也是 BF 编码格式最著名和最普遍的应用之一, 它的 R、 G 和 B 分量的位域掩码分别是 0xF800、 0x07E0 和 0x001FR 通道使用 2 个字节中的高 5 位表示, G 通道使用 2 个字节中的中间 6 位表示。而 B 通道则使用 2个字节中的最低 5 位表示。
4.1.2在LCD上显示BMP图像
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/mman.h>
/* https://blog.csdn.net/qlexcel/article/details/92656797 __attribute__的使用*/
/**** BMP文件头数据结构 ****/
typedef struct {
unsigned char type[2]; //文件类型
unsigned int size; //文件大小
unsigned short reserved1; //保留字段1
unsigned short reserved2; //保留字段2
unsigned int offset; //到位图数据的偏移量
} __attribute__ ((packed)) bmp_file_header;
/**** 位图信息头数据结构 ****/
typedef struct {
unsigned int size; //位图信息头大小
int width; //图像宽度
int height; //图像高度
unsigned short planes; //位面数
unsigned short bpp; //像素深度
unsigned int compression; //压缩方式
unsigned int image_size; //图像大小
int x_pels_per_meter; //像素/米
int y_pels_per_meter; //像素/米
unsigned int clr_used;
unsigned int clr_omportant;
} __attribute__ ((packed)) bmp_info_header;
/**** 静态全局变量 ****/
static struct fb_fix_screeninfo fb_fix;
static struct fb_var_screeninfo fb_var;
static unsigned short *screen_base = NULL; //映射后的显存基地址
/********************************************************************
* 函数名称: show_bmp_image
* 功能描述: 在LCD上显示指定的BMP图片
* 输入参数: 文件路径
* 返 回 值: 成功返回0, 失败返回-1
********************************************************************/
static int show_bmp_image(const char *path)
{
bmp_file_header file_h;
bmp_info_header info_h;
unsigned short *line_buf = NULL; //行缓冲区
unsigned long line_bytes; //BMP图像一行的字节的大小
unsigned int min_h, min_bytes;
unsigned short *base = screen_base;
unsigned int bytes_per_pixel = fb_var.bits_per_pixel / 8;
unsigned int width = fb_fix.line_length / bytes_per_pixel;
unsigned int line_length = bytes_per_pixel * fb_var.xres;
int fd = -1;
int j;
/* 打开文件 */
if (0 > (fd = open(path, O_RDONLY))) {
perror("open error");
return -1;
}
/* 读取BMP文件头 */
if (sizeof(bmp_file_header) !=
read(fd, &file_h, sizeof(bmp_file_header))) {
perror("read error");
close(fd);
return -1;
}
if (0 != memcmp(file_h.type, "BM", 2)) {
fprintf(stderr, "it's not a BMP file\n");
close(fd);
return -1;
}
/* 读取位图信息头 */
if (sizeof(bmp_info_header) !=
read(fd, &info_h, sizeof(bmp_info_header))) {
perror("read error");
close(fd);
return -1;
}
/* 打印信息 */
printf("文件大小: %d\n"
"位图数据的偏移量: %d\n"
"位图信息头大小: %d\n"
"图像分辨率: %d*%d\n"
"像素深度: %d\n", file_h.size, file_h.offset,
info_h.size, info_h.width, info_h.height,
info_h.bpp);
/* 将文件读写位置移动到图像数据开始处 */
if (-1 == lseek(fd, file_h.offset, SEEK_SET)) {
perror("lseek error");
close(fd);
return -1;
}
/* 申请一个buf、暂存bmp图像的一行数据 */
line_bytes = info_h.width * info_h.bpp / 8;
line_buf = malloc(line_bytes);
if (NULL == line_buf) {
fprintf(stderr, "malloc error\n");
close(fd);
return -1;
}
if (line_length > line_bytes)
min_bytes = line_bytes;
else
min_bytes = line_length;
/**** 读取图像数据显示到LCD ****/
/*******************************************
* 为了软件处理上方便,这个示例代码便不去做兼容性设计了
* 如果你想做兼容, 可能需要判断传入的BMP图像是565还是888
* 如何判断呢?文档里边说的很清楚了
* 我们默认传入的bmp图像是RGB565格式
*******************************************/
if (0 < info_h.height) {//倒向位图
if (info_h.height > fb_var.yres) {
min_h = fb_var.yres;
lseek(fd, (info_h.height - fb_var.yres) * line_bytes, SEEK_CUR);
base += width * (fb_var.yres - 1); //定位到屏幕左下角位置
}
else {
min_h = info_h.height;
base += width * (info_h.height - 1); //定位到....不知怎么描述 懂的人自然懂!
}
for (j = min_h; j > 0; base -= width, j--) {
read(fd, line_buf, line_bytes); //读取出图像数据
memcpy(base, line_buf, min_bytes);//刷入LCD显存
}
}
else { //正向位图
int temp = 0 - info_h.height; //负数转成正数
if (temp > fb_var.yres)
min_h = fb_var.yres;
else
min_h = temp;
for (j = 0; j < min_h; j++, base += width) {
read(fd, line_buf, line_bytes);
memcpy(base, line_buf, min_bytes);
}
}
/* 关闭文件、函数返回 */
close(fd);
free(line_buf);
return 0;
}
int main(int argc, char *argv[])
{
unsigned int screen_size;
int fd;
/* 传参校验 */
if (2 != argc) {
fprintf(stderr, "usage: %s <bmp_file>\n", argv[0]);
exit(-1);
}
/* 打开framebuffer设备 */
if (0 > (fd = open("/dev/fb0", O_RDWR))) {
perror("open error");
exit(EXIT_FAILURE);
}
/* 获取参数信息 */
ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
screen_size = fb_fix.line_length * fb_var.yres;
/* 将显示缓冲区映射到进程地址空间 */
screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
if (MAP_FAILED == (void *)screen_base) {
perror("mmap error");
close(fd);
exit(EXIT_FAILURE);
}
/* 显示BMP图片 */
memset(screen_base, 0xFF, screen_size);
show_bmp_image(argv[1]);
/* 退出 */
munmap(screen_base, screen_size); //取消映射
close(fd); //关闭文件
exit(EXIT_SUCCESS); //退出进程
}
4.2JPG格式
本文作者:hello
本文链接:https://www.cnblogs.com/feige-c/p/16444406.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步