当前位置: 首页 > Linux

图解LinuxIO模型及相关技术

时间:2023-04-06 11:35:18 Linux

阻塞IO模型(BlockingI/O)Linux内核最初提供了读写阻塞操作。客户端连接时,会在对应进程的文件描述符目录下(/proc/进程号/fd)生成对应的文件描述符(0标准输入;1标准输出;2标准错误输出;),如fd8,自由度9;当应用程序需要读取时,通过系统调用read(fd8)读取。如果数据还没有到达,这个应用程序的进程或线程就会阻塞等待。man2阅读概述#includessize_tread(intfd,void*buf,size_tcount);descriptionread()从文件描述符fd中读取count个字节的数据,并将它们放入从buf区域开始的缓冲区中。如果计数为零,则read()返回0,并且不执行其他操作。如果计数大于SSIZE_MAX,则结果将不可预测。当返回值成功时,它返回读取的字节数(零表示文件是读取描述符),这个返回值受限于文件中剩余的字节数。当返回值小于指定的字节数时,不代表错误;这可能是因为当前可以读取的字节数小于指定的字节数(例如接近文件末尾,或者正在从管道或终端读取数据,或者read()被信号中断)。当发生错误时,返回-1,并将errno设置为相应的值。在这种情况下,无法知道文件偏移位置是否发生了变化。问题如果有很多客户端连接,例如1000,那么应用程序将启用1000个进程或线程阻塞等待。这时候就会出现性能问题:CPU会不断切换,导致进程或线程上下文切换的开销,实际读取IO时间的比例会下降,造成CPU算力的浪费。因此,促进了非阻塞I/O的诞生。非阻塞IO模型(non-blockingI/O)此时,Linux内核最初提供读写非阻塞操作,可以通过socket设置SOCK_NONBLOCK标志。这时,应用程序不需要一个线程来处理每个文件描述符。只能有一个线程不断轮询读取read。如果没有数据到达,则直接返回。如果有数据,可以调度处理业务逻辑。man2socketSinceLinux2.6.27,类型参数有第二个目的:除了指定套接字类型外,它还可以包括以下任何值的按位或,以修改socket()的行为:SOCK_NONBLOCK设置O_NONBLOCK新文件描述符引用的打开文件描述(请参阅open(2))上的文件状态标志。使用此标志可节省对fcntl(2)的额外调用以实现相同的结果。从这里可以看出socketLinux2.6.27内核开始支持非阻塞模式。问题是一样的,当客户端连接很多的时候,比如1000个,就会触发1000个系统调用。(1000次系统调用的开销也是很客观的)所以就有了select。I/O多路复用模型(I/Omultiplexing)——select这时候Linux内核一开始就提供了select操作,可以将1000个系统调用简化为一个系统调用,轮询发生在内核空间。select系统调用会返回可用的fd集合,应用只需要遍历可用的fd集合读取数据进行业务处理即可。man2selectSYNOPSIS#includeintselect(intnfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout);说明select()允许程序监视多个文件描述符,等待一个或多个文件描述符为某类I/O操作“准备好”(例如,可能的输入)。如果可以在不阻塞的情况下执行相应的I/O操作(例如,read(2)或足够小的write(2)),则认为文件描述符就绪。select()只能监听小于FD_SETSIZE的文件描述符个数;poll(2)和epoll(7)没有这个限制。查看错误。支持传输的多个文件描述符由内核轮询。虽然问题从1000次系统调用减少到1次系统调用的开销,但是在系统调用开销中需要传递1000个文件描述符作为参数。这样也会造成一定的内存开销。于是就有了epoll。select()只能监听小于FD_SETSIZE的文件描述符个数;poll(2)和epoll(7)没有这个限制。SeeBUGS.IO复用模型(I/Omultiplexing)-epollmanepollman2epoll_createman2epoll_ctlman2epoll_waitepoll:SYNOPSIS#includeDESCRIPTIONepollAPI执行类似poll(2)的任务:监视多个文件描述符以查看是否可以在其中任何一个上进行I/O。epollAPI可以用作边缘触发或水平触发的接口,并且可以很好地扩展到大量监视的文件描述符。epollAPI的核心概念是epoll实例,这是一种内核数据结构,从用户空间的角度来看,可以将其视为两个列表的容器:?兴趣列表(有时也称为epoll集):进程已注册监视兴趣的文件描述符集。?就绪列表:set的I/O“准备好”的文件描述符。就绪列表是感兴趣列表中文件描述符的子集(或者更准确地说,是一组引用)。由于这些文件描述符上的I/O活动,内核会动态填充就绪列表。epoll_create:内核将生成一个epoll实例数据结构并返回一个文件描述符epfdepoll_ctl:注册、删除或修改文件描述符fd及其监听事件epoll_eventEventepoll_event。SYNOPSIS#includeintepoll_ctl(intepfd,intop,intfd,structepoll_event*event);DESCRIPTION该系统调用用于添加、修改或删除epoll(7)文件描述符epfd引用的实例。它请求对目标文件描述符fd执行操作op。op参数的有效值是:EPOLL_CTL_ADD添加一个条目到epoll文件描述符epfd的兴趣列表。该条目包括文件描述符、fd、对相应打开文件描述的引用(参见epoll(7)和open(2)),以及在event中指定的设置。EPOLL_CTL_MOD将兴趣列表中与fd关联的设置更改为事件中指定的新设置。EPOLL_CTL_DEL从兴趣列表中删除(注销)目标文件描述符fd。事件参数被忽略并且可以为NULL(但见下文BUGS)。epoll_wait:阻塞等待注册的事件发生,返回事件数,将触发的可用事件写入epoll_events数组扩展https://www.zhihu.com/questio。..https://programmer.group/5dc6...其他IO优化技巧man2mmapman2sendfileman2forkmmap:就是在用户的虚拟地址空间中找一个空闲的地址来对文件进行操作而不调用read和write的系统调用,其最终目的是将磁盘中的文件映射到用户进程的虚拟地址空间,实现用户进程对文件的直接读写,减少文件拷贝的开销,提高访问效率用户的。以阅读为例:深入剖析mmap原理——从三个关键问题出发:https://www.jianshu.com/p/eec...使用场景中kafka的数据文件是mmap,而写入文件可以不用从用户空间拷贝到内核,内核空间直接放在磁盘上。再比如Java中的MappedByteBuffer底层是Linux中的mmap。sendfile:sendfile系统调用直接在两个文件描述符之间传递数据(完全在内核操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,操作效率非常高,称为零拷贝。kafka等使用场景,消费者消费时,kafka直接调用sendfile(Java中的FileChannel.transferTo)从内存或数据文件中读取内核数据直接发送到网卡,无需经过用户空间两次Copy,至实现所谓的“零拷贝”。又如Tomcat、Nginx、Apache等Web服务器返回静态资源等,通过网络发送数据,都是使用sendfile。forkman2fork创建子进程的三种方式:fork,调用后,子进程有自己的pid和task_struct结构,并基于父进程复制所有数据资源,主要是复制自己的指针,不复制parentprocess虚拟内存空间,父子进程同时执行,变量相互隔离,互不干扰。现在Linux采用了写时复制(COW,copy-on-write)技术。为了减少开销,一开始fork不会真的产生两个不同的副本,因为那个时候,大量的数据其实是完全一样的。写时复制推迟了实际的数据复制。如果写确实发生在后面,说明父进程和子进程的数据不一致,于是发生复制动作,每个进程得到自己的副本,可以减少系统调用的开销。在Linux下,fork()是使用写时复制页面实现的,因此它带来的唯一损失是复制父页表以及为子进程创建唯一任务结构所需的时间和内存。vfork,vfork系统调用不同于fork。vfork创建的子进程与父进程共享地址空间,也就是说子进程完全运行在父进程的地址空间中,即子进程对虚拟地址空间的任何数据修改也是与父进程相同。进程看到了什么。并且vfork结束子进程后,父进程被阻塞,等待子进程结束再继续。clone可以看作是fork和vfork的混合使用。哪些资源共享,哪些资源复制,由用户参考clone_flags的设置来决定。标志CLONE_VFORK决定了子进程执行时父进程是阻塞还是运行。如果未设置该标志,则父进程和子进程同时运行。如果设置了标志,则父进程挂起,直到子进程结束。总结一下fork的目的一个进程想把自己复制一份,这样父子进程就可以同时执行不同的段代码。比如redis的RDB持久化,使用fork保证copy副本准确快速,不影响父进程继续提供服务。vfork的目的用vfork创建的进程的主要目的是使用exec函数先执行另一个程序。clone的目的是有选择地设置哪些资源需要父子进程共享,哪些资源需要复制。@SvenAugustus(https://www.flysium.xyz/)更多详情请关注微信公众号【编程不分离】: