Blocking, Non-blocking, Sync, Async 阻塞、非阻塞、同步、异步

阻塞,非阻塞,同步,异步 作者: 萧萧 链接: https://www.zhihu.com/question/19732473/answer/241673170 来源: 知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 IO 概念区分四个相关概念: 同步 (Synchronous) 异步( Asynchronous) 阻塞( Blocking ) 非阻塞( Nonblocking) 这四个概念的含义以及相互之间的区别与联系,并不如很多网络博客所写的那么简单, 通过举一些什么商店购物, 买书买报的例子就能讲清楚。 进程间通信的同步/异步, 阻塞/非阻塞首先强调一点, 网络上很多博文关于同步/异步, 阻塞非阻塞区别的解释其实都经不起推敲。 例如在 严肃 的这一高赞回答中 , 有如下解释 (不准确) : 同步/异步关注的是消息通信机制 (synchronous communication/ asynchronous communication) 。 所谓同步,就是在发出一个调用时,在没有得到结果之前, 该调用就不返回。异步则是相反,调用在发出之后,这个调用就直接返回了, 所以没有返回结果阻塞/非阻塞关注的是程序在等待调用结果 (消息,返回值) 时的状态.阻塞调用是指调用结果返回之前,当前线程会被挂起。 调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。粗一看, 好像同步/ 非同步, 阻塞/非阻塞 是两种维度的概念, 可以分别对待, 但是稍微推敲一下就会发现上述的解释存在不妥之处。如果“同步”是发起了一个调用后, 没有得到结果之前不返回, 那它毫无疑问就是被“阻塞”了 (即调用进程处于 “waiting” 状态) 。如果“异步”调用发出了以后就直接返回了, 毫无疑问, 这个进程没有被“阻塞”。 所以, 上述的解释是不准确的。 让我们看一下《操作系统概念 (第九版) 》中有关进程间通信的部分是如何解释的: 翻译一下就是: 进程间的通信是通过 send() 和 receive() 两种基本操作完成的。具体如何实现这两种基础操作,存在着不同的设计。 消息的传递有可能是阻塞的或非阻塞的 – 也被称为同步或异步的: 阻塞式发送 (blocking send) . 发送方进程会被一直阻塞, 直到消息被接受方进程收到。非阻塞式发送 (nonblocking send) 。 发送方进程调用 send() 后, 立即就可以其他操作。阻塞式接收 (blocking receive) 接收方调用 receive() 后一直阻塞, 直到消息到达可用。非阻塞式接受 (nonblocking receive) 接收方调用 receive() 函数后, 要么得到一个有效的结果, 要么得到一个空值, 即不会被阻塞。上述不同类型的发送方式和不同类型的接收方式,可以自由组合。也就是说, 从进程级通信的维度讨论时, 阻塞和同步 (非阻塞和异步) 就是一对同义词, 且需要针对发送方和接收方作区分对待。———- 下面对理解同步异步,阻塞非阻塞所需的知识点进行详细叙述———————先修知识用户空间和内核空间进程切换 系统调用 (system call) 中断 (interrupt) 进程的阻塞用户空间和内核空间操作系统为了支持多个应用同时运行,需要保证不同进程之间相对独立 (一个进程的崩溃不会影响其他的进程 , 恶意进程不能直接读取和修改其他进程运行时的代码和数据) 。 因此操作系统内核需要拥有高于普通进程的权限, 以此来调度和管理用户的应用程序。于是内存空间被划分为两部分,一部分为内核空间,一部分为用户空间,内核空间存储的代码和数据具有更高级别的权限。内存访问的相关硬件在程序执行期间会进行访问控制 ( Access Control) ,使得用户空间的程序不能直接读写内核空间的内存。有《微机原理》 课程基础同学可以 Google 搜索 DPL, CPL 这两个关键字了解硬件层面的内存访问权限控制细节进程切换上图展示了进程切换中几个最重要的步骤: 当一个程序正在执行的过程中, 中断 (interrupt) 或 系统调用 (system call) 发生可以使得 CPU 的控制权会从当前进程转移到操作系统内核。操作系统内核负责保存进程 i 在 CPU 中的上下文 (程序计数器, 寄存器) 到 PCBi (操作系统分配给进程的一个内存块) 中。从 PCBj 取出进程 j 的CPU 上下文, 将 CPU 控制权转移给进程 j , 开始执行进程 j 的指令。几个底层概念的通俗 (不严谨) 解释: 中断 (interrupt) CPU 微处理器有一个中断信号位, 在每个CPU时钟周期的末尾, CPU会去检测那个中断信号位是否有中断信号到达, 如果有, 则会根据中断优先级决定是否要暂停当前执行的指令, 转而去执行处理中断的指令。 (其实就是 CPU 层级的 while 轮询) 时钟中断( Clock Interrupt )一个硬件时钟会每隔一段 (很短) 的时间就产生一个中断信号发送给 CPU,CPU 在响应这个中断时, 就会去执行操作系统内核的指令, 继而将 CPU 的控制权转移给了操作系统内核, 可以由操作系统内核决定下一个要被执行的指令。系统调用 (system call) system call 是操作系统提供给应用程序的接口。 用户通过调用 systemcall 来完成那些需要操作系统内核进行的操作, 例如硬盘, 网络接口设备的读写等。从上述描述中, 可以看出来, 操作系统在进行进切换时,需要进行一系列的内存读写操作, 这带来了一定的开销: 对于一个运行着 UNIX 系统的现代 PC 来说, 进程切换通常至少需要花费 300 us 的时间进程阻塞上图展示了一个进程的不同状态: New. 进程正在被创建.Running. 进程的指令正在被执行Waiting. 进程正在等待一些事件的发生 (例如 I/O 的完成或者收到某个信号) Ready. 进程在等待被操作系统调度Terminated. 进程执行完毕 (可能是被强行终止的) 我们所说的 “阻塞”是指进程在发起了一个系统调用 (System Call) 后, 由于该系统调用的操作不能立即完成,需要等待一段时间,于是内核将进程挂起为等待 (waiting) 状态, 以确保它不会被调度执行, 占用 CPU 资源。友情提示: 在任意时刻, 一个 CPU 核心上 (processor) 只可能运行一个进程 。I/O System Call 的阻塞/非阻塞, 同步/异步这里再重新审视 阻塞/非阻塞 IO 这个概念, 其实阻塞和非阻塞描述的是进程的一个操作是否会使得进程转变为“等待”的状态, 但是为什么我们总是把它和 IO 连在一起讨论呢?原因是, 阻塞这个词是与系统调用 System Call 紧紧联系在一起的, 因为要让一个进程进入 等待 (waiting) 的状态, 要么是它主动调用 wait() 或 sleep() 等挂起自己的操作, 另一种就是它调用 System Call, 而 System Call 因为涉及到了 I/O 操作, 不能立即完成, 于是内核就会先将该进程置为等待状态, 调度其他进程的运行, 等到 它所请求的 I/O 操作完成了以后, 再将其状态更改回 ready 。操作系统内核在执行 System Call 时, CPU 需要与 IO 设备完成一系列物理通信上的交互, 其实再一次会涉及到阻塞和非阻塞的问题, 例如, 操作系统发起了一个读硬盘的请求后, 其实是向硬盘设备通过总线发出了一个请求,它即可以阻塞式地等待IO 设备的返回结果,也可以非阻塞式的继续其他的操作。 在现代计算机中,这些物理通信操作基本都是异步完成的, 即发出请求后, 等待 I/O 设备的中断信号后, 再来读取相应的设备缓冲区。 但是,大部分操作系统默认为用户级应用程序提供的都是阻塞式的系统调用 (blocking systemcall) 接口, 因为阻塞式的调用,使得应用级代码的编写更容易 (代码的执行顺序和编写顺序是一致的) 。但同样, 现在的大部分操作系统也会提供非阻塞I/O 系统调用接口 (Nonblocking I/O system call) 。 一个非阻塞调用不会挂起调用程序, 而是会立即返回一个值, 表示有多少bytes 的数据被成功读取 (或写入) 。非阻塞I/O 系统调用( nonblocking system call )的另一个替代品是 异步I/O系统调用 (asychronous system call) 。 与非阻塞 I/O 系统调用类似,asychronous system call 也是会立即返回, 不会等待 I/O 操作的完成, 应用程序可以继续执行其他的操作, 等到 I/O 操作完成了以后,操作系统会通知调用进程 (设置一个用户空间特殊的变量值 或者 触发一个 signal 或者 产生一个软中断 或者 调用应用程序的回调函数) 。此处, 非阻塞I/O 系统调用( nonblocking system call ) 和 异步I/O系统调用 (asychronous system call) 的区别是: 一个非阻塞I/O 系统调用 read() 操作立即返回的是任何可以立即拿到的数据, 可以是完整的结果, 也可以是不完整的结果, 还可以是一个空值。而异步I/O系统调用 read () 结果必须是完整的, 但是这个操作完成的通知可以延迟到将来的一个时间点。下图展示了同步I/O 与 异步 I/O 的区别 (非阻塞 IO 在下图中没有绘出) . 注意, 上面提到的 非阻塞I/O 系统调用( nonblocking system call ) 和 异步I/O系统调用 都是非阻塞式的行为 (non-blocking behavior) 。 他们的差异仅仅是返回结果的方式和内容不同。非阻塞 I/O 如何帮助服务器提高吞吐量考虑一个单进程服务器程序, 收到一个 Socket 连接请求后, 读取请求中的文件名,然后读请求的文件名内容,将文件内容返回给客户端。 那么一个请求的处理流程会如下图所示。R 表示读操作W 表示写操作C 表示关闭操作在这个过程中, 我们可以看到, CPU 和 硬盘IO 的资源大部分时间都是闲置的。 此时, 我们会希望在等待 I/O 的过程中继续处理新的请求。方案一: 多进程每到达一个请求, 我们为这个请求新创建一个进程来处理。 这样, 一个进程在等待 IO 时, 其他的进程可以被调度执行, 更加充分地利用 CPU 等资源。问题: 每新创建一个进程都会消耗一定的内存空间, 且进程切换也会有时间消耗, 高并发时, 大量进程来回切换的时间开销会变得明显起来。方案二: 多线程和多进程方案类似,为每一个请求新建一个线程进行处理,这样做的重要区别是, 所有的线程都共享同一个进程空间问题: 需要考虑是否需要为特定的逻辑使用锁。引申问题: 一个进程中的某一个线程发起了 system call 后, 是否造成整个进程的阻塞? 如果会, 那么多线程方案与单进程方案相比就没有明显的改善。解决办法1: 内核支持的线程 (kenerl supported threads) 操作系统内核能够感知到线程, 每一个线程都会有一个内核调用栈 (kenerl stack) 和 保存CPU 寄存器下文的 table 。在这种方案中, 如果 CPU 是多核的, 不同的线程还可以运行在不同的 CPU processor 上。 既实现了IO 并发, 也实现了 CPU 并发。问题: 内核支持线程可移植性差, 其实现对于不同的操作系统而言有所差别。解决办法2: 用户支持的线程 (user supported threads) 内核感知不到用户线程, 每一个用户的进程拥有一个调度器, 该调度器可以感知到线程发起的系统调用, 当一个线程产生系统调用时, 不阻塞整个进程, 切换到其他线程继续运行。 当 I/O 调用完成以后, 能够重新唤醒被阻塞的线程。实现细节: 应用程序基于线程库 thread libray 编写线程库中包含 “虚假的” read(), write(), accept()等系统调用。线程库中的 read(), write(), accept() 的底层实现为非阻塞系统调用 (Non-blocking system call) , 调用后,由于可以立即返回, 则将特定的线程状态标记为 waiting, 调度其他的可执行线程。 内核完成了 IO 操作后, 调用线程库的回调函数, 将原来处于 waiting 状态的线程标记为 runnable.从上面的过程可以看出,用户级支持线程 (User-Supported Threads) 的解决方案基于非阻塞IO系统调用( non-blocking system call) , 且是一种基于操作系统内核事件通知 (event-driven) 的解决方案, 该方案可以降低系统处理并发请求时的进程切换开销。 基于这个方案, 可以引申到更为宽泛的 event-driven progreamming 话题上。 但是这里就不作赘述了。总结: 阻塞/非阻塞, 同步/异步的概念要注意讨论的上下文: 在进程通信层面, 阻塞/非阻塞, 同步/异步基本是同义词, 但是需要注意区分讨论的对象是发送方还是接收方。发送方阻塞/非阻塞 (同步/异步) 和接收方的阻塞/非阻塞 (同步/异步) 是互不影响的。在 IO 系统调用层面 ( IO system call ) 层面, 非阻塞 IO 系统调用 和 异步 IO 系统调用存在着一定的差别, 它们都不会阻塞进程, 但是返回结果的方式和内容有所差别, 但是都属于非阻塞系统调用 ( non-blocing system call ) 2. 非阻塞系统调用 (non-blocking I/O system call 与 asynchronous I/O system call) 的存在可以用来实现线程级别的 I/O 并发, 与通过多进程实现的 I/O 并发相比可以减少内存消耗以及进程切换的开销。编辑于 03-02 ...

Created 13136-11-13 · Updated 9096-10-09 · 3 min · 607 words · -

IO多路复用, IO Multiplexing

“IO多路复用, IO Multiplexing” 什么是IO多路复用 I/O 多路复用技术会用一个系统调用函数来监听我们所有关心的连接,也就说可以在一个监控线程里面监控很多的连接。 一个用机场管理来解释的例子,以及对select、poll、epoll的讲解 IO 多路复用是什么意思? - 罗志宇的回答 - 知乎 有趣的比喻 这些名词比较绕口,理解涵义就好。一个epoll场景: 一个酒吧服务员 (一个线程) ,前面趴了一群醉汉,突然一个吼一声“倒酒” (事件) ,你小跑过去给他倒一杯,然后随他去吧,突然又一个要倒酒,你又过去倒上,就这样一个服务员服务好多人,有时没人喝酒,服务员处于空闲状态,可以干点别的玩玩手机。至于epoll与select,poll的区别在于后两者的场景中醉汉不说话,你要挨个问要不要酒,没时间玩手机了。io多路复用大概就是指这几个醉汉共用一个服务员。 作者: 匿名用户 链接: https://www.zhihu.com/question/32163005/answer/55687802 为什么要有IO多路复用 一个从本质上讲的清晰描述 要弄清问题 先要知道问题的出现原因 原因: 由于进程的执行过程是线性的(也就是顺序执行),当我们调用低速系统I/O(read,write,accept等等),进程可能阻塞,此时进程就阻塞 在这个调用上,不能执行其他操作.阻塞很正常. 接下来考虑这么一个问题: 一个服务器进程和一个客户端进程通信,服务器端read(sockfd1,bud,bufsize),此时客户端进程没有发送数据,那么read(阻塞调用)将 阻塞直到客户端调用write(sockfd,but,size)发来数据. 在一个客户和服务器通信时这没什么问题,当多个客户与服务器通信时,若服 务器阻塞于其中一个客户sockfd1,当另一个客户的数据到达 socket sockfd2时,服务器不 能处理,仍然阻塞在read(sockfd1,…)上;此时问题就出现了,不能及时处理另一个客户的服务,咋么办?I/O多路复用来解决! I/O多路复用:继续上面的问题,有多个客户连接,sockfd1,sockfd2,sockfd3..sockfdn 同时监听这n个客户,当其中有一个发来消息时就从select的阻塞中返回,然后就调用read 读取收到消息的sockfd,然后又循环回select阻塞;这样就不会因为阻塞在其中一个上而不能处 理另一个客户的消息 Q: 那这样子,在读取socket1的数据时,如果其它socket有数据来,那么也要等到socket1读取完了才能继续读取其它socket的数据吧。那不是也阻塞住了吗?而且读取到的数据也要开启线程处理吧,那这和多线程IO有什么区别呢? A: CPU本来就是线性的,不论什么都需要顺序处理,并行只能是多核CPU io多路复用本来就是用来解决对多个I/O监听时,一个I/O阻塞影响其他I/O的问题,跟多线程没关系. 跟多线程相比较,线程切换需要切换到内核进行线程切换,需要消耗时间和资源. 而I/O多路复用不需要切换线/进程,效率相对较高,特别是对高并发的应用nginx就是用I/O多路复用,故而性能极佳.但多线程编程逻辑和处理上比I/O多路复用简单.而I/O多路复用处理起来较为复杂. 作者总结 用自己的话解释清楚新的知识,这就是内化的过程。 【IO多路复用】和【多线程】是两种解决单个服务器应对多客户端同时IO请求阻塞问题的方案,问题出现的根源在于原始情况下,服务器收到客户端的进程的连接请求后都会调用阻塞的read()方法尝试从客户端读取数据,若读取不到则一直保持阻塞,但是这种处理方式显然会造成问题,譬如如果正在等待读取的这个客户端不传数据了,而其它有正在等待处理的客户端数据传输请求,那么显然就会造成服务器资源的浪费 (不能及时处理真正紧急的客户端请求,而浪费时间在暂时没有处理需求的客户端请求上) 。 解决这个问题有个简单的思路: 【多线程】: 即针对每一个客户端进程都新建一个新的服务器端线程,即一对一地应付客户端的通信需求。 不过这个解决方案有个问题就是: 如果通信的客户端很多,那么服务器就需要开很多线程来处理IO,服务器这边的压力就会比较大,除开开启线程与维持线程本身需要的资源以外,服务器CPU在线程之间切换也要耗时,导致效率低下。 因此,【IO多路复用】搞定了多线程解决方案的痛点,只用一个线程来解决阻塞问题,具体做法就是: 依然调用一个阻塞方法,这个阻塞方法会监听跟踪每一个IO流的状态,当有一个新的数据传输请求到来,就会通知服务器,然后服务器找到对应有需求的客户端,并读取它要传输的数据。这样,就不用开一大堆线程去一对一的监听IO状态变化了。 而select、poll、epoll三个东西都是上述思路的不同实现方式,并且是按照它们被列出的顺序被先后发明出来的,每个更新发明出来的方法都是在之前方法上做了一些改进。 poll在select的基础上,去掉了select给定的只能最多处理1024个客户端连接的限制,并不会再修改传入该方法的参数数组。epoll是在poll的基础上,使其变成了线程安全的 (不会因为通信过程中其它线程关掉了已经加入到select或者poll中的IO流而产生未知的后果) ,同时会告知服务端具体是哪个IO流来了数据,不需要靠服务器自己去找。 ———————————————— 版权声明: 本文为CSDN博主「蓝色枫魂」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接: https://blog.csdn.net/qq_32690999/article/details/80157034 ...

Created 2026-07-02 · Updated 2026-07-02 · 1 min · 132 words · -

sendfile

sendfile 在了解零拷贝之前,我们先来看看标准的的 I/O 操作. 传统 IO, 缓存 IO, Buffer I/O, 标准 IO 标准 IO 又被称作缓存 IO 或传统 IO, 大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存 (page cache) 中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。写的过程就是数据流反方向。 缓存 I/O 有以下这些优点: 缓存 I/O 使用了操作系统内核缓冲区,在一定程度上分离了应用程序空间和实际的物理设备。 缓存 I/O 可以减少读盘的次数,从而提高性能。 对于读操作: 当应用程序要去读取某块数据的时候,如果这块数据已经在页缓存中,那就返回之。而不需要经过硬盘的读取操作了。如果这块数据不在页缓存中,就需要从硬盘中读取数据到页缓存。 对于写操作: 应用程序会将数据先写到页缓存中,数据是否会被立即写到磁盘,这取决于所采用的写操作机制: 如果用户采用的是同步写机制 (synchronous writes), 那么数据会立即被写回到磁盘上,应用程序会一直等到数据被写完为止; 如果用户采用的是延迟写机制 (deferred writes), 那么应用程序就完全不需要等到数据全部被写回到磁盘,数据只要被写到页缓存中(内核的缓冲区)去就可以了。在延迟写机制的情况下,操作系统会定期地将放在页缓存中的数据刷到磁盘上。与异步写机制 (asynchronous writes) 不同的是,延迟写机制在数据完全写到磁盘上的时候不会通知应用程序,而异步写机制在数据完全写到磁盘上的时候是会返回给应用程序的。所以延迟写机制本身是存在数据丢失的风险的,而异步写机制则不会有这方面的担心。 传统 IO 的缺点 在缓存 I/O 机制中,DMA 方式可以将数据直接从磁盘读到页缓存中,或者将数据从页缓存直接写回到磁盘上,而不能直接在应用程序地址空间和磁盘之间进行数据传输,这样的话,数据在传输过程中需要在应用程序地址空间和页缓存之间进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。 当然也可以采用直接 I/O 技术来满足自缓存应用程序 ( self-caching applications) 的需求。 对于某些特殊的应用程序来说,能够绕开内核缓冲区能够获取更好的性能,这就是直接I/O出现的意义。 ...

Created 21216-04-21 · Updated 21216-04-21 · 2 min · 280 words · -

iostat

iostat command 通过 iostat 方便查看 CPU、网卡、tty 设备、磁盘、CD-ROM 等等设备的活动情况, 负载信息。 命令格式 iostat [参数] [时间] [次数] sudo pacman -S sysstat sudo yum install sysstat sudo apt install sysstat iostat -sxz 1 iostat 1 iostat -x 1 参数 -x Display extended statistics; 显示详细信息, 显示扩展统计信息(extended statistics),包括每个设备的利用率、服务时间等。 -C 显示CPU使用情况 -d 显示磁盘使用情况 -k 以 KB 为单位显示 -m 以 M 为单位显示 -N 显示磁盘阵列(LVM) 信息 -n 显示NFS 使用情况 -p [磁盘] 显示磁盘和分区的情况 -t 显示终端和CPU的信息 -V 显示版本信息 -s: 显示所有设备的汇总统计(summary) -z: 省略所有值为零的设备(即没有活动的设备)。 字段 rrqm/s: 每秒进行 merge 的读操作数目。即 delta(rmerge)/s wrqm/s: 每秒进行 merge 的写操作数目。即 delta(wmerge)/s r/s: 每秒完成的读 I/O 设备次数。即 delta(rio)/s w/s: 每秒完成的写 I/O 设备次数。即 delta(wio)/s rsec/s: 每秒读扇区数。即 delta(rsect)/s wsec/s: 每秒写扇区数。即 delta(wsect)/s rkB/s: 每秒读K字节数。是 rsect/s 的一半,因为每扇区大小为512字节。(需要计算) wkB/s: 每秒写K字节数。是 wsect/s 的一半。(需要计算) avgrq-sz: 平均每次设备 I/O操作的数据大小 (扇区)。delta(rsect+wsect)/delta(rio+wio) avgqu-sz: 平均I/O队列长度。即 delta(aveq)/s/1000 (因为aveq的单位为毫秒)。 await: 平均每次设备I/O操作的等待时间 (毫秒)。即 delta(ruse+wuse)/delta(rio+wio) svctm: 平均每次设备I/O操作的服务时间 (毫秒)。即 delta(use)/delta(rio+wio) %util: 一秒中有百分之多少的时间用于 I/O 操作, 或者说一秒中有多少时间 I/O 队列是非空的。即 delta(use)/s/1000 (因为use的单位为毫秒) Linux 系统中的 iostat 是I/O statistics (输入/输出统计) 的缩写,iostat工具将对系统的磁盘操作活动进行监视。它的特点是汇报磁盘活动统计情况,同时也会汇报出CPU使用情况。同vmstat一样,iostat也有一个弱点,就是它不能对某个进程进行深入分析,仅对系统的整体情况进行分析。iostat属于sysstat软件包。可以用yum install sysstat 直接安装。 ...

Created 10106-04-10 · Updated 10106-04-10 · 6 min · 1162 words · -

NIO

NIO JavaNIO 非堵塞应用通常适于I/O 读写等方面, 我们知道, 系统运行的性能瓶颈通常在I/O读写,包括对网络和磁盘的操作上, 过去,在打开一个I/O通道后,read()将一直等待在端口一边读取字节内容,如果没有内容进来,read()也是傻傻的等,这会影响我们程序继续做其他事情, 那么改进做法就是开设线程, 让线程去等待, 但是这样做也是相当耗费资源的(线程调度) Java NIO 非堵塞技术实际是采取 Reactor 模式, 或者说是 Observer 模式为我们监察I/O端口,如果有内容进来, 会自动通知我们,这样,我们就不必开启多个线程死等, 从外界看, 实现了流畅的I/O读写,不堵塞了。 Java NIO 出现不只是一个技术性能的提高,你会发现网络上到处在介绍它,因为它具有里程碑意义,从JDK1.4开始,Java开始提高性能相关的功能,从而使得Java在底层或者并行分布式计算等操作上已经可以和C或Perl等语言并驾齐驱。 IO 模型主要分类 同步 synchronous IO 异步 asynchronous IO 阻塞 blocking 非阻塞 (non-blocking) NIO 同步阻塞, blocking-IO, BIO 同步非阻塞, non-blocking-IO, NIO 异步阻塞: 不存在的… 异步非阻塞, Asynchronous-non-blocking-IO, AIO 同步, 异步 同步和异步关注的是消息通信机制 ( synchronous communication/ asynchronous communication ) 同步: 发送一个请求,等待返回, 再发送下一个请求,同步可以避免出现死锁,脏读的发生。 异步: 发送一个请求,不等待返回, 随时可以再发送下一个请求,可以提高效率,保证并发。 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。 举个通俗的例子: 你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了 (可能是5秒,也可能是一天) 告诉你结果 (返回结果) 。而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了 (不返回结果) 。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。 ...

Created 31316-08-31 · Updated 31316-08-31 · 2 min · 294 words · -

Go bufio

golang bufio bufio 对 io 进行了包装, 提供了缓冲. bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。 简单的说就是bufio会把文件内容读取到缓存中 (内存),然后再取读取需要的内容的时候,直接在缓存中读取,避免文件的i/o操作。同样,通过bufio写入内容,也是先写入到缓存中 (内存),然后由缓存写入到文件。避免多次小内容的写入操作I/O。 bufio.Read(p []byte) 的思路如下: 1、当缓存区有内容的时,将缓存区内容全部填入p并清空缓存区 2、当缓存区没有内容的时候且len(p)>len(buf),即要读取的内容比缓存区还要大,直接去文件读取即可 3、当缓存区没有内容的时候且len(p)<len(buf),即要读取的内容比缓存区小,缓存区从文件读取内容充满缓存区,并将p填满 (此时缓存区有剩余内容) 4、以后再次读取时缓存区有内容,将缓存区内容全部填入p并清空缓存区 (此时和情况1一样) https://www.cnblogs.com/ricklz/p/13188188.html http://www.cnblogs.com/golove/p/3282667.html // bufio 包实现了带缓存的 I/O 操作 type Reader struct { … } // NewReaderSize 将 rd 封装成一个带缓存的 bufio.Reader 对象, // 缓存大小由 size 指定 (如果小于 16 则会被设置为 16) 。 // 如果 rd 的基类型就是有足够缓存的 bufio.Reader 类型,则直接将 // rd 转换为基类型返回。 func NewReaderSize(rd io.Reader, size int) *Reader // NewReader 相当于 NewReaderSize(rd, 4096) func NewReader(rd io.Reader) *Reader ...

Created 16166-01-16 · Updated 16166-01-16 · 7 min · 1357 words · -

DMA

DMA DMA的基本定义 DMA,全称Direct Memory Access,即直接存储器访问。 DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。 DMA的主要特征 ·每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发,这些功能通过软件来配置。 ·在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推)。 ·独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。 ·支持循环的缓冲器管理。 ·每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。 ·存储器和存储器间的传输、外设和存储器、存储器和外设之间的传输。 ·闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标。 ·可编程的数据传输数目:最大为65535(0xFFFF)。 STM32F411x系列芯片DMA控制器 DMA的工作框图如下图所示。DMA控制器和Cortex™-M4核心共享系统数据总线,执行直接存储器数据传输。当CPU和DMA同时访问相同的目标(RAM或外设)时,DMA请求会暂停CPU访问系统总线达若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽。 DMA控制器传输作为AHB主设备操作直接存储器,它可以控制AHB总线的控制矩阵以启动AHB传送。它可以执行以下信息交换: •外设到内存 •内存到外设 •内存到内存 DMA控制器提供两个AHB主端口:AHB内存端口(用于连接到内存)和AHB外设端口(用于连接到外设)。 但是,为了允许内存到内存的传输,AHB外设端口也必须可以访问内存。AHB从端口用于对DMA控制器的编程控制(仅支持32位访问)。 DMA处理 对于两个DMA控制器的系统实现如下图所示。DMA1控制器AHB外设端口没有像DMA2控制器那样连接到总线矩阵,所以只有DMA2能够执行内存到内存的数据传输。 DMA事务由给定数量的数据传输序列组成,可以传输的数据项的数量及其宽度(8位,16位或32位)可以通过软件编程实现。每个DMA传输都包含三个操作步骤: ·从外设数据寄存器或者从当前外设/存储器地址寄存器指示的存储器地址取数据,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元; ·存数据到外设数据寄存器或者当前外设/存储器地址寄存器指示的存储器地址,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元; ·执行一次DMA_CNDTRx寄存器的递减操作,该寄存器包含未完成的操作数目。 通道选择 事件发生后,外设向DMA控制器发送请求信号,DMA控制器根据通道优先级来处理请求。当DMA控制器访问外围设备时,确认信号即由DMA控制器发送到外围设备,一旦外围设备从DMA控制器收到确认信号,它就会释放其请求,当外设取消了该请求后,DMA控制器将释放确认信号。 如果外设还有更多请求,它可以启动下一轮请求操作。 每个数据流流都与一个DMA请求相关,该DMA请求可以从8个可能的通道请求中选择,由DMA_SxCR寄存器中的CHSEL [2:0]位控制。 来自外设(TIM,ADC,SPI,I2C等)的8个请求独立连接到每个通道如下图所示: 仲裁器 仲裁器根据通道请求的优先级来启动外设/存储器的访问。优先权管理分2个阶段: ·软件:每个通道的优先权可以在DMA_CCRx寄存器中设置,有4个等级:最高优先级、高优先级、中等优先级、低优先级; ·硬件:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优先权。比如:如果软件优先级相同,通道2优先于通道4。 注意: 在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级。 DMA通道数据量 每个通道都可以在有固定地址的外设寄存器和存储器地址之间执行DMA传输。DMA传输的数据量是可编程的,最大达到65535(0xFFFF)。包含要传输的数据项数量的寄存器,在每次传输后递减。外设和存储器的传输数据量可以通过DMA_CCRx寄存器中的PSIZE和MSIZE位编程得到。 DMA传输模式 数据传输源和目的地都可以寻址整个4 GB区域中的外围设备和存储器,其地址介于0x0000 0000和0xFFFF FFFF之间。传输方向使用DMA_SxCR寄存器中的DIR [1:0]位进行配置,并提供三种可能性:存储器到外围设备,外围设备到存储器或存储器到存储器的传输。 外设到存储器模式 使能此模式时(通过将DMA_SxCR寄存器中的EN位置1),每次发生外设请求时,数据流都会启动传输从数据源来填充到FIFO。当达到FIFO的阈值水平时,FIFO的内容被清空并存储到目标地址。 当外设请求结束传输时(对于外设流控制器),或当DMA_SxNDTR寄存器达到零时或将DMA_SxNDTR寄存器中的EN位清零,则传输停止。 当赢得了相应流的仲裁时,该数据流通道才可以访问AHB源或目标端口,使用DMA_SxCR寄存器中的PL [1:0]位,为每个数据流通道的优先级进行仲裁。 存储器到外设模式 使能该模式时(通过将DMA_SxCR寄存器中的EN位置1),该数据流通道立即启动传输,以完全填充FIFO。每次发生外设请求时,FIFO的内容都会被清空并存储到目的地。当FIFO未满时,会从内存中重新加载数据。 当外设请求结束传输时(对于外设流控制器),或者当DMA_SxNDTR寄存器达到零时或将DMA_SxNDTR寄存器中的EN位清零,则传输停止。 当赢得了相应流的仲裁时,该数据流通道才可以访问AHB源或目标端口,使用DMA_SxCR寄存器中的PL [1:0]位,为每个数据流通道的优先级进行仲裁。 存储器到存储器模式 配置同上。存储器到存储器模式不能与循环模式同时使用。 循环模式 循环模式可用于处理循环缓冲区和连续数据流(例如ADC扫描模式)。 可以使用DMA_SxCR寄存器中的CIRC位来启用此功能。使能循环模式后,将在数据流通道配置阶段使用初始值自动装载要传输的数据,并且DMA请求将继续。 中断 每个DMA通道都可以在DMA传输过半、传输完成和传输错误时产生中断。为应用的灵活性考虑,通过设置寄存器的不同位来打开这些中断。 对于每个DMA数据流通道,可以在以下事件上产生中断: •达到半转移 •转移完成 •传输错误 ...

Created 17176-11-17 · Updated 17176-11-17 · 1 min · 171 words · -

缓冲IO, 缓冲I/O, buffered I/O

缓冲IO, 缓冲I/O, buffered I/O 在系统调用的函数中有 STDIN_FILENO 和 STDOUT_FILENO, STDERR_FILENO, 分别对应标准输入(一般使键盘),标准输出 (一般使显示器),标准错误,(他们都是非负整数,属于文件描述符)。一般在老程序中也将他们分别用 0,1,2 代替,但建议还是使用他们的别名。 在标准IO函数中还有stdin,stdout,stderr等是 FILE * 类型,是属于文件指针,属于标准I/O,高级的输入输出函数。在<stdio.h>中。 而STDIN_FILENO等是文件描述符,是非负整数,一般定义为0, 1, 2,属于没有buffer的I/O,直接调用系统调用, 在<unistd.h>zh 。 stdin是文件指针,是FILE *类型 STDIN_FILENO是文件描述符,是int类型的 stdout和STDOUT_FILENO同样。 所以使用fread(,,stdin),而read(STDOUT_FILENO),因为函数的参数类型是不一样的 二. 下面使网上的一篇文章,讲解缓冲IO和不带缓冲IO 首先,先稍微了解系统调用的概念: 系统调用,英文名system call,每个操作系统都在内核里有一些内建的函数库,这些函数可以用来完成一些系统系统调用把应用程序的请求传给内核,调用相应的的内核函数完成所需的处理,将处理结果返回给应用程序,如果没有系统调用和内核函数,用户将不能编写大型应用程序,及别的功能,这些函数集合起来就叫做程序接口或应用编程接口(Application Programming Interface,API),我们要在这个系统上编写各种应用程序,就是通过这个API接口来调用系统内核里面的函数。如果没有系统调用,那么应用程序就失去内核的支持。 现在,再聊不带缓存的I/O操作: linix对IO文件的操作分为不带缓存的IO操作和标准IO操作 (即带缓存),刚开始,要明确以下几点: 1:不带缓存,不是直接对磁盘文件进行读取操作,像read()和write()函数,它们都属于系统调用,只不过在用户层没有缓存,所以叫做无缓存IO,但对于内核来说,还是进行了缓存,只是用户层看不到罢了。如果这一点看不懂,请看第二点; 2:带不带缓存是相对来说的,如果你要写入数据到文件上时 (就是写入磁盘上),内核先将数据写入到内核中所设的缓冲储存器,假如这个缓冲储存器的长度是100个字节,你调用系统函数: ssize_t write (int fd,const void * buf,size_t count); 写操作时,设每次写入长度count=10个字节,那么你几要调用10次这个函数才能把这个缓冲区写满,此时数据还是在缓冲区,并没有写入到磁盘,缓冲区满时才进行实际上的IO操作,把数据写入到磁盘上,所以上面说的“不带缓存不是就没有缓存直写进磁盘”就是这个意思。 那么,既然不带缓存的操作实际在内核是有缓存器的,那带缓存的IO操作又是怎么回事呢? 带缓存IO也叫标准IO,符合ANSI C 的标准IO处理,不依赖系统内核,所以移植性强,我们使用标准IO操作很多时候是为了减少对read()和write()的系统调用次数,带缓存IO其实就是在用户层再建立一个缓存区,这个缓存区的分配和优化长度等细节都是标准IO库代你处理好了,不用去操心,还是用上面那个例子说明这个操作过程: 上面说要写数据到文件上,内核缓存 (注意这个不是用户层缓存区)区长度是100字节,我们调用不带缓存的IO函数write()就要调用10次,这样系统效率低,现在我们在用户层建立另一个缓存区 (用户层缓存区或者叫流缓存),假设流缓存的长度是50字节,我们用标准C库函数的fwrite()将数据写入到这个流缓存区里面,流缓存区满50字节后在进入内核缓存区,此时再调用系统函数write()将数据写入到文件 (实质是磁盘)上,看到这里,你用该明白一点,标准IO操作fwrite()最后还是要掉用无缓存IO操作write,这里进行了两次调用fwrite()写100字节也就是进行两次系统调用write()。 如果看到这里还没有一点眉目的话,那就比较麻烦了,希望下面两条总结能够帮上忙: 无缓存IO操作数据流向路径:数据——内核缓存区——磁盘 标准IO操作数据流向路径:数据——流缓存区——内核缓存区——磁盘 三. 下面是一个网友的见解,以供参考: 不带缓存的I/O对是文件描述符操作,下面带缓存的I/O是针对流的。 标准I/O库就是带缓存的I/O,它由ANSI C标准说明。当然,标准I/O最终都会调用上面的I/O例程。标准I/O库代替用户处理很多细节,比如缓存分配、以优化长度执行I/O等。 标准I/O提供缓存的目的就是减少调用read和write的次数,它对每个I/O流自动进行缓存管理 (标准I/O函数通常调用malloc来分配缓存)。它提供了三种类型的缓存: 1) 全缓存。当填满标准I/O缓存后才执行I/O操作。磁盘上的文件通常是全缓存的。 2) 行缓存。当输入输出遇到新行符或缓存满时,才由标准I/O库执行实际I/O操作。stdin、stdout通常是行缓存的。 3) 无缓存。相当于read、write了。stderr通常是无缓存的,因为它必须尽快输出。 ...

Created 28286-07-28 · Updated 28286-07-28 · 1 min · 133 words · -