高性能服务器io函数,操作系统中的I/O,及高性能IO模型

I/O(Input/Output)输入输出,总体图

5cf67fab6d61

一.操作系统与设备之间的IO

简单来说(详细的请看《现代操作系统》),操作系统通过设备驱动程序访问IO设备。方式有:

(1)轮询方式:

CPU主动在各种设备中轮询检查状态,有数据就IO。

(2)中断方式:

设备有数据的时候,发出中断,由CPU决定要不要响应中断,然后中断,去处理设备的IO。CPU不用经常轮询设备状态。被动接收中断就行。

(3)DMA直接存储器访问方式:

如果1个字节的数据中断一次,传1KB的数据得中断1024次,太浪费CPU时间,于是有了DMA方式,CPU只需要把开头和结束的地址告诉DMA,中间由DMA完成数据IO。CPU从字节干预解放到数据块的干预。

(4)通道控制方式:

DMA方式只能控制一个设备的一块数据,多块数据还是要CPU干预多次。于是有了通道来控制IO,它比DMA更强大,能控制多块数据,多个设备的IO,更加解放了CPU参与IO过程。

二.操作系统与用户进程间的IO

(进程中的线程才是CPU基本的执行/调度单元,下面用线程举例,用socket举例)

设备来的数据放在内核cache中,需要用户线程去内核cache中取数据,复制到自己进程的cache中。有5中读取数据方式:

(1)阻塞:

用户线程调用某些系统函数去内核取数据,直到数据到达内核cache前,该线程处于阻塞状态,等待数据到达。

(2)非阻塞

用户线程去取数据,不管内核cache有没有数据,都直接返回,可能拿到数据,也可能拿不到,不会使线程进入阻塞态。

(3)IO多路复用

多路就是一个线程管理多路IO,线程还是被阻塞调用,其中一路或几路IO有数据了就返回。需要线程遍历全部IO,判断是哪个IO有数据。

例如 socket 的 select() 函数,线程调用 select() 进入阻塞态,任何一个IO有数据了,线程就退出阻塞态,获得机会继续执行。

(4)信号驱动IO

给一个IO注册一个信号和信号触发的回调函数,一旦信号被触发,回调函数里读取数据。

例如给 socket 注册一个“可读”的信号,当数据来了,可读的时候,信号被触发,执行回调函数从内核cache复制数据到用户空间。

(5)异步IO

异步IO中,操作系统完成了数据从内核到用户空间的拷贝后,以信号的方式通知用户线程可以下一步操作。省去了用户线程阻塞下来拷贝数据的过程。

IO管理

假设一台服务器需要被1万个客户端连接。方法有:

(1)单路:

最简单的一个线程管理一个客户端的socket IO,那么需要1万的线程,假设每个线程占内存3MB,需要300G内存,单台服务器没那么大的内存,并且操作系统最大线程数有限制,unix下一个进程好像是最多只能开 4096 个线程。

(2)IO 多路复用:

socket一旦多起来,单路IO 就扛不住了,需要一个线程管理多个 socket IO,下面都是在一个线程内的情况。

(2.1)select

一个线程管理多个socket IO,调用 select() 进入阻塞态,任何一个IO有数据则返回,由于不知道是哪个 socket 有数据,需要遍历所有 socket fd 去判断,当1万个 socket 大部分都是有IO的时候,效率较高,如果只是那么几百个有IO,此方法效率较低。

(2.2)epoll 和 kqueue

epoll 是 linux 下的,kqueue 是 unix 下的。

由于 select 需要遍历全部的 socket fd,效率较低,于是有了 epoll, kqueue 方式,kqueue 管理多个IO,阻塞调用等待函数,当有一个或多个IO事件,kqueue 直接返回多个IO事件的 socket fd,不需要遍历全部 socket fd,效率较高。

假设一个 socket 连接的对象是 3 kb,8G的内存可以管理 280w 个连接。

select,epoll,kqueue 原理

已知的情况

内核中注册 socket 的 IO 中断处理的回掉函数,有 IO 了会回调该函数。

select:

select 管理多个 socket,select 收到一个来自网卡 IO 中断就返回,不知道这个中断对应是哪个 socket fd 的。需要用户线程遍历判断。

epoll:

epoll 收到一个 IO 中断,会去查找这个中断对应哪个 socket fd。

epoll 中建立一个红黑树(平衡二叉树的一种),红黑树查找很高效。

用户注册感兴趣的 socket 事件,就是把这个 socket fd 插入到红黑树中,用中断号做key,可以理解为(中断号,socket fd)的二元组。

用户移除事件就是,删除树上的某个节点。

然后收到一个IO中断,epoll 把网卡数据拷贝到内核cache,根据中断号在红黑树中查找对应的 fd,把 fd 加入到就绪链表中,准备返回给用户线程。用户直接得到就绪的 fd。

kqueue:

收到 socket IO 中断去哈希表中查找对应的 socket fd,再把它放到一个链表里,返回。

用户注册一个感兴趣的事件,就是往哈希表中添加一个 fd。

一些优化

Memory Map (mmap)

设备的数据到内核cache,再到用户空间,中间需要把内核数据复制到用户空间,比较耗时,那么直接用 mmap 把用户空间的地址映射到内核空间,省去了内核数据到用户空间的拷贝

if((src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) == MAP_FAILED) err_sys("mmap error for input");

if((dst = mmap(0, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fout, 0)) == MAP_FAILED) err_sys("mmap error for output");

memcpy(dst, src, statbuf.st_size);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值