根据计算机系统的工作原理, 我们知道了总体思路为: 从 CPU 到内存, 再到磁盘的过程. 内存分为: 内核空间和用户空间. 相应的 CPU 也分为内核模式和用户模式. 那一个用户级的计算机应用, 又如何的详细工作过程呢? 以下以 web 读文件为例:
Round1:
内核模式到用户模式的转换:
对于一次 IO 访问(这回以 read 举例), 数据会先被拷贝到操作系统内核的缓冲区中, 然后才会从操作系统内核的缓冲区拷贝到应用程序的缓冲区, 最后交给进程. 所以说, 当一个 read 操作发生时, 它会经历两个阶段:
1. 等待数据准备 (Waiting for the data to be ready)
2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process).
问题:
以上两个阶段, 用户空间一直处理阻塞状态, 即等待完成. 通过一个进程无法快速响应结束, 再接受另一个 Web 请求.
解决方案:
采用 prefork 模型(apache), 即多进程模式.
1. 控制权限的 httpd 进程, 监听 80 端口;
2. 主进程 (httpd) 管理服务子进程的调度, 派生和销毁, 响应用户的请求.
Round2:
问题:
多进程运作, 当进程过多的时候, 进程间切换会越发频繁. 但进程间切换开销又过大.
解决方案:
1.worker 模式(apache), 即多进程多线程模式.
优点: a. 线程切换是轻量级的, 线程间可以共享很多资源, 比如内存;
b. 单线程崩溃, 会造成父进程崩溃; 线程过多, 进行切换, 会造成线程抖动. 所以采用了多进程, 而不是完全依赖多线程.
2. 单进程绑定 CPU 解决, 直接杜绝了进程切换. nginx 支持.
Round3:
问题: 由于两次数据复制过程, 进程都处于阻塞状态, 浪费了进程数和相关资源.
解决方案: IO 优化.
Linux 系统产生了下面五种网络模式的方案:
-- 阻塞 I/O(blocking IO)
-- 非阻塞 I/O(nonblocking IO)
-- I/O 多路复用( IO multiplexing)
-- 信号驱动 I/O( signal driven IO)
-- 异步 I/O(asynchronous IO)
阻塞 I/O 模式: 内核准备数据和数据从内核拷贝到进程内存地址, 这两个过程应用进程都是阻塞的;
非阻塞 I/O 模式: 内核准备数据阶段不阻塞, 但是进程需要周期询问内核数据是否准备好; 然后再从内核拷贝到进程内存地址阶段是阻塞的;
I/O 多路复用: 两个过程都是阻塞的, 但是使用了 select 算法, 一个进程或者线程可以同时处理多个 I/O 请求; poll 算法也支持.
信号驱动 I/O: 同非阻塞 I/O 模式, 但是不用进程轮询, 而是事件驱动, 在内核数据准备好之后, 向进程通知, epoll 算法支持.
异步 I/O: 两个过程都不阻塞, 告知内核需要取的数据之后, 内核负责准备数据, 并拷贝到用户空间, 再通知进程. 而不是非阻塞里面的, 进程来询问内核.
备注:
a.apahce 除了 prefork,worker 模式, 还有就是 event 模式. event 模式, 即信号驱动 I/O 模式. 另外还优化了长连接.
基于长连接, 长期 keep-alive 在线, 浪费线程, 因此专门设定一个管理进程来管理这些长连接, 在特定条件下, 可以回收线程资源, 而不会出现空闲线程.
b.nginx 采用了异步 I/O 模式, 有效解决了 C10K 问题. 将系统性能提升了三倍以上.
另外 nginx 还使用了虚拟内存的机制, 调用了 mmap 函数, 即做了内存映射. 并不将磁盘的文件复制到内存中, 而是直接映射到内存中. 只需要一次复制突出, 而不需要两次.
来源: https://www.cnblogs.com/daiaiai/p/11073493.html