上一章讲到怎么申请和触发一次中断并且和应用层进行数据的交互。但是我们看到这个中断就跟假的中断一样。
申请中断,并设置为双边沿的触发方式。触发了之后我们的中断处理程序就会执行,把值放到我们的数据缓冲区。 那我应用层并不知道你什么按啊,我得一直去读你的数据缓冲区的值才能 读到的传过来的数据啊。这就很不好,不仅仅让应用程序什么都做不了,而且还一直占着CPU的资源。
那么为了处理这种事件我们在驱动中引入了文件IO模型。 文件IO模型有四种: 1)阻塞 2)非阻塞 3)多路复用 4)异步信号
一、阻塞模型 阻塞模型的概念:就是假设应用程序在通过文件操作结构体的读数据的时候我数据缓冲区没有数据,我就不一直读了,我就进行休眠。进入中止态。并将(READ这个进程)放入到一个等待队列里面阻塞,等待唤醒信号。 当我们触发一次中断之后,我们在中断处理程序里面加入唤醒程序。这样我们就可以实现阻塞模型,并大量减小CPU的损耗。 1)实现步骤。 1,等待队列头 wait_queue_head_t 并初始化等待队列头:init_waitqueue_head(wait_queue_head_t *q); 2,在需要等待(没有数据)的时候,进行休眠 wait_event_interruptible(wait_queue_head_t wq, condition) // 内部会构建一个等待队列项/节点wait_queue_t (内部会实现三个步骤1,将当前进程加入到等待队列头中 将当前进程状态设置成TASK_INTERRUPTIBLE set_current_state(TASK_INTERRUPTIBLE) 让出调度–休眠schedule(void)) 参数1: 等待队列头 参数2: 条件,如果是为假,就会等待,如果为真,就不会等待 可以用一标志位,来表示是否有数据 3,在一个合适的时候(有数据),会将进程唤醒 wake_up_interruptible(wait_queue_head_t *q 用法: wake_up_interruptible(&key_dev->wq_head); //同时设置标志位 key_dev->key_state = 1;
二、非阻塞模型(NONBLOCK) 非阻塞模型的概念:非阻塞模型就是我应用层要读的时候我就读,如果没有数据我就走,并且返回一个EAGEN,让用户判读是否哟啊再读一次。,如果有数据我就拿了数据就走。 实现: 1、在应用层程序open 状态设置成非阻塞模式,如果不设置默认是阻塞模式(open函数,的权限后面还可以加上模式的选择)。 open("/dev/key0", O_RDWR|O_NONBLOCK); 2、在驱动程序中去判断,当前的模式 ,如果当前是非阻塞模式,并且没有数据,立马返回一个出错码 if(filp->f_flags & O_NONBLOCK && !key_dev->key_state) return -EAGAIN; filp->f_flags & O_NONBLOCK :这句的意思是文件流指针中的F_FLAGS位中如果==O_NONBLOCK 那么这个等式就不为0。 !key_dev->key_state:如果KEY的状态位等于0(按键被按下)那么再取反后就是等式成立。 两个语句相&&,表示如果flag是非阻塞模式,并且状态位没有数据我就直接返回,一个-EAGEN给用户。 如果不是就继续执行下面的程序。 这两段代码连接有一些精巧之处。 第一句:如果为非阻塞且没有数据那么就直接返回。 如果为非阻塞但是有数据,那么IF语句不成立(是非阻塞状态)。则继续执行下面的语句。但是有数据的时候阻塞和我非阻塞状态都是一样的。 如果不为非阻塞状态那么直接执行阻塞状态下的程序。 也就是说,不管你要达到什么状态。这两条语句都可以顺利执行并达到模拟想要的状态。
三、多路复用 select/poll/epoll 多路复用: 用于当你在单进程单线程中(就是一个程序且不创建线程)中需要处理很多来自不同信号源的的信息的时候,而且没有优先级。而且你还不知道信号什么时候来。这时候就可以采用多路复用。相当于我用了一个闹铃。当我哪一个闹铃响了我就去干哪一件事。在这里就是监控我们的一些设备,当那个设备有信号的时候我就去处理涉笔传过来的信号。 我们采用POLL这个函数:但是多路复用有三个函数SELECT 和 EPOLL要区分这三个函数有什么区别。 步骤: 1、在应用程序中打开要交互信号的一些设备,并产生相应的文件描述符 2、用POLL来监控这些文件 int poll(struct pollfd *fds, nfds_t nfds, int timeout); 参数1: 表示多个文件描述符集合 struct pollfd { 描述的是文件描述符的结构体 int fd; //文件描述符 short events; //希望监控fd的什么事件:读(POLLIN),写(POLLOUT),出错(POLLERR), short revents; //结果描述,表示当前的fd是否有读(POLLIN),写(POLLOUT),出错(POLLERR), //用于判断,是内核自动赋值 }; 参数2:被监控到fd的个数 参数3: 监控的时间: 正: 表示监控多少ms 负数: 无限的时间去监控 0: 等待0ms,类似于非阻赛 返回值: 负数:出错 大于0,表示fd中有数据 等于0: 时间到超时 3、利用 short revents来判断是否产生了这些操作(信号是否发生)。 4、在驱动程序中,添加POLL的文件操作结构体 5、实现poll对应的函数 // 调用poll_wait,将当前到等待队列注册系统中 poll_wait(filp, &key_dev->wq_head, pts); // 调用poll_wait,将当前到等待队列注册系统中 if(!key_dev->key_state) mask = 0; // 1,当没有数据到时候返回一个0 if(key_dev->key_state) // 2,有数据返回一个POLLIN mask |= POLLIN ——————————————————应用层代码
#define KEY_ENTER 28
struct key_event{
int code;
int value;
};
static char in_buf[64];
int main(int argc , char *argv[]){
int fd0;
int ret;
struct key_event event;
struct pollfd pfd[2];
fd0 = open("/dev/key0", O_RDWR);
if(fd0 < 0){
perror("open /dev/dey0");
exit(0);
}
pfd[0].fd = fd0;
pfd[0].events = POLLIN;
pfd[1].fd = 0;
pfd[1].events = POLLIN;
poll(pfd, 2,-1);
while(1){
ret = poll(pfd, 2,-1);
if(ret > 0){
read(pfd[0].fd ,&event,sizeof(struct key_event));
if(pfd[0].revents & POLLIN ){
if(event.code == "KEY_ENTER"){
if(event.value){
printf("key3 is purss");
}else{
printf("key3 is down");}
}
}
if(pfd[0].revents & POLLIN){
fgets(in_buf, 64, stdin);
printf("in_buf = %s\n",in_buf);}
}else{
perror("poll");
exit(0);
}
}
——————————————————————驱动部分代码
unsigned int key_drv_poll (struct file *filp , struct poll_table_struct *pts){
unsigned int mask;
poll_wait(filp, &key_dev->wq_head, pts);
if(!key_dev->key_state)
mask = 0;
if(key_dev->key_state)
mask |= POLLIN;
return mask;
}
———————————————————————————————————————————————————— 四、异步通信 异步通信就是我应用程序正常的的运行,但你什么时候要发数据给我的时候,你就给我一个信号,我去处理你给我的信号。前面都是应用程序主动的等待数据的到来,而异步通信则是类似于信号中断的概念。 设置异步信号的步骤: 1,设置信号处理方法 signal(SIGIO,signale_headle); 参数一:SIGIO 这里用作信号传送 参数二:信号的处理方法(函数) 当SIGIO有信号的时候,就会执行signale_headle这个处理函数 2,将当前进程设置成SIGIO的属主进程 fcntl(fd, F_SETOWN, getpid()); 参数一,当前打开的文件描述符 参数二:设置OWn就是设置自己 参数三:获取当前进程PID号。 将当前进程设置成SIGIO的属主进程:这样SIGNO的信号就会发到这个进程中来,要不然乱发的去 3,将io模式设置成异步模式 int flags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, flags | FASYNC ); 参数一:打开的文件描述符,就是驱动设备 参数二:设置标志 参数三要设置的标志。 这样就完成了异步通信的设置。再完成中断处理函数,让信号来的时候,你要做就做什么。
驱动程序 b,驱动–发送信号 1,需要和进程进行关联–记录信号该发送给谁 实现一个fasync的接口和READ,OPEN等文件操作一样这是一个文件操作接口。操作它就可以实现异步通知 int key_drv_fasync(int fd, struct file *filp, int on) { //只需要调用一个函数记录信号该发送给谁 return fasync_helper(fd, filp, on, &key_dev->faysnc); } 2,在某个特定的时候去发送信号,在有数据的时候 //发送信号 kill_fasync(&key_dev->faysnc, SIGIO, POLLIN); —————————————应用程序—————————————————
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
struct key_event{
int code;
int value;
};
#define KEY_ENTER 28
static int fd;
static struct key_event event;
void catch_signale(int signo)
{
if(signo == SIGIO)
{
printf("we got sigal SIGIO\n");
read(fd, &event, sizeof(struct key_event));
if(event.code == KEY_ENTER)
{
if(event.value)
{
printf("APP__ key enter pressed\n");
}else
{
printf("APP__ key enter up\n");
}
}
}
}
int main(int argc, char *argv[])
{
int ret;
fd = open("/dev/key0", O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}
signal(SIGIO,catch_signale);
fcntl(fd, F_SETOWN, getpid());
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC );
while(1)
{
printf("I am waiting......\n");
sleep(1);
}
close(fd);
return 0;
}
—————————————————驱动程序—————————————————————
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#define GPXCON_REG 0x11000C20
#define KEY_ENTER 28
struct key_event{
int code;
int value;
};
struct key_desc{
unsigned int dev_major;
struct class *cls;
struct device *dev;
int irqno;
void *reg_base;
struct key_event event;
wait_queue_head_t wq_head;
int key_state;
struct fasync_struct *faysnc;
};
struct key_desc *key_dev;
irqreturn_t key_irq_handler(int irqno, void *devid)
{
printk("-------%s-------------\n", __FUNCTION__);
int value = readl(key_dev->reg_base + 4) & (1<<2);
if(value){
printk("key3 up\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 0;
}else{
printk("key3 pressed\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 1;
}
wake_up_interruptible(&key_dev->wq_head);
key_dev->key_state = 1;
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
return IRQ_HANDLED;
}
int get_irqno_from_node(void)
{
struct device_node *np = of_find_node_by_path("/key_int_node");
if(np){
printk("find node ok\n");
}else{
printk("find node failed\n");
}
int irqno = irq_of_parse_and_map(np, 0);
printk("irqno = %d\n", irqno);
return irqno;
}
int key_drv_open(struct inode *inode, struct file *filp)
{
printk("-------%s-------------\n", __FUNCTION__);
return 0;
}
ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
if(filp->f_flags & O_NONBLOCK && !key_dev->key_state)
return -EAGAIN;
int ret;
wait_event_interruptible(key_dev->wq_head, key_dev->key_state);
ret = copy_to_user(buf, &key_dev->event, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
memset(&key_dev->event, 0, sizeof(key_dev->event));
key_dev->key_state = 0;
return count;
}
ssize_t key_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
printk("-------%s-------------\n", __FUNCTION__);
return 0;
}
int key_drv_close (struct inode *inode, struct file *filp)
{
printk("-------%s-------------\n", __FUNCTION__);
return 0;
}
unsigned int key_drv_poll(struct file *filp, struct poll_table_struct *pts)
{
unsigned int mask;
poll_wait(filp, &key_dev->wq_head, pts);
if(!key_dev->key_state)
mask = 0;
if(key_dev->key_state)
mask |= POLLIN;
return mask;
}int key_drv_fasync(int fd, struct file *filp, int on)
{
return fasync_helper(fd, filp, on, &key_dev->faysnc);
}
const struct file_operations key_fops = {
.open = key_drv_open,
.read = key_drv_read,
.write = key_drv_write,
.release = key_drv_close,
.poll = key_drv_poll,
.fasync = key_drv_fasync,
};
static int __init key_drv_init(void)
{
int ret;
key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL,
MKDEV(key_dev->dev_major,0), NULL, "key0");
key_dev->irqno = get_irqno_from_node();
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
"key3_eint10", NULL);
if(ret != 0)
{
printk("request_irq error\n");
return ret;
}
key_dev->reg_base = ioremap(GPXCON_REG, 8);
init_waitqueue_head(&key_dev->wq_head);
return 0;
}
static void __exit key_drv_exit(void)
{
iounmap(key_dev->reg_base);
free_irq(key_dev->irqno, NULL);
device_destroy(key_dev->cls, MKDEV(key_dev->dev_major,0));
class_destroy(key_dev->cls);
unregister_chrdev(key_dev->dev_major, "key_drv");
kfree(key_dev);
}
module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");
————————————————————————————————————————————— 这个驱动程序实现了多种IO模型的接口, 阻塞:在read接口中有 wait_event_interruptible :应用程序如果不设置非阻塞就是阻塞模式。 非阻塞:在READ接口中有:判断fp的flag,如果为阻塞模式就执行非阻塞模式 多路复用:应用层调用了poll接口之后,就可以实现多路复用 异步通信:应用层调用了fasync接口之后,就可以实现多路复用 ———————————————————————————————————————————— 四种IO模型的总结:什么时候用
————————————————————————————————————————end
|