对于操作系统来说,如果要操作某个文件,通常是通过进程或者线程来先打开目标文件,再进行读写操作;
文件描述符
操作系统的底层实现是,先要“描述”,再“操作”,这个“描述”其实就是在操作系统内核中,为该文件存一个标识,这个标识就对应这个文件,就相当于对文件进行了一个抽象,存在了内核区文件描述符表,这个对文件的抽象就是文件描述符;
系统默认情况下会帮我们打开三个文件描述符,0代表是标准输入、1代表是标准输出、2代表标准Error。
每个程序可以打开的文件描述符是有上线的,每个操作系统不一致,我们可以通过 ulimit -a 命令查看默认的系统限制是多少;
系统默认情况下是有最高上限的,可以通过查看 /proc/sys/fs/file-max 文件得到上限值;
在C程序中,文件由文件指针或者文件描述符表示。ISO C的标准I/0库函数(fopen, fclose, fread, fwrite, fscanf, fprintf等)使用文件指针,UNIX的I/O函数(open, close, read, write, ioctl)使用文件描述符。
当拿到一个文件描述符都可以进行read或者write,但是具体的read和write却跟对应文件描述符的具体实现不同。比如socket的就是走网络,普通文件的就是走磁盘IO。
为了将不同的类型的I/O与对应的文件描述符绑定,则是需要不同的初始化函数的。普通文件就通过open函数,指定对应的文件路径,操作系统通过路径能够找到对应的文件系统类型,如ext4,fat等等。如果是网络呢,就通过socket函数来初始化,socket函数就通过(domain, type, protocol)来找到对应的网络协议栈,比如TCP/IP,UNIX等等。
缓冲区
操作系统在处理I/O时,为了最大限度的利用CPU,避免CPU由于等待I/O而白白的浪费掉运行周期,而将CPU从I/O工作中解放出来,DMA和通道都是为了解决这个问题的。不过CPU不可能完全被释放出来。 1、阻塞式
2、DMA
3、通道
阻塞与非阻塞
传统IO是阻塞式的,当一个线程调用read和write时,该线程被阻塞,直到有一些数据被读取或者写入,该线程在此期间不能执行其他任务,也就是在完成IO操作时,线程会被阻塞,所以服务器会为每一个客户端请求都提供一个独立的线程进行处理,当服务端有大量来自客户端的请求时,由于创建大量线程等原因,将导致性能急剧下降;
而非阻塞式的IO,读写数据是通过线程的通道进行的,若读写数据没有准备好,线程是可以进行其他任务的,通常线程的空闲时间,用于在其他通道上进行IO操作,这样单个线程可以处理很多来自客户端的IO请求,这大大减轻了服务端的压力;
选择器:是多路复用器,可以同时监控多个Channel的IO状况,通过Selector可以使单线程管理多个Channel,属于非阻塞IO的核心;
线程模型
引申阅读
1、https://blog.51cto.com/keren/170822
2、https://blog.csdn.net/u011555996/article/details/51208887
3、https://mp.weixin.qq.com/s/UWdZsvPsV46VLpr7qHjgjA
4、https://www.cnblogs.com/imstudy/p/9908791.html
5、https://www.jianshu.com/p/188ef8462100
6、https://www.cnblogs.com/crazymakercircle/p/9833847.html
7、https://www.cnblogs.com/crazymakercircle/p/10225159.html
8、http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf