Framebuffer的原理
Framebuffer
是linux
系统抽象出来供用户态进程直接写屏的一种设备,用户可以将Framebuffer
看做是显示内存的一个映像,将其映射到地址空间之后,就可以直接进行读写操作,而写操作可以立即同步显示在LCD
屏幕上。这种操作是抽象的,统一的,应用编程者不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer
设备驱动来完成的。
简单说:把一个LCD
屏当成一块内存(lcd内存是在内核空间中开辟的),从使用代码角度来看,当成一个二维数组。每个元素就是LCD
屏上的一个点
LCD基础知识
·····自行百度,不再赘述
Framebuffer应用编程
Franmebuffer
和之前学过的led
, 按键驱动一样,都属于字符设备- 设备文件:
/dev/fbx
x
值范围0-31
- 为了让程序可以适应不同的硬件平台,程序中必须获得当前运行平台的
Franmebuffer
设备的参数信息:显示屏尺寸大小,BPP
等 - 怎样获得
Franmebuffer
设备相关信息
内核提供ioctl
系统调用函数来获得当前运行的Franmebuffer
设备信息,ioctl
核心就是控制命令,关于Franmebuffer
设备的控制命令也是标准的统一的。常用的命令:
FBIOGET_FSCREENINFO: 获取固定屏幕信息:如显卡显存地址,显存大小,一行的长度,是否有硬件加速功能等
FBIOGET_VSCRENNINFO :获取可变屏幕信息:分辨率(显示的宽,高),BPP
,刷新频率等
ioctl(fd,cmd,arg);``FBIOGET_FSCREENINFO
,FBIOGET_VSCRENNINFO
属于函数中的cmd
,获取的信息存放在arg
中参数中。对于FBIOGET_FSCREENINFO
命令,获取信息存储在struct fb_fix_screeninfo
结构空间中。同理对于FBIOGET_VSCRENNINFO
命令,获取信息存储在struct fb_var_screeninfo
结构空间中
示例:
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
ioctl(fd,FBIOGET_FSCREENINFO,&finfo); //获取屏幕固定信息
ioctl(fd,FBIOGET_VSCREENINFO,&vinfo); //获取屏幕可变信息
- 如何操作
Framebuffer
设备
用户可以将Framebuffer
看做是显示内存的一个映像,将其映射到地址空间之后,就可以直接进行读写操作,而写操作可以立即同步显示在LCD
屏幕上。
映射方法:linux
系统提供了mmap
系统调用方法来实现把驱动中开辟的显存空间或显卡的物理空间映射到进程地址中来。mmap也是需要驱动程序支持的,但是本节目标是编写应用程序。
Framebuffer应用编程重要函数mmap/munmap
mmap
头文件:#include<sys/mman.h>
原型:void *mmap(void *addr,size_t length,int port,int flags,int fd,off_t offset)
功能:
在调用进程的虚拟地址空间中创建一个新映射地址空间,起始地址对新的映射在addr中被指定。长度参数指定映射的长度。(通俗说就是将设备或文件映射到用户进程的虚拟地址空间,实现用户进程对文件的直接操作,不必再调用read
,write
等系统调用)
mmap
的作用是映射文件描述符fd
指定文件的[offset,offset+length]
区域调用至进程的[addr,addr+length]
的内存区域,如下图所示:
参数:
addr:映射区的开始地址。如果为NULL则是内核自由分配起始地址,一般也为空
length:映射区的长度
port:期望的内存保护标志,不能与文件的打开模式冲突,是以下某个值,可以通过or运算合理的组合在一起:
PROT_EXEC:页内容可以被执行
PROT_READ:页内容可以被读取
PROT_WRITE:页内容可以被写入
PROT_NONE:页不可访问
flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合
MAP_SHARED:与其所映射到这个进程空间的对象共享映射空间。对共享区的写入,相当于输出到文件
MAP_PRIVATE:建立一个写入时拷贝的私有映射。内存区域的写入不影响到源文件,这个标志和以上标志是互斥的只能使用其中一个
···········不常用
MAP_FIXED:使用指定的映射起始地址,如果由addr和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃,如果指定的映射起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。一般不使用
MAP_DENYWRITE:拒绝写
MAP_EXECUTABLE:这个标志被忽略
MAP_NORESERVE:不要为这个映射保留交换空间。当交换空间被保留,对映射区的修改可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段错误。
···········不常用
最常用的是MAP_SHARED,MAP_PRIVATE
fd:有效的文件描述词。fd可以指定为-1,此时必须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)
offset:被映射对象内容的起点,一般设置为0,表示从文件头开始映射
返回值:
成功:文件映射到进程空间的地址
失败:(void*)-1
,具体错误类型很多存放在全局变量erron
中
补充:当mmap
使用来做共享内存的时候,还会涉及到一个函数msync
int msync(void *addr,size_t len,int flags);
一般来说,进程在映射空间的对共享内存的改变不直接写回磁盘文件中,往往在调用munmap()
才执行该操作,可以通过调用msync()
实现磁盘上文件内容与共享内存区的内容一致
munmap
munmap
和mmap
执行相反的操作,删除特定地址区域的对象映射
原型:
int munmap(void *addr,size_t length);
参数:
addr:要取消映射的虚拟地址首地址
length:要取消映射的虚拟地址的长度
返回值:
成功:0
,失败:-1
Framebuffer应用编程示例
- 打开
/dev/fbx
设备 - 使用
ioctl
获得运行平台的framebuffer
设备的信息 - 使用前面获得的
framebuffer
信息计算LCD
显存大小 - 调用
mmap
函数把内核空间中开辟的显存映射到进程空间中 - 使用
mmap
得到的地址操作显存空间的内容,从而改变LCD
屏显示 - 当不使用
framebuffer
设备时,使用munmap
取消映射
代码
#include<stdio.h>
#include<stdlib.h>
#include<linux/fb.h>
#include<sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
int main()
{
unsigned char *lcd_addr;
unsigned long lcd_size;
int fd,x,y;
//打开/dev/fbx设备
fd=open("/dev/fb0",O_RDWR);
if(fd<0)
{
printf("open is fail\r\n");
}
//使用ioctl获得运行平台的framebuffer设备的信息
ioctl(fd,FBIOGET_FSCREENINFO,&finfo); //获取屏幕固定信息
ioctl(fd,FBIOGET_VSCREENINFO,&vinfo); //获取屏幕可变信息
//打印物理屏分辨率 色深数据
printf("vinfo.xres:%d vinfo.yres:%d vinfo.bits_per_pixel:%d\r\n",vinfo.xres,vinfo.yres,vinfo.bits_per_pixel);
//使用前面获得的framebuffer信息计算LCD显存大小 每行所占字节数+行数
lcd_size=finfo.line_length * vinfo.yres_virtual;
//调用mmap函数把内核空间中开辟的显存映射到进程空间中
lcd_addr=mmap(NULL,lcd_size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(lcd_addr == (void *)-1)
{
printf("mmap fail\r\n");
exit(1);
}
memset(lcd_addr,0,lcd_size);
//使用mmap得到的地址操作显存空间的内容,从而改变LCD屏显示
for(y=0;y<100;y++)
for(x=0;x<100;x++)
{
unsigned int pos;
//每行的字节数*行数 +每行第几个点*每个点的字节数 得到相应点在显存中的字节数
pos= finfo.line_length*y + x*(vinfo.bits_per_pixel/8);
*(int *)(lcd_addr+pos)=0xf80000;
}
//当不使用framebuffer设备时,使用munmap取消映射
munmap(lcd_addr,lcd_size);
}
开发板运行效果
[root@ZC/zhangchao]#./app /dev/fb0
vinfo.xres:800 vinfo.yres:480 vinfo.bits_per_pixel:32
[root@ZC/zhangchao]#