找回密码
 立即注册
首页 业界区 业界 你的程序为何卡顿?从LINUX I/O三大模式寻找答案 ...

你的程序为何卡顿?从LINUX I/O三大模式寻找答案

轩辕琳芳 昨天 22:45
I/O交互流程
在LINUX中,内核空间和用户空间都位于虚拟内存中。LINUX采用两级保护机制:0级供内核使用,3级供用户程序使用。每个进程都有独立的用户空间(0~3G),对其他进程不可见,而最高的1G虚拟内核空间则由所有进程和内核共享。
操作系统和驱动程序运行在内核空间,应用程序运行在用户空间。由于LINUX使用虚拟内存机制,两者之间不能直接通过指针传递数据。用户空间必须通过系统调用请求内核协助完成I/O操作。内核会为每个I/O设备维护缓冲区,而用户空间的数据可能被换出,因此内核无法直接使用用户空间的指针。
对于一个输入操作,进程发起I/O系统调用后,内核会先检查缓冲区是否有缓存数据。如果没有,则从设备读取数据;如果有,则直接将数据复制到用户空间。因此,网络输入操作通常分为两个阶段:
1)内核空间阶段:内核通过协议栈和设备驱动程序接收数据,并将其存储在内核缓冲区;
2)用户空间阶段:数据从内核缓冲区复制到用户进程的缓冲区后,用户进程即可处理这些数据。
1.png

I/O操作方式
在操作系统中,通常有三种主要的I/O操作方式,每种方式都有其独特的特性和适用场景。
阻塞I/O
阻塞I/O(Blocking I/O)是最简单的I/O模型。当进程发起I/O操作(如read或write)时,当前线程会被阻塞,直到I/O操作完成。这种模型是标准的同步I/O实现,例如POSIX标准中的默认read和write系统调用。
阻塞I/O的优点是实现简单,适合低并发的场景,因为内核已经对这些系统调用进行了高度优化。然而,在并发场景下,阻塞I/O的性能瓶颈会显现出来:每个I/O操作都会阻塞一个线程,导致内核需要频繁地进行线程切换,这会增加上下文切换的开销,降低处理器缓存的利用率,并可能使依赖线程本地存储(Thread-Local Storage, TLS)的代码性能下降。
2.png
  1. // 伪代码: 阻塞I/O
  2. socket = accept(); // 阻塞,直到新连接到达
  3. data = read(socket); // 阻塞,直到数据被读取
  4. process(data);
复制代码
非阻塞I/O
非阻塞I/O(Non-Blocking I/O)允许I/O操作在没有数据可用时立即返回,而不会阻塞执行线程。在非阻塞I/O模式下,如果数据未准备好,系统通常会返回一个错误码(如EAGAIN 或 EWOULDBLOCK),指示操作需要稍后重试。进程可以通过轮询监控多个文件描述符的就绪状态。
非阻塞I/O的优点是提高程序的并发性,因为它允许线程在等待I/O操作完成的同时,执行其他任务。然而,这种模式也带来了更高的编程复杂度,程序需要不断检查文件描述符的状态,以确保在数据可用时及时处理。这种轮询机制不仅增加了代码的复杂性,还可能导致处理器资源的浪费。
3.png

以下伪代码,展示了非阻塞I/O的执行过程。
  1. // 伪代码: 非阻塞I/O
  2. while (true) {
  3.     data = read(socket);
  4.     if (data != EAGAIN) {
  5.         process(data);
  6.         break;
  7.     }
  8.     // do other things...
  9. }
复制代码
异步 I/O
异步I/O (Asynchronous I/O)是一种真正的异步模型,进程在发起 I/O 操作后立即返回,并通过回调函数或事件通知机制在操作完成后得到通知。典型的实现包括Windows的OVERLAPPED和I/O完成端口(IOCP),以及LINUX的原生异步I/O(AIO)。需要注意的是,LINUX的原生AIO 仅对文件I/O有效,对网络I/O的支持有限。
异步I/O的优点是能够最大限度地提高并发性能,同时减少线程阻塞和上下文切换的开销。然而,异步I/O的实现和调试复杂度较高,且在某些平台上的支持不够完善。
4.png
  1. // 伪代码: 异步I/O
  2. // 定义一个I/O操作完成后的回调函数
  3. void on_read_complete(data, error) {
  4.     if (error) {
  5.         handle_error(error);
  6.     } else {
  7.         process(data);
  8.     }
  9.     // 可以在回调中发起下一次异步读
  10.     aio_read(socket, buffer, on_read_complete);
  11. }
  12. // 1. 主程序发起异步读操作,并注册回调函数
  13. // aio_read会立即返回,不会阻塞
  14. aio_read(socket1, buffer1, on_read_complete);
  15. aio_read(socket2, buffer2, on_read_complete);
  16. // 2. 主线程可以继续执行其他任务,或进入一个等待退出的循环
  17. do_other_work();
  18. event_loop_wait_for_shutdown(); // 例如,等待信号
复制代码
未完待续
很高兴与你相遇!如果你喜欢本文内容,记得关注哦

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册