ldd3学习之十二(3):高级字符驱动程序操作--poll/select、异步通知
  TEZNKK3IfmPf 2023年11月14日 72 0

1.select

能够监听多个阻塞的文件描述符,这样,不需要fork和多进程就可以实现并发服务(网络中常用来监听多个网络连接)。

原型

  1. #include <sys/select.h>
  2. {
  3. ; /* seconds */
  4. ; /* and microseconds */
  5. };
  6.  
  7. int select(int maxfdp1,
  8. *restrict readfds,
  9. *restrict writefds,
  10. *restrict exceptfds,
  11. *restrict tvptr);

maxfdp1:select中监视的文件句柄数,一般设为要监视的文件中的最大文件号加一。

readfds:select()监视的可读文件句柄集合,当readfds映象的文件句柄状态变成可读时系统诉select函数返回。这个集合中有一个文件可读,select0timeouttimeoutselect0,若发生错误返回负值,可以传入NULL值,表示不关心任何文件的读变化;

writefds:监视的可写文件句柄集合,当writefds映象的文件句柄状态变成可写时系统告诉select函数返回。如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值,可以传入NULL值,表示不关心任何文件的写变化。

exceptfds:select()监视的异常文件句柄集合,当exceptfds映象的文件句柄上有特殊情况发生时系统会告诉select函数返回。

tvptr:select()的超时结束时间。

这个参数它使select
第一,若将NULL以形参传入,即不传入时间结构,就是将select阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
第二,若将时间值设为00毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
第三,timeout大于0,这就是等待的超时时间,即select在timeout超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

返回值:

负值: select 错误0

:等待超时,没有可读写或错误的文件

正值:某些文件可读可写或出错

对文件句柄操作函数

  1. FD_ZERO(fd_set *fdset);//清空fdset与所有文件句柄的联系。
  2. (int fd, fd_set *fdset);//建立文件句柄fd与fdset的联系。
  3. (int fd, fd_set *fdset);//清除文件句柄fd与fdset的联系。
  4. (int fd, fdset *fdset);//检查fdset联系的文件句柄fd是否可读写,>0表示可读写。

实例

  1. fd_set readfds;
  2. struct timeval tv;
  3.  
  4. {
  5. FD_ZERO(&readfds);
  6. if(fd>=0)
  7. FD_SET(fd, &readfds);
  8. tv.tv_sec=0;
  9. tv.tv_usec=20*1000;//tv.tv_usec=20*1000;
  10. ret = select((fd+1),&readfds,NULL,NULL,&tv);
  11. }

2.驱动中轮询的实现

  1. static unsigned int s3c_camif_poll(struct file *file, poll_table *wait)

第一个参数file这个函数完成两项工作。

①对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列拖添加到poll_table,若没有文件描述符可用来执行I/O,则内核将使进程在传递到该系统调用的所有文件描述符对应的等待队列上等待。

②返回表示是否能对设备进行无阻塞读写访问的掩码。

关键的用于向poll_tablepoll_wait()

  1. static inline void poll_wait(struct file * filp, wait_queue_head_t * queue, poll_table *wait)

功能:waitpoll_table

驱动poll()#define POLLIN         0x0001

#define POLLPRI        0x0002

#define POLLOUT        0x0004

#define POLLERR        0x0008

#define POLLHUP        0x0010

#define POLLNVAL       0x0020

Poll函数典型的模板

  1. static unsigned int xxx_poll(struct file *file, poll_table *wait)
  2. {
  3. int mask = 0;
  4. *dev = file->private_data;
  5.  
  6. ...
  7.  
  8. (file, &dev->r_wait, wait);
  9. (file, &dev->w_wait, wait);
  10.  
  11. if (...)//可读
  12. {
  13. |= POLLIN | POLLRDNORM;
  14. }
  15.  
  16. if (...)//可读
  17. {
  18. |= POLLOUT| POLLRDNORM;
  19. }
  20.  
  21. ;
  22. }

read 和write 的交互

正确实现poll:

从设备读取数据:

①read poll 应当返回 POLLIN|POLLRDNORM。

②readO_NONBLOCK read -EAGIN poll POLLIN|POLLRDNORM③read 0poll POLLHUP向设备写数据

①write poll应返回 POLLOUT|POLLWRNORM。

②write O_NOBLOCK write -EAGAIN, poll POLLOUT|POLLWRNORM. 若设备不能接受任何多余数据, O_NONBLOCKwrite -ENOSPC("")③writeO_NONBLOCK , fsync 刷新待处理输出
若一些应用程序需要确保数据被发送到设备,就必须实现fsync方法。对 fsync()即便这需要一些时间,

  1. int (*fsync) (struct file *file, struct dentry *dentry, int datasync);

参数datasync用于区分fsync和fdatasync两个系统调用,只与文件系统有挂,驱动程序可以忽略。fsync方法对时间没有严格要求,大部分时候,字符驱动中只给个NULL指针,而快设备总是用通用的block_fsync来实现这个方法,block_fsync会依次刷新设备的所有缓冲块,并等待所有I/O结束。

底层数据结构

只要用户应用程序调用 poll、select、或epoll_ctl,内核就会调用这个系统调用所引用的所有文件的 poll 方法,并向他们传递同一个poll_table。 poll_table 结构只是构成实际数据结构的简单封装:

  1.  
1. struct poll_table_struct;
2. /*
3. * structures and helpers for f_op->poll implementations
4. */
5. (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
6.
7. {
8. ;
9. } poll_table;

poll和 select系统调用,poll_table 是一个包含 poll_table_entry 结构内存页链表

  1. struct poll_table_entry {
  2. * filp;
  3. ;
  4. * wait_address;
  5. };

对 poll_wait 的调用有时还会将进程添加到给定的等待队列。整个的结构必须由内核维护,在 poll 或者 select 返回前,进程可从所有的队列中去除, .

如果被轮询的驱动没有一个驱动程序指明可进行非阻塞I/O,poll 调用会简单地睡眠,直到一个它所在的等待队列(可能许多)唤醒它.

当 poll 调用完成,poll_table 结构被重新分配, 所有的之前加入到 poll 表的等待队列入口都会从表和它们的等待队列中移出.

  1.  
1. struct poll_wqueues {
2. ;
3. * table;
4. int error;
5. int inline_index;
6. [N_INLINE_POLL_ENTRIES];
7. };
8.
9. {
10. * next;
11. * entry;
12. [0];
13. };

4.异步通知

通过使用异步通知,应用程序可以在数据可用时收到一个信号,而无需不停地轮询。

启用步骤

①它们指定一个进程作为文件的拥有者:使用 fcntl 系统调用发出 F_SETOWN 命令,这个拥有者进程的 ID 被保存在 filp->f_owner。目的:让内核知道信号到达时该通知哪个进程。

②使用 fcntl 系统调用,通过 F_SETFL 命令设置 FASYNC 标志。

内核操作过程

①.F_SETOWN被调用时filp->f_owner被赋值。

②. 当 F_SETFL 被执行来打开 FASYNC, 驱动的 fasync 方法被调用.这个标志在文件被打开时缺省地被清除。

③. 当数据到达时,所有的注册异步通知的进程都会被发送一个 SIGIO 信号。

Linux 提供的通用方法是基于一个数据结构和两个函数,定义在 。

数据结构:

  1.  
1. struct fasync_struct {
2. int magic;
3. int fa_fd;
4. *fa_next; /* singly linked list */
5. *fa_file;
6. };

驱动调用的两个函数的原型:

  1. int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);
  2. (struct fasync_struct **fa, int sig, int band);

当一个打开的文件的FASYNC标志被修改时,调用fasync_helper 来从相关的进程列表中添加或去除文件。除了最后一个参数, 其他所有参数都时被提供给 fasync 方法的相同参数并被直接传递。 当数据到达时,kill_fasync 被用来通知相关的进程,它的参数是被传递的信号(常常是 SIGIO)和 band(几乎都是 POLL_IN)。

这是 scullpipe 实现 fasync 方法的:

  1. static int scull_p_fasync(int fd, struct file *filp, int mode)
  2. {
  3. struct scull_pipe *dev = filp->private_data;
  4. return fasync_helper(fd, filp, mode, &dev->async_queue);
  5. }

当数据到达, 下面的语句必须被执行来通知异步读者. 因为对 sucllpipe 读者的新数据通过一个发出 write 的进程被产生, 这个语句出现在 scullpipe 的 write 方法中:

  1. if (dev->async_queue)
  2. (&dev->async_queue, SIGIO, POLL_IN); /* 注意, 一些设备也针对设备可写而实现了异步通知,在这个情况,kill_fasnyc 必须以 POLL_OUT 模式调用.*/

当文件被关闭时必须调用fasync 方法,来从活动的异步读取进程列表中删除该文件。尽管这个调用仅当 filp->f_flags 被设置为 FASYNC 时才需要,但不管什么情况,调用这个函数不会有问题,并且是普遍的实现方法。 以下是 scullpipe 的 release 方法的一部分:

  1. /* remove this filp from the asynchronously notified filp's */
  2. (-1, filp, 0);

异步通知使用的数据结构和 struct wait_queue 几乎相同,因为他们都涉及等待事件。区别异步通知用 struct file 替代 struct task_struct. 队列中的 file 用获取 f_owner,

1. void input_handler(int num)
2. {
3. ("process SIGIO signal.\n");
4. //system("poweroff");
5. }
6. int main(void)
7. {
8. int fd, oflags;
9. = open(DEVICE_NAME, O_RDWR);
10.
11. if (fd == -1) {
12. ("Failed to open alarm device %s\n", DEVICE_NAME);
13. -1;
14. }
15. (SIGIO, input_handler);
16. (fd, F_SETOWN, getpid());
17. = fcntl(fd, F_GETFL);
18.
19. (fd, F_SETFL, oflags | FASYNC);
20.
21. while (1);
22. ;
23. }
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月14日 0

暂无评论

TEZNKK3IfmPf