1.一个以太网接口接收发送到它的单播地址和以太网广播地址的帧。当一个完整的帧可用时,接口就产生一个硬中断,而且内核调用接口层函数leintr
2.leintr检测硬件,而且若是有一个帧到达,就调用leread把这个帧从接口转移到一个mbuf(各层之间传输数据都用这个)链中,构造单独的地址信息etherheaher。
etherinput检查结构etherheaher来判断接收到的数据的类型,根据以太网类型字段来跳转。对于一个IP分组,schednetisr调度一个IP软件中断,并选择IP输入队列,ipintrq。对于一个ARP分组,调度ARP软件中断,并选择arpintrq。并将接收到的分组加入到队列中等待处理。
3.当收到的数据报的协议字段指明这是一个TCP报文段时,ipintrq(经过协议转换表中的prinput函数)会调用tcpinp t进行处理.从mbuf中取ip,tcp首部,寻找pcb,发送给插口层
3.1个关于pcb
每一个线程的task_struct都有打开文件描述符,若是是sock类型还会关联到全局inpcb中.
listen某个fd时(new socket()=>bind(listen的端口)=>lisnten())在pcb中该fd是listen状态
3.2in_pcblookup
搜索它的整个Internet PCB表,找到一个匹配。彻底匹配得到最高优先级,包含通配的最小通配数量的优先级高。因此当同一个端口已经创建链接后有了外部地址和端口,再有数据会选择该插口。好比当140.252.1.11:1500来的数据直接就匹配到第三个的插口,其余地址的发送到第一个插口。
4.插口层
4.1 listen 若是监听插口收到了报文段
listen状态下只接收SYN,即便携带了数据也等创建链接后才发送,建立新的so,在收到三次握手的最后一个报文段后,调用soisconnected唤醒插口层acceptreact
4.2 插口层accept
while (so->so_qlen == 0 && so->so_error == 0) {tsleep((caddr_t)&so->so_timeo, PSOCK | PCATCH,netcon, 0))}
当so_qlen不是0,调用falloc(p, &fp, &tmpfd)建立新的插口fd,从插口队列中复制,返回,此时该fd也在线程的打开文件中了。调用soqremque将插口从接收队列中删除。
4.3 插口层read,send 从缓冲区复制mbuf。略
5.线程调用内核的accept,从调用开始会sleep直到此时能够返回fd。
【后文若用了epoll在lsfd有事件时通知线程再调用accept会节省调用到sleep时间。】linux
DMA:硬件到内存数据拷贝脱离cpu
CPU与外设之间的数据传送方式有程序传送方式(cpu轮询检查,执行输入指令(IN)或输出指令(OUT))、中断传送方式(外设主动,请求前外设与CPU能够并行工做,须要进行断点和现场的保护和恢复,浪费了不少CPU的时间,适合少许数据的传送)。CPU是经过系统总线与其余部件链接并进行数据传输。
一般系统总线是由CPU管理的,在DMA方式时,由DMA控制器发一个信号给CPU。DMA控制器得到总线控制权,控制传送的字节数,判断DMA是否结束,以及发出DMA结束信号nginx
须要复制硬件数据到socket发送时,原方案4次用户空间与内核空间的上下文切换,以及4次数据拷贝(涉及到TCP时,tcp/ip维护send buffer和recv buffer缓冲区——内核空间,须要cpu从用户态复制到内核态,而后DMA经过网卡发送。)
如下叫领拷贝(不拷贝到用户态)编程
2次用户空间与内核空间的上下文切换,以及3次数据的拷贝,但用户不能直接操做写安全
user space不拷贝共享kernel space数据:4次用户空间与内核空间的上下文切换,以及3次数据拷贝网络
mmap是os到用户内存无拷贝,用指针。首先,应用程序调用mmap(图中1),陷入到内核中后调用do_mmap_pgoff(图中2)。该函数从应用程序的地址空间中分配一段区域做为映射的内存地址,并使用一个VMA(vm_area_struct)结构表明该区域,以后就返回到应用程序(图中3)。当应用程序访问mmap所返回的地址指针时(图中4),因为虚实映射还没有创建,会触发缺页中断(图中5)。以后系统会调用缺页中断处理函数(图中6),在缺页中断处理函数中,内核经过相应区域的VMA结构判断出该区域属于文件映射,因而调用具体文件系统的接口读入相应的Page Cache项(图中七、八、9),并填写相应的虚实映射表。session
是否当即返回。数据结构
阻塞:空cpu,IO阻塞线程 非阻塞
是否由本线程执行多线程
同步IO 异步
accept后一个链接所有过程在一个进程/线程 ,结束后结束线程/进程,每次有新链接建立一个新的进程/线程去处理请求并发
线程池/进程池+非阻塞+IO多路复用 (非阻塞+IO多路复用 少了哪一个这种模型都没有意义)
reactor 监听全部类型事件,区分accept和业务处理
这个网上的图是错的。 accept后全部的读写处理都在一个线程中,无共享数据须要传递
基本上是accept确定要在一个线程中,由于只有一个fd。
1)单reactor单线程 accept+read/process/send
2)单reactor多线程 accept+read/send =》多process
3)多reactor多线程 accepct=>多read/process/send
4)另外一种 accepct[0号]=>子多read/send =》多process 当只有一个时退化为单reactor多线程。线上就这个。
假设4个请求并发链接,2个线程
若p是瓶颈,好比p1占用4个格子
1)单reactor多线程
r1->r2->r3->r4->s1->s2->s3->s4 p1w|w|w|w|->p3w|w|w|w| p2w|w|w|w|->p4w|w|w|w|
2)多reactor多线程
r1->p1w|w|w|w|->r3->p3w|w|w|w|->s1->s3 r2->p2w|w|w|w|->r4->p4w|w|w|w|->s2->s4
此时单reactor多线程更快。多reactor多线程编写简单
若r是瓶颈(好比占3个,4个换行了=。=)
1)单reactor多线程
r1w|w|w|->r2w|w|w|->r3w|w|w|->r4w|w|w|->s1w|w|w|->s2w|w|w|->s3w|w|w|->s4w|w|w| p1-> p3 p2-> p4
2)多reactor多线程
r1w|w|w|->p1->r3w|w|w|->p3->s1w|w|w|->s3w|w|w| r2w|w|w|->p2->r4w|w|w|->p4->s2w|w|w|->s4w|w|w|
此时多reactor多线程快
最后一种模式分别
r1->r3 ->s1 ->s3 p1w|w|w|w|->p3w|w|w|w| r2->r4 ->s2 ->s4 p2w|w|w|w|->p4w|w|w|w| r1w|w|w|->r3w|w|w|->s1w|w|w|->s3w|w|w| p1-> p3 r2w|w|w|->r4w|w|w|->s2w|w|w|->s4w|w|w| p2-> p4
与reactor区别是reactor是同步读写,preactor是异步读写
早在1973年,actor模式被提出(跟图灵机一个级别的模式,只是个模式),主要是对立于数据共享加锁的。一个是分布式计算中没办法很好的共享数据,另外一个是共享数据加锁会阻塞
线程(单机的话)/请求超时(分布式),还有个不重要的有时候锁不住(好比cpu的多级cache,没个线程里的cache不一样步),所以采用一种非阻塞和通讯的方式,数据发送到actor排队
后即返回不加锁,经过通讯传递改变,actor是整个数据+行为(对象)的组合
,不只仅负责数据的锁,actor之间要作到无共享,发给actor的本身复制一份新的,actor能够继续调actor,因此要都无共享
。(这么说非阻塞IO,select等都是借鉴的这个。)
actor行为
1.Actor将消息加入到消息队列的尾部。
2.假如一个Actor并未被调度执行,则将其标记为可执行。
3.一个(对外部不可见)调度器对Actor的执行进行调度。
4.Actor从消息队列头部选择一个消息进行处理。
5.Actor在处理过程当中修改自身的状态,并发送消息给其余的Actor。
为了实现这些行为,Actor必须有如下特性:
● 邮箱(做为一个消息队列)
● 行为(做为Actor的内部状态,处理消息逻辑)
● 消息(请求Actor的数据,可当作方法调用时的参数数据)
● 执行环境(好比线程池,调度器,消息分发机制等)
● 位置信息(用于后续可能会发生的行为)
另一种:CSP,不要经过共享内存来通讯,而应该经过通讯来共享内存的思想
,Actor 和 CSP 就是两种基于这种思想的并发编程模型.Actor 模型的重点在于参与交流的实体,而 CSP 模型的重点在于用于交流的通道.channel。channel共享带锁,再也不扩展了。actor的设计也要抽象好,好比好比左右两个叉子,若是加锁。锁(左右)/ 为了并发,单独左右。actor要左右组合在一块儿,每次判断左叉子和右叉子都有返回成功。zmq 每一个线程绑定一个cpu,线程之间不会共享session,不须要加锁。每一个链接的操做都在一个worker中.通讯传递数据。