一、数据结构抽象
对于每一个设备,每一个模块,都用一个结构体来表示它,以后就会很方便的替换这些模块,所以对于设备本身我们需要抽象出一个结构体,所以对于输入系统我们需要抽象出两个结构体,这两个结构分别是:1.数据本身、2.设备本身。
输入来源:1.网络数据输入 2.点击事件输入
1. 数据本身
通过这个数据抽象,就可以既表示出触摸屏本身的数据也能表示出网络本身的数据
触摸屏数据:
int iType : 判断是什么数据,是触摸屏数据还是网络数据
int iX、int iY: 判断具体坐标
int iPressure: 压力值
网络数据:
char str[1024]: 网络数据
2. 设备本身:
*name: 设备名字
(*GetInputEvent)(PInputEvent ptInputEvent): 上层代码通过这个函数得到数据,返回值判断数据是否成功,如果成功数据保存至PInputEvent ptInputEvent中
(*DeviceInit): 提供初始化函数,比如打开设备节点等
(*DeviceExit): 提供退出函数
InputDevice *ptNext : 如果想支持多个设备,就应该将这些设备列到一起,所以需要一个链表指针
3. input_manager.h
#ifndef _INPUT_MANAGER_H #define _INPUT_MANAGER_H #include <sys/time.h> #ifndef NULL #define NULL (void *)0 #endif #define INPUT_TYPE_TOUCH 1 #define INPUT_TYPE_NET 2 typedef struct InputEvent { struct timeval tTime; int iType; int iX; int iY; int iPressure; char str[1024]; }InputEvent, *PInputEvent; typedef struct InputDevice { char *name; int (*GetInputEvent)(PInputEvent ptInputEvent); int (*DeviceInit)(void); int (*DeviceExit)(void); struct InputDevice *ptNext; }InputDevice, *PInputDevice; void RegisterInputDevice(PInputDevice ptInputDev); void InputInit(void); void IntpuDeviceInit(void); int GetInputEvent(PInputEvent ptInputEvent); #endif
第10行:触摸屏事件
第11行:网络事件
第14~21行:抽象出数据本身结构体InputEvent
第14行:定义出一个时间
第24~30行:抽象出设备本身结构体InputDevice
第33行:用于输入系统管理,提供一个注册函数,下面的各个设备进行引用它
第34行:用注册函数进行引用
第35行:对于每个设备初始化它,并且创建线程
第36行:最上层的代码只要调用这个函数就可以得到这些设备的数据
二、触摸屏编程
设备结构体的实现如下结构体:使用tslib
touchscreen.c
#include <input_manager.h> #include <tslib.h> #include <stdio.h> static struct tsdev *g_ts; static int TouchscreenGetInputEvent(PInputEvent ptInputEvent) { struct ts_sample samp; int ret; ret = ts_read(g_ts, &samp, 1); if (ret != 1) return -1; ptInputEvent->iType = INPUT_TYPE_TOUCH; ptInputEvent->iX = samp.x; ptInputEvent->iY = samp.y; ptInputEvent->iPressure = samp.pressure; ptInputEvent->tTime = samp.tv; return 0; } static int TouchscreenDeviceInit(void) { g_ts = ts_setup(NULL, 0); if (!g_ts) { printf("ts_setup err\n"); return -1; } return 0; } static int TouchscreenDeviceExit(void) { ts_close(g_ts); return 0; } static InputDevice g_tTouchscreenDev ={ .name = "touchscreen", .GetInputEvent = TouchscreenGetInputEvent, .DeviceInit = TouchscreenDeviceInit, .DeviceExit = TouchscreenDeviceExit, }; void TouchscreenRegister(void) { RegisterInputDevice(&g_tTouchscreenDev); }
第4行:初始化结果放到全局变量g_ts中
第8行:定义一个ts_sample 的结构体samp
static int TouchscreenGetInputEvent(PInputEvent ptInputEvent)
第6~23行:获得输入事件
static int TouchscreenDeviceInit(void)
第25~35行:对设备的初始化
static int TouchscreenDeviceExit(void)
第37~41行:关闭设备
void TouchscreenRegister(void)
第52~55行:将结构体g_tTouchscreenDev注册到上一层,上一层就是输入管理器,用注册函数进行引用
三、触摸屏单元测试
1.touchscreen.c
在touchscreen.c 代码下面加一个main函数进行触摸屏的单元测试
#if 1 int main(int argc, char **argv) { InputEvent event; int ret; g_tTouchscreenDev.DeviceInit(); while (1) { ret = g_tTouchscreenDev.GetInputEvent(&event); if (ret) { printf("GetInputEvent err!\n"); return -1; } else { printf("Type : %d\n", event.iType); printf("iX : %d\n", event.iX); printf("iY : %d\n", event.iY); printf("iPressure : %d\n", event.iPressure); } } return 0; } #endif
注意:#if 1则执行,# if 0则不执行
第5行:定义一个InputEvent 结构体 event 获取数据本身传给GetInputEvent()
第8行:调用g_tTouchscreenDev结构体中的DeviceInit()进行初始化
第12行:调用g_tTouchscreenDev结构体中的GetInputEvent(&event)进行获取输入事件
第17~23行:如果返回值ret=0的话,则将数据信息打印出来
EXTRA_CFLAGS := CFLAGS_file.o := #obj-y += disp_test.o
将unittest文件夹中的Makefile中的main函数部分注释掉,因为一个程序只能允许有一个main
EXTRA_CFLAGS := CFLAGS_file.o := obj-y += touchscreen.o
input目录下的Makefile
CROSS_COMPILE ?= AS = $(CROSS_COMPILE)as LD = $(CROSS_COMPILE)ld CC = $(CROSS_COMPILE)gcc CPP = $(CC) -E AR = $(CROSS_COMPILE)ar NM = $(CROSS_COMPILE)nm STRIP = $(CROSS_COMPILE)strip OBJCOPY = $(CROSS_COMPILE)objcopy OBJDUMP = $(CROSS_COMPILE)objdump export AS LD CC CPP AR NM export STRIP OBJCOPY OBJDUMP CFLAGS := -Wall -O2 -g CFLAGS += -I $(shell pwd)/include LDFLAGS := -lts export CFLAGS LDFLAGS TOPDIR := $(shell pwd) export TOPDIR TARGET := test obj-y += display/ obj-y += input/ all : start_recursive_build $(TARGET) @echo $(TARGET) has been built! start_recursive_build: make -C ./ -f $(TOPDIR)/Makefile.build $(TARGET) : built-in.o $(CC) -o $(TARGET) built-in.o $(LDFLAGS) clean: rm -f $(shell find -name "*.o") rm -f $(TARGET) distclean: rm -f $(shell find -name "*.o") rm -f $(shell find -name "*.d") rm -f $(TARGET)
需要修改顶层目录下的Makefile
第20行:LDFLAGS := -lts 设置链接
第31行:打开input目录下的文件
2.上机测试
进行make编译,如果出现以下情况,则重新译一下tslib
上板效果如下:点击开发板会将点击的信息显示出来
四、网络编程
对网络输入构造出同触摸屏编程中一样的结构体
netiput.c
#include <input_manager.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <stdio.h> #include <string.h> /* socket * bind * sendto/recvfrom */ #define SERVER_PORT 8888 static int g_iSocketServer; static int NetinputGetInputEvent(PInputEvent ptInputEvent) { struct sockaddr_in tSocketClientAddr; struct int iRecvLen; char aRecvBuf[1000]; int iAddrLen = sizeof(struct sockaddr); iRecvLen = recvfrom(g_iSocketServer, aRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen); if (iRecvLen > 0) { aRecvBuf[iRecvLen] = '\0'; //printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf); ptInputEvent->iType = INPUT_TYPE_NET; gettimeofday(&ptInputEvent->tTime, NULL); strncpy(ptInputEvent->str, aRecvBuf, 1000); ptInputEvent->str[999] = '\0'; return 0; } else return -1; } static int NetinputDeviceInit(void) { struct sockaddr_in tSocketServerAddr; int iRet; g_iSocketServer = socket(AF_INET, SOCK_DGRAM, 0); if (-1 == g_iSocketServer) { printf("socket error!\n"); return -1; } tSocketServerAddr.sin_family = AF_INET; tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */ tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; memset(tSocketServerAddr.sin_zero, 0, 8); iRet = bind(g_iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); if (-1 == iRet) { printf("bind error!\n"); return -1; } return 0; } static int NetinputDeviceExit(void) { close(g_iSocketServer); return 0; } static InputDevice g_tNetinputDev ={ .name = "touchscreen", .GetInputEvent = NetinputGetInputEvent, .DeviceInit = NetinputDeviceInit, .DeviceExit = NetinputDeviceExit, }; void NetInputRegister(void) { RegisterInputDevice(&g_tNetinputDev); }
第29行:端口
第22行:获得的数据
第28行:获得的数据保存到ucRecvBuf里面
static int NetinputGetInputEvent(PInputEvent ptInputEvent)
第24~45行:获得输入事件
static int NetinputDeviceInit(void)
第47~74行:初始化socket
static int NetinputDeviceExit(void)
第76~80行:关闭设备
void NetInputRegister(void)
第88~91行:将结构体g_tNetinputDev注册到上一层,上一层就是输入管理器,用注册函数进行引用
五、网络单元测试
1.netiput.c
这里充当服务器端,用来接收数据
#if 1 int main(int argc, char **argv) { InputEvent event; int ret; g_tNetinputDev.DeviceInit(); while (1) { ret = g_tNetinputDev.GetInputEvent(&event); if (ret) { printf("GetInputEvent err!\n"); return -1; } else { printf("Type : %d\n", event.iType); printf("str : %s\n", event.str); } } return 0; } #endif
注意:需要把触摸屏里面的main函数注释掉
Makefile
EXTRA_CFLAGS := CFLAGS_file.o := obj-y += touchscreen.o obj-y += netinput.o
2.client.c
这里充当客户端,用来发送数据
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <stdio.h> /* socket * connect * send/recv */ #define SERVER_PORT 8888 int main(int argc, char **argv) { int iSocketClient; struct sockaddr_in tSocketServerAddr; int iRet; int iSendLen; int iAddrLen; if (argc != 3) { printf("Usage:\n"); printf("%s <server_ip> <str>\n", argv[0]); return -1; } iSocketClient = socket(AF_INET, SOCK_DGRAM, 0); tSocketServerAddr.sin_family = AF_INET; tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */ //tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr)) { printf("invalid server_ip\n"); return -1; } memset(tSocketServerAddr.sin_zero, 0, 8); #if 0 iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); if (-1 == iRet) { printf("connect error!\n"); return -1; } #endif iAddrLen = sizeof(struct sockaddr); iSendLen = sendto(iSocketClient, argv[2], strlen(argv[2]), 0, (const struct sockaddr *)&tSocketServerAddr, iAddrLen); close(iSocketClient); return 0; }
arm-buildroot-linux-gnueabihf-gcc -o client unittest/client.c make
cp test ~/nfs_rootfs/ [root@100ask:~]# cd /mnt/
3.上机测试
六、 输入系统的框架
如果只有一个输入设备,那么我们只需要写一个main函数即可,但是我们需要好几个设备输入,那么要想统一管理,那么就需要设计一个输入管理系统进行管理选择到底哪个设备进行输入。
在输入管理器中我们需要初始化触摸屏,并且为触摸屏提供一个线程,初始化网络设备,为网络设备提供一个线程,上层应用程序提供一些函数就可以方便的得到各类的输入事件,线程的创建线程的管理都由输入管理器进行设置。
1.框架思路:
1.输入管理器input_manager实现的函数有:
(1).InputInit (2).IntpuDeviceInit (3).GetInputEvent
2.输入管理器支持多个设备,这多个设备怎么管理起来呢?
使用InputInit 创建一个链表,链表指向触摸屏设备本身和网络设备本身,通过链表就可以找到所有的输入设备
3.对于每一个设备都可以调用里面的 GetInputEvent 来获得数据,如果想同时获得多个设备的输入数据的话,那么该怎么获得?
调用IntpuDeviceInit从链表里面把每一个设备取出来,调用里面的DeviceInit 初始化,并且为每一个输入设备创造一个线程thread,线程不断调用设备里面的GetInputEvent 等待数据,一旦得到数据,就可以将获得的数据放到某个buffer
4.上层的应用程序调用GetInputEvent 时候就会去某个buffer里面查看是否有数据,有数据则返回,没数据则休眠
2.input_manager.c
#include <pthread.h> #include <stdio.h> #include <unistd.h> #include <semaphore.h> #include <string.h> #include <input_manager.h> static PInputDevice g_InputDevs = NULL; void RegisterInputDevice(PInputDevice ptInputDev) { ptInputDev->ptNext = g_InputDevs; g_InputDevs = ptInputDev; } /* */ void InputInit(void) { /* regiseter touchscreen */ extern void TouchscreenRegister(void); TouchscreenRegister(); /* regiseter netinput */ extern void NetInputRegister(void); NetInputRegister(); } static void *input_recv_thread_func (void *data) { PInputDevice tInputDev = (PInputDevice)data; InputEvent tEvent; int ret; while (1) { /* 读数据 */ ret = tInputDev->GetInputEvent(&tEvent); if (!ret) { /* 保存数据 */ /* 唤醒等待数据的线程 */ pthread_mutex_lock(&g_tMutex); pthread_cond_wait(&g_tConVar, &g_tMutex); pthread_mutex_unlock(&g_tMutex); } } return NULL; } void IntpuDeviceInit(void) { int ret; pthread_t tid; /* for each inputdevice, init, pthread_create */ PInputDevice ptTmp = g_InputDevs; while (ptTmp) { /* init device */ ret = ptTmp->DeviceInit(); /* pthread create */ if (!ret) { ret = pthread_create(&tid, NULL, input_recv_thread_func, ptTmp); } ptTmp= ptTmp->ptNext; } } int GetInputEvent(PT_InputEvent ptInputEvent) { /* 无数据则休眠 */ /* 返回数据 */ }
第7行:这一层要接受下一层传输上来的注册信息 ,所以要定义一个 g_InputDevs的链表头
g_InputDevs这个链表中存放设备。
void RegisterInputDevice(PInputDevice ptInputDev)
第9~13行:提供一个注册函数
void InputInit(void)
第16~25行:向上提供一个InputInit(void) 函数
第19、20行:注册触摸屏设备
第23、24行:注册网络设备
注意:其中extern是外部函数的意思
void IntpuDeviceInit(void)
第54~74行:对于每个设备初始化它,并且创建线程
第57行:定义一个临时变量的线程id
第60行:将g_InputDevs链表中的每个设备都取出来,ptTmp就是对应的每一个设备
第64行:调用 DeviceInit() 进行初始化
第69行:创造子线程
第67~70行:为每个设备创造线程
第72行:指向下一个设备
static void *input_recv_thread_func (void *data)
第27~52行:线程启动以后需要提供一个函数
对于不同的设备使用同一个函数,这个函数需要使用某个参数来区分这些设备 ,
第29行:一开始要从data里面得到传进来的ptTmp结构体
第36行:不断的读数据,得到的数据放到 InputEvent 下的 tEvent
第38行:判断是否有数据,如果有数据保存数据和唤醒等待数据线程
int GetInputEvent(PT_InputEvent ptInputEvent)
第76行:最重要的一个函数 ,最上层的代码只要调用这个函数就可以得到这些设备的数据
无数据则休眠,有数据则返回数据
怎么避免数据丢失 ?
比如触摸屏,它会一下子上报很多数据
对于网络输入,也有可能同时又多个client发来数据
所以,不能使用单一的变量来保存数据,而是使用一个数组来保存数据
使用“环形缓冲区”
3.环形缓冲区
环形缓冲区也是一个一维数组,并不是一个环形的数组
例:chat buf[5];
4.input_manager.c
#include <pthread.h> #include <stdio.h> #include <unistd.h> #include <semaphore.h> #include <string.h> #include <input_manager.h> static PInputDevice g_InputDevs = NULL; static pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t g_tConVar = PTHREAD_COND_INITIALIZER; /* start of 实现环形buffer */ #define BUFFER_LEN 20 static int g_iRead = 0; static int g_iWrite = 0; static InputEvent g_atInputEvents[BUFFER_LEN]; static int isInputBufferFull(void) { return (g_iRead == ((g_iWrite + 1) % BUFFER_LEN)); } static int isInputBufferEmpty(void) { return (g_iRead == g_iWrite); } static void PutInputEventToBuffer(PInputEvent ptInputEvent) { if (!isInputBufferFull()) { g_atInputEvents[g_iWrite] = *ptInputEvent; g_iWrite = (g_iWrite + 1) % BUFFER_LEN; } } static int GetInputEventFromBuffer(PInputEvent ptInputEvent) { if (!isInputBufferEmpty()) { *ptInputEvent = g_atInputEvents[g_iRead]; g_iRead = (g_iRead + 1) % BUFFER_LEN; return 1; } else { return 0; } } /* end of 实现环形buffer */ void RegisterInputDevice(PInputDevice ptInputDev) { ptInputDev->ptNext = g_InputDevs; g_InputDevs = ptInputDev; } /* */ void InputInit(void) { /* regiseter touchscreen */ extern void TouchscreenRegister(void); TouchscreenRegister(); /* regiseter netinput */ extern void NetInputRegister(void); NetInputRegister(); } static void *input_recv_thread_func (void *data) { PInputDevice ptInputDev = (PInputDevice)data; InputEvent tEvent; int ret; while (1) { /* 读数据 */ ret = ptInputDev->GetInputEvent(&tEvent); if (!ret) { /* 保存数据 */ pthread_mutex_lock(&g_tMutex); PutInputEventToBuffer(&tEvent); /* 唤醒等待数据的线程 */ pthread_cond_signal(&g_tConVar); /* 通知接收线程 */ pthread_mutex_unlock(&g_tMutex); } } return NULL; } void IntpuDeviceInit(void) { int ret; pthread_t tid; /* for each inputdevice, init, pthread_create */ PInputDevice ptTmp = g_InputDevs; while (ptTmp) { /* init device */ ret = ptTmp->DeviceInit(); /* pthread create */ if (!ret) { ret = pthread_create(&tid, NULL, input_recv_thread_func, ptTmp); } ptTmp= ptTmp->ptNext; } } int GetInputEvent(PInputEvent ptInputEvent) { InputEvent tEvent; int ret; /* 无数据则休眠 */ pthread_mutex_lock(&g_tMutex); if (GetInputEventFromBuffer(&tEvent)) { ptInputEvent = tEvent; pthread_mutex_unlock(&g_tMutex); return 0; } else { /* 休眠等待 */ pthread_cond_wait(&g_tConVar, &g_tMutex); if (GetInputEventFromBuffer(&tEvent)) { *ptInputEvent = tEvent; ret = 0; } else { ret = -1; } pthread_mutex_unlock(&g_tMutex); } return ret; }
第14行:buffer数组的长度
第15、16行:设置读写
第9~10行:定义一个互斥锁,用于环形缓冲区的访问
第17行:定义一个buffer
static int isInputBufferFull(void) static int isInputBufferEmpty(void)
第20~23行和25~28行:判断是空还是满
第20行的函数是判断满的
第25行的函数是判断空的
static void PutInputEventToBuffer(PInputEvent ptInputEvent)
第30~37行:往这个buffer数组里面存放数据
第32行:如果不满的话往进放数据
static int GetInputEventFromBuffer(PInputEvent ptInputEvent)
第40~47行:得到数据
第42行:如果不空读数据
static void *input_recv_thread_func (void *data)
第76~100行:线程启动以后需要提供一个函数
对于不同的设备使用同一个函数,这个函数需要使用某个参数来区分这些设备 ,
第78行:一开始要从data里面得到传进来的ptTmp结构体
第79行:不断的读数据,得到的数据放到 InputEvent 下的 tEvent
第85行:判断是否有数据,如果有数据保存数据和唤醒等待数据线程
第90、91行:上锁,保存数据,放入到环形缓冲区内
第91行:唤醒等待数据的线程
第92行:释放锁
int GetInputEvent(PT_InputEvent ptInputEvent)
第124~153行:将从输入设备得到的数据放入到缓冲区
触摸屏和网络设备,上层线程都要去访问环形缓冲区,那么访问环形缓冲区时应该给它加上一个锁。
第129行:加锁
第132、142行:进行数据传输
第133行:如果获得数据则释放锁
第136~151行:否则休眠等待,直到获得数据,ret = 0 表示成功,ret = -1表示失败
第149行:释放锁
献上韦东山老师对以上代码流程的梳理过程
电子产品量产工具输入系统代码流程
七、输入管理单元测试
1.input_test.c
#include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <linux/fb.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <sys/ioctl.h> #include <input_manager.h> int main(int argc, char **argv) { int ret; InputEvent event; InputInit(); IntpuDeviceInit(); while (1) { printf("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); ret = GetInputEvent(&event); printf("%s %s %d, ret = %d\n", __FILE__, __FUNCTION__, __LINE__, ret); if (ret) { printf("GetInputEvent err!\n"); return -1; } else { printf("%s %s %d, event.iType = %d\n", __FILE__, __FUNCTION__, __LINE__, event.iType ); if (event.iType == INPUT_TYPE_TOUCH) { printf("Type : %d\n", event.iType); printf("iX : %d\n", event.iX); printf("iY : %d\n", event.iY); printf("iPressure : %d\n", event.iPressure); } else if (event.iType == INPUT_TYPE_NET) { printf("Type : %d\n", event.iType); printf("str : %s\n", event.str); } } } return 0; }
第16行:读到的输入事件存到event中
第18行:调用InputInit进行初始化
第19行:调用IntpuDeviceInit从链表里面把每一个设备取出来
第34~40行:如果设备是触摸屏的话就把触点打印出来
第41~45行:如果设备是网络的话就把字符串打印出来
2.input下的Makefile
EXTRA_CFLAGS := CFLAGS_file.o := obj-y += touchscreen.o obj-y += netinput.o obj-y += input_manager.o
3.unittest下的Makefile
EXTRA_CFLAGS := CFLAGS_file.o := #obj-y += disp_test.o obj-y += input_test.o
4.顶层Makefile
CROSS_COMPILE ?= AS = $(CROSS_COMPILE)as LD = $(CROSS_COMPILE)ld CC = $(CROSS_COMPILE)gcc CPP = $(CC) -E AR = $(CROSS_COMPILE)ar NM = $(CROSS_COMPILE)nm STRIP = $(CROSS_COMPILE)strip OBJCOPY = $(CROSS_COMPILE)objcopy OBJDUMP = $(CROSS_COMPILE)objdump export AS LD CC CPP AR NM export STRIP OBJCOPY OBJDUMP CFLAGS := -Wall -O2 -g CFLAGS += -I $(shell pwd)/include LDFLAGS := -lts -lpthread export CFLAGS LDFLAGS TOPDIR := $(shell pwd) export TOPDIR TARGET := test obj-y += display/ obj-y += input/ obj-y += unittest/ all : start_recursive_build $(TARGET) @echo $(TARGET) has been built! start_recursive_build: make -C ./ -f $(TOPDIR)/Makefile.build $(TARGET) : built-in.o $(CC) -o $(TARGET) built-in.o $(LDFLAGS) clean: rm -f $(shell find -name "*.o") rm -f $(TARGET) distclean: rm -f $(shell find -name "*.o") rm -f $(shell find -name "*.d") rm -f $(TARGET)
第20行:LDFLAGS := -lts -lpthread
第31行:obj-y += unittest/
5.上板测试
book@100ask:~/12_input_manager_unittest$ arm-buildroot-linux-gnueabihf-gcc -o client unittest/client.c book@100ask:~/12_input_manager_unittest$ make book@100ask:~/12_input_manager_unittest$ cp client test ~/nfs_rootfs/
[root@100ask:/mnt]# input_test.c main 23