本文共 8181 字,大约阅读时间需要 27 分钟。
file_ops 中 最重要的概念莫过于 同步异步,阻塞非阻塞 概念及其实现.
同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由*调用者*主动等待这个*调用*的结果。而异步则是相反,*调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在*调用*发出后,*被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用。---阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。其实如果是循环的只等待消息结果,就跟调度出去(挂起)没什么两样.也被称作阻塞.(私人理解)非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
同步, 只有ops->read直接监听的,都是同步 // 什么时候读确定异步 不只有 ops->read 直接监听的,或者根本没有 ops_>read 参与的,都是异步 // 什么时候读不确定阻塞, 进程会换出,进程会循环卡死到某处非阻塞 进程不会因为api 被换出同步非阻塞 read NON_BLOCK同步阻塞 read BLOCK异步阻塞 select,poll方案(阻塞) (异步通知方案)加 read NON_BLOCK ,并不是直接read监听的,都是异步非阻塞 // 也被称作多路复用 异步非阻塞 epoll 方案 epoll 方案是将poll 方案针对 socket 进行的优化,一般用于socket fd 的监听 fasync (非阻塞) 加 read NON_BLOCK(在信号中) // 也被称作 信号驱动IO aio 方案 // 也被称作异步IO // aio方案有三种(https://my.oschina.net/yangpeng/blog/631905 http://blog.sina.com.cn/s/blog_533074eb01013zdp.html)当前介绍的是glibc 实现的aio方案 aio_read aio_error (循环检测,阻塞,没有换出) , aio_return // aio_return 得到读出的结果 // 其实是 异步阻塞方案 aio_read aio_suspend (阻塞,换出) , aio_return // aio_return 得到读出的结果 //异步非阻塞方案 填充 aio_sigevent (注册信号),aio_read , 有信号来时,执行信号函数(在函数中,aio_error,aio_return ) // 异步非阻塞方案 注意: aio_return 为 非阻塞函数.
阻塞// 读 fifo is empty if(file->f_flags & O_NONBLOCK) return _EAGAIN; wait_event_interruptible(read_queue)// 其他路径 wake_up(read_queue);
非阻塞// 读 fifo is empty if(file->f_flags & O_NONBLOCK) return _EAGAIN;
多路复用 poll unsigned int mask = 0; poll_wait(file, &button_waitq, wait); /* 将进程挂接到button_waitq等待队列下 */ // 该api不会引起阻塞, // poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的 /* 根据实际情况,标记事件类型 */ if (ev_press) mask |= POLLIN | POLLRDNORM; /* 如果mask为0,那么证明没有请求事件发生;如果非零说明有时间发生 */ return mask; //----用户空间 poll //-----------内核空间 sys_poll do_sys_poll do_poll for(;;) do_pollfd(pfd, pt) file->f_op->poll(file, pwait); schedule_timeout(__timeout); // --- 内核空间 除了休眠到指定时间被系统唤醒外,还可以被驱动程序唤醒──记住这点,这就是为什么驱动的poll里要调用poll_wait的原因task 1 DECLARE_WAITQUEUE(wait, current); add_wait_queue(w_wait, wait); __set_current_state(TASK_INTERRUPTIBLE); schedule(); //task1 进程休眠 ................//唤醒后从这儿执行task 2 //唤醒task1 wake_up(w_wait);//可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)//只能唤醒处于TASK_INTERRUPTIBLE状态的进程#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)经过以上驱动程序的poll()函数应该返回设备资源的可获取状态,即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏的位"或"结果.每个宏的含义都表示设备的一种状态,如:常量 说明POLLIN 普通或优先级带数据可读POLLRDNORM 普通数据可读POLLRDBAND 优先级带数据可读POLLPRI 高优先级数据可读POLLOUT 普通数据可写POLLWRNORM 普通数据可写POLLWRBAND 优先级带数据可写POLLERR 发生错误POLLHUP 发生挂起POLLNVAL 描述字不是一个打开的文件SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds, int, timeout_msecs) do_sys_poll(ufds, nfds, to); do_poll(nfds, head, &table, end_time); poll_table* pt = &wait->pt; for (;;) { for (; pfd != pfd_end; pfd++) { do_pollfd(pfd, pt, &can_busy_loop, busy_flag); } poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack); } __put_user(fds[j].revents, &ufds->revents)static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait, pwait->_key = pollfd->events|POLLERR|POLLHUP; mask = f.file->f_op->poll(f.file, pwait); // 由驱动中设置. mask &= pollfd->events | POLLERR | POLLHUP; pollfd->revents = mask; // 用户空间在poll 返回之后,会在用户空间检查这个值.#define FD_ISSET(fd, fdsetp) __FD_ISSET (fd, fdsetp)#define __FD_ISSET(d, s) \ ((__FDS_BITS (s)[__FD_ELT (d)] & __FD_MASK (d)) != 0)如果当前不可读(先调用驱动.poll确定是否可读,然后继续do_poll),那么在sys_poll->do_poll中当前进程就会睡眠在等待队列上,这个等待队列是由驱动程序提供的(就是poll_wait中传入的那个)。当可读的时候,驱动程序可能有一部分代码运行了(比如驱动的中断服务程序),那么在这部分代码中,就会唤醒等待队列上的进程,也就是之前睡眠的那个,当那个进程被唤醒后do_poll会再一次的调用驱动程序的poll函数,这个时候应用程序就知道是可读的了。
异步IO fasynchandler main act.sa_sigaction = handler; sigaction(SIGIO,&act,&oldact) open fcntl() // 设置异步IO所有权 fcntk() // 设置SIGIO信号 fcntl()//获取文件flags fcntl() // 设置FASYNC while(1)---fops .fasync = demo_fasyc;demo_fasyc fasyc_helper(fd,file,on,xxx);write kill_fasync(xxx,SIGIO,POLLIN);
aio 方案 libc 线程和阻塞IO模拟 #includelinux 内核fops->aio_read 实现 .aio_read = pipe_read #include libeio 类似libc方案 #include "eio.h"异步IO aio_readhttps://blog.csdn.net/summer_zgh/article/details/82416427http://blog.sina.com.cn/s/blog_6028e2630100y0d1.html
#include#include #include #include #include #include #include #include #include int main(){ io_context_t ctx; unsigned nr_events = 10; memset(&ctx, 0, sizeof(ctx)); // It's necessary,这里一定要的 int errcode = io_setup(nr_events, &ctx); if (errcode == 0) printf("io_setup success\n"); else printf("io_setup error: :%d:%s\n", errcode, strerror(-errcode)); // 如果不指定O_DIRECT,则io_submit操作和普通的read/write操作没有什么区别了,将来的LINUX可能 // 可以支持不指定O_DIRECT标志 int fd = open("./direct.txt", O_CREAT|O_DIRECT|O_WRONLY, S_IRWXU|S_IRWXG|S_IROTH); printf("open: %s\n", strerror(errno)); char* buf; errcode = posix_memalign((void**)&buf, sysconf(_SC_PAGESIZE), sysconf(_SC_PAGESIZE)); printf("posix_memalign: %s\n", strerror(errcode)); strcpy(buf, "hello xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); struct iocb *iocbpp = (struct iocb *)malloc(sizeof(struct iocb)); memset(iocbpp, 0, sizeof(struct iocb)); iocbpp[0].data = buf; iocbpp[0].aio_lio_opcode = IO_CMD_PWRITE; iocbpp[0].aio_reqprio = 0; iocbpp[0].aio_fildes = fd; iocbpp[0].u.c.buf = buf; iocbpp[0].u.c.nbytes = page_size;//strlen(buf); // 这个值必须按512字节对齐 iocbpp[0].u.c.offset = 0; // 这个值必须按512字节对齐 // 提交异步操作,异步写磁盘 int n = io_submit(ctx, 1, &iocbpp); printf("==io_submit==: %d:%s\n", n, strerror(-n)); struct io_event events[10]; struct timespec timeout = { 1, 100}; // 检查写磁盘情况,类似于epoll_wait或select n = io_getevents(ctx, 1, 10, events, &timeout); printf("io_getevents: %d:%s\n", n, strerror(-n)); close(fd); io_destroy(ctx); return 0;}
#include#include #include #include #include #include #include #include #include #include #include #include #define BUFFER_SIZE 1025 int main(int argc,char **argv){ //定义aio控制块结构体 struct aiocb wr; int ret,fd; char str[20] = { "hello,world"}; //置零wr结构体 bzero(&wr,sizeof(wr)); fd = open("test.txt",O_WRONLY | O_APPEND); if(fd < 0) { perror("test.txt"); } //为aio.buf申请空间 wr.aio_buf = (char *)malloc(BUFFER_SIZE); if(wr.aio_buf == NULL) { perror("buf"); } wr.aio_buf = str; //填充aiocb结构 wr.aio_fildes = fd; wr.aio_nbytes = 1024; //异步写操作 ret = aio_write(&wr); if(ret < 0) { perror("aio_write"); } //等待异步写完成 while(aio_error(&wr) == EINPROGRESS) { printf("hello,world\n"); } //获得异步写的返回值 ret = aio_return(&wr); printf("\n\n\n返回值为:%d\n",ret); return 0;}
https://blog.csdn.net/lanyan822/article/details/7644745linux下主要有两套异步IO,一套是由glibc实现的(以下称之为glibc版本)、一套是由linux内核实现,并由libaio来封装调用接口(以下称之为linux版本)libeio 是 后期 开发的 , 类似 glibc 版本,相比 glibc 的aio 来说bug 比较少,而且架构清晰#include#include #include #include #include #include #include #include #include #include "eio.h" int respipe [2]; /* * 功能:子线程通知主线程已有回执放入回执队列. */voidwant_poll (void){ char dummy; printf ("want_poll ()\n"); write (respipe [1], &dummy, 1);} /* * 功能:主线程回执处理完毕,调用此函数 */voiddone_poll (void){ char dummy; printf ("done_poll ()\n"); read (respipe [0], &dummy, 1);}/* * 功能:等到管道可读,处理回执信息 */voidevent_loop (void){ // an event loop. yeah. struct pollfd pfd; pfd.fd = respipe [0]; pfd.events = POLLIN; printf ("\nentering event loop\n"); while (eio_nreqs ()) { poll (&pfd, 1, -1); printf ("eio_poll () = %d\n", eio_poll ()); } printf ("leaving event loop\n");} /* * 功能:自定义函数,用户处理请求执行后的回执信息 */intres_cb (eio_req *req){ printf ("res_cb(%d|%s) = %d\n", req->type, req->data ? req->data : "?", EIO_RESULT (req)); if (req->result < 0) abort (); return 0;} intmain (void){ printf ("pipe ()\n"); if (pipe (respipe)) abort (); printf ("eio_init ()\n"); if (eio_init (want_poll, done_poll)) //初始化libeio库 abort (); eio_mkdir ("eio-test-dir", 0777, 0, res_cb, "mkdir"); event_loop (); return 0;}