linux输入设备在应用层的多种读取方式

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方式实验方式一样

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值