main.c
|
Input_manager.c
| |
Touchscreen.c Stdin.c
输入模块分为三层,Input_manager.c通过链表管理底层各种输入设备,每个设备向上注册InputOpr结构体,提供该设备的各种操作函数和数据。
其中Input_manager.c实现了几个函数供main.c调用。
//调用所有"输入模块"的设备相关的初始化函数并创建用于读取输入数据的子线程
int AllInputDevicesInit(void);
//获得输入数据,它会使得当前线程休眠,当各输入模块的子线程读到数据后会把它
//唤醒(与Touchscreen.c等提供的GetInputEvent函数不同)供下层调用
int GetInputEvent(PT_InputEvent ptInputEvent);
//注册"输入模块"调用下层
int RegisterInputOpr(PT_InputOpr ptInputOpr);
//调用各个输入模块的初始化函数,就是注册各个输入模块
int InputInit(void);
输入操作
typedef struct InputOpr {
char *name; /* 输入模块的名字 */
pthread_t tTreadID; /* 子线程ID */
int (*DeviceInit)(void); /* 设备初始化函数 */
int (*DeviceExit)(void); /* 设备退出函数 */
int (*GetInputEvent)(PT_InputEvent ptInputEvent); /* 获得输入数据 */
struct InputOpr *ptNext;
}T_InputOpr, *PT_InputOpr;
输入事件结构体
typedef struct InputEvent {
struct timeval tTime; /* 发生这个输入事件时的时间 */
int iType; /* 类别: stdin, touchsceen */
int iX; /* X/Y座标 */
int iY;
int iKey; /* 按键值 */
int iPressure; /* 压力值 */
}T_InputEvent, *PT_InputEvent;
main.c调用Input_manager.c的GetInputEvent函数获取输入事件进入休眠,AllInputDevicesInit函数中初始化各个输入设备,并创建子线程,其中线程函数传递的参数就是该设备的GetInputEvent函数,各个设备都循环调用read函数并以阻塞方式打开,所以都会进入休眠,当获得输入事件后,子线程唤醒主线程
参考 Unix_Linux_Windows_OpenMP多线程编程.pdf P18 3.3.2 条件变量
前面两种方式都是单线程实现的,第一种轮询方式while死循环,不断调用GetInputEvent函数,CPU占用率高。第二种也会调用GetInputEvent函数,执行到select会休眠,由内核唤醒,不断循环。第三种多线程,主线程休眠,子线程也在休眠,一旦有数据就唤醒子线程,子线程得到数据再来唤醒主线程,传递输入事件结构体InputEvent,注意使用互斥锁保护临界资源(全局变量)。
互斥锁和条件变量
main函数
iError = AllInputDevicesInit(); //调用所有输入设备初始化
while (1){
if (0 == GetInputEvent(&tInputEvent)){ //input_manager.c的获得输入事件函数GetInputEvent
if (tInputEvent.iVal == INPUT_VALUE_DOWN){
ShowNextPage();
}
else if (tInputEvent.iVal == INPUT_VALUE_UP){
ShowPrePage();
}
else if (tInputEvent.iVal == INPUT_VALUE_EXIT){
return 0;
}
}
}
input_manager.c函数
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 *InputEventTreadFunction(void *pVoid){ //输入事件子线程函数
T_InputEvent tInputEvent;
int (*GetInputEvent)(PT_InputEvent ptInputEvent); //定义函数指针,指向pVoid参数
GetInputEvent = (int (*)(PT_InputEvent))pVoid;
while (1){
if(0 == GetInputEvent(&tInputEvent)){ //若成功获得输入事件
/* 唤醒主线程, 把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()){ //调用设备初始化函数成功
/* 创建子线程 参数:保存线程ID结构体,线程属性对象,线程开始执行时调用函数,传给函数的参数 */
pthread_create(&ptTmp->tTreadID, NULL, InputEventTreadFunction, 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;
}
main函数先调用AllInputDevicesInit()函数,调用各输入设备的初始化函数,还会创建子线程。子线程的InputEventTreadFunction函数根据传入参数不同,调用touchscreen或stdin的GetInputEvent(&tInputEvent)函数来获得输入事件。
main函数中,循环调用input_manager.c的GetInputEvent函数来获得输入事件,获得互斥量,等待条件变量pthread_cond_wait休眠。
若touchscreen或stdin中获得输入事件,获得互斥量,把值赋给全局变量g_tInputEvent,唤醒主线程pthread_cond_signal。
input_manager.c的GetInputEvent函数被唤醒后,此时全局变量g_tInputEvent的值为touchscreen或stdin传来的输入事件,将该值返回给main函数,释放互斥量。
stdin.c函数
static int StdinGetInputEvent(PT_InputEvent ptInputEvent){
/* 如果有数据就读取、处理、返回
* 如果没有数据, 立刻返回, 不等待*/
char c;
/* 处理数据 */
ptInputEvent->iType = INPUT_TYPE_STDIN;
c = fgetc(stdin); /* 没有数据会休眠直到有输入 */
gettimeofday(&ptInputEvent->tTime, NULL); //记录时间值
if (c == 'u')
ptInputEvent->iVal = INPUT_VALUE_UP;
else if (c == 'n')
ptInputEvent->iVal = INPUT_VALUE_DOWN;
else if (c == 'q')
ptInputEvent->iVal = INPUT_VALUE_EXIT;
else
ptInputEvent->iVal = INPUT_VALUE_UNKNOWN;
return 0;
}
touchscreen.c函数
static int TouchScreenGetInputEvent(PT_InputEvent ptInputEvent){
struct ts_sample tSamp;
struct ts_sample tSampPressed; //压下值
struct ts_sample tSampReleased; //松开值
int iRet;
int bStart = 0;
int iDelta; // x差值
static struct timeval tPreTime;
while (1){
iRet = ts_read(g_tTSDev, &tSamp, 1); /* 以阻塞方式打开的,如果无数据则休眠 */
if (iRet == 1){ //若读到数据
if ((tSamp.pressure > 0) && (bStart == 0)){ // 刚按下
tSampPressed = tSamp; // 记录刚开始压下的点
bStart = 1; //标记位:已按下
}
if (tSamp.pressure <= 0){ /* 松开 */
tSampReleased = tSamp;
if (!bStart){ /* 处理数据 */
return -1;
}
else{
iDelta = tSampReleased.x - tSampPressed.x; // x差值
ptInputEvent->tTime = tSampReleased.tv; // 记录时间
ptInputEvent->iType = INPUT_TYPE_TOUCHSCREEN; // 记录类型
if (iDelta > giXres/5){
/* 翻到上一页 */
ptInputEvent->iVal = INPUT_VALUE_UP; // 记录值
}
else if (iDelta < 0 - giXres/5){
/* 翻到下一页 */
ptInputEvent->iVal = INPUT_VALUE_DOWN;
}
else{
ptInputEvent->iVal = INPUT_VALUE_UNKNOWN;
}
return 0;
}
}
}
else{
return -1;
}
}
return 0;
}
其中
触摸屏read得到的数据,参考ts_print和tslib.h
struct ts_sample {
int x;
int y;
unsigned int pressure;
struct timeval tv;
};