电子书完善二:多种方式[轮询/select/多线程]实现多输入[终端和触摸屏]
一、电子书支持多输入的框架
要想电子书支持多输入,如触摸屏、控制终端等,轻而易举。因为前面分层分离的思想搭建起的框架拓展性很好。
可以很灵活的添加。只需按面向对象编程方式模块化实现即可。即像前面三个子模块的实现一样。框架图如下
![添加输入子模块后的电子书框架](https://i-blog.csdnimg.cn/blog_migrate/1eb1fd755c210b6cd36e75ce9c60d85e.png)
二、轮询方式
轮询实现最简单,但也最浪费CPU。
对于输入子模块的实现,概括解释下stdinc.c 和 touchscreen.c 的函数功能:
1)在stdin.c 里,在DeviceInit初始化函数里要设置终端属性,即输入一个字符就立刻返回,而无需回车。
注意,在DeviceExit函数 恢复回去。
如果终端没有数据,如何立即返回呢?这里巧妙用了select,将超时时间设置为0,并监听标准输入,
通过FD_ISSET判断有没事件发生,没有就立刻返回
2)对于 touchscreen.c 文件,在DeviceInit()里调用tslib提供的ts_open打开触摸屏设备,并做一些配置
(可参考tslib-1.4版本的ts_print.c 文件)
触摸屏如何读数据呢?也是调用tslib提供的函数,ts_read(xxx, 1 )设置为1为非阻塞,即如果没有数据
立刻返回,而不是休眠。
3)input_manager.c 中,如何实现轮询呢?因为前面两个的读都是立即返回的,因此在main.c里,
不断执行读,不就是轮询了嘛?因此,轮询很简单。具体细节看下面源码实现。
#include <input_manager.h>
#include <config.h>
#include <string.h>
static PT_InputOpr g_ptInputOprHead;
int RegisterInputOpr(PT_InputOpr ptInputOpr)
{
PT_InputOpr ptTmp;
if(!g_ptInputOprHead)
{
g_ptInputOprHead = ptInputOpr;
ptInputOpr->ptNext = NULL;
}
else
{
ptTmp = g_ptInputOprHead;
while (ptTmp->ptNext)
{
ptTmp = ptTmp->ptNext;
}
ptTmp->ptNext = ptInputOpr;
ptInputOpr->ptNext = NULL;
}
ptTmp = NULL;
return 0;
}
int AllInputDevicesInit(void)
{
PT_InputOpr ptTmp = g_ptInputOprHead;
int iError = -1;
while (ptTmp)
{
if (0 == ptTmp->DeviceInit())
{
iError = 0;
}
ptTmp = ptTmp->ptNext;
}
return iError;
}
int GetInputEvent(PT_InputEvent ptInputEvent)
{
PT_InputOpr ptTmp = g_ptInputOprHead;
while (ptTmp)
{
if (0 == ptTmp->GetInputEvent(ptInputEvent))
{
return 0;
}
ptTmp = ptTmp->ptNext;
}
return -1;
}
void ShowInputOpr(void)
{
int i = 0;
PT_InputOpr ptTmp = g_ptInputOprHead;
while(ptTmp)
{
printf("%02d %s\n", i++, ptTmp->name);
ptTmp = ptTmp->ptNext;
}
ptTmp = NULL;
}
int InputInit(void)
{
int iError;
iError = StdinInit();
iError |= TouchScreenInit();
return iError;
}
#include <input_manager.h>
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
static int StdinDeviceInit(void);
static int StdinDeviceExit(void);
static int GetStdinInputEvent(PT_InputEvent ptInputEvent);
static T_InputOpr g_tStdinOpr = {
.name = "stdin",
.DeviceInit = StdinDeviceInit,
.DeviceExit = StdinDeviceExit,
.GetInputEvent = GetStdinInputEvent,
};
static int StdinDeviceInit(void)
{
struct termios ttystate;
tcgetattr(STDIN_FILENO, &ttystate);
ttystate.c_lflag &= ~ICANON;
ttystate.c_cc[VMIN] = 1;
tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);
return 0;
}
static int StdinDeviceExit(void)
{
struct termios ttystate;
tcgetattr(STDIN_FILENO, &ttystate);
ttystate.c_lflag |= ICANON;
tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);
return 0;
}
static int GetStdinInputEvent(PT_InputEvent ptInputEvent)
{
fd_set fds;
char c;
struct timeval tTV;
tTV.tv_sec = 0;
tTV.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
select(STDIN_FILENO+1, &fds, NULL, NULL, &tTV);
if(FD_ISSET(STDIN_FILENO, &fds))
{
ptInputEvent->type = INPUT_EVENT_TYPE_STDIN;
gettimeofday(&ptInputEvent->time, NULL);
c = fgetc(stdin);
if(c == 'u')
{
ptInputEvent->code = INPUT_EVENT_UP;
}
else if(c == 'n')
{
ptInputEvent->code = INPUT_EVENT_DOWN;
}
else if(c == 'q')
{
ptInputEvent->code = INPUT_EVENT_EXIT;
}
return 0;
}
else
{
return -1;
}
return 0;
}
int StdinInit(void)
{
return RegisterInputOpr(&g_tStdinOpr);
}
#include <input_manager.h>
#include <config.h>
#include <draw.h>
#include <stdlib.h>
#include <tslib.h>
static struct tsdev *g_tTSDev;
static int giXres;
static int giYres;
static int TouchScreenDeviceInit(void);
static int TouchScreenDeviceExit(void);
static int GetTouchScreenInputEvent(PT_InputEvent ptInputEvent);
static T_InputOpr g_tTouchScreenOpr = {
.name = "touchscreen",
.DeviceInit = TouchScreenDeviceInit,
.DeviceExit = TouchScreenDeviceExit,
.GetInputEvent = GetTouchScreenInputEvent,
};
static int TouchScreenDeviceInit(void)
{
char *tsdevice=NULL;
if( (tsdevice = getenv("TSLIB_TSDEVICE")) != NULL ) {
g_tTSDev = ts_open(tsdevice, 1);
} else {
g_tTSDev = ts_open("/dev/event0", 1);
}
if (!g_tTSDev) {
DBG_PRINTF("ts_open error!\n");
return -1;
}
if (ts_config(g_tTSDev)) {
DBG_PRINTF("ts_config error!\n");
return -1;
}
if (GetDispResolution(&giXres, &giYres))
{
return -1;
}
return 0;
}
static int TouchScreenDeviceExit(void)
{
return 0;
}
static int isOutOf500ms(struct timeval *ptPreTime, struct timeval *ptNowTime)
{
int iPreMs;
int iNowMs;
iPreMs = ptPreTime->tv_sec * 1000 + ptPreTime->tv_usec / 1000;
iNowMs = ptNowTime->tv_sec * 1000 + ptNowTime->tv_usec / 1000;
return (iNowMs > iPreMs + 500);
}
static int GetTouchScreenInputEvent(PT_InputEvent ptInputEvent)
{
struct ts_sample tSamp;
static struct timeval tPreTime;
int iRet;
iRet = ts_read(g_tTSDev, &tSamp, 1);
if (iRet < 0) {
return -1;
}
DBG_PRINTF("Before call isOutOf500ms\n");
if (isOutOf500ms(&tPreTime, &tSamp.tv))
{
DBG_PRINTF("isOutOf500ms\n");
tPreTime = tSamp.tv;
ptInputEvent->time = tSamp.tv;
ptInputEvent->type = INPUT_EVENT_TYPE_TOUCHSCREEN;
if(tSamp.y < giYres/3)
{
ptInputEvent->code = INPUT_EVENT_UP;
}
else if(tSamp.y > 2*giYres/3)
{
ptInputEvent->code = INPUT_EVENT_DOWN;
}
else
{
ptInputEvent->code = INPUT_EVENT_UNKNOWN;
}
return 0;
}
else
{
return -1;
}
return 0;
}
int TouchScreenInit(void)
{
return RegisterInputOpr(&g_tTouchScreenOpr);
}
三、select机制
何为select机制呢?
简单来说,就是调用select监听多个文件,当其一文件有读/写/异常事情发生或超时,内核会自动唤醒该进程
然后只需判断是那个文件发生了什么事情即可。反之,如果没啥事情或未超时,则进程会睡眠。
因此,相比前面的轮询,大大降低了CPU占用率.
编程思路:
围绕“ int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);” 的参数设置。
1> 既然需要用到fd,则把fd当作 InputOpr结构体的成员,然后再stdin.c和touchscreen.c的
DeviceInit函数初始化各自的fd
2> 在input_manager.c里,在AllInputDeviceInit()函数里在DeviceInit初始化后使用FD_SET设置g_rfdset;
3> 在 GetInputEvent函数里调用select开始监听,当有事件发生时,判断哪个fd再调用对应的结构体的
GetInputEvent并return0
这里并不贴出全部代码,只指出修改的地方
typedef struct InputOpr{
char * name;
int iFd;
int (*DeviceInit)(void);
int (*DeviceExit)(void);
int (*GetInputEvent)(PT_InputEvent ptInputEvent);
struct InputOpr * ptNext;
}T_InputOpr, *PT_InputOpr;
static int TouchScreenDeviceInit(void)
{
g_tTouchScreenOpr.iFd = ts_fd(g_tTSDev);
return 0;
}
static int StdinDeviceInit(void)
{
g_tStdinOpr.iFd = STDIN_FILENO;
return 0;
}
static PT_InputOpr g_ptInputOprHead;
static struct fd_set g_rfdSet;
static int g_MaxFd = -1;
int AllInputDevicesInit(void)
{
PT_InputOpr ptTmp = g_ptInputOprHead;
int iError = -1;
FD_ZERO(g_rfdSet);
while (ptTmp)
{
if (0 == ptTmp->DeviceInit())
{
FD_SET(ptTmp->iFd, &g_rfdSet);
if(ptTmp->iFd > g_MaxFd)
g_MaxFd = ptTmp->iFd;
iError = 0;
}
ptTmp = ptTmp->ptNext;
}
g_MaxFd++;
return iError;
}
int GetInputEvent(PT_InputEvent ptInputEvent)
{
int iRet;
fd_set tRFds;
PT_InputOpr pTmp = g_ptInputOprHead;
tRFds = g_rfdSet;
iRet = select(g_MaxFd, &tRFds, NULL, NULL, NULL);
if(iRet > 0)
{
while(pTmp)
{
if(FD_ISSET(pTmp->iFd, &tRFds))
{
if(0 == pTmp->GetInputEvent(ptInputEvent))
return 0;
}
pTmp = pTmp->ptNext;
}
}
return -1;
}
四、多线程
前面的实现都是单线程,要么轮询,要么休眠,只干一件事。
而使用多线程可以充分利用CPU,而不会傻傻的轮询或休眠等待。
我觉得多线程的实现更简单。只需用到一把互斥锁(子线程需要访问临界资源) + 一个条件变量(用于唤醒/休眠主线程)
编程思路如下:
1> 在调用AllInputDeviceInit()时,一旦调用ts或stdin的DeviceInit成功后,则创建子线程。
而它们两个干相同的事情,即尝试获得互斥锁,不能获得就休眠等待。一旦有事件发生,获得锁成功
然后唤醒主线程,最后释放锁
2> 对于主线程,先休眠(通过条件变量),被子线程唤醒后,就返回数据。
具体实现看下面源码
static T_InputEvent g_tInputEvent;
static pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_tConVar = PTHREAD_COND_INITIALIZER;
static void* InputEventThreadFunction(void* pVoid)
{
T_InputEvent tInputEvent;
int (*GetInputEvent)(PT_InputEvent ptInputEvent);
GetInputEvent = (int (*)(PT_InputEvent))pVoid;
while (1)
{
if(0 == GetInputEvent(&tInputEvent))
{
pthread_mutex_lock(&g_tMutex);
g_tInputEvent = tInputEvent;
pthread_cond_signal(&g_tConVar);
pthread_mutex_unlock(&g_tMutex);
}
}
return NULL;
}
int AllInputDevicesInit(void)
{
PT_InputOpr ptTmp = g_ptInputOprHead;
int iError = -1;
while (ptTmp)
{
if (0 == ptTmp->DeviceInit())
{
pthread_create(&ptTmp->tThreadID, NULL, InputEventThreadFunction,
ptTmp->GetInputEvent);
iError = 0;
}
ptTmp = ptTmp->ptNext;
}
return iError;
}
int GetInputEvent(PT_InputEvent ptInputEvent)
{
pthread_mutex_lock(&g_tMutex);
pthread_cond_wait(&g_tConVar, &g_tMutex);
*ptInputEvent = g_tInputEvent;
pthread_mutex_unlock(&g_tMutex);
return 0;
}
五、实验方法
实验方法:
a. insmod s3c_ts.ko
确定是哪个设备节点对应触摸屏
b.
export TSLIB_TSDEVICE=/dev/event0
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/lib/ts
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0
nfs 30000000 192.168.1.103:/work/nfs_root/uImage_nots
bootm 30000000
c. 较准
ts_calibrate
d. telnetd -l /bin/sh //启动telnet服务,为了登录进去观察CPU占用率
e. ./show_file -s 24 -d fb -f ./MSYH.TTF ./utf8_novel.txt
f. telnet上开发板执行top命令观察
说明:来源自韦东山老师课堂笔记