Linux 中的五种IO模型
IO模型的选择在Linux网络编程中十分重要,在Unix/Linux环境中主要提供了五种不同的IO模型,分别是阻塞式IO、非阻塞式IO、IO多路复用、信号驱动式IO和异步IO。
通常一个输入操作包含两个不同阶段:
等待数据准备好
从内核向进程复制数据
例如,对于一个网络套接字上的输入操作,第一步通常涉及到发生系统调用,用户态切换到内核态并等待数据从网络中到达,当所有等待分组到达时,数据被复制到内核中的某个缓冲区。第二步则是将数据从内核缓冲区复制到应用进程缓冲区。
磁盘文件的IO比较特殊,内核采用缓冲区cache加速磁盘IO请求。因而一旦请求的数据到达内核缓冲区cache,对磁盘的write()操作立即返回,而不用等待将数据写入磁盘后再返回(除非在打开文件时指定了O_SYNC标志)。与之相对应的read()操作将数据从内核缓冲区cache移动到用户的缓冲区中,如果请求的数据不在内核缓冲区cache中,内核会让进程休眠,同时执行对磁盘的读操作。所以实际上在磁盘IO中,等待阶段是不存在的,因为磁盘文件并不像网络IO那样,需要等待远程传输数据。
阻塞式I/O模型
...
Linux内核之epoll模型
同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的。
这里主要讨论Linux环境下的network IO。
一 概念说明在进行解释之前,首先要说明几个概念:
用户空间和内核空间
进程切换
进程的阻塞
文件描述符
缓存 I/O
用户空间与内核空间现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。
针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间。
而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。
进程切换为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的 ...
Linux内核网络UDP数据包发送(三)—IP协议层分析
1. 前言Linux内核网络 UDP 协议层通过调用 ip_send_skb 将 skb 交给 IP 协议层,本文通过分析内核 IP 协议层的关键函数来分享内核数据包发送在 IP 协议层的处理,并分享了监控IP层的方法。
2. ip_send_skbip_send_skb 函数定义在 net/ipv4/ip_output.c 中,非常简短。它只是调用ip_local_out,如果调用失败,就更新相应的错误计数:
1234567891011121314int ip_send_skb(struct net *net, struct sk_buff *skb){ int err; err = ip_local_out(skb); if (err) { if (err > 0) err = net_xmit_errno(err); if (err) IP_I ...
Linux内核网络udp数据包发送(一)
1.前言本文首先从宏观上概述了数据包发送的流程,然后分析了协议层注册进内核以及被套接字的过程,最后介绍了通过套接字发送网络数据的过程。
2.数据包发送宏观视角从宏观上看,一个数据包从用户程序到达硬件网卡的整个过程如下:
1、使用系统调用(如 sendto,sendmsg 等)写数据2、数据分段socket顶部,进入socket协议族(protocol family)系统3、协议族处理:数据跨越协议层,这一过程(在许多情况下)转变数据(数据)转换成数据包(packet)4、数据传输路由层,这会涉及路由缓存和ARP缓存的更新;如果目的MAC不在ARP缓存表中,将触发一次ARP广播来查找MAC地址5、穿过协议层,packet到达设备无关层(设备不可知层)6、使用XPS(如果启用)或散列函数选择发送坐标7、调用网卡驱动的发送函数8、数据传送到网卡的 qdisc(queue纪律,排队规则)9、qdisc会直接发送数据(如果可以),或者将其放到串行,然后触发NET_TX类型软中断(softirq)的时候再发送10、数据从qdisc传送给驱动程序11、驱动程序创建所需的DMA映射,刹车网卡从RAM读 ...
Linux内核网络udp数据包发送(二)-UDP协议层分析
1.前言本文分享了Linux内核网络数据包发送在UDP协议层的处理,主要分析了udp_sendmsg和udp_send_skb函数,并分享了UDP层的数据统计和监控以及套接字发送大小的调优。
2.udp_sendmsg这个函数定义在net / ipv4 / udp.c,函数很长,分段来看。
2.1 UDP插入UDP udp_sendmsg corking是一项优化技术,允许内核将多个数据累积成一体的数据报发送。在用户程序中有两种方法可以启用此选项:
使用 setsockopt 系统调用设置socket的 UDP_CORK 选项
程序调用 send,sendto 或 sendmsg 时,带 MSG_MORE 参数
udp_sendmsg 代码检查 up->pending 套接字socket当前是否已被塞住(corked),如果是,则直接跳到 do_append_data 进行数据追加(append)。
1234567891011121314151617181920212223int udp_sendmsg(struct kiocb *iocb, struct ...
Linux内核网络中断下半部处理
网卡接收和发过数据在 Linux 内核中的处理过程,我们先来回顾一下网卡接收和发送数据的过程,如 图1 所示:
图1 网卡接收和发送数据过程
如上图所示,当网卡接收到从网络中发送过来的数据后,网卡会向 CPU 发起一个硬件中断。当 CPU 接收到网卡的硬件中断后,便会调用网卡驱动向内核注册的中断处理服务,如 NS8390网卡驱动 会向内核注册 ei_interrupt 中断服务。
由于在处理硬件中断服务时会关闭硬件中断,所以在处理硬件中断服务的过程中,如果发生了其他的硬件中断,也不能得到有效的处理,从而导致硬件中断丢失的情况。
为了避免这种情况出现,Linux 内核把中断处理分为:中断上半部 和 中断下半部,上半部在关闭中断的情况下进行,而下半部在打开中断的情况下进行。
由于中断上半部在关闭中断的情况下进行,所以必须要快速完成,从而避免中断丢失的情况。而中断下半部处理是在打开中断的情况下进行的,所以可以慢慢进行。
一般来说,网卡驱动向内核注册的中断处理服务属于 中断上半部,如前面介绍的 NS8390网卡驱动 注册的 ei_interrupt 中断处理服务,而本文主要分析网卡 中断下 ...
Linux内核网络协议栈accept函数剖析
1、应用层——accept 函数该函数返回一个已建立连接的可用于数据通信的套接字。
12345#include <sys/socket.h> int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); //返回:非负描述子——成功,-1——出错 /*参数sockfd是监听后的套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用一个与这个套接字关联的端口号, 比较特别的是:参数cliaddr和addrlen是一个结果参数,用来返回已连接客户的协议地址。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL*/
2、BSD Socket 层——sock_accept 函数1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768/* * For accept, we attem ...
Linux内核网络协议栈bind函数使用
socket 函数并没有为套接字绑定本地地址和端口号,对于服务器端则必须显性绑定地址和端口号。bind 函数主要是服务器端使用,把一个本地协议地址赋予套接字。
1、应用层——bind 函数123#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);/*sockfd是由socket函数返回的套接口描述字,第二个参数是一个指向特定于协议的地址结构的指针,第三个参数是该地址结构的长度*/
bind 函数的功能则是将socket 套接字绑定指定的地址。
2、BSD Socket 层——sock_bind 函数同样是通过一个共同的入口函数 sys_socket
1234567891011121314151617181920212223242526272829303132/* * Bind a name to a socket. Nothing much to do here since it's * the protocol's ...
Linux内核网络协议栈套接字的绑定、监听、连接和断开
1、套接字的绑定创建完套接字服务器端会在应用层使用bind函数进行套接字的绑定,这时会产生系统调用,sys_bind内核函数进行套接字。
系统调用函数的具体实现
12345678910111213141516171819202122SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen){ struct socket *sock; struct sockaddr_storage address; int err, fput_needed; sock = sockfd_lookup_light(fd, &err, &fput_needed); if (sock) { err = move_addr_to_kernel(umyaddr, addrlen, (struct sockaddr *)&address); if (err >= 0) { err = security_socket_bind(sock, ...
Linux内核网络协议栈数据包发送
由于在connect函数中涉及数据包的发送与接收问题,事实上,发送与接收函数不限于connect函数,所以这里单独剖析。
承前文继续剖析 connect 函数,数据包的发送和接收在 ip_queue_xmit 函数和 release_sock 函数中实现。本文着重分析 ip_queue_xmit 函数,下篇将补充分析 connect 函数剩下的部分。
值得注意的是:这些函数是数据包发送函数,在数据传输阶段,基本上都会调用该函数,因为connect涉及该函数,就放在这里介绍了,不意味着这个函数只属于connect下层函数。
1、网络层——ip_queue_xmit 函数123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107 ...