uknetdev 源码分析

uknetdev

论文部分

uknetdev 部分 API 的主要目标是将驱动与平台分离开,使驱动可以在多种平台上重用。

How?

API 给用户提供三种操作 Unikraft 驱动的方式:轮询、中断和混合。另外,API 还将内存分配的部分开放给了应用,同时支持一些高性能的特性例如多队列,零拷贝 I/O 和数据包批处理。应用程序可以通过 API 获取信息以操控驱动。

Where?

netbuf.h

netbuf.h 文件中,位于首部的注释给我们描述了 netbuf 的结构,归结如下图:

netbuf

在分配好的内存中的结构如下:

netbuf_mem

netbuf 结构体代码定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
struct uk_netbuf {
struct uk_netbuf *next;
struct uk_netbuf *prev;
// netbuf 的标志
uint8_t flags; /**< Flags for this netbuf */
// 指向负载的开始部分
void *data; /**< Payload start, is part of buf. */
// 负载长度
uint16_t len; /**< Payload length (should be <= buflen). */
// 引用次数
__atomic refcount; /**< Reference counter */
// 指向 private meta data 部分
void *priv; /**< Reference to user-provided private data */
// 指向整个 buffer 的起始部分
void *buf; /**< Start address of contiguous buffer. */
size_t buflen; /**< Length of buffer. */
// 校验和的起始位置
uint16_t csum_start; /**< Used if UK_NETBUF_F_PARTIAL_CSUM is set;
* Offset within this netbuf's data segment to
* begin checksumming
*/
// 校验和偏移量
uint16_t csum_offset; /**< Used if UK_NETBUF_F_PARTIAL_CSUM is set;
* Number of bytes starting from `csum_start`
* pointing to the checksum field
*/
// 析构函数
uk_netbuf_dtor_t dtor; /**< Destructor callback */
struct uk_alloc *_a; /**< @internal Allocator for free'ing */
void *_b; /**< @internal Base address for free'ing */
};

从定义的开头部分即可看出所有的 netbuf 由一个双向链表索引起来,所以在后面的代码中我们也可以看到对于链表的相关操作函数以及宏定义:

为什么是链表?

1
2
3
4
5
6
//这里是两个用于遍历链表的宏定义
#define UK_NETBUF_CHAIN_FOREACH(var, head) \
for ((var) = (head); (var) != NULL; (var) = (var)->next)

#define UK_NETBUF_CHAIN_FOREACH_R(var, tail) \
for ((var) = (tail); (var) != NULL; (var) = (var)->prev)

在 netbuf.h 中除了对于双向链表的操作外,还包含对于 netbuf 本身操作的函数,例如分配空间、使用用户给定值进行初始化等等,各函数间的调用关系图如下:

该文件中各函数调用关系

其中,init_indir 函数用于初始化 uk_netbuf 结构体,.data 字段的定义就如同前面绘制的结构图那样,起始位置为 *buf 指针加上 headroom 的长度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void uk_netbuf_init_indir(struct uk_netbuf *m,
void *buf, size_t buflen, uint16_t headroom,
void *priv, uk_netbuf_dtor_t dtor)
{
UK_ASSERT(m);
UK_ASSERT(buf || (buf == NULL && buflen == 0));
UK_ASSERT(headroom <= buflen);
// 使用给定值为各变量进行初始化

/* Reset pbuf, non-listed fields are automatically set to 0 */
*m = (struct uk_netbuf) {
.buf = buf,
.buflen = buflen,
.data = (void *) ((uintptr_t) buf + headroom),
.priv = priv,
.dtor = dtor
};
// 初始化引用次数为1
uk_refcount_init(&m->refcount, 1);
}

alloc_indir 函数用于为 uk_netbuf 部分分配内存空间,在分配好相应的空间后调用 init_indir 函数对分配的内存进行初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct uk_netbuf *uk_netbuf_alloc_indir(struct uk_alloc *a,
void *buf, size_t buflen,
uint16_t headroom,
size_t privlen, uk_netbuf_dtor_t dtor)
{
struct uk_netbuf *m;
// 若 privlen != 0 则为 private data 部分额外分配内存
if (privlen)
m = uk_malloc(a, NETBUF_ADDR_ALIGN_UP(sizeof(*m)) + privlen);
else
m = uk_malloc(a, sizeof(*m));
// 分配失败返回 NULL
if (!m)
return NULL;
// 对分配好内存的变量进行初始化,privlen 的起始地址为 netbuf 起始地址加上 netbuf 结构体大小
uk_netbuf_init_indir(m,
buf,
buflen,
headroom,
privlen > 0
? (void *)((uintptr_t) m
+ NETBUF_ADDR_ALIGN_UP(sizeof(*m)))
: NULL,
dtor);

/* Save reference to allocator that is used
* for free'ing this uk_netbuf.
*/
// 存储用于释放空间的构造器以及首地址
m->_a = a;
m->_b = m;

return m;
}

alloc_buf 函数用于为整个 buffer 申请内存地址,在计算完所需要的内存大小后调用 uk_memalign 以一定的粒度大小进行内存的分配,随后调用 prepare_buf 进行整块 buffer 的初始化。

与上一个函数相比,多了 netbuf 中指针指向区域的内存分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct uk_netbuf *uk_netbuf_alloc_buf(struct uk_alloc *a, size_t buflen,
size_t bufalign, uint16_t headroom,
size_t privlen, uk_netbuf_dtor_t dtor)
{
void *mem;
size_t alloc_len;
struct uk_netbuf *m;

UK_ASSERT(buflen > 0);
UK_ASSERT(headroom <= buflen);
// 三部分长度之和 buflen + netbuf + priv
alloc_len = NETBUF_ADDR_ALIGN_UP(buflen)
+ NETBUF_ADDR_ALIGN_UP(sizeof(*m) + privlen);
mem = uk_memalign(a, bufalign, alloc_len);
if (!mem)
return NULL;

m = uk_netbuf_prepare_buf(mem,
alloc_len,
headroom,
privlen,
dtor);
if (!m) {
uk_free(a, mem);
return NULL;
}

/* Save reference to allocator and allocation
* that is used for free'ing this uk_netbuf.
*/
m->_a = a;
m->_b = mem;

return m;
}

prepare_buf 函数用于初始化整个 buffer,通过计算令各个指针指向各自的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct uk_netbuf *uk_netbuf_prepare_buf(void *mem, size_t size,
uint16_t headroom,
size_t privlen, uk_netbuf_dtor_t dtor)
{
struct uk_netbuf *m;
size_t meta_len = 0;

UK_ASSERT(mem);

/* Place headroom and buf at the beginning of the allocation,
* This is done in order to forward potential alignments of the
* underlying allocation directly to the netbuf data area.
* `m` (followed by `m->priv` if privlen > 0) will be placed at
* the end of the given memory.
*/
// meta_len长度为netbuf结构体大小+private部分大小并向上对齐
meta_len = NETBUF_ADDR_ALIGN_UP(sizeof(*m) + privlen);
// 元数据长度大于总长度则无法分配
if (meta_len > NETBUF_ADDR_ALIGN_DOWN((__uptr) mem + size))
return NULL;
// *m 所指向的位置是总长度减去 meta data 的长度
m = (struct uk_netbuf *) (NETBUF_ADDR_ALIGN_DOWN((__uptr) mem + size)
- meta_len);
// 调用函数对分配的空间进行初始化
uk_netbuf_init_indir(m,
mem,
(size_t) ((__uptr) m - (__uptr) mem),
headroom,
privlen > 0 ? (void *) ((__uptr) m+ sizeof(*m))
: NULL,
dtor);
return m;
}

随后该文件中还包含了一系列的对于双向链表的操作函数或宏定义

获取链中开始/结尾的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define uk_netbuf_chain_last(m)						\
({ \
struct uk_netbuf *__ret = NULL; \
struct uk_netbuf *__iter; \
UK_NETBUF_CHAIN_FOREACH(__iter, (m)) \
__ret = __iter; \
\
(__ret); \
})
#define uk_netbuf_chain_first(m) \
({ \
struct uk_netbuf *__ret = NULL; \
struct uk_netbuf *__iter; \
UK_NETBUF_CHAIN_FOREACH_R(__iter, (m)) \
__ret = __iter; \
\
(__ret); \
})

连接两条 netbuf 链(末尾)

1
2
3
4
5
6
7
8
9
10
11
void uk_netbuf_connect(struct uk_netbuf *headtail,
struct uk_netbuf *tail)
{
UK_ASSERT(headtail);
UK_ASSERT(!headtail->next);
UK_ASSERT(tail);
UK_ASSERT(!tail->prev);

headtail->next = tail;
tail->prev = headtail;
}

连接两条 netbuf 链(非末尾)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void uk_netbuf_append(struct uk_netbuf *head,
struct uk_netbuf *tail)
{
struct uk_netbuf *headtail;

UK_ASSERT(head);
UK_ASSERT(!head->prev);
UK_ASSERT(tail);
UK_ASSERT(!tail->prev);

headtail = uk_netbuf_chain_last(head);
headtail->next = tail;
tail->prev = headtail;
}

将一个 buffer 从链中断开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct uk_netbuf *uk_netbuf_disconnect(struct uk_netbuf *m)
{
struct uk_netbuf *remhead = NULL;

UK_ASSERT(m);

/* We want to return the next element of m as the
* remaining head of the chain. If there is no next element
* there was no chain.
*/
remhead = m->next;

/* Reconnect the chains before and after m. */
if (m->prev)
m->prev->next = m->next;
if (m->next)
m->next->prev = m->prev;

/* Disconnect m. */
m->prev = NULL;
m->next = NULL;

return remhead;
}

有分配函数必然就对应着释放函数

在什么情况下一个 netbuf 会被多次引用?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void uk_netbuf_free_single(struct uk_netbuf *m)
{
struct uk_alloc *a;
void *b;

UK_ASSERT(m);

/* Decrease refcount and call destructor and free up memory
* when last reference was released.
*/
// 当引用次数等于 1 时才可释放此 netbuf
if (uk_refcount_release(&m->refcount) == 1) {
uk_pr_debug("Freeing netbuf %p (next: %p)\n", m, m->next);

/* Disconnect this netbuf from the chain. */
uk_netbuf_disconnect(m);

/* Copy the reference of the allocator and base address
* in case the destructor is free'ing up our memory
* (e.g., uk_netbuf_init_indir() used).
* In such a case `a` and `b` should be (NULL),
* however we need to access them for a check after
* we have called the destructor.
*/
a = m->_a;
b = m->_b;

if (m->dtor)
m->dtor(m);
if (a && b)
uk_free(a, b);
} else {
uk_pr_debug("Not freeing netbuf %p (next: %p): refcount greater than 1",
m, m->next);
}
}

netdev_core.h

此报头包含所有 API 数据类型。其中⼀些是公共 API 和⼀些是内部 API 的⼀部分。设备的数据与操作是分离的。这种分割允许函数指针和驱动程序数据分散在每个进程中,但实际上共享设备的配置数据。

用于与网络设备进行交互的结构体为 uk_netdev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct uk_netdev {
//数据包单次接收与发送
/** Packet transmission. */
uk_netdev_tx_one_t tx_one; /* by driver */

/** Packet reception. */
uk_netdev_rx_one_t rx_one; /* by driver */

/** Pointer to API-internal state data. */
// 该结构体中包含网络设备的状态、事件处理函数、ID以及名称
struct uk_netdev_data *_data;

/** Functions callbacks by driver. */
/*
* 操作配置选项集合,包括是否⽀持队列中断、硬件物理地址设置、
* mtu(最⼤传输单元)、队列信息获取、设备声明周期管理等
*/
const struct uk_netdev_ops *ops; /* by driver */

/** Pointers to queues (API-private) */
// 队列包含设备、队列唯⼀id、描述符、以及队列描述配置
struct uk_netdev_rx_queue *_rx_queue[CONFIG_LIBUKNETDEV_MAXNBQUEUES];
struct uk_netdev_tx_queue *_tx_queue[CONFIG_LIBUKNETDEV_MAXNBQUEUES];

UK_TAILQ_ENTRY(struct uk_netdev) _list;

/** Netdevice address configuration */
// 这⾥特指ipv4⽹络配置
struct uk_netdev_einfo *_einfo;

#if (CONFIG_UK_NETDEV_SCRATCH_SIZE > 0)
char scratch_pad[CONFIG_UK_NETDEV_SCRATCH_SIZE];
#endif /* CONFIG_UK_NETDEV_SCRATCH_SIZE */
};

netdev_driver.h

在该文件中共有两个函数,主要作用是为用户提供 Unikraft 网络设备驱动的 API。

uk_netdev_drv_rx_event 函数用于将一个 RX 队列事件转发给 API 的使用者。由于接收方不知道什么时候会接收到数据包,故此函数通常也应该由设备中断上下文调用,以此通知该函数进行相应处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static inline void uk_netdev_drv_rx_event(struct uk_netdev *dev,
uint16_t queue_id)
{
struct uk_netdev_event_handler *rxq_handler;

UK_ASSERT(dev);
UK_ASSERT(dev->_data);
UK_ASSERT(queue_id < CONFIG_LIBUKNETDEV_MAXNBQUEUES);

rxq_handler = &dev->_data->rxq_handler[queue_id];
// 若定义了调度函数线程,则操作信号量使位于 netdev.c 中的 dispatcher 工作
#ifdef CONFIG_LIBUKNETDEV_DISPATCHERTHREADS
uk_semaphore_up(&rxq_handler->events);
#else
// 调用回调函数
if (rxq_handler->callback)
rxq_handler->callback(dev, queue_id, rxq_handler->cookie);
#endif
}

uk_netdev_drv_register 函数的主体部分位于 netdev.c 文件中,其主要作用是向设备列表中添加一个网络设备,将在下文中进行介绍。

netdev.h/netdev.c

这两个文件中主要包含工具函数、内存分配、设备注册、获取及设置设备信息、创建和销毁调度线程、配置 RX/TX 队列以及启动设备。由于配置部分函数结构相同,在这里仅介绍几个比较关键的函数。

首先就是对网络设备进行注册的函数 uk_netdev_drv_register,在该函数中为各字段分配了内存,最后将该设备插入至设备列表的末尾并维护总设备数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
int uk_netdev_drv_register(struct uk_netdev *dev, struct uk_alloc *a,
const char *drv_name)
{
UK_ASSERT(dev);
UK_ASSERT(!dev->_data);

/* Assert mandatory configuration */
UK_ASSERT(dev->ops);
UK_ASSERT(dev->ops->info_get);
UK_ASSERT(dev->ops->configure);
UK_ASSERT(dev->ops->rxq_info_get);
UK_ASSERT(dev->ops->rxq_configure);
UK_ASSERT(dev->ops->txq_info_get);
UK_ASSERT(dev->ops->txq_configure);
UK_ASSERT(dev->ops->start);
UK_ASSERT(dev->ops->promiscuous_get);
UK_ASSERT(dev->ops->mtu_get);
UK_ASSERT((dev->ops->rxq_intr_enable && dev->ops->rxq_intr_disable)
|| (!dev->ops->rxq_intr_enable
&& !dev->ops->rxq_intr_disable));
UK_ASSERT(dev->rx_one);
UK_ASSERT(dev->tx_one);
// 为 _data 字段分配内存并赋值
dev->_data = _alloc_data(a, netdev_count, drv_name);
if (!dev->_data)
return -ENOMEM;
// 为网络设备分配 IPv4 相关的内存
if (ipv4_addr || ipv4_subnet_mask || ipv4_gw_addr) {
dev->_einfo = _alloc_einfo(a);
if (PTRISERR(dev->_einfo))
return PTR2ERR(dev->_einfo);
}
// 将当前设备添加至设备列表的末尾
UK_TAILQ_INSERT_TAIL(&uk_netdev_list, dev, _list);
uk_pr_info("Registered netdev%"PRIu16": %p (%s)\n",
netdev_count, dev, drv_name);
// 令设备数 +1
return netdev_count++;
}

dispatcher 是调度线程所执行的函数,可以看出其中有一个不会终止的 for 循环持续对信号量进行监听,该信号量的 +1 操作由 netdev_driver.h 中的 uk_netdev_drv_rx_event 进行。

scheduler 与 dispatcher 的区别
通常来说,scheduler 才是真正意义上的调度器,因为其带有一定的策略。而 dispatcher 执行的只是简单的无策略的分发工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifdef CONFIG_LIBUKNETDEV_DISPATCHERTHREADS
static void _dispatcher(void *arg)
{
struct uk_netdev_event_handler *handler =
(struct uk_netdev_event_handler *) arg;

UK_ASSERT(handler);
UK_ASSERT(handler->callback);

for (;;) {
uk_semaphore_down(&handler->events);
handler->callback(handler->dev,
handler->queue_id,
handler->cookie);
}
}
#endif

有了线程执行的函数本体,我们就要有相应的创建线程和销毁线程的函数。在这个函数中,通过判断 CONFIG_LIBUKNETDEV_DISPATCHERTHREADS 项来决定是否创建单独的线程进行调度操作,若无需线程则仅进行回调函数的赋值。线程间通信所用到的信号量也是在该函数中定义并初始化的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
static int _create_event_handler(uk_netdev_queue_event_t callback,
void *callback_cookie,
#ifdef CONFIG_LIBUKNETDEV_DISPATCHERTHREADS
struct uk_netdev *dev, uint16_t queue_id,
const char *queue_type_str,
struct uk_sched *s,
#endif
struct uk_netdev_event_handler *h)
{
UK_ASSERT(h);
UK_ASSERT(callback || (!callback && !callback_cookie));
#ifdef CONFIG_LIBUKNETDEV_DISPATCHERTHREADS
UK_ASSERT(!h->dispatcher);
#endif

h->callback = callback;
h->cookie = callback_cookie;

#ifdef CONFIG_LIBUKNETDEV_DISPATCHERTHREADS
/* If we do not have a callback, we do not need a thread */
// 若不存在回调函数则无需创建线程
if (!callback)
return 0;
// 初始化变量以及传递信息所用的信号量
h->dev = dev;
h->queue_id = queue_id;
uk_semaphore_init(&h->events, 0);
h->dispatcher_s = s;

/* Create a name for the dispatcher thread.
* In case of errors, we just continue without a name
*/
if (asprintf(&h->dispatcher_name,
"netdev%"PRIu16"-%s[%"PRIu16"]",
dev->_data->id, queue_type_str, queue_id) < 0) {
h->dispatcher_name = NULL;
}
// 新建线程运行调度函数
h->dispatcher = uk_sched_thread_create(h->dispatcher_s,
h->dispatcher_name, NULL,
_dispatcher, h);
if (!h->dispatcher) {
if (h->dispatcher_name)
free(h->dispatcher_name);
h->dispatcher_name = NULL;
return -ENOMEM;
}
#endif

return 0;
}

销毁部分的函数则相对简单一些,通过调用 unikraft 所提供的线程管理函数即可完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void _destroy_event_handler(struct uk_netdev_event_handler *h
__maybe_unused)
{
UK_ASSERT(h);

#ifdef CONFIG_LIBUKNETDEV_DISPATCHERTHREADS
UK_ASSERT(h->dispatcher_s);

if (h->dispatcher) {
uk_thread_kill(h->dispatcher);
uk_thread_wait(h->dispatcher);
}
h->dispatcher = NULL;

if (h->dispatcher_name)
free(h->dispatcher_name);
h->dispatcher_name = NULL;
#endif
}

由于只有在处理 RX 队列时才需要创建调度器线程,故上述的两个函数均由 uk_netdev_rxq_configure 调用。该函数的主要作用就是对 RX 队列进行配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
int uk_netdev_rxq_configure(struct uk_netdev *dev, uint16_t queue_id,
uint16_t nb_desc,
struct uk_netdev_rxqueue_conf *rx_conf)
{
int err;

UK_ASSERT(dev);
UK_ASSERT(dev->_data);
UK_ASSERT(dev->ops);
UK_ASSERT(dev->ops->rxq_configure);
UK_ASSERT(queue_id < CONFIG_LIBUKNETDEV_MAXNBQUEUES);
UK_ASSERT(rx_conf);
UK_ASSERT(rx_conf->alloc_rxpkts);
#ifdef CONFIG_LIBUKNETDEV_DISPATCHERTHREADS
UK_ASSERT((rx_conf->callback && rx_conf->s)
|| !rx_conf->callback);
#endif

if (dev->_data->state != UK_NETDEV_CONFIGURED)
return -EINVAL;

/* Make sure that we are not initializing this queue a second time */
if (!PTRISERR(dev->_rx_queue[queue_id]))
return -EBUSY;
// 调用函数进行事件处理器的配置
err = _create_event_handler(rx_conf->callback, rx_conf->callback_cookie,
#ifdef CONFIG_LIBUKNETDEV_DISPATCHERTHREADS
dev, queue_id, "rxq", rx_conf->s,
#endif
&dev->_data->rxq_handler[queue_id]);
if (err)
goto err_out;
// 进行配置
dev->_rx_queue[queue_id] = dev->ops->rxq_configure(dev, queue_id,
nb_desc, rx_conf);
if (PTRISERR(dev->_rx_queue[queue_id])) {
err = PTR2ERR(dev->_rx_queue[queue_id]);
goto err_destroy_handler;
}

uk_pr_info("netdev%"PRIu16": Configured receive queue %"PRIu16"\n",
dev->_data->id, queue_id);
return 0;
// 出错时销毁事件处理器
err_destroy_handler:
_destroy_event_handler(&dev->_data->rxq_handler[queue_id]);
err_out:
return err;
}

上文中提到,接收事件处理函数需要由中断上下文来调用,这就需要通知该队列打开中断或关闭中断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static inline int uk_netdev_rxq_intr_enable(struct uk_netdev *dev,
uint16_t queue_id)
{
UK_ASSERT(dev);
UK_ASSERT(dev->ops);
UK_ASSERT(dev->_data);
UK_ASSERT(dev->_data->state == UK_NETDEV_RUNNING);
UK_ASSERT(queue_id < CONFIG_LIBUKNETDEV_MAXNBQUEUES);
UK_ASSERT(!PTRISERR(dev->_rx_queue[queue_id]));

if (unlikely(!dev->ops->rxq_intr_enable))
return -ENOTSUP;
return dev->ops->rxq_intr_enable(dev, dev->_rx_queue[queue_id]);
}
static inline int uk_netdev_rxq_intr_disable(struct uk_netdev *dev,
uint16_t queue_id)
{
UK_ASSERT(dev);
UK_ASSERT(dev->ops);
UK_ASSERT(dev->_data);
UK_ASSERT(dev->_data->state == UK_NETDEV_RUNNING);
UK_ASSERT(queue_id < CONFIG_LIBUKNETDEV_MAXNBQUEUES);
UK_ASSERT(!PTRISERR(dev->_rx_queue[queue_id]));

if (unlikely(!dev->ops->rxq_intr_disable))
return -ENOTSUP;
return dev->ops->rxq_intr_disable(dev, dev->_rx_queue[queue_id]);
}

最后就是包的收发操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static inline int uk_netdev_rx_one(struct uk_netdev *dev, uint16_t queue_id,
struct uk_netbuf **pkt)
{
UK_ASSERT(dev);
UK_ASSERT(dev->rx_one);
UK_ASSERT(queue_id < CONFIG_LIBUKNETDEV_MAXNBQUEUES);
UK_ASSERT(dev->_data->state == UK_NETDEV_RUNNING);
UK_ASSERT(!PTRISERR(dev->_rx_queue[queue_id]));
UK_ASSERT(pkt);

return dev->rx_one(dev, dev->_rx_queue[queue_id], pkt);
}
static inline int uk_netdev_tx_one(struct uk_netdev *dev, uint16_t queue_id,
struct uk_netbuf *pkt)
{
UK_ASSERT(dev);
UK_ASSERT(dev->tx_one);
UK_ASSERT(queue_id < CONFIG_LIBUKNETDEV_MAXNBQUEUES);
UK_ASSERT(dev->_data->state == UK_NETDEV_RUNNING);
UK_ASSERT(!PTRISERR(dev->_tx_queue[queue_id]));
UK_ASSERT(pkt);

return dev->tx_one(dev, dev->_tx_queue[queue_id], pkt);
}