unix环境高级编程--IO

1.IO模型

a) IO操作分为两步:1.等待数据(可以复制了);2.从内核复制准备好的数据

b) 【同步IO】包括【阻塞式IO】、【非阻塞式IO】、【IO复用】和【信号驱动式IO】。这四种IO的区别在于第一阶段——等待数据。阻塞式IO是一直傻等,非阻塞式IO如果数据未准备好,那么返回错误。IO复用打包一众非阻塞式描述字,大家一块儿等,直到有一个描述字准备好了。信号驱动式IO,安装信号,由信号来告诉进程数据准备好了。但是,但是他们都需要进程自己去将数据复制到内核或从内核将数据复制到自己的用户空间

c) 【异步IO】发送IO请求后,就不用等了,也不再需要发送IO请求获取结果了。等到通知后,其实是系统帮你把数据读取好了的,你等到的通知也不再是要求你去读写IO了,而是告诉你IO请求过程已经结束了

d) 附图:

 

e) 【同步异步和阻塞非阻塞

i. 同步异步关注的是:是不是进程自己执行了那个真正的IO操作。

ii. 阻塞非阻塞关注的是获取结果的过程中有没有等待:

1. 我是等待直至调用结果返回,那么就是阻塞的;我只是查询一下,没结果也返回,那就是非阻塞的。

2. 阻塞:会使本进程线程投入睡眠,但不占用cpu时间,如果需要阻塞的内容多,维护的线程和进程会很多,而服务器维护的线程和进程个数是有上限的。即一个线程只能处理一个流的IO事件效率低非阻塞:不会将本线程投入睡眠,但是逻辑层面的轮询会导致该线程进程没干什么事,但是占用cpu资源。

3. 阻塞:内核对于IO事件的处理是阻塞或者唤醒;非阻塞模式下则是把IO时间交给其他对象(selectepoll等)处理甚至直接忽略。

f) 线程同步异步阻塞非阻塞

i. 单线程同步阻塞:只有一个人,在那儿傻等办事结果,获取结果后返回

ii. 单线程同步非阻塞:只有一个人,在那儿看有没有结果,有无结果都立刻走

iii. 单线程异步非阻塞:只有一个人,在那儿通告了一下请求,然后走人,不关心、不办事,异步IO会办完事,获取结果,然后将【已经获取了结果】这个事实通知进程

iv. 多线程同步阻塞:多个人,派其中一个人,在那儿专人傻等结果返回

v. 多线程同步非阻塞:多个人,派其中一个人,在那儿看有没有结果,有无结果都立刻走人

vi. 多线程异步非阻塞:多个人,派其中一个人,然后走人,不关心、不主动要取结果


2.IO缓冲区

a) 包括用户缓冲区和内核缓冲区。用户将数据先读入到用户缓冲区,然后写入内核缓冲区;内核缓冲区将数据先写入用户缓冲区,然后用户读。从内核写数据到用户,从用户写数据到内核,都要用到系统调用,很慢的。所以引入缓冲区以减少频繁IO操作而引起频繁的系统调用。(【系统调用】:是内核中的一些内建函数。如readwrite都调用内核中的一些系统调用)

b) 【文件描述符的唤醒】:要不就是【内核缓冲区】满了,不能再写了,要读了,要不就是内核缓冲区空了,用户缓冲区该往里面写东西了,不能再读了。描述符的唤醒也就内核缓冲区满空或达到了规定可以读写的线

c) 有缓存IO和无缓存IO有无缓存是对用户空间而言的,内核一直都有

i. 【从内核缓存区写入磁盘的操作才是真正的IO操作】。

ii. readwrite是无缓存IO,即用户空间中无缓存,内核空间中还是有缓存的。无缓存IO操作数据流向:数据---->内核缓冲区----->磁盘

iii. 有缓存IO,即用户空间中有缓存,内核空间中一直是有缓存的。缓冲区的建立是为了减少调用readwrite这种系统调用的次数。有缓存IO操作数据流向:数据---->用户缓存区----->内核缓存区------>磁盘


3.文件IO与标准IO

1.标准IO库与文件IO的区别

a) 文件IO与标准IO的应用场景

i. socket还是用readwrite吧,毕竟返回的是文件描述符,即文件已经在内存中打开了。标准IO需要的是文件路径,然后打开文件在内存中开辟缓冲区进行操作,操作完之后在往回读写。【主要还是看需要操作的对象是文件描述符,还是文件路径(流)】

b) 【文件IO】最为原始的调用系统调用来处理文件的那批IO

i. 用于读写磁盘文件,常见的read(stockfd文件描述符)writeopen

ii. 文件IO操作的是文件描述符

iii. 文件IO默认不带有缓冲区

c) 【标准IOstdio.h中的函数。麻烦的是只有当fclose时才会写入文件

i. 默认采用缓冲机制stdinstdoutstderrfopen。全缓冲(填满用户缓冲区才将内容输入到内核缓冲区)、行缓冲(当遇到换行符或者缓冲区满才执行IO操作)、不缓冲(stderr,立即执行,需要尽快输入输出)。

ii. 标准IO操作的是缓冲区,操作的是

iii. 标准IO在打开的时候开辟一个缓冲区,所有操作是对缓冲区中的内容进行读写,fclose后将缓冲区中的内容写回磁盘文件

d) 关系

i. 我觉得标准IOfopenfclose的时候是使用到了文件IO的函数或者直接是调用系统调用在内核执行真正的IO操作。而在一般的fseek这些操作时是对缓冲区内容进行的操作,并没有执行真正的IO操作,所以相对效率会高一些。而文件IO是直接调用系统调用进行了真正的IO操作,所以频繁调用会慢一些

ii. 论操作效率应该是标准IO高,毕竟缓冲区。论实时性那么是文件IO高,毕竟一直读写磁盘文件

2.文件IOreadwrite

a) readwrite系【文件IO

i. 【readwrite】的实现立即调用系统调用将数据放入内核缓冲区,等内核缓冲区满,将缓冲区中内容写入磁盘

ii. 【readnwriten】的实现是加入了用户空间的缓冲区,减少调用readwrite的次数,用户缓冲区中的内容输入到系统缓冲区,系统缓冲区将内容写入磁盘

1. 有一点如果readnwriten的参数缓冲区是静态的,全局的或者是进程的共享资源,那么readnwriten就是线程不安全的。如果他的缓冲区是由malloc开辟,那么进程fork的时候由于只是复制了指针,所以这个readnwriten连进程间也是不安全的

2. 

3.标准IO的缓冲

a) 全缓冲、行缓冲和不缓冲


4.IO复用【文件IO范畴】

a) select

i. 【功能】阻塞调用,打包多个描述字,等待这些描述字中的任何一个准备就绪

ii. 【内部流程

1. fd集合从用户空间拷贝到内核空间

2. 内核遍历fd集合

a) 得到结果

b) 未得到结果,将进程投入睡眠,超时后唤醒进程,再遍历一遍

3. fd的状态结果从内核区拷贝到用户空间

iii. 【缺点

1. 描述字有最大的限制:本质应该是用32位来表示轮询的最大范围。可以通过设置FD_SETSIZE的大小来更改最大的描述字范围。

2. 【拷贝受监视的fd】:每次调用select,都需要把fd集合从用户区拷贝到内核区,这个开销在fd很多的时候很大。从select返回时,也会把fd集合(状态),从内核拷贝到yoghurt空间。

3. 【轮询】:每次调用select,都需要在内核遍历传递进来的fd,这个开销在fd很多时很大。

iv. 【其他Maxfdp1=最大描述字+1。待测试描述字{0,1,4,5}那么maxfdp1就是6maxfdp1是指描述字的个数而不是描述字编号,故+1)。

b) poll

i. pollselect的实现相似,只知道poll使用的描述字结构和select使用的描述字结构不一样,可以获取其他信息。其余的不太清楚。

c) epoll

i. 【工作原理1】:不同于无差别轮询,epoll会把哪个流发生了怎样的IO事件通知我们。此时我们对这些流的操作都是有意义的。

ii. epoll_create 创建一个epoll对象,一般epollfd = epoll_create()

iii. epoll_ctl epoll_add/epoll_del的合体),往epoll对象中增加/删除某一个流的某一个事件(包括事件类型,触发方式,描述字等)

iv. 比如

1. epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注册缓冲区非空事件,即有数据流入

2. epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注册缓冲区非满事件,即流可以被写入

3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);等待直到注册的事件发生

a) epfd,这个epoll的描述字

b) *event,已经开辟好用户空间内存,内核用于将数据写在这上面的,这里使用了nmap内存共享技术,减少了描述字复制的开销

c) maxevents,最大的事件数量

d) timeout,阻塞超时时间

e) 返回值:已经就绪的描述符的数量。【只有就绪的描述符被返回了,置放在event中,且都按注册时的类型分了类,不在轮询更加高效】。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

4. (注:当对一个非阻塞流的读写发生缓冲区满或缓冲区空,write/read会返回-1,并设置errno=EAGAIN。而epoll只关心缓冲区非满和缓冲区非空事件)。

v. 水平触发式和边缘触发式

1. 水平触发式

a) 支持阻塞与非阻塞。只要对应的套接字缓冲区中有数据那么水平触发模式下,下一轮epoll_wait中还会获得该事件

2. 边缘触发式

a) 只支持非阻塞【以避免一个文件句柄的阻塞读/写操作把处理多个文件描述符的任务饿死了。这事件ABC都是等缓冲区D,事件A触发后,要是没有读完缓冲区,事件BC也就不会再触发,直到有人又往缓冲区中写东西了。epoll只关心缓冲区非满和缓冲区非空事件】。上一轮epoll获取了本事件,那么只要本次没读完数据,在下次到来事件之前,即使缓冲区中仍有未读完的数据,下一轮的epoll也不会获取该事件(通知)

vi. 优点

1. 【一个进程所能支持的最大描述符】:适用于大量并发链接中只有少量活跃的情况下的系统CPU利用率。

2. 【使用mmap加速内核与用户空间的消息传递】:Selectepoll都需要在内核和用户空间之间复制数据(将受监视的fd拷贝到内核空间以及),epoll使用了mmap(内存映射)技术,将内核和用户空间指向同一块内存(避免不必要的内存拷贝)。

3. 【IO效率不随FD数目增加而线性下降】:Select中内核对所有监视的描述符轮询,epoll中当被监视的描述符准备Ready时,内核会采用类似callback的回调机制激活描述符。只有"活跃"socket才会主动的去调用callback函数,其他idle状态socket则不会。




展开阅读全文

没有更多推荐了,返回首页