天天看点

linux应用程序获取驱动数据一、应用程序获取驱动数据的几种方式和他们的关系二、获取数据的程序示例和机制

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);/* 设置进程启用异步通知功能 */ 
......

}