不落辰

知不可乎骤得,托遗响于悲风

0%

  • 关于如何发送TCP segment

    • user负责 TCP
    • user负责 TCP,IP
    • user负责 TCP IP Ethernet
    • CS144目标实现的就是从TCP到Ethernet全部在用户态实现.
    • Ethernet报文的封装和解封装就由NetWorkInterface来做
  • 实现一个NetWorkInterface

    • 发送IP datagram –封装–> linker-layer frame
    • 接收 linker-layer frame —解封–> IP datagram
    • 在执行封装IP datagram成linker-layer frame时 , 需要ip->mac地址,这时就需要用到arp协议
      • 涉及处理 arp table , 缓存未知mac的ipdatagram等
阅读全文 »

  • TCPConnection : 基于之前实现的 TCPReciver和TCPSender ; 作为完整的一端的TCP实现
    • 功能以及对应核心函数如下
      • 接收segment : segment_received
        • 本端接收seg 并根据自身receiver以及sender状态 发送相应seg给peer
        • 注意在segment_receive中 会自动调用send_segments()
      • 发送segment : send_segment. (会捎带ack)
      • pass时间 : tick. 可能触发重传
      • shutdown关闭连接
        • unclean_shutdown : 清空要发送的seg , 根据是否主动异常,发送rst , 设置_sender和receiver状态error , active_ = false
          • active unclean_shutdown : send rst
          • passive unclean shut_down : not send rst
        • clean_shutdown : active = false
          • active clean_shutdown : 在tick时 判断是否经历完TIME_WAIT, 经历完,则clean_shutdown
          • passive clean_shutdown : 在segment_recieve接收完FIN的ACK后发现进入CLOSED状态,则clean_shutdown
  • 最核心的一点 : 如何确定结束连接. When TCPConnection is Done
    • unclean shutdown : 接收或发送rst就立刻结束连接
    • clean shutdown
      • 需要满足#1#2#3#4
      • 只有一端同时满足四个条件,该端才能认为TCPConnection is done. 结束连接.
      • 对于#4有两种情况可以满足
        • active close : A. 无法确定100%满足,只能通过TIME_WAIT接近100%
          • 这也就是由于两军问题,不可能去保证两端(主动关闭的无法一定达成clean shutdown)都能达成clean shutdown,但是TCP已经尽可能地接近了。
        • passive close : B. 100%满足
          • 被动关闭的一端无需linger time,可以100%达成clean_shutdown
阅读全文 »

muduo httpServer原理以及使用

为什么要加个httpServer ?
因为之前做校园猫管理平台的时候,底层用的就是这个之前学习做的tinyMuduo.
但是由于时间紧张,当时只是client和server裸发送json字符串,没有使用正经的应用层协议,遂在期末结束后给原先光秃秃的tcp server加一层http.
基本都是看完Muduo之后默着写 然后再改的。。
不难,就是string处理琐碎烦人.

阅读全文 »

  • 实现 TCPSender
    • 发送新segment : fill_window() when 上层有数据 && receive_window有空间
      • fill_window 负责一直向后走 发送新数据. 更新send_window
      • 重传的部分交由tick()来做
    • 定时器超时重传 : tick()
    • 收到ACK报文 : ack_received()
      • 更新rwnd , sender自动继续发送fill_window.(因为rwnd可能变大)
  • 我们Sender并没有实现快速重传(可以不实现,易知不影响其正确性,仍然可以可靠传输)
  • 有趣的是我们Sender并没有实现拥塞控制,但TCP仍然可以正常运行.
    • 原因如下 : 拥塞控制只是为了TCP的公平性,使得其不会无节制的占用带宽. 如我们的信道中有两条TCP连接,若都实现了拥塞控制,那么最终二者所占带宽会向y=x收敛.
    • 而为了保证己方Sender可以和对端Receiver正常交互,我们只需要保证:
      • 每个字节都令对方接受到并返回ACK : 重传保证
      • 对端receiver不会由于己方发送过快而导致receiver buf缓存溢出
        • 这通过流量控制就解决了.
阅读全文 »

一言以蔽之

  • lab2实现的tcp receiver

    • (0) 转换stream_idx ,abs_seq ,seq(属于内部功能)
    • (1) 利用 StreamReassembler对 接收到的segment进行重组 并压入bytestream
    • (2) 填充receive_window的相关信息:ackno , window_size, 从而实现flow-control流量控制
    • (3) 根据 接收到的报文 对tcp receiver进行状态划分. listen syn_recv fin_recv
  • 我们的receiver只实现了234

阅读全文 »

  • lab1 : 实现一个接收端的重排器
    • lab2实现的 TCP receiver 的重要功能:处理乱序到达的数据包 ,该功能即lab1 所谓的 重排器 reassembler
    • 就是让我们实现一个tcp的滑动窗口协议的接收端。
    • TCP协议中的滑动窗口协议是SR协议和GBN协议的结合
      • 发送窗口 > 1
      • ack代表 : 接收方接收到的顺序到来的最后一个字节 + 1
      • 像GBN协议的部分 :
          1. 发送窗口内只有一个定时器(窗口内最老的分组的定时器)
          1. 发送ack累计确认
      • 像SR协议的部分 :
          1. 可以乱序接收
          1. sender重传超时分组(最老的),receiver给sender发送新的ackno , sender可以发送新的数据. 是否重传发送窗口中的其他分组,取决于ackno的大小. 若大于send window中的所有segment , 则清空send window,不必重传所有分组. 若小于send window中的某些segment , 则会重传这某些分组.
          • 我目前认为网上有些人说的 重传 所有分组 是不对的。又不是GBN协议. receve window是 > 0的
  • receiving_window 接收窗口 [the first unassembled , the first unaccepted)
    capacity = bytestream.buffer_size() + receiving_window.size()

  • 从某种意义上来说,receive_window就是就是从bytestream中划出了一部分用作乱序缓存
    bytestream(顺序字节) + receive_window(乱序字节) = capacity

阅读全文 »

实现流量有限的bytestream,支持读写.
是TCPSender和TCPReceiver与用户层的接口.
app通过bytestream向下TCPSender写入
TCPReceiver通过bytestream向上app交付数据

阅读全文 »

历时2月有余。完成了lab1-lab10。由于疫情遣返一堆事儿,故net-work lab只能有时间再做了。
不得不说,跟这门课真的是相见恨晚,之前不清不楚的知识点和机制,在阅读xv6的代码时都有了更为清晰的认知。
比如一个进程究竟是怎么被划分成用户线程和内核线程的?(内核进程:就是在内核维护了进程陷入内核后的函数调用栈)
比如页表究竟是如何运作的?我原先甚至都不知道页表还分成用户页表和内核页表,内核究竟是如何使用用户传入的虚拟地址的?(进程的内核页表中维护了用户的地址空间)
比如系统调用究竟是如何使得进程陷入内核的?通过trap,那么trap机制究竟是如何做到的?ecall -> trampoline -> 保存用户线程上下文(cpu regs)到结构体trapframe,加载trapframe之前保存的进程的内核线程的信息到cpu上,用户页表切换成内核页表,然后跳转到kernel C code usertrap。然后就是内核根据用户陷入内核的原因以及传入参数,通过一系列函数syscall实现系统调用。
比如进程切换究竟是如何实现的?内核究竟是如何调度不同进程的?内涵和为什么调度不了用户态线程?:内核中的上下文分为每个进程的内核线程,以及一个调度器线程。会有定时器中断等机制,使得os陷入内核,执行调度器线程,其会寻找一个runnable的内核线程(也即寻找一个runnbale的进程),然后运行该内核线程;该内核线程离开内核态时,os就会在用户态运行该进程的用户线程。如此,就实现了进程切换。这也解释了内核为什么不会调度同一个进程的用户态线程,因为内核的调度器线程只能看到进程的内核线程,看不到用户态线程。
比如用户究竟是如何拿到物理内存的?我们常说的malloc啥的,究竟是在那一层次做出的设计。
又比如常说的内核的buffer,是在哪里?是dram里吗?是,就是xv6中的bcache,所有和disk的交互都要通过bacche
又比如socket buffer,是在哪里?:有点忘了,大概是net.c通过kalloc申请出的一些内存用作socket buffer。
又比如lazy allocation? 是如何工作的?:先声明user的一段虚拟地址为合法但不建立映射,然后在用户第一次使用到该地址时,再通过pgfault陷入内核,在进程的user pagetable上为该地址建立虚拟地址到物理地址映射。
又比如cow 是如何实现的?: fork时假分配,哪些process先write,那些process cow真分配
又比如mmap 如何实现的 ? 为什么叫映射 ? 为什么高效 ? :基于lazy allocation实现. 高效: 直接映射到buffer
又比如文件系统

  • 本节的重点

    • 一在于锁机制的优化
    • 二在于物理内存如何分配
    • 三在于buffer cache如何分配。
    • 关于锁机制的优化,我基本上是参考博客
    • 至于buffer cache如何分配,在操作系统-xv6-文件系统 buffercache中也已经讲过。
    • 下面重点介绍 物理内存是如何分配的,也即user是如何通过malloc申请到物理内存的。
  • 物理内存分配流程

    • kernel是如何组织free physical page的。
      • xv6 : freelist
    • kernel为user提供的sycall接口
      • sbrk->sys_sbrk()
    • user层是如何封装sbrk的。
      • malloc and free (维护了user态的cache list)
阅读全文 »