NAPI篇【3】——如何编写一个NAPI函数

        在NAPI篇【2】中讲解了cpp目录中各文件的作用,默认生成的hello.cpp源代码文件中,包含NAPI函数编写、定义与注册,本篇以分析hello.cpp的Add函数用来讲解一下NAPI函数的编写。

        首先,明确一个概念,NAPI的目的是提供了JS与C/C++不同语言模块之间的相互访问(主要是数据的传递),但是JS与C/C++是不同语言,无法直接进行数据传递,需要进行数据类型转化,这需要一座桥梁。

一、NAPI数据类型

        OpenHarmony NAPI 将 ECMAScript 标准中定义的 Boolean、Null、Undefined、Number、BigInt、String、Symbol和 Object 这八种数据类型以及函数对应的 Function 类型统一封装成了napi_value 类型,是 JS 数据类型和 C/C++ 数据类型之间的桥梁。

        napi_value 表示 JS 值的不透明指针,在 C/C++ 端要使用 JS 端传递的数据类型,都是通过 NAPI 提供的相关方法把napi_value转换成 C/C++ 类型后再使用,同理当需要把 C/C++的数据传递给 JS 应用层也要通过 NAPI 提供的方法把 C/C++ 端的数据转换成 napi_value 再向上传递

   常用的C/C++与napi_value数据类型的转换。

        1、C/C++转napi_value

        (1)int类型转换

NAPI_EXTERN napi_status napi_create_int32(napi_env env,
                                          int32_t value,
                                          napi_value* result);
NAPI_EXTERN napi_status napi_create_uint32(napi_env env,
                                           uint32_t value,
                                           napi_value* result);
NAPI_EXTERN napi_status napi_create_int64(napi_env env,
                                          int64_t value,
                                          napi_value* result);

说明:

        env:方法调用者的运行环境,包含 JS 引擎等。
        value:C/C++端的 int 类型的值。
        result

你问得非常精准: > ❓ **“那你岂不是在 NAPI 中又使用了一个 NAPI?”** 这个问题触及了 Linux 内核网络栈中一个**深层次的设计哲学问题**: > 🔍 **NAPI 是一种机制,不是唯一的收包入口。你可以有多个 NAPI 实例,服务于不同的目的。** --- ## ✅ 答案是:✅ **可以!而且这是标准做法!** ### 📌 正确理解: - 一个 `napi_struct` 不代表“整个网卡”,而是一个 **可调度的软中断处理单元** - 内核允许存在多个 NAPI 实例,它们可以在同一 CPU 上共存 - 每个 NAPI 实例都有自己的 `poll()` 函数和 budget 控制 - 这种设计被广泛用于:GRO、XDP、AF_XDP、虚拟设备(如 tun/tap)、桥接、QoS 调度等 --- ## ✅ 类比:NAPI 就像“线程池里的工作队列” 你可以把每个 `napi_struct` 想象成一个: > 🧩 **带优先级的异步任务处理器(softirq 上下文)** | 类比项 | 对应内核概念 | |--------|---------------| | 工作线程 | softirq / ksoftirqd | | 任务队列 | `napi_struct` 的 backlog | | poll() 函数 | 相当于 worker thread 的 run() 方法 | | budget | 单次执行最多处理 N 个包,避免长时间占用 CPU | 所以你在主 NAPI 收到包后,交给另一个 NAPI 去处理分类后的流量 —— 这就像主线程把任务提交给另一个工作队列去异步执行。 --- ## ✅ 实际案例:内核中早已这样做了! ### 💡 案例 1:`GRO` 使用独立 NAPI 处理聚合流 ```c struct napi_struct gro_napi; ``` 当驱动调用 `napi_gro_receive()` 时: - 包被加入 GRO 队列 - 触发 `gro_napi` 的轮询 - 在后续 softirq 中由 `gro_flush_qlist()` 统一处理 👉 这正是“在一个 NAPI 路径中启动另一个 NAPI”的经典用法。 --- ### 💡 案例 2:`tun/tap` 虚拟设备有自己的 NAPI `tun` 设备虽然不对应物理网卡,但它也注册了自己的 `napi_struct`: ```c static int tun_attach(struct tun_struct *tun, bool persistent) { ... netif_napi_add(dev, &tun->napi, tun_napi_poll, NAPI_POLL_WEIGHT); napi_enable(&tun->napi); } ``` 当你往 `tun` 写数据时,它会通过 `napi_schedule()` 触发软中断上送用户态。 👉 完全脱离物理网卡的 NAPI,但依然合法有效。 --- ### 💡 案例 3:`XDP_REDIRECT` 到 AF_XDP 或其他队列 XDP 程序可以把包重定向到: - 另一个网卡的 RX 队列 - AF_XDP socket 的 umem - 用户态程序 这些目标通常都绑定到独立的 `napi_struct` 上。 --- ## ✅ 回到你的场景:为什么需要“第二个 NAPI”? 你现在的情况是: ```text [物理网卡 NAPI] → rx_qos_scheduler(skb) → enqueue to high/low queue ↓ 我们希望:延迟低、不丢关键包 ``` 如果你直接在原 NAPI 中同步处理所有队列: - ⛔️ 会拖慢整体收包速度 - ⛔️ 可能超 budget 导致 `napi_complete()` 提前退出 - ⛔️ 私有队列积压无法恢复 而如果你创建一个 **专用 NAPI 实例来消费你的高优队列**: ```text [物理网卡 NAPI] → enqueue skb → schedule(qos_napi) ↓ [qos_napi] → poll() → 优先处理 high_pri ``` 优点: - ✅ 原 NAPI 快速返回,继续收新包 - ✅ 关键包由独立 NAPI 异步处理,不受 budget 影响 - ✅ 可控调度频率,避免饥饿 --- ## ✅ 性能对比:两种方式的区别 | 方式 | 是否阻塞原 NAPI | 是否支持持续调度 | 是否符合内核规范 | 推荐程度 | |------|------------------|--------------------|--------------------|---------| | 同步处理(你原来的做法) | ✅ 是 | ❌ 否 | ❌ 否 | ⚠️ 不推荐 | | 新建 NAPI 异步处理 | ❌ 否 | ✅ 是 | ✅ 是 | ✅ 强烈推荐 | --- ## ✅ 更进一步:你可以为不同优先级设置多个 NAPI 例如: ```c static struct napi_struct high_pri_napi; // ICMP/DHCP/BFD static struct napi_struct medium_pri_napi; // VoIP/ECSP static struct napi_struct low_pri_napi; // 普通数据流 ``` 然后根据优先级 `__napi_schedule()` 不同实例,实现真正的分层 QoS。 甚至可以通过 `tasklet` 或 `workqueue` 实现更复杂的调度策略。 --- ## ✅ 常见误解澄清 | 误解 | 澄清 | |------|------| | “只能有一个 NAPI” | ❌ 错!系统中有 dozens of NAPI instances | | “NAPI 必须绑定真实设备” | ❌ 错!虚拟设备也可以有 NAPI | | “NAPI 之间会冲突” | ❌ 错!只要不竞争同一资源,完全独立 | | “这样做太重” | ❌ 错!`napi_struct` 很轻量,只是一个结构体 + 函数指针 | --- ## ✅ 如何验证你的新 NAPI 是否正常工作? ### 1. 查看 `/proc/interrupts` 中是否有相关统计(如果有名字) ```bash cat /proc/interrupts | grep -i napi ``` ### 2. 打印调试信息 ```c printk(KERN_DEBUG "qos_napi scheduled, queue depth: high=%d, low=%d\n", skb_queue_len(&qos_queue.high_pri), skb_queue_len(&qos_queue.low_pri)); ``` ### 3. 使用 ftrace 跟踪 `napi_poll` ```bash echo function_graph > /sys/kernel/debug/tracing/current_tracer echo '*napi*' > /sys/kernel/debug/tracing/set_ftrace_filter cat /sys/kernel/debug/tracing/trace_pipe ``` 可以看到 `qos_poll` 是否被调用。 --- ## ✅ 总结:回答你的核心疑问 > ❓ **“那你岂不是在 NAPI 中又使用了一个 NAPI?”** ✅ 正确答案是: > **是的,我在一个 NAPI 上下文中触发了另一个 NAPI,但这不仅是允许的,而且是 Linux 内核中实现高级网络功能的标准模式。** 📌 关键在于: - 第一个 NAPI 负责“快速收包” - 第二个 NAPI 负责“智能调度” - 两者分工明确,互不干扰 这才是构建高性能、低延迟、抗洪泛的 QoS 系统的正确架构。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值