linux基础编程:IO模型:阻塞/非阻塞/IO复用 同步/异步 Select/Epoll/AIO

非阻塞IO(NIO) 也称作同步阻塞IO。 阅读详情

IO概念


Linux的内核将所有外部设备都可以看做一个文件来操作。那么我们对与外部设备的操作都可以看做对文件进行操作。我们对一个文件的读写,都通过调用内核提供的系统调用;内核给我们返回一个file descriptor(fd,文件描述符)。而对一个socket的读写也会有相应的描述符,称为socketfd(socket描述符)。描述符就是一个数字,指向内核中一个结构体(文件路径,数据区,等一些属性)。那么我们的应用程序对文件的读写就通过对描述符的读写完成。

 linux将内存分为内核区,用户区。linux内核给我们管理所有的硬件资源,应用程序通过调用系统调用和内核交互,达到使用硬件资源的目的。应用程序通过系统调用read发起一个读操作,这时候内核创建一个文件描述符,并通过驱动程序向硬件发送读指令,并将读的的数据放在这个描述符对应结构体的内核缓存区中,然后再把这个数据读到用户进程空间中,这样完成了一次读操作;

但是大家都知道I/O设备相比cpu的速度是极慢的。linux提供的read系统调用,也是一个阻塞函数。这样我们的应用进程在发起read系统调用时,就必须阻塞,就进程被挂起而等待文件描述符的读就绪,那么什么是文件描述符读就绪,什么是写就绪?

读就绪:就是这个文件描述符的接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水位标记的当前大小;

写就绪:该描述符发送缓冲区的可用空间字节数大于等于描述符发送缓冲区低水位标记的当前大小。(如果是socket fd,说明上一个数据已经发送完成)。

接收低水位标记和发送低水位标记:由应用程序指定,比如应用程序指定接收低水位为64个字节。那么接收缓冲区有64个字节,才算fd读就绪;

综上所述,一个基本的IO,它会涉及到两个系统对象,一个是调用这个IO的进程对象,另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:

  • 通过read系统调用想内核发起读请求。
  • 内核向硬件发送读指令,并等待读就绪。 
  • 内核把将要读取的数据复制到描述符所指向的内核缓存区中。
  • 将数据从内核缓存区拷贝到用户进程空间中。

IO模型


在linux系统下面,根据IO操作的是否被阻塞以及同步异步问题进行分类,可以得到下面五种IO模型:
1、阻塞I/O模型
最流行的I/O模型是阻塞I/O模型,缺省情形下,所有文件操作都是阻塞的。我们以套接口为例来讲解此模型。在进程空间中调用recvfrom,其系统调用直到数据报到达且被拷贝到应用进程的缓冲区中或者发生错误才返回,期间一直在等待。我们就说进程在从调用recvfrom开始到它返回的整段时间内是被阻塞的。

2、非阻塞I/O模型
进程把一个套接口设置成非阻塞是在通知内核:当所请求的I/O操作不能满足要求时候,不把本进程投入睡眠,而是返回一个错误。也就是说当数据没有到达时并不等待,而是以一个错误返回。

3、I/O复用模型
linux提供select/poll,进程通过将一个或多个fd传递给select或poll系统调用,阻塞在select;这样select/poll可以帮我们侦测许多fd是否就绪。但是select/poll是顺序扫描fd是否就绪,而且支持的fd数量有限。linux还提供了一个epoll系统调用,epoll是基于事件驱动方式,而不是顺序扫描,当有fd就绪时,立即回调函数rollback;

4、信号驱动异步I/O模型
首先开启套接口信号驱动I/O功能, 并通过系统调用sigaction安装一个信号处理函数(此系统调用立即返回,进程继续工作,它是非阻塞的)。当数据报准备好被读时,就为该进程生成一个SIGIO信号。随即可以在信号处理程序中调用recvfrom来读数据报,井通知主循环数据已准备好被处理中。也可以通知主循环,让它来读数据报。

5、异步I/O模型
告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核拷贝到用户自己的缓冲区)通知我们。这种模型与信号驱动模型的主要区别是:信号驱动I/O:由内核通知我们何时可以启动一个I/O操作;异步I/O模型:由内核通知我们I/O操作何时完成。


非阻塞IO详解


通过上面,我们知道,所有的IO操作在默认情况下,都是属于阻塞IO。尽管上图中所示的反复请求的非阻塞IO的效率底下(需要反复在用户空间和进程空间切换和判断,把一个原本属于IO密集的操作变为IO密集和计算密集的操作),但是在后面IO复用中,需要把IO的操作设置为非阻塞的,此时程序将会阻塞在select和poll系统调用中。把一个IO设置为非阻塞IO有两种方式:在创建文件描述符时,指定该文件描述符的操作为非阻塞;在创建文件描述符以后,调用fcntl()函数设置相应的文件描述符为非阻塞。
创建描述符时,利用open函数和socket函数的标志设置返回的fd/socket描述符为O_NONBLOCK。
  1. int sd=socket(int domain, int type|O_NONBLOCK, int protocol);  
  2. int fd=open(const char *pathname, int flags|O_NONBLOCK);  
创建描述符后,通过调用fcntl函数设置描述符的属性为O_NONBLOCK
  1. #include <unistd.h>  
  2. #include <fcntl.h>  
  3.   
  4. int fcntl(int fd, int cmd, ... /* arg */ );  
  5.   
  6. //例子  
  7. if (fcntl(fd, F_SETFL, fcntl(sockfd, F_GETFL, 0)|O_NONBLOCK) == -1) {  
  8.     return -1;  
  9. }  
  10.     return 0;  
  11. }  


IO复用详解


在IO编程过程中,当需要处理多个请求的时,可以使用多线程和IO复用的方式进行处理。上面的图介绍了整个IO复用的过程,它通过把多个IO的阻塞复用到一个select之类的阻塞上,从而使得系统在单线程的情况下同时支持处理多个请求。和多线程/进程比较,I/O多路复用的最大优势是系统开销小,系统不需要建立新的进程或者线程,也不必维护这些线程和进程。IO复用常见的应用场景:
  1. 客户程序需要同时处理交互式的输入和服务器之间的网络连接。
  2. 客户端需要对多个网络连接作出反应。
  3. 服务器需要同时处理多个处于监听状态和多个连接状态的套接字
  4. 服务器需要处理多种网络协议的套接字。
目前支持I/O复用的系统调用有select、pselect、poll、epoll,下面几小结分别来学习一下select和epoll的使用。

select

  1. #include <sys/select.h>  
  2.   
  3. int select(int maxfdps, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);  
  4.   
  5. struct timeval{  
  6. long tv_sec; //秒  
  7. long tv_usec; //微秒  
  8. };  
  9. void FD_CLR(int fd, fd_set *set); //将一个给定的文件描述符从集合中删除  
  10. int  FD_ISSET(int fd, fd_set *set);  // 检查集合中指定的文件描述符是否可以读写 ?  
  11. void FD_SET(int fd, fd_set *set); //将一个给定的文件描述符加入集合之中  
  12. void FD_ZERO(fd_set *set);//清空集合  
  13.              
  14. <span>返回:就绪描述字的正数目,0-超时,-1-出错</span>  
struct timeval *time结构体告知内核等待所指定描述字中的任何一个就绪可花多少时间。参数取值:
(1)(struct timeval *)0:永远等待下去,仅在有一个描述字准备好I/O时才返回。
(2)struct timeval *time:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。如果超过时间,没有描述字准备好,那就返回0。如果秒=微秒=0,检查描述字后立即返回,此时相当于轮询。
中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。如果我们对某一个的条件不感兴趣,就可以把它设为空指针。fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过上面的四个宏进行设置(注意,fd_set不是struct fd_set。。。 刚刚开始调试程序就犯错误,返回storage size of 'fds' isn't known)。
第一个参数maxfdp1指定待测试的描述字个数,它的值是待测试的最大描述字加1(因此我们把该参数命名为maxfdp1),描述字0、1、2...maxfdp1-1均将被测试。
下面是一个从网上看到的一个比较好的测试程序,太长了,移到空间代码功能里面了, click me to see奋斗

pselect

  1. #include <sys/select.h>  
  2.   
  3. int pselect(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, const struct timespec *timeout,  
  4.                    const sigset_t *sigmask);  
  5. struct timespec{  
  6.     time_t tv_sec; //秒  
  7.     long tv_nsec; //纳秒  
  8. };  
比较select和pselect函数,我们发现在原型上面有两个不同:
1、pselect使用timespec结构,而不使用timeval结构。timespec结构是POSIX的又一个发明。 这两个结构的区别在于第二个成员:新结构的该成员tv_nsec指定纳秒数,而旧结构的该成员tv_usec指定微秒数。
2、pselect函数增加了第六个参数:一个指向信号掩码的指针。该参数允许程序先禁止递交某些信号,再测试由这些当前被禁止的信号处理函数设置的全局变量,然后调用pselect,告诉它重新设置信号掩码。
首先我们看看信号掩码的概念:在POSIX下,每个进程有一个信号掩码(signal mask)。简单地说,信号掩码是一个“位图”,其中每一位都对应着一种信号。如果位图中的某一位为1,就表示在执行当前信号的处理程序期间相应的信号暂时被“屏蔽”,使得在执行的过程中不会嵌套地响应那种信号。为什么对某一信号进行屏蔽呢?我们来看一下对CTRL_C的处理。大家知道,当一个程序正在运行时,在键盘上按一下CTRL_C,内核就会向相应的进程发出一个SIGINT 信号,而对这个信号的默认操作就是通过do_exit()结束该进程的运行。但是,有些应用程序可能对CTRL_C有自己的处理,所以就要为SIGINT另行设置一个处理程序,使它指向应用程序中的一个函数,在那个函数中对CTRL_C这个事件作出响应。但是,在实践中却发现,两次CTRL_C事件往往过于密集,有时候刚刚进入第一个信号的处理程序,第二个SIGINT信号就到达了,而第二个信号的默认操作是杀死进程,这样,第一个信号的处理程序根本没有执行完。为了避免这种情况的出现,就在执行一个信号处理程序的过程中将该种信号自动屏蔽掉。所谓“屏蔽”,与将信号忽略是不同的,它只是将信号暂时“遮盖”一下,一旦屏蔽去掉,已到达的信号又继续得到处理。有关信号相关的知识参考我另外一个文章:《 linux基础编程:进程通信之信号》。在我们开发过程中,如果进程阻塞与select函数,此时该阻塞被信号所打断,select返回-1,errno=EINTR的错误。由于这个原因,我们才有了pselect函数在信号上的优化处理。因此在信号和select都被使用的系统里,pselect有其作用。否则和select没有任何区别。

poll

  1. #include <poll.h>  
  2.   
  3. int poll(struct pollfd *fds, nfds_t nfds, int timeout);  
  4.     struct pollfd {  
  5.     int fd; /* file descriptor */  
  6.     short events; /* requested events to watch */  
  7.     short revents; /* returned events witnessed */  
  8.     };   
和select()不一样,poll()没有使用低效的三个基于位的文件描述符set,而是采用了一个单独的结构体pollfd数组,由fds指针指向这个数组的第一个元素,其中nfds表示该数组的大小。每一个pollfd 结构体指定了一个被监视的文件描述符,每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码。内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回,而部分事件不能出现在events中,详细如下表。

常量说明
POLLIN普通或优先级带数据可读
POLLRDNORM普通数据可读
POLLRDBAND优先级带数据可读
POLLPRI高优先级数据可读
POLLOUT普通数据可写
POLLWRNORM普通数据可写
POLLWRBAND优先级带数据可写
POLLERR发生错误
POLLHUP发生挂起
POLLNVAL描述字不是一个打开的文件

 注意:后三个只能作为描述字的返回结果存储在revents中,而不能作为测试条件用于events中。

最后一个参数timeout是指定poll函数返回前等待多长时间。它的取值如下:

timeout值说明
INFTIM永远等待
0立即返回,不阻塞进程
>0等待指定数目的毫秒数
成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1,并设置errno为下列值之一:
EBADF:一个或多个结构体中指定的文件描述符无效。EFAULT:fds指针指向的地址超出进程的地址空间。EINTR:请求的事件之前产生一个信号,调用可以重新发起。EINVAL:nfds参数超出PLIMIT_NOFILE值。ENOMEM:可用内存不足,无法完成请求。
关键代码如下:
  1. client[0].fd = listenfd;         /*将数组中的第一个元素设置成监听描述字*/  
  2. client[0].events = POLLIN;    
  3.   
  4. while(1)  
  5. {  
  6.     nready = poll(client, maxi+1,INFTIM); //将进程阻塞在poll上  
  7.     if( client[0].revents & POLLIN/*POLLRDNORM*/ ) /*先测试监听描述字*/  


epoll

在linux的网络编程中,很长的一段时间都在使用select来做事件触发。然而select逐渐暴露出了一些缺陷,使得linux不得不在新的内核中寻找出替代方案,那就是epoll。其实,epoll与select原理类似,只不过,epoll作出了一些重大改进,即:
1.支持一个进程打开大数目的socket描述符(FD)
select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。
2.IO效率不随FD数目增加而线性下降
传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。
3.使用mmap加速内核与用户空间的消息传递。
这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。
4.内核微调
这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小 --- 通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。

epoll api函数比较简单,包括 创建一个epoll描述符,添加监听事件,阻塞等待所监听的事件发生,关闭epoll描述符,如下:
  1. #include <sys/epoll.h>  
  2. #include <unistd.h>  
  3.   
  4. int epoll_create(int size); //epoll描述符  
  5. int close(int fd);//关闭epoll描述符  
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。需要注意的是,当创建好epoll句柄后,epoll本身就占用一个fd值,所以用完后必须调用close()关闭,以防止fd被耗尽。
  1. #include <sys/epoll.h>  
  2. #include <unistd.h>  
  3.   
  4. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//添加监听事件  
  5.   
  6. typedef union epoll_data {  
  7.     void        *ptr;  
  8.     int          fd;  
  9.     uint32_t     u32;  
  10.     uint64_t     u64;  
  11. } epoll_data_t;  
  12.   
  13. struct epoll_event {  
  14.     uint32_t     events;      /* Epoll events */  
  15.     epoll_data_t data;        /* User data variable */  
  16. };  

epoll_ctl为事件注册函数,第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:

  • EPOLL_CTL_ADD:注册新的fd到epfd中;
  • EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
  • EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的描述符fd,第四个参数是告诉内核需要监听什么事件,struct epoll_event结构如上所示,其中events为需要注册的事件,可以为下面几个宏的集合:
  • EPOLLIN:表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
  • EPOLLOUT:表示对应的文件描述符可以写;
  • EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
  • EPOLLERR:表示对应的文件描述符发生错误;
  • EPOLLHUP:表示对应的文件描述符被挂断;
  • EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。详细见下面描述
  • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
操作系统对被注册的文件描述符的事件发送以后有两种处理方式,分别有LT和ET模式,默认情况下为LT,如果需要设置为ET模式,设置struct epoll_event.events|EPOLLET。
  • LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点,传统的select/poll都是这种模型的代表。
  • ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。
struct epoll_event.data为一个epoll_data_t类型的联合体,详细结构如上,其中可以保存指针,描述符,32/64位的整数。如果在监听过程中,该描述符上面有相应的事件发生,系统将会把该字段返回。先看监听函数吧。看完就知道

  1. #include <sys/epoll.h>  
  2. #include <unistd.h>  
  3.   
  4. int epoll_wait(int epfd, struct epoll_event *events,  int maxevents, int timeout);//阻塞等待所监听的事件发生  
阻塞监听函数,类似于select()调用。参数events用来从内核得到事件的集合,返回的结构也是struct epoll_event,其中event为相应的事件,data为注册时,设置的值(常见情况,data设置为注册的描述符,这样就可以对相应的描述符进行IO操作)。maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
  1. struct epoll_event ev, *events;   
  2. int kdpfd = epoll_create(100);   
  3. ev.events = EPOLLIN | EPOLLET; // 注意这个EPOLLET,指定了边缘触发   
  4. ev.data.fd =listener;   
  5. epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev);   
  6. for(;;)   
  7. {   
  8.     nfds = epoll_wait(kdpfd, events, maxevents, -1);  
  9.     for(n = 0; n < nfds; ++n)  
  10.      {   
  11.         if(events[n].data.fd == listener)  
  12.         {   
  13.                 client = accept(listener, (struct sockaddr *) &local, &addrlen);   
  14.                 if(client < 0){   
  15.                         perror("accept");   
  16.                         continue;   
  17.                 }   
  18.                 setnonblocking(client);   
  19.                  ev.events = EPOLLIN | EPOLLET;   
  20.                  ev.data.fd = client;   
  21.                 if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0)   
  22.             {   
  23.                         fprintf(stderr, "epoll set insertion error: fd=%d0, client);   
  24.                         return -1;   
  25.                 }   
  26.              }else{  
  27.             do_use_fd(events[n].data.fd);   
  28.         }                     
  29.      }   
  30. }  
最后我们说一下,用来克服select/poll缺点的方法不只有epoll。epoll只是一种linux 的方案,在freeBSD下有kqueue,而dev/poll 是最古老的Solaris的方案,使用难度依次递增。 kqueue 是freebsd 的宠儿,kqueue 实际上是一个功能相当丰富的kernel 事件队列,它不仅仅是select/poll 的升级,而且可以处理 signal 、目录结构变化、进程等多种事件。Kqueue 是边缘触发的。/dev/poll 是Solaris的产物,是这一系列高性能API 中最早出现的。Kernel 提供一个特殊的设备文件/dev/poll 。应用程序打开这个文件得到操纵fd_set 的句柄,通过写入pollfd 来修改它,一个特殊ioctl 调用用来替换select 。由于出现的年代比较早,所以/dev/poll 的接口现在看上去比较笨拙可笑。

信号驱动异步IO详解


上面介绍了不管是阻塞/非阻塞或者IO复用,在应用程序层面都需要进行阻塞轮询指定的IO上面是否有相应的事件发生。本节我们将将介绍一个全新的模型:异步模型,异步则意味着不需要用户层进行轮询,在特定IO或者事件发生时候,会主动通知应用程序IO就绪,本节介绍的通知机制即信号,我们把这个模型叫着信号驱动的异步I/O。在《 linux基础编程:进程通信之信号》文章中,我们详细介绍信号操作。
Unix上有定义了许多信号,源自Berkeley的实现使用的是SIGIO信号来支持套接字和终端设备上的信号驱动IO。在套接字IO中,信号驱动IO模型主要是在UDP套接字上使用,在TCP套接字上几乎是没有什么使用的(在TCP上,由于TCP是双工的,它的信号产生过于平凡,并且信号的出现几乎没有告诉我们发生了什么事情。因此对于TCP套接字,SIGIO信号是没有什么使用的)。
使用信号驱动异步IO主要有下面几个步骤:
为了让套接字描述符可以工作于信号驱动I/O模式,应用进程必须完成如下三步设置: 
  • 1.注册SIGIO信号处理程序。(安装信号处理器) 
  • 2.使用fcntl的F_SETOWN命令,设置套接字所有者。 
  • 3.使用fcntl的F_SETFL命令,置O_ASYNC和O_NONBLOCK标志,允许套接字信号驱动I/O。
注意,必须保证在设置套接字所有者之前,向系统注册信号处理程序,否则就有可能在fcntl调用后,信号处理程序注册前内核向应用交付SIGIO信号,导致应用丢失此信号。
代码片段如下:
  1. struct sigaction sigio_action;  
  2. memset(&sigio_action, 0, sizeof(sigio_action));  
  3. sigio_action.sa_flags = 0;  
  4. sigio_action.sa_handler = do_sigio;//信号发生时的处理函数  
  5. sigaction(SIGIO, &sigio_action, NULL);  
  6.    
  7. fcntl(listenfd1, F_SETOWN, getpid());  
  8. int flags;  
  9. flags = fcntl(listenfd1, F_GETFL, 0);  
  10. flags |= O_ASYNC | O_NONBLOCK;  
  11. fcntl(listenfd1, F_SETFL, flags);  
本地AI语音生成与配音工作台Pandrator:从TTS到RVC的完整实践指南 语音合成(TTS)技术通过深度学习模型将文本转换为自然语音,其核心原理涉及声学建模与波形生成。这项技术不仅提升了人机交互体验,更在内容创作领域展现出巨大价值,广泛应用于有声书制作、视频配音、虚拟助手等场景。本文聚焦于一个集成了XTTS-v2、RVC等先进模型的本地化AI语音工作台——Pandrator,它通过模块化架构将语音合成、声音克隆、音频转录与翻译等流程无缝整合,为用户提供了一个图形化、可定制的完整解决方案。文章深入解析了其基于TTS与RVC(Retrieval-based Voice Convers 阅读详情

相关推荐

安卓 实现灯光闪过效果

有个小需求,给按钮加一个灯光划过的效果,这样的需求一般有UI,肯定就找UI了要个gif图不就完事了,可是这次没有,好久自己做一个嘛。Web端的大佬实现很快,到安卓这边呢,百度一搜发现诶?怎么没有,好吧自己来一个 一切动效相关的不给图看都是耍流氓 所以俺不能这么干啊,OK!直接上效果图,再来解释下怎么实现的 效果就是这样子,怎么实现呢? 从图分析下,就是一个光写着重左边移动要右边循环嘛,这有啥难的?? 来看一看我的第一版,第二版 这是个什么?????一点都不像啊? 赶紧再问了问Web端大佬,噢,加了一个

SummerCloudXT的博客 1378

Linux下异步I/0:O_ASYNC标志,aio_read

原文链接:http://blog.sina.com.cn/s/blog_3e3fcadd0100grgh.html#SinaEditor_Temp_FontName 方法一:使用fcntl来置O_ASYNC位。               这个方法的效果是,当输入缓存中的输入数据就绪时(输入数据可读),内核向用F_SETOWN来绑定的那个进程发送SIGIO信号。此时程序应该用get

hustcxl的博客 4249

EndNote全文检索实战:如何快速配置学校图书馆资源并精准查找文献

本文详细介绍了EndNote全文检索功能的实战配置方法,核心在于通过正确设置OpenURL链接解析器,无缝对接学校图书馆资源,实现一键精准获取文献全文。文章提供了从理解机制、四步配置法到进阶技巧的完整指南,帮助科研人员高效管理海量PDF,构建个人知识体系,大幅提升文献调研与写作效率。

weixin_29228781的博客 279

Linux使用O_ASYNC实现异步IO

本文探讨一个《Unix/Linux编程实践教程》P219出现的bug,在Linux上bounce_async.c程序代码运行失败。 Unix有两个异步输入(asynchronous input)系统。 一种方法是当输入就绪时发送信号,另一个系统当输入被读入时发送信号。UCB(BSD)中通过设置文件描述块(file descriptor)的O_ASYNC位来实现第一种方法。 第二种方...

weixin_38168547的博客 406

MFC下使用C++操作Word文档

由于项目需求,在MFC环境下操作将数据和图片写入word文档,之前没有接触过,网上查阅了很多资料,终于把任务完成。理解的不是很透彻,但是这些东西应该够用了,写篇博客记录一下,以便以后再用到。 (1)VS环境下创建一个MFC应用程序,然后在项目上单击右键,选择 类向导-----添加类----类型库中的MFC类。在弹出的对话框中选择文件,位置选择office安装目录下的...

GIS_yuhua的博客 7577

【Python脚本】Python自动大麦网抢票,准点原价秒杀演唱会门票,拒绝黄牛交易!从我做起!!!1

你是否错过了周杰伦的票,最近又没抢到五月天的票,不要太难过,今晚有张学友的票,后天还有张杰的票,加油哦!

weixin_45841831的博客 6万+

【Linux高级 I/O(4)】异步 IO实例及其优化(全文代码)

上一小节介绍了异步 I/O 的原理以及使用方法,在一个需要同时检查大量文件描述符(譬如数千个)的应用程序中,例如某种类型的网络服务端程序,与 select()和 poll()相比,异步 I/O 能够提供显著的性能优势。O_ASYNC 标志可用于使能文件描述符的异步 I/O 事件,当文件描述符可执行 I/O 操作时,内核会向异步 I/O 事件的接收进程发送 SIGIO 信号(默认情况下),该标志主要用于异步 I/O。为文件描述符设置异步 I/O 事件的接收进程,也就是设置异步 I/O 的所有者。

cj_lsk的博客 1652

使用Open3D进行RGB-D图像点云处理

在本文中,我们将使用Open3D来处理RGB-D图像,提取点云并进行一些基本的操作。总结起来,本文介绍了如何使用Open3D库处理RGB-D图像并提取点云。我们展示了如何读取RGB-D图像,创建RGB-D图像和点云,并显示点云。除了显示点云,Open3D还提供了许多其他功能,如点云滤波、点云配准、表面重建等。在创建RGB-D图像时,我们需要指定深度图像的缩放因子和截断值,以及是否将RGB图像转换为强度图像。希望本文能够帮助您入门Open3D,并在处理RGB-D图像和点云数据时提供一些指导。

QfcaLinux的博客 445

select poll epoll之间的区别

前导 首先在分析他们之前的区别前,需要明确几个概念。 同步阻塞、 同步非阻塞 异步非阻塞 同步和异步的概念: 同步是指用户线程发起IO请求后,需要等待或者轮询内核IO操作完成后才能继续执行; 异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程或者调用用户线程注册的回调函数。 阻塞和非阻塞的概念: 阻塞是指IO操作需要彻底完成后才返回到用户空间; 非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。 I/O 同步和异步的区别在于:

m0_61802230的博客 1万+

SAP-ABAP:SAP信贷管理核心:CL_UKM_BUPA_FACTORY类完整解析与实践指南

SAP信贷管理核心类CL_UKM_BUPA_FACTORY解析 CL_UKM_BUPA_FACTORY是SAP信贷管理(UKM)模块的核心类,作为业务伙伴信贷数据的中央控制台,它提供: 统一入口:标准化实例化客户/供应商信贷对象 主要功能: 获取单条/批量业务伙伴数据 安全保存数据变更 封装底层数据访问逻辑 典型应用场景: 信贷主数据批量维护 自动化限额调整 订单信贷实时检查 性能优化建议: 使用批量方法替代循环单条处理 合理设置字段选择 实施缓存策略 该类通过简洁API简化复杂信贷操作,是SAP信贷管理开

baidu_35680696的博客 1208

使用 Pfam 和 InterProScan 进行蛋白质家族和功能域的分析

本文介绍了使用Pfam和InterProScan进行蛋白质家族与功能域分析的方法。主要内容包括:1) Pfam数据库的本地化使用,通过hmmscan工具比对Pfam-A.hmm数据库,并提供了批量处理Python脚本;2) InterProScan的安装配置与命令行参数详解,支持多数据库整合分析;3) 补充介绍了基于curl的Uniprot在线查询方法。文章提供了从软件安装到实战分析的完整流程,适用于本地化、批量化蛋白质功能注释需求,特别是无网络环境或大规模数据分析场景,适合不同水平的生物信息学用户。

Protein Designer的博客 1918

Unix五种I/O模型(阻塞、非阻塞、多路复用、信号驱动、异步)

本文介绍了Unix五种I/O模型及其对比。

weixin_38597669的博客 1621

学 Simulink—— 基于高频方波电压注入的表贴式永磁电机无感控制仿真

本文手把手教授基于Simulink的高频方波电压注入法实现表贴式永磁同步电机(SPMSM)无传感器控制。针对表贴式电机微弱凸极特性(Lq/Ld≈1.05-1.15),详解方波注入相比正弦注入的优势:通过5kHz方波(PWM频率半值)集中能量提升信噪比,并采用相邻周期电流直接相减的零延迟分离技术。系统架构包含高频注入器、无滤波器信号分离模块、锁相环观测器和磁饱和极性辨识等关键环节,提供参数设置经验(如注入幅值10-15%Vdc)和典型性能指标(位置误差<0.2rad)。特别强调工程实现要点:PWM同步时

xiaoheshang_123的博客 167

彻底理解非阻塞IO(NIO)

BIO和NIO是基于Linux的后端开发的重要IO模型.非阻塞IO, 由于还有一个AIO(Asynchronous I/O, 异步IO), 为了区分, NIO也被称为同步非阻塞IO,现在常用的NIO, 是NIO+IO多路复用(IO Multiplexing)的结合体, epoll还是会阻塞线程, 不是真正的非阻塞IO了.阻塞IO, 为了和NIO保持队形, 也被称为同步阻塞IO.不同操作系统对于IO模型的支持程度不同,

lic721的博客 7057

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

【Linux网络】五种IO模型与非阻塞IO

本文聚焦操作系统与 Linux 网络编程核心 IO 体系,从 IO 最底层的本质概念入手,拆解 IO 执行的两大核心步骤,解释 IO 运行低效的根本原因,并给出优化 IO 效率的核心思路。为了降低抽象概念的理解难度,全文采用钓鱼生活化案例类比业界通用的五种经典 IO 模型,依次讲解阻塞 IO、非阻塞 IO、信号驱动 IO、IO 多路复用、异步 IO 的运行逻辑,同时精准界定同步 IO 与异步 IO的判定标准,搭配可直接编译运行的 C 语言代码完成信号驱动 IO、POSIX 异步 IO 的实操演示。

艾莉丝努力练剑的博客,感谢大家一直以来的对艾莉丝的支持和鼓励,uu们如果对C/C++方向感兴趣的话,可以通过艾莉丝主页的微信名片加艾莉丝的好友,和努力练剑的艾莉丝一起加油! 2697

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

BIO、NIO、AIO 与 IO 多路复用:select、poll、epoll 详解

IO 多路复用的目标是:“让一个线程同时监听多个文件描述符,当其中某些就绪时再处理。selectpollepoll它们本质上都在做同一件事:帮助应用进程找到“哪些连接现在可以读写”。BIO:一个线程盯一个连接,简单但浪费NIO:线程不阻塞,可以管理多个连接IO 多路复用:帮助线程高效发现哪些连接就绪epoll:Linux 高并发场景下最常见的选择AIO:由系统在完成后主动通知,更偏异步如果你后面准备继续学 Netty、Reactor 模型,这一篇就是基础前置。

小红的布丁的博客 935

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

【Linux网络】深入理解五种IO模型与非阻塞IO:从本质到实战

做后端开发的朋友们应该都有一个共识:绝大多数服务的性能瓶颈,最终都会落到 IO 上。无论是网络请求的收发、磁盘文件的读写,还是数据库的交互,本质上都是在处理 IO 操作。很多人写代码时只会用默认的阻塞 IO,遇到高并发就只会无脑加线程,结果线程越开越多,上下文切换的开销反而拖垮了整个系统。其实问题的根源在于没有真正理解 IO 的本质,也不知道操作系统提供了哪些更高效的 IO 模型。本文将从最底层的 IO 本质讲起,用大家都能听懂的 “钓鱼” 类比,带你彻底搞懂 Linux 下的五种 IO 模型,最后通过实战

会当凌绝顶,一览众山小 7740

linux基础——linux下五种IO模型小结(阻塞IO、非阻塞IO、IO复用、信号驱动式IO、异步IO)

一、阻塞IO模型 (同步I/O) 阻塞IO是指进程进行IO操作的时候,因为数据没准备好或者缓冲区里没有空间而无法进行IO操作会进入睡眠,直到数据准备或者缓冲区有空间才回被唤醒的行为。阻塞IO是最通用的IO类型,所有套接字默认情况下都是阻塞的。 输入操作:read、readv、recv、recvfrom和recvmsg,调用这些输入函数之一,如果缓冲区没有数据可读,该进程会投入睡眠,直到

千里之行,始于足下 3329

Linux/Unix I/O模型全解析:阻塞、非阻塞、多路复用、信号驱动、异步I/O与Reactor/Proactor模式及select/poll/epoll实践

本文全面解析Linux/Unix系统中的I/O模型,包括阻塞I/O、非阻塞I/O、I/O多路复用、信号驱动I/O和异步I/O,详述内核数据准备与拷贝的两个阶段。深入探讨Reactor模式(单/多Reactor单/多线程)与Proactor模式的实现、优缺点及应用场景,对比select、poll、epoll的性能与机制,并分析BIO、NIO、AIO及信号驱动I/O的特点。为高性能网络编程提供理论基础与实践参考

weixin_45922644的博客 1340

网络编程卡壳?五种IO模型+select/poll一篇讲透,搞懂网络编程核心,阻塞/多路复用不再懵!

本篇将介绍五种IO模型,以及对其中多路转接IO的select,poll进行介绍,使用,总结等。

羑悻的博客. 1万+

同步,异步,阻塞,非阻塞,IO,协程,websocket

同步,异步,阻塞,非阻塞,IO 同步与异步 同步就是在发出一个调用时,在没有得到结果之前,该调用就不会返回。但是一旦调用返回,就得到返回值了,换句话说,同步是指调用者主动等到这个调用的结果。 异步是指在调用发出之后,这个调用就直接返回了,没有直接返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果,调用者也不会等待调用结果。而是在调用发出后,被调用者会主动通知调用者,或者通过回调函数来处理这个调用结果。 阻塞和非阻塞 阻塞调用是指调用结果返回之前,调用程序会一直进行等待。 非阻

zy010101博客 2315

多路复用

讲多路复用先我觉得有必要讲一下什么是阻塞IO、非阻塞IO、同步IO、异步IO这几个东西;linux的五种IO模型: 1)阻塞I/O(blocking I/O) 2)非阻塞I/O(nonblocking I/O) 3) I/O复用(select和poll)(I/O multiplexing) 4)信号驱动I/O(signal driven I/O (SIGIO)) 5)异步I/O(asyn...

WuYujun's blog 1万+
上一篇: 分布式一致性算法
下一篇: 尾递归
wongson
博客等级 码龄19年 76粉丝 59原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值