MIT 6.828 操作系统课程系列7 Networking#
中断和驱动#
看手册第5章
当设备需要和操作系统交互时,会产生一个中断,是trap的一种。在devintr()
中处理。
驱动的执行可分”上/下”两部分。
上半部分由read/write之类的syscall发起,请求设备做一些io操作。然后等待设备结果。
设备完成操作触发一个中断,开始下半部分,即中断处理。
plic https://github.com/riscv/riscv-plic-spec/blob/master/riscv-plic.adoc
设备模拟#
见https://www.qemu.org/docs/master/system/target-riscv.html
lab0中已有一些讨论。我们模拟的cpu是64-bit RISC-V。用qemu-system-riscv64
命令启动。
列出machine类型
qemu-system-riscv64 -machine help
Supported machines are:
none empty machine
sifive_e RISC-V Board compatible with SiFive E SDK
sifive_u RISC-V Board compatible with SiFive U SDK
spike RISC-V Spike Board (default)
spike_v1.10 RISC-V Spike Board (Privileged ISA v1.10)
spike_v1.9.1 RISC-V Spike Board (Privileged ISA v1.9.1)
virt RISC-V VirtIO board
我们用的是virt
,也就是Generic Virtual Platform
。
https://www.qemu.org/docs/master/system/riscv/virt.html
qemu-system-riscv64 -device help
可列出所有模拟设备。
可以看到name "e1000", bus PCI, alias "e1000-82540em", desc "Intel Gigabit Ethernet"
uart我不知道是哪个设备。页没有找到qemu的uart相关文档。可能只能从它代码里找信息。
qemu/qemu
qemu/qemu
可以看到这两个代码开头都有Copyright (c) 2017 SiFive, Inc.
memlayout.h
中前半部分都是硬件相关的定义。
Advanced Core Local Interruptor
(ACLINT)#
ACLINT是virt
平台的一部分,定义了一批memory mapped设备。为每个cpu提供inter-processor中断
(IPI)和timer功能。
前身是SiFive E31
的CLINT
,SiFive E31
是riscv的一个实现。qemu的virt的相关代码应该也是SiFive贡献的。
CLINT提供了统一的IPI和timer的寄存器映射,没有提供supervisor-level IPI。
ACLINT在CLINT基础上变得更模块化。
ACLINT向回兼容CLINT。xv6只用到CLINT。直接看SiFive E31
的文档。
https://static.dev.sifive.com/E31-RISCVCoreIP.pdf
见SiFive文档第8章
cpu0的mtimecmp映射0x2004000,每个占8字节。
mtime寄存器映射0x200BFF8。
所以代码有
// core local interruptor (CLINT), which contains the timer.
#define CLINT 0x2000000L
#define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 8*(hartid))
#define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot.
CLINT_MTIME
可获取当前的tick数。CLINT_MTIMECMP
(hartid)用来设置某个cpu的timer中断触发时间。
Platform-Level Interrupt Controller
(PLIC)#
riscv/riscv-plic-spec
https://wiki.osdev.org/PLIC
PLIC的base映射在
0x0c000000
。每个中断源都对应一个id。id为0时为无中断。id同时可以表示优先级,越小优先级越高。
中断源从
0x0c000000
开始排,每4字节一个。最多可有1024个中断源。0x0c000000
是第一个也就是0号,是特殊的,不实际使用。0x0c001000
开始是Interrupt Pending。到0x0c001080
一共128字节,1024bit正好对应1024个中断源。0x0c002000
开始是一系列context,每个占0x80字节,1024bit。正好对应1024个中断源。每个bit配置对应中断源是否在此context开启。
而一个hart分machine模式和supervisor模式两种配置。所以一个hart占0x80*2=0x100字节
,2个context。一直到
0x0c200000
开始Priority threshold的配置,前4字节为threshold,5-8为claim/complete。
之后为reserved,一整个占0x1000。同样分两种模式,一个hart占0x2000。threshold作用是,只有当中断的优先级大于配置的threshold,plic才会实际触发这个中断。
中断数据流#
interrupt source
发signal到gateway
排队,gateway
挨个处理,请求plic core
。
plic core
进行interrupt notification
通知目标,目标进行claim
以获取中断源(哪个中断)。
处理完以后目标要通知plic已经完成。然后gateway开始请求处理下一个中断。
console流程#
// qemu puts UART registers here in physical memory.
#define UART0 0x10000000L
#define UART0_IRQ 10 // qemu的virt.h定义
// core local interruptor (CLINT), which contains the timer.
#define CLINT 0x2000000L
#define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 8*(hartid))
#define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot.
// qemu puts platform-level interrupt controller (PLIC) here.
#define PLIC 0x0c000000L
#define PLIC_PRIORITY (PLIC + 0x0)
#define PLIC_PENDING (PLIC + 0x1000)
#define PLIC_MENABLE(hart) (PLIC + 0x2000 + (hart)*0x100)
#define PLIC_SENABLE(hart) (PLIC + 0x2080 + (hart)*0x100)
#define PLIC_MPRIORITY(hart) (PLIC + 0x200000 + (hart)*0x2000)
#define PLIC_SPRIORITY(hart) (PLIC + 0x201000 + (hart)*0x2000)
#define PLIC_MCLAIM(hart) (PLIC + 0x200004 + (hart)*0x2000)
#define PLIC_SCLAIM(hart) (PLIC + 0x201004 + (hart)*0x2000)
plicinit(void)
{
// set desired IRQ priorities non-zero (otherwise disabled).
// 设置全局UART0中断源
*(uint32*)(PLIC + UART0_IRQ*4) = 1;
}
// 每个hart进行配置
plicinithart(void)
{
int hart = cpuid();
// set enable bits for this hart's S-mode
// 只用s模式。开启此cpu的uart中断。
*(uint32*)PLIC_SENABLE(hart) = (1 << UART0_IRQ);
// 只用s模式。threshold=0
// set this hart's S-mode priority threshold to 0.
*(uint32*)PLIC_SPRIORITY(hart) = 0;
}
// console初始化
#define CONSOLE 1
consoleinit
uartinit
// 基本就是对uart硬件进行各种配置
// 见http://byterunner.com/16550.html
// 设置回调
devsw[CONSOLE].read = consoleread;
devsw[CONSOLE].write = consolewrite;
// uart数据
struct spinlock uart_tx_lock;
#define UART_TX_BUF_SIZE 32
char uart_tx_buf[UART_TX_BUF_SIZE];
uint64 uart_tx_w;
uint64 uart_tx_r;
// console的数据
struct {
struct spinlock lock;
// input
#define INPUT_BUF_SIZE 128
char buf[INPUT_BUF_SIZE];
uint r; // Read index
uint w; // Write index
uint e; // Edit index
} cons;
// 都配置好后。系统运行sh。当按下键盘,触发中断。
usertrap
devintr
uint64 scause = r_scause();
// 查riscv-privileged 4.1.8
// 64bit的第一位为1表示是Interrupt
// Exception Code 9,为Supervisor external interrupt
if((scause & 0x8000000000000000L) &&
(scause & 0xff) == 9){
// this is a supervisor external interrupt, via PLIC.
// irq indicates which device interrupted.
// claim获取中断编号
int irq = plic_claim();
int hart = cpuid();
int irq = *(uint32*)PLIC_SCLAIM(hart);
if(irq == UART0_IRQ){
uartintr();
// 进入uart驱动流程
// 当uart有输入,或者uart准备好接收更多的数据。都会进中断。
// 然后这里既要尝试读。又要尝试写。
// read and process incoming characters.
// 不断尝试从uart读一个字符,并抛给console。
while(1){
int c = uartgetc();
if(ReadReg(LSR) & 0x01){
#define Reg(reg) ((volatile unsigned char *)(UART0 + reg))
#define ReadReg(reg) (*(Reg(reg))) // 解地址
#define RHR 0
#define LSR 5 // line status register
// 见http://byterunner.com/16550.html
// LSR为1说明有输入
// 此时读RHR得到键盘输入的字符
// input data is ready.
return ReadReg(RHR);
} else {
return -1; // 无输入
}
if(c == -1)
break; // 无输入
consoleintr(c); // 处理得到的输入字符c
acquire(&cons.lock); // 获取锁
// case字符
switch(c){
case C('P'): // 特殊功能。打印进程列表
procdump();
break;
case C('U'): // 特殊功能。命令行删除一行
while(cons.e != cons.w &&
// 更新buf。发数据给硬件。
cons.buf[(cons.e-1) % INPUT_BUF_SIZE] != '\n'){
cons.e--;
consputc(BACKSPACE);
// console发给uart一个字符
if(c == BACKSPACE){ // 特殊的BACKSPACE。删除一个字符。
// 这里为什么要删了做空格再删?
uartputc_sync('\b');
uartputc_sync(' ');
uartputc_sync('\b');
push_off(); // 关中断
if(panicked){
for(;;)
;
}
#define LSR_TX_IDLE (1<<5)
// LSR的bit5
// 0 = transmit holding register is full. 16550 will not accept any data for transmission.
// 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character.
// 如果设备不能接收数据了,就一直等。
// wait for Transmit Holding Empty to be set in LSR.
while((ReadReg(LSR) & LSR_TX_IDLE) == 0)
;
WriteReg(THR, c); // 写THR就是给串口喂数据
pop_off(); // 开中断
} else {
uartputc_sync(c);
}
}
break;
case C('H'): // Backspace
case '\x7f': // Delete key
if(cons.e != cons.w){
cons.e--;
consputc(BACKSPACE); // 删除一个字符
}
break;
default:
if(c != 0 && cons.e-cons.r < INPUT_BUF_SIZE){ // 不能超出buf
c = (c == '\r') ? '\n' : c;
// echo back to the user.
// console发字符c发给uart
consputc(c);
// store for consumption by consoleread().
// 更新buf
cons.buf[cons.e++ % INPUT_BUF_SIZE] = c;
if(c == '\n' || c == C('D') || cons.e-cons.r == INPUT_BUF_SIZE){
// wake up consoleread() if a whole line (or end-of-file)
// has arrived.
cons.w = cons.e;
wakeup(&cons.r);
}
}
break;
}
release(&cons.lock); // 释放console锁
}
// 上面是尝试读uart
// 下面尝试写
// send buffered characters.
acquire(&uart_tx_lock);
uartstart();
// 不断尝试发数据给uart
while(1){
// uart_tx_w是buf的目前写入index,需要把buf的数据都发给uart
// uart_tx_r是uart目前读到的位置。
// 如果uart_tx_r追上uart_tx_w,说明uart已经读完了。否则要发数据非uart。
if(uart_tx_w == uart_tx_r){
// transmit buffer is empty.
return;
}
// 如果设备不能接收数据了,可直接结束。等它能接收了,会再起中断的。
if((ReadReg(LSR) & LSR_TX_IDLE) == 0){
// the UART transmit holding register is full,
// so we cannot give it another byte.
// it will interrupt when it's ready for a new byte.
return;
}
int c = uart_tx_buf[uart_tx_r % UART_TX_BUF_SIZE];
uart_tx_r += 1;
// maybe uartputc() is waiting for space in the buffer.
wakeup(&uart_tx_r);
// 发字符给uart
WriteReg(THR, c);
}
release(&uart_tx_lock);
} else if(irq){
printf("unexpected interrupt irq=%d\n", irq);
}
// the PLIC allows each device to raise at most one
// interrupt at a time; tell the PLIC the device is
// now allowed to interrupt again.
if(irq)
plic_complete(irq);
int hart = cpuid();
// 写入claim地址表示完成
*(uint32*)PLIC_SCLAIM(hart) = irq;
return 1;
}
printf流程#
#define CONSOLE 1
// init.c
main
mknod("console", CONSOLE, 0); // init最开始就会用mknod创建一个device文件
create(path, T_DEVICE, major, minor)
printf(const char *fmt, ...) // user空间
va_start(ap, fmt);
vprintf(1, fmt, ap); // 默认写到fd1
putc(fd, c); // 最终就是调putc写字符
write(fd, &c, 1); // syscall
sys_write // kernel
filewrite(f, p, n)
if(f->type == FD_DEVICE) // 开始已经create为device类型。会走到这里回调
if(f->major < 0 || f->major >= NDEV || !devsw[f->major].write)
return -1;
ret = devsw[f->major].write(1, addr, n);
// 走consoleinit设置的回调consolewrite
consolewrite(int user_src, uint64 src, int n)
int i;
for(i = 0; i < n; i++){
char c;
if(either_copyin(&c, user_src, src+i, 1) == -1) // 从user取数据
break;
// 数据放到uart的buf并尝试写入uart
uartputc(c);
acquire(&uart_tx_lock);
if(panicked){
for(;;)
;
}
while(uart_tx_w == uart_tx_r + UART_TX_BUF_SIZE){
// buffer is full.
// wait for uartstart() to open up space in the buffer.
sleep(&uart_tx_r, &uart_tx_lock);
}
uart_tx_buf[uart_tx_w % UART_TX_BUF_SIZE] = c;
uart_tx_w += 1;
uartstart();
release(&uart_tx_lock);
}
return ret;
E1000流程#
这个lab要做一个网卡驱动。设备叫做E1000,是qemu的模拟设备,连接到qemu的模拟lan网络。
xv6会得到ip地址10.0.2.15。host的地址为10.0.2.2。
make server
会起一个python的简单udp server。
此时另起模拟器运行nettests,会测试网络功能。现在要调通网络功能。
// nettests.c
uint16 dport = NET_TESTS_PORT;
ping(uint16 sport, uint16 dport, int attempts)
int fd;
char *obuf = "a message from xv6!";
uint32 dst;
// qemu写死的10.0.2.2。作为host的ip。
dst = (10 << 24) | (0 << 16) | (2 << 8) | (2 << 0);
fd = connect(dst, sport, dport) // syscall
sys_connect
// 获取ip和两个端口
argint(0, (int*)&raddr);
argint(1, (int*)&lport);
argint(2, (int*)&rport);
sockalloc(&f, raddr, lport, rport)
// 各种分配和填数据
fd=fdalloc(f)
// 得到socket fd
return fd
write(fd, obuf, strlen(obuf) // syscall
sys_write
filewrite(struct file *f, uint64 addr, int n)
if(f->type == FD_SOCK) // 走socket类型
ret = sockwrite(f->sock, addr, n);
m = mbufalloc(MBUF_DEFAULT_HEADROOM);
// copyin要发送的数据到kernel本地buf
copyin(pr->pagetable, mbufput(m, n), addr, n)
// 发送udp
net_tx_udp(m, si->raddr, si->lport, si->rport);
net_tx_ip(m, IPPROTO_UDP, dip);
net_tx_eth(m, ETHTYPE_IP);
e1000_transmit(m)
// 到这就需要我们写代码把m中的数据发给网卡
接下来需要学习E1000手册,查各种资料。
E1000手册
https://pdos.csail.mit.edu/6.828/2022/readings/8254x_GBe_SDM.pdf
PCI概念
https://en.wikipedia.org/wiki/Peripheral_Component_Interconnect
Peripheral Component Interconnect(PCI)
Peripheral Component Interconnect eXtended(PCI-X)
Peripheral Component Interconnect Express(PCIe)
使用Direct Memory Access(DMA)技术直接和内存交互,不用映射寄存器。
Ethernet以太网,一种有线网络技术。协议为IEEE802.3,去IEEE网站注册下载2022版本。有7000页pdf。
packet的接收#
总的来说需要识别线路上的包,做地址过滤,把包存到FIFO,把数据转到host的接收buffer,更新receive descriptor
。
packet地址过滤#
过滤模式
Exact Unicast/Multicast
完全匹配Promiscuous Unicast
接收所有unicastMulticast
Promiscuous Multicast
接收所有multicastVLAN
接收所有vlan包
正常情况只接收”好的”包,即没有CRC/symbol/时序/长度等错误。
Receive Data Storage#
硬件支持7中buffer长度。
256B
512B
1024B
2048B
4096B
8192B
16384B
Receive Descriptor Format#
receive descriptor
是一个数据结构,包含接收buffer的地址和其他一些包信息。
见e1000_dev.h
中的rx_desc
。
packet的发送#
协议栈收到一块待发送数据
协议栈根据MTU大小等信息算出发送完这块数据需要的包数量
对于每个包
准备Ethernet,IP,TCP/UDP头。
协议栈对接设备驱动,请求发送包数据。
驱动拿到frame然后请求设备
设备通过DMA读数据
设备完成后通过中断通知驱动,驱动再往上回复。
Transmit Descriptors#
描述发送数据的结构体。
见e1000_dev.h
中的tx_desc
。
PCI配置/网卡初始化#
// pci.c
pci_init()
{
// we'll place the e1000 registers at this address.
// vm.c maps this range.
// e1000的内存映射。我们可以自己决定。
uint64 e1000_regs = 0x40000000L;
// qemu -machine virt puts PCIe config space here.
// vm.c maps this range.
// 见https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c
// VIRT_PCIE_ECAM映射在0x30000000
// vm初始化时会做映射
// https://en.wikipedia.org/wiki/PCI_configuration_space
// ECAM是pcie的配置方法
// Enhanced Configuration Access Mechanism
uint32 *ecam = (uint32 *) 0x30000000L;
// 配置参考这个文档
// https://wiki.qemu.org/images/f/f6/PCIvsPCIe.pdf
// https://en.wikipedia.org/wiki/PCI_configuration_space
// 也可直接看pcie文档。我找了pcie5.0文档。
// 但是xv6代码实际走的PCI的CAM而不是PCIE的ECAM?
// 而qemu中都显示的pcie。先不纠结。
// https://wiki.osdev.org/PCI
// look at each possible PCI device on bus 0.
// 遍历device
// dev占5bit,最多32个设备。
// func占3bit,最多8个function。
for(int dev = 0; dev < 32; dev++){
int bus = 0;
int func = 0;
int offset = 0;
uint32 off = (bus << 16) | (dev << 11) | (func << 8) | (offset);
volatile uint32 *base = ecam + off;
uint32 id = base[0];
// 100e:8086 is an e1000
// 设备型号的对应见网卡手册5.2
// 互联网可查PCI设备
// https://devicehunt.com/view/type/pci/vendor/8086/device/100E
if(id == 0x100e8086){
// command and status register.
// bit 0 : I/O access enable
// bit 1 : memory access enable
// bit 2 : enable mastering
// 网卡手册4.1
// 写 Command Register。3个bit打开
base[1] = 7;
__sync_synchronize();
// 6个Base Address
// Base Address Registers(BAR)
for(int i = 0; i < 6; i++){
uint32 old = base[4+i];
// writing all 1's to the BAR causes it to be
// replaced with its size.
base[4+i] = 0xffffffff;
__sync_synchronize();
base[4+i] = old;
}
// tell the e1000 to reveal its registers at
// physical address 0x40000000.
base[4+0] = e1000_regs;
e1000_init((uint32*)e1000_regs);
int i;
initlock(&e1000_lock, "e1000");
regs = xregs;
// Reset the device
// 13.4.20 Interrupt Mask Set/Read Register 关中断
regs[E1000_IMS] = 0; // disable interrupts
// 13.4.1 Device Control Register 主要配置
regs[E1000_CTL] |= E1000_CTL_RST; // |= 0x00400000 22bit置1 SDP0_IODIR?
// 再次关。没懂
regs[E1000_IMS] = 0; // redisable interrupts
__sync_synchronize();
// tx_ring的定义。就是一个buffer的数组
// #define TX_RING_SIZE 16
// static struct tx_desc tx_ring[TX_RING_SIZE] __attribute__((aligned(16)));
// static struct mbuf *tx_mbufs[TX_RING_SIZE];
// 设置发送buffer
// [E1000 14.5] Transmit initialization
memset(tx_ring, 0, sizeof(tx_ring));
for (i = 0; i < TX_RING_SIZE; i++) {
tx_ring[i].status = E1000_TXD_STAT_DD; // 3.3.3.2 初始化为发送完成状态
tx_mbufs[i] = 0;
}
// 13.4.36 Transmit Descriptor Base Address Low
// 设置发送buffer
regs[E1000_TDBAL] = (uint64) tx_ring;
if(sizeof(tx_ring) % 128 != 0)
panic("e1000");
// 3.4 Transmit Descriptor Ring Structure
// 13.4.38 Transmit Descriptor Length
// tx_ring的字节数
regs[E1000_TDLEN] = sizeof(tx_ring);
// 13.4.39 Transmit Descriptor Head
// 硬件控制。tx ring中第一个在处理的buffer的offset
// 13.4.40 Transmit Descriptor Tail
// 软件层写入值,配置要发送的新数据。
regs[E1000_TDH] = regs[E1000_TDT] = 0;
// 设置接收buffer
// [E1000 14.4] Receive initialization
memset(rx_ring, 0, sizeof(rx_ring));
for (i = 0; i < RX_RING_SIZE; i++) {
rx_mbufs[i] = mbufalloc(0);
if (!rx_mbufs[i])
panic("e1000");
rx_ring[i].addr = (uint64) rx_mbufs[i]->head;
}
// 13.4.25 Receive Descriptor Base Address Low
// 设置接收buffer
regs[E1000_RDBAL] = (uint64) rx_ring;
if(sizeof(rx_ring) % 128 != 0)
panic("e1000");
// 13.4.28 Receive Descriptor Head
regs[E1000_RDH] = 0;
// 13.4.29 Receive Descriptor Tail
regs[E1000_RDT] = RX_RING_SIZE - 1;
// 13.4.27 Receive Descriptor Length
regs[E1000_RDLEN] = sizeof(rx_ring);
// filter by qemu's MAC address, 52:54:00:12:34:56
// 13.5.2 Receive Address Low
// 13.5.3 Receive Address High
regs[E1000_RA] = 0x12005452;
regs[E1000_RA+1] = 0x5634 | (1<<31);
// multicast table
// 13.5.1 Multicast Table Array
for (int i = 0; i < 4096/32; i++)
regs[E1000_MTA + i] = 0;
// transmitter control bits.
// 13.4.33 Transmit Control Register
// 发送配置
regs[E1000_TCTL] = E1000_TCTL_EN | // enable
E1000_TCTL_PSP | // pad short packets
(0x10 << E1000_TCTL_CT_SHIFT) | // collision stuff
(0x40 << E1000_TCTL_COLD_SHIFT);
// 13.4.34 Transmit IPG Register
regs[E1000_TIPG] = 10 | (8<<10) | (6<<20); // inter-pkt gap
// receiver control bits.
// 13.4.22 Receive Control Register
// 接收配置
regs[E1000_RCTL] = E1000_RCTL_EN | // enable receiver
E1000_RCTL_BAM | // enable broadcast
E1000_RCTL_SZ_2048 | // 2048-byte rx buffers
E1000_RCTL_SECRC; // strip CRC
// ask e1000 for receive interrupts.
// 13.4.30 Receive Delay Timer Register
regs[E1000_RDTR] = 0; // interrupt after every received packet (no timer)
// 13.4.31 Receive Interrupt Absolute Delay Timer
regs[E1000_RADV] = 0; // interrupt after every packet (no timer)
// 13.4.20 Interrupt Mask Set/Read Register
// RXT0
// Sets mask for Receiver Timer Interrupt
regs[E1000_IMS] = (1 << 7); // RXDW -- Receiver Descriptor Write Back
}
}
}
看网卡手册3.4的发送ring 有一个buffer数组也就是ring buffer映射给硬件。head指向硬件正在处理的第一个buffer,tail指向硬件正在处理的最后一个buffer之后。
e1000_transmit中 直接读tail寄存器的值,就是ring buffer可用的index。如果有残余数据,free掉。
把新的buffer和其他参数填好。tail寄存器+1。
有错的话返回-1。e1000来数据后触发中断,走e1000_recv。
检查rx_ring,用net_rx()把数据传给协议栈。
需要申请新的mbuf代替原buf,这样来新数据的话有地方存。
rdt指向的是可取数据的前一格。rdh指向的是最后收到的数据之后一格。每次收一条数据,网卡把rdh+1。
如果两者相等,buffer满,网卡不再收数据,造成丢数据。直到应用程序从rdt+1处取走一条数据,并把rdt+1,即空出一个buffer。具体参照hints
总体看
cpu0对pci初始化,初始化网卡硬件。
然后每个cpu都能跑设备驱动。
应用程序从某个进程开始,调syscall进内核态,走协议栈逻辑,产生协议数据,发给网卡硬件,网卡发到外部网络。
网卡收到外部数据,反向走,产生中断,取出数据,发给协议栈解析,最后返到应用程序。网卡产生的中断发到哪个hart?
看plic文档第3节
The PLIC hardware only supports multicasting of interrupts, such that all enabled targets will receive interrupt notifications for a given active interrupt.
只支持multicast,会通知所有打开该中断的hart。然后hart按规则必须进行claim,得到中断的具体信息。
claim是原子操作,不会出现重复claim同一个中断,谁先claim到,谁就来处理。
见plic文档第7节
还可以通过配置项做各种策略。
这样看来所有hart一样配置的话,默认可认为是随机挑选hart来执行设备中断。并行环境下,能正确地返到对应地应用程序。见
sockrecvudp()
等。并行环境下要考虑多个进程同时跑驱动流程。需要加锁。
用户态或kernel态都可能发生中断。
外部的多条数据发到网卡,填入多个buffer,可能只产生一个中断。
多进程收发测试
ring buffer有限,如果驱动收数据不及时,可造成网卡直接丢弃数据,测试失败。
目前的理解本质上要么叫对方重发,要么直接加大buffer完事。