數碼相冊——電子書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工具鏈
一、前言
在【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,處於休眠
有輸入時,才喚醒程序