大淘客网站怎么做,wordpress 体育,哪个学校有网站建设,wordpress关闭搜索在前面学习网络编程时#xff0c;曾经学过I/O模型 Linux 系统应用编程——网络编程#xff08;I/O模型#xff09;#xff0c;下面学习一下I/O模型在设备驱动中的应用。 回顾一下在Unix/Linux下共有五种I/O模型#xff0c;分别是#xff1a; a -- 阻塞I/O b -- 非阻塞I/O… 在前面学习网络编程时曾经学过I/O模型 Linux 系统应用编程——网络编程I/O模型下面学习一下I/O模型在设备驱动中的应用。 回顾一下在Unix/Linux下共有五种I/O模型分别是 a -- 阻塞I/O b -- 非阻塞I/O c -- I/O复用select和poll d -- 信号驱动I/OSIGIO e -- 异步I/OPosix.1的aio_系列函数 下面我们先学习阻塞I/O、非阻塞I/O 、I/O复用select和poll先学习一下基础概念 a -- 阻塞 阻塞操作是指在执行设备操作时若不能获得资源则挂起进程知道满足可操作的条件后再进行操作被挂起的进程进入休眠状态放弃CPU被从调度器的运行队列移走直到等待的条件被满足 b -- 非阻塞 非阻塞的进程在不能进行设备操作时并不挂起继续占用CPU它或者放弃或者不停地查询直到可以操作为止 二者的区别可以看应用程序的调用是否立即返回 驱动程序通常需要提供这样的能力当应用程序进行 read()、write() 等系统调用时若设备的资源不能获取而用户又希望以阻塞的方式访问设备驱动程序应在设备驱动的xxx_read()、xxx_write() 等操作中将进程阻塞直到资源可以获取此后应用程序的 read()、write() 才返回整个过程仍然进行了正确的设备 访问用户并没感知到若用户以非阻塞的方式访问设备文件则当设备资源不可获取时设备驱动的 xxx_read()、xxx_write() 等操作立刻返回 read()、write() 等系统调用也随即被返回。 因为阻塞的进程会进入休眠状态因此必须确保有一个地方能够唤醒休眠的进程否则进程就真的挂了。唤醒进程的地方最大可能发生在中断里面因为硬件资源获得的同时往往伴随着一个中断。 阻塞I/O通常由等待队列来实现而非阻塞I/O由轮询来实现。 一、阻塞I/O实现 —— 等待队列 1、基础概念 在Linux 驱动程序中可以使用等待队列wait queue来实现阻塞进程的唤醒。wait queue 很早就作为一个基本的功能单位出现在Linux 内核里了它以队列为基础数据结构与进程调度机制紧密结合能够实现内核中的异步事件通知机制。等待队列可以用来同步对系统资源的访问上一篇文章所述的信号量在内核中也依赖等待队列来实现。 在Linux内核中使用等待队列的过程很简单首先定义一个wait_queue_head然后如果一个task想等待某种事件那么调用wait_event等待队列事件就可以了。 等待队列应用广泛但是内核实现却十分简单。其涉及到两个比较重要的数据结构__wait_queue_head该结构描述了等待队列的链头其包含一个链表和一个原子锁结构定义如下 struct __wait_queue_head
{spinlock_t lock; /* 保护等待队列的原子锁 */struct list_head task_list; /* 等待队列 */
};typedef struct __wait_queue_head wait_queue_head_t;__wait_queue该结构是对一个等待任务的抽象。每个等待任务都会抽象成一个wait_queue并且挂载到wait_queue_head上。该结构定义如下 struct __wait_queue
{unsigned int flags;void *private; /* 通常指向当前任务控制块 */ /* 任务唤醒操作方法该方法在内核中提供通常为autoremove_wake_function */wait_queue_func_t func; struct list_head task_list; /* 挂入wait_queue_head的挂载点 */
};Linux中等待队列的实现思想如下图所示当一个任务需要在某个wait_queue_head上睡眠时将自己的进程控制块信息封装到wait_queue中然后挂载到wait_queue的链表中执行调度睡眠。当某些事件发生后另一个任务进程会唤醒wait_queue_head上的某个或者所有任务唤醒工作也就是将等待队列中的任务设置为可调度的状态并且从队列中删除。 使用等待队列时首先需要定义一个wait_queue_head这可以通过DECLARE_WAIT_QUEUE_HEAD宏来完成这是静态定义的方法。该宏会定义一个wait_queue_head并且初始化结构中的锁以及等待队列。当然动态初始化的方法也很简单初始化一下锁及队列就可以了。 一个任务需要等待某一事件的发生时通常调用wait_event该函数会定义一个wait_queue描述等待任务并且用当前的进程描述块初始化wait_queue然后将wait_queue加入到wait_queue_head中。 函数实现流程说明如下 a -- 用当前的进程描述块PCB初始化一个wait_queue描述的等待任务。 b -- 在等待队列锁资源的保护下将等待任务加入等待队列。 c -- 判断等待条件是否满足如果满足那么将等待任务从队列中移出退出函数。 d -- 如果条件不满足那么任务调度将CPU资源交与其它任务。 e -- 当睡眠任务被唤醒之后需要重复b、c 步骤如果确认条件满足退出等待事件函数。 2、等待队列接口函数 1、定义并初始化 /* 定义“等待队列头” */ wait_queue_head_t my_queue; /* 初始化“等待队列头”*/
init_waitqueue_head(my_queue); 直接定义并初始化。init_waitqueue_head()函数会将自旋锁初始化为未锁等待队列初始化为空的双向循环链表。DECLARE_WAIT_QUEUE_HEAD(my_queue); 定义并初始化可以作为定义并初始化等待队列头的快捷方式。 2、定义等待队列 DECLARE_WAITQUEUE(name,tsk);定义并初始化一个名为name的等待队列。 3、(从等待队列头中)添加移出等待队列 /* add_wait_queue()函数,设置等待的进程为非互斥进程并将其添加进等待队列头(q)的队头中*/
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
/* 该函数也和add_wait_queue()函数功能基本一样只不过它是将等待的进程(wait)设置为互斥进程。*/
void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);4、等待事件 (1)wait_event()宏 [cpp] view plaincopy /** * wait_event - sleep until a condition gets true * wq: the waitqueue to wait on * condition: a C expression for the event to wait for * * The process is put to sleep (TASK_UNINTERRUPTIBLE) until the * condition evaluates to true. The condition is checked each time * the waitqueue wq is woken up. * * wake_up() has to be called after changing any variable that could * change the result of the wait condition. */ #define wait_event(wq, condition) \ do { \ if (condition) \ break; \ __wait_event(wq, condition); \ } while (0) 在等待会列中睡眠直到condition为真。在等待的期间进程会被置为TASK_UNINTERRUPTIBLE进入睡眠直到condition变量变为真。每次进程被唤醒的时候都会检查condition的值. (2)wait_event_interruptible()函数: 和wait_event()的区别是调用该宏在等待的过程中当前进程会被设置为TASK_INTERRUPTIBLE状态.在每次被唤醒的时候,首先检查condition是否为真,如果为真则返回,否则检查如果进程是被信号唤醒,会返回-ERESTARTSYS错误码.如果是condition为真,则返回0. (3)wait_event_timeout()宏: 也与wait_event()类似.不过如果所给的睡眠时间为负数则立即返回.如果在睡眠期间被唤醒,且condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回0 (4)wait_event_interruptible_timeout()宏: 与wait_event_timeout()类似,不过如果在睡眠期间被信号打断则返回ERESTARTSYS错误码. (5) wait_event_interruptible_exclusive()宏 同样和wait_event_interruptible()一样,不过该睡眠的进程是一个互斥进程. 5、唤醒队列 (1)wake_up()函数 [cpp] view plaincopy #define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL) /** * __wake_up - wake up threads blocked on a waitqueue. * q: the waitqueue * mode: which threads * nr_exclusive: how many wake-one or wake-many threads to wake up * key: is directly passed to the wakeup function */ void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, void *key) { unsigned long flags; spin_lock_irqsave(q-lock, flags); __wake_up_common(q, mode, nr_exclusive, 0, key); spin_unlock_irqrestore(q-lock, flags); } EXPORT_SYMBOL(__wake_up); 唤醒等待队列.可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERUPTIBLE状态的进程,和wait_event/wait_event_timeout成对使用.
(2)wake_up_interruptible()函数:#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)和wake_up()唯一的区别是它只能唤醒TASK_INTERRUPTIBLE状态的进程.,与wait_event_interruptible/wait_event_interruptible_timeout/ wait_event_interruptible_exclusive成对使用。 下面看一个实例 [cpp] view plaincopy static ssize_t hello_read(struct file *filep, char __user *buf, size_t len, loff_t *pos) { /* 实现应用进程read的时候如果没有数据就阻塞 */ if(len64) { len 64; } wait_event_interruptible(wq, have_data 1); if(copy_to_user(buf,temp,len)) { return -EFAULT; } have_data 0; return len; } static ssize_t hello_write(struct file *filep, const char __user *buf, size_t len, loff_t *pos) { if(len 64) { len 64; } if(copy_from_user(temp,buf,len)) { return -EFAULT; } printk(write %s\n,temp); have_data 1; wake_up_interruptible(wq); return len; } 注意两个概念 a -- 疯狂兽群 wake_up的时候所有阻塞在队列的进程都会被唤醒但是因为condition的限制只有一个进程得到资源其他进程又会再次休眠如果数量很大称为 疯狂兽群。 b -- 独占等待 等待队列的入口设置一个WQ_FLAG_EXCLUSIVE标志就会添加到等待队列的尾部没有设置设置的添加到头部wake up的时候遇到第一个具有WQ_FLAG_EXCLUSIVE这个标志的进程就停止唤醒其他进程。 二、非阻塞I/O实现方式 —— 多路复用 1、轮询的概念和作用 在用户程序中select() 和 poll() 也是设备阻塞和非阻塞访问息息相关的论题。使用非阻塞I/O的应用程序通常会使用select() 和 poll() 系统调用查询是否可对设备进行无阻塞的访问。select() 和 poll() 系统调用最终会引发设备驱动中的 poll()函数被执行。 2、应用程序中的轮询编程 在用户程序中select()和poll()本质上是一样的 不同只是引入的方式不同前者是在BSD UNIX中引入的后者是在System V中引入的。用的比较广泛的是select系统调用。原型如下 int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptionfds, struct timeval *timeout);其中readfswritefdsexceptfds分别是select()监视的读写和异常处理的文件描述符集合numfds的值是需要检查的号码最高的文件描述符加1timeout则是一个时间上限值超过该值后即使仍没有描述符准备好也会返回。 struct timeval
{int tv_sec; //秒int tv_usec; //微秒
} 涉及到文件描述符集合的操作主要有以下几种:
1)清除一个文件描述符集 FD_ZERO(fd_set *set);2)将一个文件描述符加入文件描述符集中 FD_SET(int fd,fd_set *set); 3)将一个文件描述符从文件描述符集中清除 FD_CLR(int fd,fd_set *set); 4)判断文件描述符是否被置位 FD_ISSET(int fd,fd_set *set); 最后我们利用上面的文件描述符集的相关来写个验证添加了设备轮询的驱动把上边两块联系起来 3、设备驱动中的轮询编程 设备驱动中的poll() 函数原型如下 unsigned int(*poll)(struct file *filp, struct poll_table * wait);第一个参数是file结构体指针第二个参数是轮询表指针poll设备方法完成两件事 a -- 对可能引起设备文件状态变化的等待队列调用poll_wait()函数将对应的等待队列头添加到poll_table如果没有文件描述符可用来执行 I/O, 则内核使进程在传递到该系统调用的所有文件描述符对应的等待队列上等待。 b -- 返回表示是否能对设备进行无阻塞读、写访问的掩码。 位掩码POLLRDNORM POLLINPOLLOUTPOLLWRNORM 设备可读通常返回(POLLIN | POLLRDNORM) 设备可写通常返回(POLLOUT | POLLWRNORM) poll_wait()函数用于向 poll_table注册等待队列 void poll_wait(struct file *filp, wait_queue_head_t *queue,poll_table *wait) poll_wait()函数不会引起阻塞它所做的工作是把当前进程添加到wait 参数指定的等待列表poll_table中。 真正的阻塞动作是上层的select/poll函数中完成的。select/poll会在一个循环中对每个需要监听的设备调用它们自己的poll支持函数以使得当前进程被加入各个设备的等待列表。若当前没有任何被监听的设备就绪则内核进行调度调用schedule让出cpu进入阻塞状态schedule返回时将再次循环检测是否有操作可以进行如此反复否则若有任意一个设备就绪select/poll都立即返回。 具体过程如下 a -- 用户程序第一次调用select或者poll驱动调用poll_wait并使两条队列都加入poll_table结构中作为下次调用驱动函数poll的条件一个mask返回值指示设备是否可操作0为未准备状态如果文件描述符未准备好可读或可写用户进程被会加入到写或读等待队列中进入睡眠状态。 b -- 当驱动执行了某些操作例如写缓冲或读缓冲写缓冲使读队列被唤醒读缓冲使写队列被唤醒于是select或者poll系统调用在将要返回给用户进程时再次调用驱动函数poll驱动依然调用poll_wait 并使两条队列都加入poll_table结构中并判断可写或可读条件是否满足如果mask返回POLLIN | POLLRDNORM或POLLOUT | POLLWRNORM则指示可读或可写这时select或poll真正返回给用户进程如果mask还是返回0则系统调用select或poll继续不返回 下面是一个典型模板 [cpp] view plaincopy static unsigned int XXX_poll(struct file *filp, poll_table *wait) { unsigned int mask 0; struct XXX_dev *dev filp-private_data; //获得设备结构指针 ... poll_wait(filp, dev-r_wait, wait); //加读等待对列头 poll_wait(filp ,dev-w_wait, wait); //加写等待队列头 if(...)//可读 mask | POLLIN | POLLRDNORM; //标识数据可获得 if(...)//可写 mask | POLLOUT | POLLWRNORM; //标识数据可写入 .. return mask; } 4、调用过程 Linux下select调用的过程: 1、用户层应用程序调用select(),底层调用poll()) 2、核心层调用sys_select() ------ do_select() 最终调用文件描述符fd对应的struct file类型变量的struct file_operations *f_op的poll函数。 poll指向的函数返回当前可否读写的信息。 1)如果当前可读写返回读写信息。 2)如果当前不可读写则阻塞进程并等待驱动程序唤醒重新调用poll函数或超时返回。 3、驱动需要实现poll函数 当驱动发现有数据可以读写时通知核心层核心层重新调用poll指向的函数查询信息。 poll_wait(filp,wait_q,wait) // 此处将当前进程加入到等待队列中但并不阻塞 在中断中使用wake_up_interruptible(wait_q)唤醒等待队列。4、实例分析1、memdev.h /*mem设备描述结构体*/
struct mem_dev
{ char *data; unsigned long size; wait_queue_head_t inq;
};#endif /* _MEMDEV_H_ */ 2、驱动程序 memdev.c [cpp] view plaincopy #include linux/module.h #include linux/types.h #include linux/fs.h #include linux/errno.h #include linux/mm.h #include linux/sched.h #include linux/init.h #include linux/cdev.h #include asm/io.h #include asm/system.h #include asm/uaccess.h #include linux/poll.h #include memdev.h static mem_major MEMDEV_MAJOR; bool have_data false; /*表明设备有足够数据可供读*/ module_param(mem_major, int, S_IRUGO); struct mem_dev *mem_devp; /*设备结构体指针*/ struct cdev cdev; /*文件打开函数*/ int mem_open(struct inode *inode, struct file *filp) { struct mem_dev *dev; /*获取次设备号*/ int num MINOR(inode-i_rdev); if (num MEMDEV_NR_DEVS) return -ENODEV; dev mem_devp[num]; /*将设备描述结构指针赋值给文件私有数据指针*/ filp-private_data dev; return 0; } /*文件释放函数*/ int mem_release(struct inode *inode, struct file *filp) { return 0; } /*读函数*/ static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) { unsigned long p *ppos; unsigned int count size; int ret 0; struct mem_dev *dev filp-private_data; /*获得设备结构体指针*/ /*判断读位置是否有效*/ if (p MEMDEV_SIZE) return 0; if (count MEMDEV_SIZE - p) count MEMDEV_SIZE - p; while (!have_data) /* 没有数据可读考虑为什么不用if而用while */ { if (filp-f_flags O_NONBLOCK) return -EAGAIN; wait_event_interruptible(dev-inq,have_data); } /*读数据到用户空间*/ if (copy_to_user(buf, (void*)(dev-data p), count)) { ret - EFAULT; } else { *ppos count; ret count; printk(KERN_INFO read %d bytes(s) from %d\n, count, p); } have_data false; /* 表明不再有数据可读 */ /* 唤醒写进程 */ return ret; } /*写函数*/ static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos) { unsigned long p *ppos; unsigned int count size; int ret 0; struct mem_dev *dev filp-private_data; /*获得设备结构体指针*/ /*分析和获取有效的写长度*/ if (p MEMDEV_SIZE) return 0; if (count MEMDEV_SIZE - p) count MEMDEV_SIZE - p; /*从用户空间写入数据*/ if (copy_from_user(dev-data p, buf, count)) ret - EFAULT; else { *ppos count; ret count; printk(KERN_INFO written %d bytes(s) from %d\n, count, p); } have_data true; /* 有新的数据可读 */ /* 唤醒读进程 */ wake_up((dev-inq)); return ret; } /* seek文件定位函数 */ static loff_t mem_llseek(struct file *filp, loff_t offset, int whence) { loff_t newpos; switch(whence) { case 0: /* SEEK_SET */ newpos offset; break; case 1: /* SEEK_CUR */ newpos filp-f_pos offset; break; case 2: /* SEEK_END */ newpos MEMDEV_SIZE -1 offset; break; default: /* cant happen */ return -EINVAL; } if ((newpos0) || (newposMEMDEV_SIZE)) return -EINVAL; filp-f_pos newpos; return newpos; } unsigned int mem_poll(struct file *filp, poll_table *wait) { struct mem_dev *dev filp-private_data; unsigned int mask 0; /*将等待队列添加到poll_table */ poll_wait(filp, dev-inq, wait); if (have_data) mask | POLLIN | POLLRDNORM; /* readable */ return mask; } /*文件操作结构体*/ static const struct file_operations mem_fops { .owner THIS_MODULE, .llseek mem_llseek, .read mem_read, .write mem_write, .open mem_open, .release mem_release, .poll mem_poll, }; /*设备驱动模块加载函数*/ static int memdev_init(void) { int result; int i; dev_t devno MKDEV(mem_major, 0); /* 静态申请设备号*/ if (mem_major) result register_chrdev_region(devno, 2, memdev); else /* 动态分配设备号 */ { result alloc_chrdev_region(devno, 0, 2, memdev); mem_major MAJOR(devno); } if (result 0) return result; /*初始化cdev结构*/ cdev_init(cdev, mem_fops); cdev.owner THIS_MODULE; cdev.ops mem_fops; /* 注册字符设备 */ cdev_add(cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS); /* 为设备描述结构分配内存*/ mem_devp kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL); if (!mem_devp) /*申请失败*/ { result - ENOMEM; goto fail_malloc; } memset(mem_devp, 0, sizeof(struct mem_dev)); /*为设备分配内存*/ for (i0; i MEMDEV_NR_DEVS; i) { mem_devp[i].size MEMDEV_SIZE; mem_devp[i].data kmalloc(MEMDEV_SIZE, GFP_KERNEL); memset(mem_devp[i].data, 0, MEMDEV_SIZE); /*初始化等待队列*/ init_waitqueue_head((mem_devp[i].inq)); //init_waitqueue_head((mem_devp[i].outq)); } return 0; fail_malloc: unregister_chrdev_region(devno, 1); return result; } /*模块卸载函数*/ static void memdev_exit(void) { cdev_del(cdev); /*注销设备*/ kfree(mem_devp); /*释放设备结构体内存*/ unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/ } MODULE_AUTHOR(David Xie); MODULE_LICENSE(GPL); module_init(memdev_init); module_exit(memdev_exit); 3、应用程序 app-write.c [cpp] view plaincopy #include stdio.h int main() { FILE *fp NULL; char Buf[128]; /*打开设备文件*/ fp fopen(/dev/memdev0,r); if (fp NULL) { printf(Open Dev memdev Error!\n); return -1; } /*写入设备*/ strcpy(Buf,memdev is char dev!); printf(Write BUF: %s\n,Buf); fwrite(Buf, sizeof(Buf), 1, fp); sleep(5); fclose(fp); return 0; } 4、应用程序 app-read.c [cpp] view plaincopy #include stdio.h #include stdlib.h #include unistd.h #include sys/ioctl.h #include sys/types.h #include sys/stat.h #include fcntl.h #include sys/select.h #include sys/time.h #include errno.h int main() { int fd; fd_set rds; int ret; char Buf[128]; /*初始化Buf*/ strcpy(Buf,memdev is char dev!); printf(BUF: %s\n,Buf); /*打开设备文件*/ fd open(/dev/memdev0,O_RDWR); FD_ZERO(rds); FD_SET(fd, rds); /*清除Buf*/ strcpy(Buf,Buf is NULL!); printf(Read BUF1: %s\n,Buf); ret select(fd 1, rds, NULL, NULL, NULL); if (ret 0) { printf(select error!\n); exit(1); } if (FD_ISSET(fd, rds)) read(fd, Buf, sizeof(Buf)); /*检测结果*/ printf(Read BUF2: %s\n,Buf); close(fd); return 0; }