交叉编译结束后,打开树莓派(Ubuntu系统)的framebuffer后,操作填充buffer中存储的颜色,系统会把buffer中的内容显示在屏幕上,进而达到控制屏幕显示的内容。此时的树莓派处于纯命令行模式,该单独打开的终端窗口占一个进程,所以树莓派连接的屏幕除了显示framebuffer进程的所展示的内容外,还会显示另一个进程,也就是原来的命令行窗口,这俩进程同时使用着屏幕,这时就会出现闪屏的问题,因为两个进程都在刷新屏幕。
但是在运行framebuffer进程的时候,我们需要屏幕只显示该进程展示的画面,该问题的解决办法就是操作tty设备。
Linux /dev/console & /dev/tty
- /dev/console是系统控制台,系统的错误信息什么的都输出到这里。简单的说,能直接显示系统消息的那个终端称为控制台,其他的则称为终端(控制台也是一个终端)。通常在linux下看到的控制台一般都指向/dev/tty*。
- /dev/tty*是进程的控制台,如果在进程里打开一个这样的文件且该文件不是其他进程的控制终端时,那么该文件就是这个进程的控制终端,进程printf一个数据就会输出到这里。tty1-tty6都代表了虚拟终端设备。
利用tty命令可以看到控制台当前指向的终端,向该终端输出信息,可以显示在控制台上。
要想解决闪屏问题,需要打开一个新的虚拟终端tty设备,并把该设备激活,设为图形模式,让它独占 framebuffer设备,然后再输出图形。这样,即使有其他进程在操作framebuffer,当前终端的framebuffer就不会被重刷,从而实现了稳定的输出。
用ioctl函数操纵虚终端设备文件
虚终端,也叫虚屏,它的英文名字为virtual terminal,缩写为VT或vt。利用系统提供的系统调用来编制虚终端程序,并可把它加入到我们的应用程序中去。可利用ioctl函数操纵虚终端设备文件,实现与系统内核的交互,得到我们所需要的服务。想操作VT接口,必须包含头文件
1.打开当前的使用的虚拟终端
ConsoleFD = open("/dev/tty0", O_WRONLY, 0);
2.查看当前打开的虚拟终端数量vtnumber,假如数量为4,那么已经打开的虚拟终端编号为tty0-tty3;
ioctl(ConsoleFD, VT_OPENQRY, &vtnumber);
3.关闭打开的console,利用vtnumber打开新终端
close(ConsoleFD); char *ttystr; sprintf(ttystr, "/dev/tty%d", vtnumber); ConsoleFD = open(ttystr, O_RDWR | O_NDELAY, 0);//新终端
4.切换当前的虚拟机控制台,使ConsoleFD成为虚拟控制台
ioctl(ConsoleFD,TIOCCONS);
5.查看并设置这个虚拟终端的状态。在结构vt_mode中,mode字段(虚终端模式)的值VT_PROCESS(进程控制模式),使得在图形状态下也能切换控制台。
ioctl中操作VT的参数例如VT_PROCESS等具体意义可以在该网站查找:
Linux Programming Hints | Linux Journal
ioctl(ConsoleFD, VT_GETMODE, &vt); vt.mode = VT_PROCESS; vt.relsig = SIGUSR1; vt.acqsig = SIGUSR1; ioctl(ConsoleFD, VT_SETMODE, &vt);
6.将终端设置为图形模式
ioctl(ConsoleFD, KDSETMODE, KD_GRAPHICS)
经过以上步骤,便可解决问题。
整体代码
int openDev(){
char vtname[128];
int vtnum,con_fd;
con_fd=open("/dev/tty1", O_RDONLY, 0);//当前控制台虚拟终端
if (con_fd< 0) {
printf("error opening /dev/ttyn");
return 0;
}
if (ioctl(con_fd, VT_OPENQRY, &vtnum) < 0 || vtnum < 0) {
printf("error: couldn't get a free vtn");
return 0;
}
close(con_fd);
printf("vtnum:%dn",vtnum);
sprintf(vtname, "/dev/tty%d", vtnum); // /dev/tty1-64
con_fd = open(vtname, O_RDWR | O_NDELAY, 0);
if (con_fd < 0) {
perror("open console fd");
return 0;
}
ioctl(con_fd,TIOCCONS);
//保存这个虚拟终端的状态
struct vt_mode vt;
if (ioctl(con_fd, VT_GETMODE, &vt) < 0) {
perror("ioctl VT_GETMODE");
close(con_fd);
return 0;
}
//更改模式
vt.mode = VT_PROCESS;
vt.relsig = SIGUSR1;
vt.acqsig = SIGUSR1;
if(ioctl(con_fd, VT_SETMODE, &vt) < 0)
{
perror("ioctl VT_SETMODE");
close(con_fd);
return 0;
}
//修改显示模式
if (ioctl(con_fd, KDSETMODE, KD_GRAPHICS) < 0)
{
perror("KDSETMODE");
close(con_fd);
return 0;
}
return 1;
}
出现的问题及解决
在这个过程中出现了一些问题,做个记录。
- open新设备tty*时遇到错误返回
命令ls -l /dev/看看树莓派系统的tty*设备所属用户,这里打开失败的是tty3,下图显示的tty3所属用户为tty和root。
PS:crw开头的字符串是文件的操作权限,若权限不够可用chmod 666 /dev/tty3进行更改。
whoami查看当前的用户名称(本系统的是pi),若和上面显示的不同,用chown pi /dev/tty3 将tty3所有者设为pi。
- ioctl操作VT时错误,并返回Operation not permitted
有两种办法可以试一试,一是在运行程序的时候用sudo命令执行,二是用命令:setcap 'cap_sys_tty_config+ep' [可执行程序的名字],允许该程序配置TTY设备,然后再执行。
参考:
linux framebuffer 例子 (2)(图形,终端) - AI牛丝
利用framebuffer和tty在linux编写图形驱动-cywcdwxjf-ChinaUnix博客



