linux应用程序获取驱动数据
一、应用程序获取驱动数据的几种方式和他们的关系
Linux应用层的程序获取驱动层的数据有几种方式:1、查询 2、阻塞 3、非阻塞 4、异步通知。查询、阻塞、非阻塞都是应用层程序主动去获取驱动层的数据,异步通知是驱动主动告知应用程序。
多说一句,为什么应用程序主动去获取驱动程序要分查询、阻塞、非阻塞那么多种,这是多任务系统和驱动数据非实时性的性质决定的,一个应用程序,只是系统运行的其中一个任务,如果驱动程序一直没有数据返回,用查询死等的方式,就会导致这个应用程序的CPU占有率很高导致其他任务很卡。因此引入了阻塞的概念,就是数据还没准备好时,该应用程序进入休眠,把CPU资源让给别的任务,直到数据准备好。而非阻塞相比阻塞还多了一个定时唤醒的机制,允许休眠等待一段时间该应用程序能唤醒处理别的事情,不一定要一直挂在那里等数据。
这几种方式的关系简单描述如下:
应用程序主动获取数据 | 应用程序被动通知获取数据 | |
死等高CPU占有率 | 休眠唤醒低CPU占有率 | 4、异步通知 |
1、查询 | 不带定时唤醒 | 带定时唤醒 |
2、阻塞 | 3、非阻塞 |
二、获取数据的程序示例和机制
-
查询就是简单的读不做详细说明
-
阻塞
用到了休眠唤醒和等待队列的程序机制,下面是示例代码:
1、在驱动初始化函数初始化等待队列头
Wait_queue_head_t r_wait; //定义等待队列头
init_waitqueue_head(&r_wait); //初始化队列头
2、在驱动读取函数定义等待队列项并添加到队列头并进行任务切换
DECLARE_WAITQUEUE(wait,current); //定义等待队列项
add_wait_queue(&r_wait, &wait); //添加等待队列项到等待队列头
__set_current_state(TASK_INTERRUPTIBLE); // 设置任务状态
schedule(); //进行任务切换
__set_current_state(TASK_RUNNING); //设置任务为运行状态
remove_wait_queue(&r_wait, &wait); //移除等待队列项
3、在驱动函数中断里面调用唤醒队列函数
wake_up_interruptible(&r_wait); //唤醒进程
-
非阻塞
该方式就是轮询,linux下可以用poll、select处理轮询。应用程序通过select或poll函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。当应用程序调用select或poll函数的时候设备驱动程序中的poll函数就会执行,因此需要在设备驱动程序中编写poll函数。下面是他们的介绍:
函数 | 说明 |
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) | nfds:所要监视的这三类文件描述集合中,最大文件描述符加1。 readfds、writefds和exceptfds:这三个指针指向描述符集合,这三个参数指明了关心哪些描述符、需要满足哪些条件等等,这三个参数都是fd_set类型的。当我们定义好一个fd_set变量以后可以使用如下所示几个宏进行操作: void FD_ZERO(fd_set *set) void FD_SET(int fd, fd_set *set) void FD_CLR(int fd, fd_set *set) int FD_ISSET(int fd, fd_set *set) timeout:超时时间 返回值:0,表示的话就表示超时发生,但是没有任何文件描述符可以进行操作;-1,发生错误;其他值,可以进行操作的文件描述符个数。 |
int poll(struct pollfd *fds, nfds_t nfds, int timeout) | fds:要监视的文件描述符集合以及要监视的事件,为一个数组,数组元素都是结构体pollfd类型的,pollfd结构体如下所示: struct pollfd { int fd; short events; short revents; }; fd是要监视的文件描述符,如果fd无效的话那么events监视事件也就无效,并且revents返回0。events是要监视的事件,可监视的事件类型如下所示: POLLIN 有数据可以读取。 POLLPRI 有紧急的数据需要读取。 POLLOUT 可以写数据。 POLLERR 指定的文件描述符发生错误。 POLLHUP 指定的文件描述符挂起。 POLLNVAL 无效的请求。 POLLRDNORM 等同于POLLIN nfds:poll函数要监视的文件描述符数量。 imeout:超时时间,单位为ms。 |
Select应用程序示例代码:
void main(void)
{
int ret, fd; /* 要监视的文件描述符 */
fd_set readfds; /* 读操作文件描述符集 */
struct timeval timeout; /* 超时结构体 */
fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
FD_ZERO(&readfds); /* 清除readfds */
FD_SET(fd, &readfds); /* 将fd添加到readfds里面 */
/* 构造超时时间 */
timeout.tv_sec = 0;
timeout.tv_usec = 500000; /* 500ms */
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
switch (ret) {
case 0: /* 超时 */
printf("timeout!\r\n");
break;
case -1: /* 错误 */
printf("error!\r\n");
break;
default: /* 可以读取数据 */
if(FD_ISSET(fd, &readfds)) { /* 判断是否为fd文件描述符 */
/* 使用read函数读取数据 */
}
break;
}
}
Poll应用程序示例代码:
void main(void)
{
int ret;
int fd; /* 要监视的文件描述符 */
struct pollfd fds;
fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
/* 构造结构体 */
fds.fd = fd;
fds.events = POLLIN; /* 监视数据是否可以读取 */
ret = poll(&fds, 1, 500); /* 轮询文件是否可操作,超时500ms */
if (ret) { /* 数据有效 */
......
/* 读取数据 */
......
} else if (ret == 0) { /* 超时 */
......
} else if (ret < 0) { /* 错误 */
......
}
}
Linux驱动下的poll操作函数
函数 | 说明 |
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait) | filp:要打开的设备文件(文件描述符)。 wait:结构体poll_table_struct类型指针,由应用程序传递进来的。一般将此参数传递给poll_wait函数。 返回值:向应用程序返回设备或者资源状态,可以返回的资源状态如下: POLLIN 有数据可以读取。 POLLPRI 有紧急的数据需要读取。 POLLOUT 可以写数据。 POLLERR 指定的文件描述符发生错误。 POLLHUP 指定的文件描述符挂起。 POLLNVAL 无效的请求。 POLLRDNORM 等同于POLLIN,普通数据可读 |
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p) | 我们需要在驱动程序的poll函数中调用poll_wait函数,poll_wait函数不会引起阻塞,只是将应用程序添加到poll_table。参数wait_address是要添加到poll_table中的等待队列头,参数p就是poll_table,就是file_operations中poll函数的wait参数。 |
驱动示例代码:
3.1、在驱动读取函数加入非阻塞读取分支
if (filp->f_flags & O_NONBLOCK)
3.2、添加设备操作函数
static struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.open = xxx_open,
.read = xxx_read,
.poll = xxx_poll,
};
3.3、编写驱动poll函数
unsigned int xxx_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(filp, &r_wait, wait);
if(atomic_read(xxx)) { /* 按键按下 */
mask = POLLIN | POLLRDNORM; /* 返回PLLIN */
}
return mask;
}
-
异步通知
异步通知的核心是信号,信号类似于硬件的中断的机制。
信号类型 |
#define SIGHUP 1 |
#define SIGINT 2 |
#define SIGQUIT 3 |
#define SIGILL 4 |
#define SIGTRAP 5 |
#define SIGABRT 6 |
#define SIGIOT 6 |
#define SIGBUS 7 |
#define SIGFPE 8 |
#define SIGKILL 9 |
#define SIGUSR1 10 |
#define SIGSEGV 11 |
#define SIGUSR2 12 |
#define SIGPIPE 13 |
#define SIGALRM 14 |
#define SIGTERM 15 |
#define SIGSTKFLT 16 |
#define SIGCHLD 17 |
#define SIGCONT 18 |
#define SIGSTOP 19 |
#define SIGTSTP 20 |
#define SIGTTIN 21 |
#define SIGTTOU 22 |
#define SIGURG 23 |
#define SIGXCPU 24 |
#define SIGXFSZ 25 |
#define SIGVTALRM 26 |
#define SIGPROF 27 |
#define SIGWINCH 28 |
#define SIGIO 29 |
#define SIGPOLL SIGIO |
#define SIGPWR 30 |
#define SIGSYS 31 |
#define SIGUNUSED 31 |
我们使用中断的时候需要设置中断处理函数,同样的,如果要在应用程序中使用信号,那么就必须设置信号所使用的信号处理函数,在应用程序中使用signal函数来设置指定信号的处理函数。
函数 | 说明 |
sighandler_t signal(int signum, sighandler_t handler) | signum:要设置处理函数的信号。 handler:信号的处理函数。 返回值:设置成功的话返回信号的前一个处理函数,设置失败的话返回SIG_ERR。 |
typedef void (*sighandler_t)(int) | 信号处理函数原型 |
示例代码:
#include "stdlib.h"
#include "stdio.h"
#include "signal.h"
void sigint_handler(int num)
{
printf("xxx");
exit(0);
}
int main(void)
{
signal(SIGINT, sigint_handler);
while(1);
return 0;
}
驱动中的信号处理
1、定义一个fasync_struct结构体
struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next;
struct file *fa_file;
struct rcu_head fa_rcu;
};
2、在file_operations操作集中实现fasync函数
int (*fasync) (int fd, struct file *filp, int on)
fasync函数里面一般通过调用fasync_helper函数来初始化前面定义的fasync_struct结构体指针,fasync_helper函数原型如下:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
fasync_helper函数的前三个参数就是fasync函数的那三个参数,第四个参数就是要初始化的fasync_struct结构体指针变量。
驱动中的fasync函数示例:
struct xxx_dev {
......
struct fasync_struct *async_queue; /* 异步相关结构体 */
};
static int xxx_fasync(int fd, struct file *filp, int on)
{
struct xxx_dev *dev = (xxx_dev)filp->private_data;
if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
return -EIO;
return 0;
}
//在关闭驱动文件的时候需要在file_operations操作集中的release函数中释放fasync_struct,
//fasync_struct的释放函数同样为fasync_helper,release函数参数参考实例如下:
static int xxx_release(struct inode *inode, struct file *filp)
{
return xxx_fasync(-1, filp, 0); /* 删除异步通知 */
}
static struct file_operations xxx_ops = {
......
.fasync = xxx_fasync,
.release = xxx_release,
......
};
3、通过kill_fasync发送信号
函数 | 说明 |
void kill_fasync(struct fasync_struct **fp, int sig, int band) | fp:要操作的fasync_struct。 sig:要发送的信号。 band:可读时设置为POLL_IN,可写时设置为POLL_OUT。 返回值:无。 |
应用程序的信号处理
1、注册信号处理函数
2、将本应用程序的进程号告诉给内核
使用fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。
3、开启异步通知
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);
重点就是通过fcntl函数设置进程状态为FASYNC,经过这一步,驱动程序中的fasync函数就会执行。
示例代码:
static void xxx_signal_func(int signum)
{
......
}
int main(int argc, char *argv[])
{
......
fd = open(filename, O_RDWR);
/* 设置信号SIGIO的处理函数 */
signal(SIGIO, sigio_signal_func);
fcntl(fd, F_SETOWN, getpid()); /* 将当前进程的进程号告诉给内核 */
flags = fcntl(fd, F_GETFD); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC);/* 设置进程启用异步通知功能 */
......
}