1).查询方式与休眠唤醒方式(非阻塞方式与阻塞方式)
查询方式
APP调用open函数时,传入“O_NONBLOCK”表示“非阻塞”。
APP调用read函数读取数据时,如果驱动程序中有数据,那么APP的read函数会返回数据,否则也会立刻返回错误。
休眠唤醒方式
APP调用open函数时,不要传入“O_NONBLOCK”。
APP调用read函数读取数据时,如果驱动程序中有数据,那么APP的read函数会返回数据;否则APP就会在内核态休眠,当有数据时驱动程序会把APP唤醒,read函数恢复执行并返回数据给APP。
当应用程序调用read时,内核的evdev.c中的evdev_read会被调用
static ssize_t evdev_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{...
struct input_event event;
....
}
我们需要在app中也定义一个struct input_event event;来接收输入设备信息
操作方式:当传入noblock时选择查询方式,当不传入noblock时选择休眠唤醒方式
/*eg: ./01_get_input_info /dev/input/event0 noblock */
if ( argc < 2 )
{
printf("Usage :%s <dev> [noblock]\n",argv[0]);
return -1;
}
if ( argc == 3 && !strcmp(argv[2],"noblock"))
{
fd = open(argv[1],O_RDWR | O_NONBLOCK);
}
else
{
fd = open(argv[1],O_RDWR);
}
if ( fd < 0)
{
printf("Open %s err!\n",argv[1]);
return -1;
}
main()中:
while(1)
{
len = read(fd,&event,sizeof(event));将读到的输入设备信息放入event结构体中
if ( len == sizeof(event)) //如果读到数据
{
printf("get event:type = 0x%x, code = 0x%x, value = 0x%x\n",event.type,event.code,event.value);
}
else //如果没有读到数据
{
printf("read err %d\n",len);
}
}
在上层传入noblock时,即选择查询方式读取,等运行到read函数,当读到数据时打印数据,当没有读到数据时会一直打印read err xx
在上层未传入noblock时,即选择休眠唤醒方式读取,等运行到read函数,当读到数据时打印数据,当没有读到数据时APP就会在内核态休眠,当有数据时驱动程序会把APP唤醒,read函数恢复执行并返回数据给APP。即当数据打印数据,无数据也不会执行到else然后打印err而是进入休眠等待唤醒。
源码:
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/*eg: ./01_get_input_info /dev/input/event0 noblock */
int main(int argc,char **argv)
{
int fd;
int ret;
struct input_id id;
unsigned int evbit[2];
int len;
int i;
unsigned char byte;
int bit;
char *ev_names[] = {
"EV_SYN" ,
"EV_KEY" ,
"EV_REL" ,
"EV_ABS" ,
"EV_MSC" ,
"EV_SW" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"EV_LED" ,
"EV_SND" ,
"NULL" ,
"EV_REP" ,
"EV_FF" ,
"EV_PWR" ,
};
struct input_event event;
if ( argc < 2 )
{
printf("Usage :%s <dev> [noblock]\n",argv[0]);
return -1;
}
if ( argc == 3 && !strcmp(argv[2],"noblock"))
{
fd = open(argv[1],O_RDWR | O_NONBLOCK);
}
else
{
fd = open(argv[1],O_RDWR);
}
if ( fd < 0)
{
printf("Open %s err!\n",argv[1]);
return -1;
}
ret = ioctl(fd,EVIOCGID,&id);
if ( ret == 0)
{
printf("bustype = 0x%x\n",id.bustype);
printf("vendor = 0x%x\n",id.vendor);
printf("product = 0x%x\n",id.product);
printf("version = 0x%x\n",id.version);
}
/*0表示读evbit,读的大小sizeof(evbit),&evbit表示读到evbit*/
len = ioctl(fd,EVIOCGBIT(0,sizeof(evbit)),&evbit);
if( len > 0 && len <= sizeof(evbit))
{
printf("Suppport ev type:");
for( i = 0; i < len;i++)
{
byte = ((unsigned char *)evbit)[i];
for (bit = 0;bit < 8;bit++)
{
if ( byte & (1<<bit))
{
printf("%s ",ev_names[i*8 + bit]);
}
}
}
printf("\n");
}
while(1)
{
len = read(fd,&event,sizeof(event));
if ( len == sizeof(event))
{
printf("get event:type = 0x%x, code = 0x%x, value = 0x%x\n",event.type,event.code,event.value);
}
else
{
printf("read err %d\n",len);
}
}
return 0;
}
在linux下编译执行:
arm-buildroot-linux-gnueabihf-gcc -o 02_get_input_info_read get_input_info_read.c -Wall
cp 02_get_input_info_read ~/nfs_rootfs/
在板子上运行可见,当没有加noblock没有读到数据时进入了休眠没有打印read err,而加入noblock没有读到时一直打印read err
2).poll方式与select方式
POLL机制、SELECT机制是完全一样的,只是APP接口函数不一样
简单地说,它们就是“定个闹钟”:在调用poll、select函数时可以传入“超时时间”。在这段时间内,条件合适时(比如有数据可读、有空间可写)就会立刻返回,否则等到“超时时间”结束时返回错误
poll方式:
APP先调用open函数时选择用查询(非阻塞)方式,这样当poll在定时时间到检查到有数据时read函数就可以一直读数据直到把数据读完,就可以等待下一次poll,而不用进入休眠状态,如果采用堵塞方式,当然有data也能全显示,但当没有data,app就进入了休眠,等待接收data唤醒,这样poll机制的定时查询特点就完全没有体现出来,因此采用非堵塞方式。
所以在open时采用非堵塞:
fd = open(argv[1],O_RDWR | O_NONBLOCK);
在调用poll函数时,要指明:
① 你要监测哪一个文件:哪一个fd
② 你想监测这个文件的哪种事件:是POLLIN、还是POLLOUT
最后,在poll函数返回时,要判断状态
poll函数需要一个
struct pollfd fds[1];//用于poll函数
nfds_t nfds = 1; //poll次数
fds[0].fd = fd;
fds[0].events = POLLIN;
fds[0].revents = 0;
ret = poll( fds, nfds , 5000);//5000ms即5s poll一次
源码:
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
/*eg: ./01_get_input_info /dev/input/event0 */
int main(int argc,char **argv)
{
int fd;
int ret;
struct input_id id;
unsigned int evbit[2];
int len;
int i;
unsigned char byte;
int bit;
char *ev_names[] = {
"EV_SYN" ,
"EV_KEY" ,
"EV_REL" ,
"EV_ABS" ,
"EV_MSC" ,
"EV_SW" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"EV_LED" ,
"EV_SND" ,
"NULL" ,
"EV_REP" ,
"EV_FF" ,
"EV_PWR" ,
};
struct input_event event;
struct pollfd fds[1];//用于poll函数接收data
nfds_t nfds = 1; //number
if ( argc != 2 )
{
printf("Usage :%s <dev>\n",argv[0]);
return -1;
}
fd = open(argv[1],O_RDWR | O_NONBLOCK);
if ( fd < 0)
{
printf("Open %s err!\n",argv[1]);
return -1;
}
ret = ioctl(fd,EVIOCGID,&id);
if ( ret == 0)
{
printf("bustype = 0x%x\n",id.bustype);
printf("vendor = 0x%x\n",id.vendor);
printf("product = 0x%x\n",id.product);
printf("version = 0x%x\n",id.version);
}
/*0表示读evbit,读的大小sizeof(evbit),&evbit表示读到evbit*/
len = ioctl(fd,EVIOCGBIT(0,sizeof(evbit)),&evbit);
if( len > 0 && len <= sizeof(evbit))
{
printf("Suppport ev type:");
for( i = 0; i < len;i++)
{
byte = ((unsigned char *)evbit)[i];
for (bit = 0;bit < 8;bit++)
{
if ( byte & (1<<bit))
{
printf("%s ",ev_names[i*8 + bit]);
}
}
}
printf("\n");
}
while(1)
{
fds[0].fd = fd;
fds[0].events = POLLIN;
fds[0].revents = 0;
ret = poll( fds, nfds , 5000);//5000ms即5s
if ( ret > 0 )
{
if ( fds[0].revents == POLLIN )
{
while( read(fd,&event,sizeof(event)) == sizeof(event) )
{
printf("get event:type = 0x%x, code = 0x%x, value = 0x%x\n",event.type,event.code,event.value);
}
}
}
else if(ret == 0)
{
printf("time out\n");
}
else
{
printf("poll err\n");
}
}
return 0;
}
在linux下编译,板子上实验:
linux下执行:
arm-buildroot-linux-gnueabihf-gcc -o 03_get_input_info_read_poll get_input_info_read_poll.c -Wall
cp 03_get_input_info_read_poll ~/nfs_rootfs/
板子上挂在nfs 后运行:
可以看见当没有触摸屏幕,到了5s就会提示 time out 而当触摸屏幕就会打印data
select方式:
struct timeval tv; // 设置超时时间
/* 设置超时时间 */
tv.tv_sec = 5;/* seconds */
tv.tv_usec = 0;/* microseconds */
/* 想监测哪些文件? */
fd_set readfds;
FD_ZERO(&readfds); /* 先全部清零 */
FD_SET(fd, &readfds); /* 想监测fd */
int nfds;
nfds = fd + 1;/* nfds 是最大的文件句柄+1, 注意: 不是文件个数, 这与poll不一样 */
/* 函数原型为:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
* 我们为了"read"而监测, 所以只需要提供readfds
*/
ret = select(nfds, &readfds, NULL, NULL, &tv);
if (FD_ISSET(fd, &readfds))/* 再次确认fd有数据 */
源码:
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <signal.h>
/*eg: ./01_get_input_info /dev/input/event0 */
int main(int argc,char **argv)
{
int fd;
int ret;
struct input_id id;
unsigned int evbit[2];
int len;
int i;
unsigned char byte;
int bit;
char *ev_names[] = {
"EV_SYN" ,
"EV_KEY" ,
"EV_REL" ,
"EV_ABS" ,
"EV_MSC" ,
"EV_SW" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"EV_LED" ,
"EV_SND" ,
"NULL" ,
"EV_REP" ,
"EV_FF" ,
"EV_PWR" ,
};
struct input_event event;
struct timeval tv; // 设置超时时间
fd_set readfds;
int nfds;/* nfds 是最大的文件句柄+1, 注意: 不是文件个数, 这与poll不一样 */
if ( argc != 2 )
{
printf("Usage :%s <dev>\n",argv[0]);
return -1;
}
fd = open(argv[1],O_RDWR | O_NONBLOCK);
if ( fd < 0)
{
printf("Open %s err!\n",argv[1]);
return -1;
}
ret = ioctl(fd,EVIOCGID,&id);
if ( ret == 0)
{
printf("bustype = 0x%x\n",id.bustype);
printf("vendor = 0x%x\n",id.vendor);
printf("product = 0x%x\n",id.product);
printf("version = 0x%x\n",id.version);
}
/*0表示读evbit,读的大小sizeof(evbit),&evbit表示读到evbit*/
len = ioctl(fd,EVIOCGBIT(0,sizeof(evbit)),&evbit);
if( len > 0 && len <= sizeof(evbit))
{
printf("Suppport ev type:");
for( i = 0; i < len;i++)
{
byte = ((unsigned char *)evbit)[i];
for (bit = 0;bit < 8;bit++)
{
if ( byte & (1<<bit))
{
printf("%s ",ev_names[i*8 + bit]);
}
}
}
printf("\n");
}
while(1)
{
/* 设置超时时间 */
tv.tv_sec = 5;/* seconds */
tv.tv_usec = 0;/* microseconds */
/* Watch stdin (fd 0) to see when it has input. */
/* 想监测哪些文件? */
FD_ZERO(&readfds); /* 先全部清零 */
FD_SET(fd, &readfds); /* 想监测fd */
/* 函数原型为:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
* 我们为了"read"而监测, 所以只需要提供readfds
*/
nfds = fd + 1;/* nfds 是最大的文件句柄+1, 注意: 不是文件个数, 这与poll不一样 */
ret = select(nfds, &readfds, NULL, NULL, &tv);
if ( ret > 0 )
{
if (FD_ISSET(fd, &readfds))/* 再次确认fd有数据 */
{
while( read(fd,&event,sizeof(event)) == sizeof(event) )
{
printf("get event:type = 0x%x, code = 0x%x, value = 0x%x\n",event.type,event.code,event.value);
}
}
}
else if(ret == 0)
{
printf("time out\n");
}
else
{
printf("select err\n");
}
}
return 0;
}
上机实验与poll方式一样
3).异步通知方式
所谓同步,就是“你慢我等你”。
那么异步就是:你慢那你就自己玩,我做自己的事去了,有情况再通知我。
所谓异步通知,就是APP可以忙自己的事,当驱动程序用数据时它会主动给APP发信号,这会导致APP执行信号处理函数。
仔细想想“发信号”,这只有3个字,却可以引发很多问题:
① 谁发:驱动程序发
② 发什么:信号
③ 发什么信号:SIGIO
④ 怎么发:内核里提供有函数
⑤ 发给谁:APP,APP要把自己告诉驱动
⑥ APP收到后做什么:执行信号处理函数
⑦ 信号处理函数和信号,之间怎么挂钩:APP注册信号处理函数
Linux系统中也有很多信号,在Linux内核源文件include\uapi\asm-generic\signal.h中,有很多信号的宏定义:
驱动程序通知APP时,它会发出“SIGIO”这个信号,表示有“IO事件”要处理。
就APP而言,你想处理SIGIO信息,那么需要提供信号处理函数,并且要跟SIGIO挂钩。这可以通过一个signal函数来“给某个信号注册处理函数”,用法如下:
除了注册SIGIO的处理函数,APP还要做什么事?想想这几个问题:
① 内核里有那么多驱动,你想让哪一个驱动给你发SIGIO信号?
APP要打开驱动程序的设备节点。
② 驱动程序怎么知道要发信号给你而不是别人?
APP要把自己的进程ID告诉驱动程序。
③ APP有时候想收到信号,有时候又不想收到信号:
应该可以把APP的意愿告诉驱动:设置Flag里面的FASYNC位为1,使能“异步通知”。
① 编写信号处理函数:
static void sig_func(int sig)
{
int val;
read(fd, &val, 4);
printf("get button : 0x%x\n", val);
}
② 注册信号处理函数:
signal(SIGIO, sig_func);
③ 打开驱动:
fd = open(argv[1],O_RDWR | O_NONBLOCK);
//异步通知方式使用非堵塞来打开,防止read后休眠而没有返回到while(1)中的程序执行
,也就是防止回不去原本任务
④ 把进程ID告诉驱动:
fcntl(fd, F_SETOWN, getpid());
⑤ 使能驱动的FASYNC功能:
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);
源码:
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <fcntl.h>
static int fd;
static struct input_event event;
//① 编写信号处理函数:
static void sig_func(int sig)
{
while( read(fd,&event,sizeof(event)) == sizeof(event) )
{
printf("get event:type = 0x%x, code = 0x%x, value = 0x%x\n",event.type,event.code,event.value);
}
}
/*eg: ./05_get_input_info_read_fasync /dev/input/event0 */
int main(int argc,char **argv)
{
int ret;
struct input_id id;
unsigned int evbit[2];
int len;
int i;
unsigned char byte;
int bit;
char *ev_names[] = {
"EV_SYN" ,
"EV_KEY" ,
"EV_REL" ,
"EV_ABS" ,
"EV_MSC" ,
"EV_SW" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"NULL" ,
"EV_LED" ,
"EV_SND" ,
"NULL" ,
"EV_REP" ,
"EV_FF" ,
"EV_PWR" ,
};
int count = 0;
int flags;
if ( argc != 2 )
{
printf("Usage :%s <dev>\n",argv[0]);
return -1;
}
//② 注册信号处理函数:
signal(SIGIO, sig_func);
//③ 打开驱动:
fd = open(argv[1],O_RDWR | O_NONBLOCK);
if ( fd < 0)
{
printf("Open %s err!\n",argv[1]);
return -1;
}
//④ 把进程ID告诉驱动:
fcntl(fd, F_SETOWN, getpid());
//⑤ 使能驱动的FASYNC功能:
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);
ret = ioctl(fd,EVIOCGID,&id);
if ( ret == 0)
{
printf("bustype = 0x%x\n",id.bustype);
printf("vendor = 0x%x\n",id.vendor);
printf("product = 0x%x\n",id.product);
printf("version = 0x%x\n",id.version);
}
/*0表示读evbit,读的大小sizeof(evbit),&evbit表示读到evbit*/
len = ioctl(fd,EVIOCGBIT(0,sizeof(evbit)),&evbit);
if( len > 0 && len <= sizeof(evbit))
{
printf("Suppport ev type:");
for( i = 0; i < len;i++)
{
byte = ((unsigned char *)evbit)[i];
for (bit = 0;bit < 8;bit++)
{
if ( byte & (1<<bit))
{
printf("%s ",ev_names[i*8 + bit]);
}
}
}
printf("\n");
}
while(1)
{
printf("count:%d\n",count++);
sleep(2);
}
return 0;
}
上机实验与poll方式实验方式一样