MIT6.S081 - Lab11 networking(网络栈)
本篇是 MIT6.S081 2020 操作系统课程 Lab11 的实验笔记,这是课程的最后一个实验了,目标是为 xv6 实现 E1000 网卡驱动的两个核心函数:发送数据包 e1000_transmit()
和接收数据包 e1000_recv()
。
- Lab11 地址:https://pdos.csail.mit.edu/6.828/2020/labs/net.html
- 我的实验记录:https://github.com/yibaoshan/xv6-labs-2020/tree/net
在开始实验之前,你需要:
- 观看 Lecture 21 课程录播视频:Networking(网络栈)
- YouTube 原版:https://www.youtube.com/watch?v=Fcjychg4Tvk
- 哔哩哔哩中译版:https://www.bilibili.com/video/BV19k4y1C7kA?vd_source=6bce9c6d7d453b39efb8a96f5c8ebb7f&p=20
- 中译文字版:https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/lec21-networking-robert
聊聊网络模型
本小节是 Lec 21 Networking 的课程笔记。
应用程序 → 协议栈 → 驱动程序 → 网卡硬件 → 物理网络
1、物理层 网卡硬件
位于物理层的网卡工作内容是:
- 主要由网卡硬件(内部电路)处理
- 包括前导码、SFD 的添加和识别
- 信号的编码和解码
- 电平转换和时钟同步
如果不考虑以太网协议啥的,两台有网卡的设备之间,通过一根网线直接连接(类似于两个串口直连),就可以互相传递信号了。
2、数据链路层 以太网
这一层由 OS 和网卡驱动程序支持,它负责:
- 初始化网卡硬件
- 配置网卡参数(如 MAC 地址、工作模式)
- 管理发送和接收队列
- 处理中断
- 与操作系统的网络协议栈交互
在两台设备,通过网线相连,能互相发送数据的基础上,增加为,多台设备之间需要互相通信
ID:MAC 地址
- 需要为每台设备新增一个唯一 ID,用于身份标识,也就是现在的 MAC 地址。
- 需要创建一个规则/协议,用于区分谁和谁通信,现在一般都是
以太网(Ethernet)
协议。
集线器和交换机
多台设备之间,有好几种连接方式:
- 总线型(Bus):所有设备共享一条通信线路,结构简单,成本低,但容易发生冲突,一个设备故障可能影响整个网络
设备A ─┬─ 设备B ─┬─ 设备C│ │共享总线 共享总线
- 星型(Star):所有设备连接到中心节点,容易管理,性能好,单台设备挂了不影响其他设备,缺点是如果交换机挂了网络就没了
设备A ─┐ 设备B ─┼─ 交换机/集线器 设备C ─┘
- 环形(Ring):Token Ring 网络使用这种方式,不考虑
设备A ──→ 设备B ↑ ↓ 设备D ←── 设备C
现在基本都使用 星型 连接方式,多个设备之间,通过网线和 交换机/集线器 连接
集线器的工作方式是:
- 多个设备都需要和 “集线器” 连接
- 设备发送数据(目标地址 + 自己是谁 + 数据)给集线器
- 集线器相当于村口、学校、商场里面的 “大喇叭”,自己不处理数据,收到以后大喇叭通知给接入它的所有设备,谁谁谁发消息要找谁谁谁,这个数据包你看看你要不要?
交换机内部会维护一个 MAC 表,记录了每个设备对应的 MAC 地址,当收到数据时,会根据 MAC 地址找到对应的设备,将数据转发给该设备。首次收到数据 MAC 表为空的情况涉及到 IP 地址下一小节介绍。
以太网帧结构和 MAC 地址
因为增加了 唯一 ID :MAC 地址 的关系,所以,在数据链路层及以后的通信,每个数据包都必须要包含 目标 MAC 地址 和 源 MAC 地址,这样,集线器/交换机 才可以根据 MAC 地址 找到对应的设备,将数据转发给该设备。
物理层部分:
+----------+-----+
| 前导码 | SFD | 8字节
| 7字节 | 1字节|
+----------+-----+数据链路层帧:
+----------+----------+----------+------------+--------+
| 目标MAC | 源MAC | 类型/长度 | 数据 | CRC |
| 6字节 | 6字节 | 2字节 | 46-1500字节| 4字节 |
+----------+----------+----------+------------+--------+
- 最小帧:64字节
- MAC地址:12字节
- 类型:2字节
- 数据:最少46字节
- CRC:4字节
- 最大帧:1518字节
- MAC地址:12字节
- 类型:2字节
- 数据:最多1500字节(MTU)
- CRC:4字节
Frame(信封):
+-----------------+------------------+------------------+
| 目标MAC(收件人) | 源MAC(寄件人) | Packet(信件) |
+-----------------+------------------+------------------+|
Packet(信件): ↓+----------------+------------------+| IP地址(详细地址)| 数据(信件内容) |+----------------+------------------+
3、网络层 IP 协议
网络层由 OS 提供 IP 协议支持
- 两台设备之间,通过网线连接,可以互相通信。
- 多台设备之间,通过交换机连接,形成一个局域网,局域网之间可以互相通信。
现在,在上面这两条的基础上,增加为:局域网和局域网之间也可以互相通信。
局域网A 局域网B
设备1 ──┐ 设备3 ──┐
设备2 ──┼── 交换机A 设备4 ──┼── 交换机B| |└────── ??? ─────────┘
ID:IP 地址
局域网之间距离可能很远,相差十万八千里,摆在面前的有两个选择:
- 继续用 Mac 地址,另外再创建一个 “全球的交换机中心”,每台交换机都通过一根网线连接在上面,这样交换机之间就可以互相通信了。
- 增加额外的设备和协议,创建一种新的寻址方案。
IP 协议发展史
现在的 IP 协议就是这个寻址方案,它为每台设备新增一个 IP 地址,用于寻址(在网络中找到对应的路径)。并且,为这种寻址方案引入了新的设备,路由器
以太网方案:设备1(MAC1) ── 交换机A ── 交换机B ── 设备2(MAC2)网络层方案:设备1 ── 交换机A ── 路由器A ── 路由器B ── 交换机B ── 设备2192.168.1.2 | | | | 192.168.2.2| | | |192.168.1.0/24 1.1.1.0/24 1.1.2.0/24 192.168.2.0/24
现在家庭网络的路由器往往身兼数职(调制解调器
+路由器
+交换机
+WiFi
),刚创造路由器那会,它的功能是非常单一的:决定到我这儿的数据包走哪条路,是把数据包转发到另一个网络,还是走 NAT
[MAC帧]↓路由器拆开MAC帧↓查看IP地址↓选择下一跳↓封装新的MAC帧
回到以太网协议和 IP 协议的发展历程,事实上,IP 协议 和 以太网协议 的出现也就是前后脚的关系:
1973年 ─── 以太网发明│
1974年 ─── TCP/IP构思│
1981年 ─── IPv4规范│
1983年 ─── IEEE 802.3(以太网标准)│ └── ARPANET切换到TCP/IP│
1990年 ─── 以太网使用双绞线│
1995年 ─── 快速以太网│ └── IPv6开始设计│
1999年 ─── 千兆以太网
73 年出现以太网以后, TCP/IP 紧随其后,在 74 年就提出了 TCP/IP 协议的概念。
IP 数据包格式
以太网帧结构:
+----------+----------+----------+------------------------+--------+
| 目标MAC | 源MAC | 类型=0x0800 | IP数据包 | CRC |
| 6字节 | 6字节 | 2字节 | | 4字节 |
+----------+----------+-------------+--------------------+--------+↑│
IP 数据包: │
+--------+--------+--------+----------------+
| IP头 | TTL | 协议 | 数据 |
+--------+--------+--------+----------------+
4、传输层 TCP/UDP
以太网协议 + IP 协议,已经能够保证,在网络上,把数据包发送到哪一台设备,但是我们希望做的更好一些。
每一个机器都运行了大量需要使用网络的应用,所以,我们需要有一种方式能区分一个 数据包 应该传递给目的主机的哪一个应用程序,而 IP 协议明显不包含这种区分方式。
ID:Port
所以,发明了新的协议来完成了这里的区分工作,其中一个是 TCP,它比较复杂,而另一个是 UDP
- 早期阶段(1970年代初):ARPANET 网络初期,只有简单的主机到主机通信,没有可靠传输机制,应用需要自己处理所有的连接通信问题
- TCP的诞生(1974):Vint Cerf 和 Bob Kahn 提出 TCP 的概念,最初的 TCP 和 IP 是一体的,目标是解决网络通信的可靠性问题,它包含了:连接管理 、可靠传输 、流量控制
- TCP/IP分离(1978):TCP 和 IP 被分为独立的协议,原因是有些应用不需要可靠传输以及 分层更清晰、更灵活
- UDP的加入(1980):UDP 提供简单的不可靠传输服务,它适用于:实时应用 、简单查询响应 、广播/多播
- 现代应用,TCP 用于需要可靠传输的场景(如网页、邮件),UDP用于需要快速传输的场景(如视频流、游戏),两种协议互补,满足不同需求
面试经常会被问到的 HTTP 的三握四挥,指的就是这里的 TCP 协议,HTTP 是建立在 TCP 协议之上的。
TCP 为什么要三握四挥?
复习一下三次握手,建立连接的过程
客户端 服务器│ ││──── SYN=1, seq=x ───────>│ # 第一次:我想建立连接【确保客户端发送能力】│ ││<── SYN=1,ACK=x+1,seq=y ──│ # 第二次:好的,我准备好了【确保服务器收发能力】│ ││──── ACK=y+1 ────────────>│ # 第三次:我也准备好了【确保客户端接收能力】│ │
连接建立完成 连接建立完成
断开连接的四次挥手:
客户端 服务器│ ││──── FIN=1, seq=x ───────>│ # 第一次:我想关闭连接【客户端请求关闭】│ ││<─── ACK=x+1 ────────────│ # 第二次:好的,我知道了【服务器确认,但可能还有数据要发】│ ││<─── FIN=1, seq=y ───────│ # 第三次:我也想关闭了【服务器发送完毕】│ ││──── ACK=y+1 ────────────>│ # 第四次:好的,再见【客户端确认】│ │
连接关闭 连接关闭
老八股,TCP 协议为什么需要三握四挥?
- 三握,保证双方的收发能力
- 两次不够,无法确认客户端的接收能力
- 四次多余,三次已经确认了双方的收发能力
- 四挥,确保双方都完成了数据传输
- 三次不够,因为服务器(如果是客户端发起断连请求)可能还有数据要发送
- 必须等双方都确认没有数据要发送
- 总结,为了保证通信的可靠性,三握四挥已经是最少的通信次数了。
其他知识点补充
端口(Port)
- 用于区分同一主机上的不同应用程序
- 16位数字(0-65535)
- 知名端口:
- HTTP:80
- HTTPS:443
- FTP:21
- SSH:22
TCP的重要机制,滑动窗口和拥塞控制,忽略
TCP和UDP的数据包格式,忽略
加入 TCP/UDP 协议了以后的数据包格式:
以太网帧:
+------+------+----------------------+------+
| MAC头 | 类型 | IP数据包 | CRC |
+------+------+----------------------+------+↓
IP数据包:
+--------+----------------------+
| IP头 | TCP/UDP段 |
+--------+----------------------+↓
TCP/UDP段:
+-------------+----------------+
| TCP/UDP头 | 应用层数据 |
+-------------+----------------+
5、应用层 HTTP
常见的一些协议,待会会一一介绍:
- HTTP/HTTPS(网页)
- FTP(文件传输)
- DNS(域名解析)
- SSH(安全远程登录)
HTTP
HTTP 基于 TCP 实现,所以,一次完整的 HTTP 请求-响应,先是三次 TCP 握手的通信创建连接,然后是中间 N 次的 TCP 传输 HTTP 的报文数据,最后是四次 TCP 通信挥手挂断。
应用层:HTTP↓
传输层:TCP↓
网络层:IP
HTTPS
HTTPS 是 HTTP 的安全版本,在三次握手之后,会增加一次 SSL/TLS 握手,然后中间是 N 次的 TCP 通信传输数据,最后还是四次挥手断联。
客户端 服务器│ ││── TCP握手 ─────────────>││ ││── SSL/TLS握手 ─────────>│ # 协商加密参数│ ││── 加密的HTTP数据 ───────>│ # 安全通信
中间的通信会变为:
客户端 服务器│ ││── 支持的加密算法列表 ───>│ # 协商使用什么加密方式│<── 选择的加密算法 ──────││ ││── 交换密钥 ────────────>│ # 生成通信用的密钥│<── 交换密钥 ────────────│
因为首次通信的秘钥交换依旧是明文,SSL/TLS使用非对称加密(公钥加密)来解决首次密钥交换的安全问题:
服务器:
- 私钥(自己保存),私钥才能解密
- 公钥(可以公开),只能用来加密客户端 服务器│ ││<── 公钥 ────────────────│ # 服务器发送公钥│ ││── 用公钥加密的密钥 ────>│ # 客户端生成会话密钥│ │ # 服务器用私钥解密
也就是说,公钥私钥都是由服务器保存,三次握手以后进入 SSL/TLS 握手阶段,服务器会将公钥信息发给客户端,客户端使用公钥进行加密,数据到达服务端以后,服务端再用私钥解密
FTP
FTP 是一个经典的文件传输协议,同样基于 TCP 协议二次开发,但是有安全问题,它的发展历史
- 1971年:首个FTP规范
- 1980年:TCP/IP版本的FTP
- 1985年:添加了认证机制
- 现在:逐渐被SFTP、FTPS等安全版本替代
客户端 ←───── 控制连接(21端口)────→ 服务器←───── 数据连接(20端口)────→
emmm,细节不展开了,不是很感兴趣,忽略。
DNS
域名解析协议,负责将域名解析为 IP 地址,然后客户端就可以通过 IP 地址访问服务器了。
DNS 跟楼上的 HTTP/FTP 不同,它同时使用 TCP 和 UDP 实现,主要使用 UDP,除非响应超过 512 字节,才会使用 TCP
客户端:54321 → 服务器:53
"查询 yibs.space 的IP"服务器:53 → 客户端:54321
"IP是 x.x.x.x"
如果响应超过了 512 字节,那么 DNS 服务器会返回一个错误码,然后客户端会重新请求,但是这次使用 TCP
# 先用UDP
客户端:54321 → 服务器:53
"查询所有 yibs.space 记录"服务器:53 → 客户端:54321
"响应太大,请用TCP"# 切换到TCP
[TCP三次握手]
客户端:54322 → 服务器:53
"查询所有 yibs.space 记录"
SSH
SSH 是一个安全远程登录协议,诞生于 1995 年,使用 TCP 实现,默认端口:22
使用方式:
# 生成 RSA 密钥对
ssh-keygen -t rsa -b 4096# 服务器配置 /etc/ssh/sshd_config
Port 22
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes# 客户端配置 ~/.ssh/config
Host myserverHostName 192.168.1.100User adminPort 22IdentityFile ~/.ssh/id_rsa
Networking (hard)
Your job is to complete e1000_transmit() and e1000_recv(), both in kernel/e1000.c, so that the driver can transmit and receive packets. You are done when make grade says your solution passes all the tests.
你的任务是在 e1000_transmit()
和 e1000_recv()
中完成 kernel/e1000.c 中的代码,使驱动程序能够发送和接收数据包。
一些提示
以下来自 Lab 11 的提示(机翻)
Start by adding print statements to e1000_transmit() and e1000_recv(), and running make server and (in xv6) nettests. You should see from your print statements that nettests generates a call to e1000_transmit.
从 e1000_transmit()
和 e1000_recv()
中添加打印语句,并运行 make server
和(在 xv6 中) nettests
。你应该从打印语句中看到 nettests
生成了对 e1000_transmit
的调用。
Some hints for implementing e1000_transmit:
实现 e1000_transmit()
的一些提示:
- First ask the E1000 for the TX ring index at which it’s expecting the next packet, by reading the E1000_TDT control register.
首先通过读取E1000_TDT
控制寄存器,询问E1000
它期望的下一个数据包所在的TX 环索引
。 - Then check if the the ring is overflowing. If E1000_TXD_STAT_DD is not set in the descriptor indexed by E1000_TDT, the E1000 hasn’t finished the corresponding previous transmission request, so return an error.
然后检查是否出现了 环溢出。如果在由E1000_TD
T 索引的描述符中未设置E1000_TXD_STAT_DD
,则E1000
尚未完成相应的先前传输请求,因此,返回错误。 - Otherwise, use mbuffree() to free the last mbuf that was transmitted from that descriptor (if there was one).
否则,使用mbuffree()
释放最后一个从该描述符传输的mbuf
(如果有的话)。 - Then fill in the descriptor. m->head points to the packet’s content in memory, and m->len is the packet length. Set the necessary cmd flags (look at Section 3.3 in the E1000 manual) and stash away a pointer to the mbuf for later freeing.
然后填充描述符。m->head
指向内存中的数据包内容,m->len
是数据包长度。设置必要的cmd
标志(参见 E1000 手册中的第 3.3 节),并保存一个mbuf
指针以供以后释放。 - Finally, update the ring position by adding one to E1000_TDT modulo TX_RING_SIZE.
最后,通过将E1000_TDT
加 1 并对TX_RING_SIZE
取模来更新环位置。 - If e1000_transmit() added the mbuf successfully to the ring, return 0. On failure (e.g., there is no descriptor available to transmit the mbuf), return -1 so that the caller knows to free the mbuf.
如果e1000_transmit()
成功将mbuf
添加到环中,则返回 0。如果失败(例如,没有可用的描述符来传输mbuf
),则返回-1,以便调用者知道需要释放mbuf
。
Some hints for implementing e1000_recv:
实现 e1000_recv()
的一些提示:
- First ask the E1000 for the ring index at which the next waiting received packet (if any) is located, by fetching the E1000_RDT control register and adding one modulo RX_RING_SIZE.
首先通过获取E1000_RDT
控制寄存器并加一(%RX_RING_SIZE
)来询问E1000
下一个等待接收的包(如果有)位于环中的索引位置。 - Then check if a new packet is available by checking for the E1000_RXD_STAT_DD bit in the status portion of the descriptor. If not, stop.
然后通过检查描述符的status
部分中的E1000_RXD_STAT_DD
位来检查是否有新包可用。如果没有,则停止。 - Otherwise, update the mbuf’s m->len to the length reported in the descriptor. Deliver the mbuf to the network stack using net_rx().
否则,将mbuf
的m->len
更新为描述符中报告的长度。使用net_rx()
将mbuf
传递给网络堆栈。 - Then allocate a new mbuf using mbufalloc() to replace the one just given to net_rx(). Program its data pointer (m->head) into the descriptor. Clear the descriptor’s status bits to zero.
然后使用mbufalloc()
为刚刚传递给net_rx()
的mbuf
分配一个新的mbuf
。将数据指针(m->head
)编程描述符。清除描述符的状态位为零。 - Finally, update the E1000_RDT register to be the index of the last ring descriptor processed.
最后,将E1000_RDT
寄存器更新为处理的最后一个环描述符的索引。 - e1000_init() initializes the RX ring with mbufs, and you’ll want to look at how it does that and perhaps borrow code.
e1000_init()
用mbuf
初始化RX
环,你可能需要查看它是如何做到这一点的,并且可以借用一些代码。 - At some point the total number of packets that have ever arrived will exceed the ring size (16); make sure your code can handle that.
某个时刻,总共到达的包的数量将超过环的大小(16);确保你的代码能够处理这种情况。
You’ll need locks to cope with the possibility that xv6 might use the E1000 from more than one process, or might be using the E1000 in a kernel thread when an interrupt arrives.
你需要锁来应对 xv6 可能从多个进程使用 E1000,或者可能在中断到达时在内核线程中使用 E1000 的情况。
实现 e1000_transmit()
Lab 11 实验本身不是很难,按照提示一步步实现 e1000_transmit()
和 e1000_recv()
函数功能即可。
kernel/e1000.c
int
e1000_transmit(struct mbuf *m)
{// 并发安全acquire(&e1000_lock);// 从网卡寄存器中,获取下一个可用的发送描述符索引uint32 tdt = regs[E1000_TDT];// 检查这个 buf 是否是空闲可用的if(!(tx_ring[tdt].status & E1000_TXD_STAT_DD)){release(&e1000_lock);return -1; // 环形缓冲区已满}// 尝试释放上一个已发送完数据但未释放的 mbuf,如果存在的话if(tx_mbufs[tdt]){mbuffree(tx_mbufs[tdt]);}// 一些赋值操作,设置发送描述符,网卡将通过 DMA 设备直接从这个地址读取数据tx_ring[tdt].addr = (uint64)m->head;tx_ring[tdt].length = m->len;tx_ring[tdt].cmd = E1000_TXD_CMD_RS | E1000_TXD_CMD_EOP;tx_ring[tdt].status = 0;// 保存 mbuf 的指针,等网卡发送完后释放内存tx_mbufs[tdt] = m;// 更新网卡的发送队列指针,通知网卡有新的数据包要发送regs[E1000_TDT] = (tdt + 1) % TX_RING_SIZE;release(&e1000_lock);return 0;
}
e1000_transmit() 里面看起来没有和网卡驱动通信,这是因为用了 内存映射寄存器(MMIO) 技术,把硬件的寄存器映射到内存了,这样,读写内存就相当于和硬件通信了。
网卡驱动程序通过 regs 数组,来访问 E1000 网卡硬件的控制寄存器(把硬件寄存器映射到内存)
// regs 是一个指向内存的指针,但实际上访问的是网卡的寄存器
uint32 tdt = regs[E1000_TDT];
比如这段代码,regs[E1000_TDT] 这行代码看起来像是在访问普通的内存数组,但实际上这里的 regs 指向的是映射到内存空间的网卡硬件寄存器。
读操作会直接从网卡硬件取值
// 更新网卡的发送队列指针,通知网卡有新的数据包要发送
regs[E1000_TDT] = (tdt + 1) % TX_RING_SIZE;
写操作也会直接写入到网卡硬件,上面这行就是更新网卡硬件的状态,告诉网卡有新的数据包需要发送。
实现 e1000_recv()
kernel/e1000.c
static void
e1000_recv(void)
{// 并发安全acquire(&e1000_lock);while(1){// 计算下一个接收描述符索引uint32 rdt = (regs[E1000_RDT] + 1) % RX_RING_SIZE;// 检查是否全部读完了,rx_ring 由 DMA 更新// E1000_RXD_STAT_DD 表示网卡是否已经通过 DMA 写入了新数据if(!(rx_ring[rdt].status & E1000_RXD_STAT_DD)){break;}// 读接收到的数据包内容struct mbuf *m = rx_mbufs[rdt];m->len = rx_ring[rdt].length;// 分配新的 mbuf,准备下一次接受struct mbuf *new_mbuf = mbufalloc(0);if(!new_mbuf){panic("e1000_recv");}// 更新接收描述符rx_mbufs[rdt] = new_mbuf;rx_ring[rdt].addr = (uint64)new_mbuf->head;rx_ring[rdt].status = 0; // 清除状态位,准备接收新的数据包// 通知网卡有空闲的接收描述符可以用了regs[E1000_RDT] = rdt;// 传递数据包给网络栈release(&e1000_lock);net_rx(m);acquire(&e1000_lock);}release(&e1000_lock);
}
基本上都是按照提示来实现的,每行代码都增加了注释。
更换 DNS 地址
执行 nettests
测试,会发现前面几个测试用例都通过了,执行到 starting DNS 时卡住了,半天没个结果,也没有报错信息。
打开 nettests.c 查看 DNS() 函数的测试用例,发现 DNS 测试的地址是 Google 的 8.8.8.8,我本地网络可能无法访问,所以这里换成了阿里的 DNS 服务器地址
user/nettests.c
static void
dns()
{...memset(obuf, 0, N);memset(ibuf, 0, N);// // 8.8.8.8: google's name server
// dst = (8 << 24) | (8 << 16) | (8 << 8) | (8 << 0);// 223.5.5.5: 阿里的 name serverdst = (223 << 24) | (5 << 16) | (5 << 8) | (5 << 0);if((fd = connect(dst, 10000, 53)) < 0){fprintf(2, "ping: connect() failed\n");exit(1);}...
}
再次运行 nettests
测试程序
测试通过,完整代码在:https://github.com/yibaoshan/xv6-labs-2020/commit/496ed823f685bf97712e9417e88df2c86d66961a
参考资料
- CS自学指南:https://csdiy.wiki/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/MIT6.S081/
相关文章:
MIT6.S081 - Lab11 networking(网络栈)
本篇是 MIT6.S081 2020 操作系统课程 Lab11 的实验笔记,这是课程的最后一个实验了,目标是为 xv6 实现 E1000 网卡驱动的两个核心函数:发送数据包 e1000_transmit() 和接收数据包 e1000_recv()。 Lab11 地址:https://pdos.csail.mi…...
创龙全志T536全国产(4核A55 ARM+RISC-V+NPU 17路UART)工业开发板硬件说明书
前 言 本文档主要介绍TLT536-EVM评估板硬件接口资源以及设计注意事项等内容。 T536MX-CXX/T536MX-CEN2处理器的IO电平标准一般为1.8V、3.3V,上拉电源一般不超过3.3V或1.8V,当外接信号电平与IO电平不匹配时,中间需增加电平转换芯片或信号隔离芯片。按键或接口需考虑ESD设计…...
《解锁CSS Flex布局:重塑现代网页布局的底层逻辑》
网页布局作为用户体验的基石,其重要性不言而喻。从早期简单的表格布局,到后来基于浮动与定位的复杂尝试,网页布局技术始终在不断演进。而CSS Flex布局的出现,宛如一颗璀璨的新星,彻底革新了网页布局的设计理念与实践方…...
deepseek_ai_ida_plugin开源插件,用于使用 DeepSeekAI 将函数反编译并重命名为人类可读的视图。该插件仅在 ida9 上进行了测试
一、软件介绍 文末提供程序和源码下载 deepseek_ai_ida_plugin开源插件,用于使用 DeepSeekAI 将函数反编译并重命名为人类可读的视图。该插件仅在 ida9 上进行了测试。FunctionRenamerDeepseekAI.cpp 此文件包含 Hex-Rays 反编译器的主要插件实现。它反编译当前函数…...
完整的 SSL 证书生成与 Spring Boot 配置流程
一、生成 SSL 证书 目标:创建 PKCS12 格式的密钥库文件(keystore.p12),供 Spring Boot 使用。 方法 1:使用 keytool(推荐,直接生成 PKCS12 文件) 生成密钥库:keytool -genkeypair \-alias mydomain \ # 别名(自定义,如 mydomain)-keyalg RSA \ …...
taro小程序如何实现大文件(视频、图片)后台下载功能?
一、需求背景 1、需要实现小程序下载最大500M视频 2、同时需支持图片下载 3、退到其他页面再次回到当前页面时,下载进度也需要展示 二、实现步骤 1、在app.ts文件定义一个全局变量globalDownLoadData 2、写一个独立的下载hooks,代码如下(…...
阿里云bgp服务器优势有哪些?搭建bgp服务器怎么做?
阿里云服务器bgp优势有哪些?搭建bgp服务器怎么做? BGP服务器的优势 BGP(Border Gateway Protocol) 是互联网的核心路由协议,用于在不同自治系统(AS)之间交换路由信息。BGP服务器的核心优势体现在网络连接…...
java连接redis服务器
直接从 Redis 获取数据通常是 Redis通过 客户端库实现的,Jedis 是 Java 中一个常用的 Redis 客户端库。有以下两种主要方式: 1. 使用单个 Jedis 实例(不使用连接池) import redis.clients.jedis.Jedis; public class RedisExampl…...
从零开始:Android Studio开发购物车(第二个实战项目)
一年经验的全栈程序员,目前头发健在,但不知道能撑多久。 文章目录 前言 一、页面编写 1. 顶部标签栏title_shopping.xml 2. 商品展现列表activity_shopping_channel.xml 3. 商品详情页面activity_shopping_detail.xml 4. 购物车页面activity_shopping…...
2. python协程/异步编程详解
目录 1. 简单的异步程序 2. 协程函数和协程对象 3. 事件循环 4. 任务对象Task及Future对象 4.1 Task与Future的关系 4.2 Future对象 4.3 全局对象和循环事件对象 5. await关键字 6. 异步上下文管理 7.异步迭代器 8. asyncio的常用函数 8.1 asyncio.run 8.2 asyncio.get…...
XSS靶场实战(工作wuwuwu)
knoxss knoxss Single Reflection Using QUERY of URL ——01 测试标签 <script>alert(666666)</script>——02: " <h1>test</h1>没有反应,查看源码 现在需要闭合双引号,我计划还是先搞标签 "><h1>tes…...
DNA复制过程3D动画教学工具
DNA复制过程3D动画教学工具 访问工具页面: DNA复制动画演示 工具介绍 我开发了一个交互式的DNA复制过程3D动画演示工具,用于分子生物学教学。这个工具直观展示了: DNA双螺旋结构的解旋过程碱基互补配对原理半保留复制机制完整的复制周期动画 主要特点…...
在Mybatis中写sql的常量应用
下面示例把原来写死的 1、2、3 都替换成了绑定好的常量,同时额外演示了如何把第五个状态也一起统计(如果你的 DTO 没有对应字段,也可删掉相应那一行)。 <!-- 1. 定义可复用的常量绑定 --> <sql id"DeviceStatusCon…...
一次讲明白SaaS、PaaS、IaaS、aPaaS、iPaaS、RaaS、RPAaaS
在数字化浪潮与5G技术的强势驱动下,各行业对云服务的需求正呈现出井喷式增长态势,众多企业纷纷投身云服务的怀抱,以期在激烈的市场竞争中抢占先机。而谷云科技作为iPaaS领域的佼佼者,也在这股浪潮中大放异彩,助力企业实…...
RTDETRv2 pytorch训练
RTDETRv2 pytorch训练 1. 代码获取2. 数据集制作3. 环境配置4. 代码修改1)configs/dataset/coco_detection.yml2) configs/src/data/coco_dataset.py3)configs/src/core/yaml_utils.py4)configs/rtdeterv2/include/optimizer.yml 5. 代码训练…...
Unity3D仿星露谷物语开发39之非基于网格的光标
1、目标 当鼠标移动到reapable scenary(可收割庄稼)上方时,光标会变成十字架。 之前章节中,Grid有Dug/Watered属性,光标移动上方时会显示方框。 而这次的功能并非基于Grid的属性,而是基于scenary&#x…...
什么是 MCP?AI 应用的“USB-C”标准接口详解
目录 🧩 什么是 MCP?AI 应用的“USB-C”标准接口详解 📌 背景与动机 🧠 核心概念 🏗️ 技术架构 🚀 应用场景 🧩 什么是 MCP?AI 应用的“USB-C”标准接口详解 📌 背…...
狼人杀中的智能策略:解析AI如何理解复杂社交游戏
想要掌握如何将大模型的力量发挥到极致吗?叶梓老师带您深入了解 Llama Factory —— 一款革命性的大模型微调工具(限时免费)。 1小时实战课程,您将学习到如何轻松上手并有效利用 Llama Factory 来微调您的模型,以发挥其…...
10 基于Gazebo和Rviz实现导航仿真,包括SLAM建图,地图服务,机器人定位,路径规划
在9中我们已经实现了机器人的模块仿真,现在要在这个基础上实现SLAM建图,地图服务,机器人定位,路径规划 1. 还是在上述机器人的工作空间下,新建功能包(nav),导入依赖 gmapping ma…...
jmeter-Beashell获取请求body data
在使用JMeter的BeanShell处理器或BeanShell断言中获取HTTP请求的body数据,可以通过几种方式实现。下面是一些常用的方法: 方法1:使用prev变量 在BeanShell处理器或断言中,prev变量可以用来访问最近的sampler(采样器&…...
区块链密码学核心
文章目录 概要1. 基础密码学哈希函数(Hash Function)对称加密与非对称加密数字签名(Digital Signature)密钥管理 2. 区块链专用密码学技术零知识证明(Zero-Knowledge Proof, ZKP)同态加密(Homom…...
Git 多账号切换及全局用户名设置不生效问,GIT进行上传无权限问题
解决 Git 多账号切换及全局用户名设置不生效问题 在软件开发过程中,我们经常会使用 Git 进行版本控制。有时,我们需要在同一台机器上管理多个 Git 账号,最近我在进行使用git的时候因为项目要进行上传的不同的git账号,但是通过本地…...
阿里云服务迁移实战: 04-IP 迁移
普通过户 如资料过户按量付费EIP所述,如果原账号是个人账号,则目标账号无限制,如果原账号是企业账号,则目标账号必须为相同认证主体的企业账号。 其主要操作就是,在原账号发起过户,在新账号接收过户。具体…...
探索PyTorch中的空间与通道双重注意力机制:实现concise的scSE模块
探索PyTorch中的空间与通道双重注意力机制:实现concise的scSE模块 在深度学习领域,尤其是在计算机视觉任务中,特征图的注意力机制变得越来越重要。近期,我在研究一种结合了通道和空间两种注意力机制的模块——Concise Spatial an…...
关闭正点原子atk-qtapp-start.service
# 查找相关服务 systemctl list-units --typeservice --staterunning # 查看详细信息 systemctl status atk-qtapp-start.service >> ● atk-qtapp-start.service - Qt App Start …...
[按键安卓ios脚本辅助插件开发]数组排序函数例子
按键安卓ios工具辅助脚本插件开发教程,教程目的是让大家掌握Lua基本语法与按键精灵手机版的插件开发制作。 在按键精灵中排序需要我们自己写算法实现,例如快速排序,冒泡排序等,而在Lua中有内置的table.sort()排序命令。 这个命令…...
【BotSharp框架示例 ——实现聊天机器人,并通过 DeepSeek V3实现 function calling】
BotSharp框架示例 ——实现聊天机器人,并通过 DeepSeek V3实现 function calling 一、一点点感悟二、创建项目1、创建项目2、添加引用3、MyWeatherPlugin项目代码编写4、WeatherApiDefaultService项目代码编写5、WebAPI MyWeatherAPI 的项目代码编写6、data文件夹中…...
记录一个单独读取evt.bdf的方法
问题描述 之前只能使用eeglab的工具,读取博瑞康达的data.bdf和evt.bdf时,使用的是eeglab的下面这个读取文件的插件。 evt.bdf使用记事本文件查看是乱码的形式。 实现方法 事实上,我们可以直接对这个文件的16进制进行解析。 对文件的位和…...
是否想要一个桌面哆啦A梦的宠物
是否想拥有一个在指定时间喊你的桌面宠物呢(手动狗头) 如果你有更好的想法,欢迎提出你的想法。 是否考虑过跟开发者一对一,提出你的建议(狗头)。 https://wwxc.lanzouo.com/idKnJ2uvq11c 密码:bbkm...
防爆风扇储能轴流风机风量风压如何保障通风安全?
在化工车间、煤矿巷道等高危环境中,通风安全是保障生产与人员生命安全的关键防线。防爆风扇储能轴流风机凭借独特的风量风压设计与性能优势,成为守护通风安全的可靠屏障。那么,它究竟是如何发挥作用的呢? 从风量设计来看,防爆风…...
Centos 7系统 宝塔部署Tomcat项目(保姆级教程)
再看文章之前默认已经安装好系统,可能是云系统,或者是虚拟机。 宝塔安装 这个比较简单,参考这个老哥的即可: https://blog.csdn.net/weixin_42753193/article/details/125959289 环境配置 进入宝塔面板之后会出现环境安装&…...
Electron读取本地文件
在 Electron 应用中,可以使用 Node.js 的 fs 模块来读取本地文件。以下是如何实现这一功能的详细步骤。 1. 设置项目结构 假设你的项目目录如下: my-electron-app/ ├── main.js ├── index.html └── renderer.js2. 使用 fs 模块读取文件 在主…...
Plesk 下的 IP 地址管理
Plesk是一个方便管理的控制面板,可以简化网站主机和服务器数据中心的自动化管理。它专为提供Windows和Linux服务器的供应商设计。Plesk面板适用于虚拟主机和独立服务器 服务器管理员可以使用Plesk来配置新网站、电子邮件系统和转售商账户,也可以通过Ple…...
基于STM32、HAL库的DS28E15P安全验证及加密芯片驱动程序设计
一、简介: DS28E15P是Maxim Integrated (现为Analog Devices)生产的一款1-Wire EEPROM芯片,具有以下特点: 1-Wire接口通信,仅需单根数据线加地线 1024位(128字节)EEPROM存储器 每个器件具有唯一的64位ROM ID 工作电压范围:2.8V至5.25V 内置CRC16校验功能 可编程写保护功能…...
浅析localhost、127.0.0.1 和 0.0.0.0的区别
文章目录 三者的解释三者的核心区别总结使用场景示例什么是回环地址常见问题开发工具中的地址使用为什么开发工具同时支持localhost和127.0.0.1?实际应用示例VSCode中的Live Server插件VSCode中的VUE项目IDEA中的Spring Boot应用 最佳实践建议 localhost、 127.0.0…...
antd+react实现html图片预览效果
import { Image } from ‘antd’; import { useEffect, useRef, useState } from ‘react’; import styles from ‘./index.module.less’; interface PreviewHtmlWithImagesProps { htmlContent: string; } const PreviewHtmlWithImages: React.FC ({ htmlContent }) >…...
【React】轻松掌握 React 中 useEffect的使用
你有没有想过,为什么你的 React 组件能够轻松应对周围发生的变化,比如每当有新数据到来时自动更新,或者处理可以动态响应实时事件的组件?这就是 useEffect 的用武之地!这个强大的钩子(Hook)就像…...
请简述一下什么是 Kotlin?它有哪些特性?
1 JVM 语言的共性:编译成字节码文件 Kotlin 和 Java 同属于 JVM(Java Virtual Machine)语言,它们的代码最终都会被编译成 JVM 字节码(.class)文件。 编译流程: Kotlin 编译:Kotli…...
Post与Get以及@Requestbody和@Pathvariable标签的应用
Post的使用场景:简单来讲适用于有安全性限制的,因为post请求的内容会被存在某个封装内容中(比如表单、jason格式等),这部分内容是不会被浏览器的cache所捕捉,安全性较强。 Get的使用场景:与pos…...
基于tabula对pdf中的excel进行识别并转换成word(三)
上一节中是基于PaddleOCR对图片中的excel进行识别并转换成word优化,本节改变思路,直接从pdf中读取表格的信息,具体思路如下所述。 PDF中的表格数据如下截图所示: 一、基于tabula从PDF中提取表格 df_list tabula.read_pdf("…...
k8s集群环境部署业务系统
k8s集群环境部署业务系统,通过shell脚本整合部署过程,简化部署流程。操作流程如下: A,B为业务系统服务名。 一.部署前准备。在k8s集群各节点执行该脚本,完成业务系统镜像加载。 #!/bin/bash # 1.删除deployment ech…...
MySQL 8.4.4 安全升级指南:从漏洞修复到版本升级全流程解析
目录 二、升级前关键注意事项 1. 数据安全与备份 2. 版本兼容性与路径规划 三、分步升级操作流程 1. 环境预检与准备 2. 安装包部署 3. 强制升级组件 4. 验证与启动 一、背景与必要性 近期安全扫描发现生产环境的 MySQL 数据库存在多个高危漏洞(CVE 详情参见Oracle 官…...
“假读“操作在I2C接收流程中的原因
在I2C接收流程中,"假读"操作是NXP I2C控制器工作特性要求的必要操作,具体原因如下: // 接收函数关键代码 void i2c_master_read(I2C_Type *base, unsigned char *buf, unsigned int size) {// ...dummy base->I2DR; /* 假读 *…...
TA学习之路——2.3图形的HLSL常用函数详解
1.基本数学运算 函数作用max(a,b)返回a,b值中较大的那个min(a,b)返回a,b值中较小的那个mul(a,b两变量相乘,常用于矩阵abs(a)返回a的绝对值sqrt(x)返回x的平方根rsqrt(x)返回x的平方根的倒数degrees(x)将弧度转成角度radians(x)将角度转成弧度noise(x)噪声函数1.1 创建一个测试…...
Python数据容器:数据容器的分类、数据容器特点总结、各数据容器遍历的特点、数据容器通用操作(统计,转换,排序)
目录 数据容器的分类 数据容器特点总结 数据容器遍历的特点 通用操作 通用统计len()、max()、min() 通用转换list()、tuple()、str()、set() 通用排序sorted 数据容器的分类 分类: 是否支持下标索引 支持:列表、元组、字符串-序列类型不支持&…...
FastAPI的发展历史
参考:https://zhuanlan.zhihu.com/p/710831974 FastAPI 于 2019 年发布,由 Sebastian Ramirez 创建。他是 Pydantic 框架的创建者,也是多个开源项目的贡献者。 FastAPI 的设计初衷是为了解决 Python Web 框架在数据类型验证和文档生成方面的问…...
本地大模型编程实战(28)查询图数据库NEO4J(1)
本文将基于langchain 框架,用LLM(大语言模型)查询图数据库NEO4J。 使用 qwen2.5 做实验,用 llama3.1 查不出内容。 文章目录 安装 NEO4J准备图数据查询图数据总结代码 安装 NEO4J 参见:在windows系统中安装图数据库NEO4J 。 准备图数据 我…...
从厨房到云端:从预制菜到云原生
小美:小猿,你最近在忙什么呢?看你总是加班。 小猿:唉,公司在搞什么“云原生”改造,说是要把我们的应用搬到云上,搞得我头都大了。 小美:云原生?听起来很高大上啊&#…...
单片机-89C51部分:9、串行口通讯
飞书文档https://x509p6c8to.feishu.cn/wiki/WSh3wnADkixHspk7kc8c5esRnad 一、什么是串口?它的作用? 串行口,简称为串口,什么是串口?它的作用是什么? 两个人交流,一般通过在说话在空气中产生…...
C++程序退出时的对象析构陷阱:深度解析与避坑指南
C++程序退出时的对象析构陷阱:深度解析与避坑指南 一、从诡异案例说起:局部对象为何"神秘消失"?二、全局对象 vs 局部对象1. 全局对象生命周期2. 局部对象生命周期三、程序终止的两种姿势:exit() vs return四、atexit():最后的救命稻草1.基础用法2. 核心特性3…...