数码相册——电子书select支持多输入
- 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
- 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
- 参考资料:《嵌入式Linux应用开发手册》、《嵌入式Linux应用开发手册第2版》、【linux select函数详解】
- 开发环境:Linux 3.4.2内核、arm-linux-gcc 4.3.2工具链
- 源码仓库:https://gitee.com/d_1254436976/Embedded-Linux-Phase-3
一、前言
在【1.7 数码相册—电子书(3)—轮询方式支持多输入】博文中,采用了轮询的方式,实现了电子书支持多输入模式。
- 缺点:由于采用轮询的方式,程序一直在跑,CPU占用率高达90%,非常不友好。
- 改进:采用Linux中的select来函数来了优化标准输入的方式,使得在没有标准输入的时候,程序处于休眠,当有标准输入的时候,程序立刻被唤醒,通过对于触摸屏的输入影响也不大。
二、select函数优化
1、简单介绍下select函数
- 作用:
使用select
就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行才返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高) 方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况–读写或是异常。 - 函数原型:
#include <sys/select.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
-
参数说明:
int maxfdp1
:指集合中所有文件描述符的范围,即所有文件描述符的最大值加1
fd_set *readset
:fd_set结构体,一个文件描述符的集合,监视这些文件描述符的读变化
fd_set *writeset
:fd_set结构体,一个文件描述符的集合,监视这些文件描述符的写变化
fd_set *exceptset
:fd_set结构体,一个文件描述符的集合,监视这些文件描述符的异常情况
struct timeval *timeout
:程序需等待的时间 -
struct timeval
结构体:
struct timeval{
long tv_sec; /*秒 */
long tv_usec; /*微秒 */
}
fd_set
结构体:描述符集
可以理解为是一个二进制数的数组如下图所示:当文件发生变化时,该文件的文件描述符对应为置为1
对于fd_set
结构体,通过以下的宏来控制:
int FD_ZERO(int fd, fd_set *fdset); //将一个fd_set描述符集的所有位都置0
int FD_CLR(int fd, fd_set *fdset); //清除fd_set描述符集的某个位置0
int FD_SET(int fd, fd_set *fd_set); //将一个给定的文件描述符加入fd_set描述符集中
int FD_ISSET(int fd, fd_set *fdset); //检查fd_set描述符集中指定的文件描述符是否可以读写
2、修改input管理者文件input_manager.c
通过修改此文件实现以下功能:
- 在
main.c
中调用AllInputDeviceInit()
把定义的静态全局变量描述符集s_tRfds
置为0; - 在主函数的死循环中,调用
GetInputEvent()
,在这个函数中:对多路输入设备的文件描述符进行读监视,此时程序处于休眠状态。 - 当监视到其中一个文件描述符对应的输入设备有读信号,唤醒程序,调用该输入设备的获取输入事件函数
ptTmp->GetInputEvent(ptInputEvent)
。
#include <config.h>
#include <string.h>
#include <stdlib.h>
#include <sys/select.h>
#include "input_manager.h"
static PT_InputOpr s_ptInputOprHead; //链表头
static fd_set s_tRfds; //文件描述符集
static int s_MaxFd = -1; //最大文件句柄
/* 函数名: 注册函数
* 函数功能:构建一个链表:把多个拓展文件的结构体“串”起来
* 函数实现:根据传入的结点,首先判断该链表头是否为空
* 空则,头结点指向传入的节点,且把节点的ptNext域指向NULL
* 不空则,尾插法插入链表
*/
int RegisterInputOpr(PT_InputOpr ptInputOpr)
{
PT_InputOpr ptTmp;
if (!s_ptInputOprHead)
{
s_ptInputOprHead = ptInputOpr;
ptInputOpr->ptNext = NULL;
}
else
{
ptTmp = s_ptInputOprHead;
while (ptTmp->ptNext)
{
ptTmp = ptTmp->ptNext;
}
ptTmp->ptNext = ptInputOpr;
ptInputOpr->ptNext = NULL;
}
return 0;
}
/* 显示支持拓展文件的名字 */
void ShowInputOpr(void)
{
int i = 0;
PT_InputOpr ptTmp = s_ptInputOprHead;
while (ptTmp) {
printf("%02d %s\n", i++, ptTmp->name);
ptTmp = ptTmp->ptNext;
}
}
/* 初始化函数 */
int InputInit(void)
{
int error;
error = StdinInit();
error |= TouchScreenInit();
return error;
}
/* 用select函数监测stdin,touchscreen
* 有数据时在调用他们的GetInputEvent()获得具体事件
*/
int GetInputEvent(PT_InputEvent ptInputEvent)
{
PT_InputOpr ptTmp;
fd_set rfds;
int ret;
rfds = s_tRfds;
ptTmp = s_ptInputOprHead;
ret = select(s_MaxFd, &rfds, NULL, NULL, NULL); //只对可读文件进行监视
if (ret > 0) {
/* 多路文件至少有一个可读 */
while (ptTmp) {
/* 查找该结构体的fds是否可读 */
if (FD_ISSET(ptTmp->fds, &rfds)) {
if (ptTmp->GetInputEvent(ptInputEvent) == 0)
return 0;
}
ptTmp = ptTmp->ptNext;
}
}
return 0;
}
/* 初始化所有支持的Input设备 */
int AllInputDeviceInit()
{
int error;
PT_InputOpr ptTmp;
error = -1;
ptTmp = s_ptInputOprHead;
FD_ZERO(&s_tRfds);
while (ptTmp) {
if (ptTmp->DeviceInit() == 0) {
FD_SET(ptTmp->fds, &s_tRfds);
/* 更新文件句柄 */
if (s_MaxFd < ptTmp->fds)
s_MaxFd = ptTmp->fds;
error = 0;
}
ptTmp = ptTmp->ptNext;
}
s_MaxFd++;
return error;
}
三、编译与运行
1、编译
执行make
,得到可执行文件show_file
2、运行
由于使用到触摸屏,需要调用tslib库来进行校准
- 执行
./show_file -l
,显示出当前支持的设备
- 执行
./shoe_file -s 16 -h HZK16 -f ./MSYH.TTF hz.txt
执行top
,可以看到此时应用程序:
在没有任何外部输入的时候,CPU占用率为0,处于休眠
有输入时,才唤醒程序