上篇文章中,提到用户态 netmap 的使用过程,其中有一个 nm_open 函数。
nm_open 封装好了一些内部操作,包括注册部分ioctl(d->fd, NIOCREGIF, &d->req)
,这次我们来分析一下netmap_ioctl
函数。
这个函数比较长,这里只分析注册部分:
netmap_ioctl()
|-- netmap_do_regif()
|-- netmap_update_config()
|-- nm_config()
netmap_ioctl
int netmap_ioctl(struct netmap_priv_d *priv, u_long cmd, caddr_t data, struct thread *td)
{
struct nmreq *nmr = (struct nmreq *) data;
struct netmap_adapter *na = NULL;
struct ifnet *ifp = NULL;
int error = 0;
struct netmap_if *nifp;
enum txrx t;
if (cmd == NIOCGINFO || cmd == NIOCREGIF) {
nmr->nr_name[sizeof(nmr->nr_name) - 1] = '\0'; // nr_name 就是 net interface 名
if (nmr->nr_version != NETMAP_API) {
nmr->nr_version = NETMAP_API;
}
// 判断版本是否有效,当前 netmap API 版本是 11
if (nmr->nr_version < NETMAP_MIN_API ||
nmr->nr_version > NETMAP_MAX_API) {
return EINVAL;
}
}
switch (cmd) {
case NIOCREGIF:
i = nmr->nr_cmd; // nr_cmd 为 0
NMG_LOCK(); // 防止并发注册
do {
u_int memflags;
struct ifnet *ifp;
if (priv->np_nifp != NULL) { // 如果已经注册过了,跳出 switch
error = EBUSY;
break;
}
error = netmap_get_na(nmr, &na, &ifp, 1); // 获取 na 和 interface
if (error)
break;
if (NETMAP_OWNED_BY_KERN(na)) { // 查看设备是否是 Busy 状态
netmap_unget_na(na, ifp);
error = EBUSY;
break;
}
// 判断是否可以处理 virtio-net
if (na->virt_hdr_len && !(nmr->nr_flags & NR_ACCEPT_VNET_HDR)) {
netmap_unget_na(na, ifp);
error = EIO;
break;
}
error = netmap_do_regif(priv, na, nmr->nr_ringid, nmr->nr_flags);
if (error) { // 如果注册失败
netmap_unget_na(na, ifp);
break;
}
nifp = priv->np_nifp;
priv->np_td = td;
// 队列数和槽位数赋值
nmr->nr_rx_rings = na->num_rx_rings;
nmr->nr_tx_rings = na->num_tx_rings;
nmr->nr_rx_slots = na->num_rx_desc;
nmr->nr_tx_slots = na->num_tx_desc;
error = netmap_mem_get_info(na->nm_mem, &nmr->nr_memsize, &memflags,
&nmr->nr_arg2);
// 遍历所有等待队列 wait_queue
for_rx_tx(t) {
priv->np_si[t] = nm_si_user(priv, t) ?
&na->si[t] : &NMR(na, t)[priv->np_qfirst[t]].si;
}
nmr->nr_offset = netmap_mem_if_offset(na->nm_mem, nifp); // 计算 nifp 的偏移
priv->np_ifp = ifp;
} while (0);
NMG_UNLOCK();
break;
}
return error;
}
netmap_do_regif —— 真 · 注册部分
int netmap_do_regif(struct netmap_priv_d *priv, struct netmap_adapter *na,
uint16_t ringid, uint32_t flags)
{
struct netmap_if *nifp = NULL;
int error;
netmap_update_config(na); // 更新配置,详细见下文
priv->np_na = na;
error = netmap_set_ringid(priv, ringid, flags); // 设置 ring ID
error = netmap_mem_finalize(na->nm_mem, na); // 更新一些配置
error = netmap_krings_get(priv); // 确保 netmap 有内核态队列
nifp = netmap_mem_if_new(na); // 创建一个新的 nifp
na->active_fds++; // 增加这个 interface 的引用计数
mb();
priv->np_nifp = nifp;
return 0;
}
netmap_update_config
从硬件设备中获取相关配置信息并更新到 netmap 中
int netmap_update_config(struct netmap_adapter *na)
{
u_int txr, txd, rxr, rxd;
txr = txd = rxr = rxd = 0;
// nm_config 指向 netmap_linux_config
if (na->nm_config == NULL || na->nm_config(na, &txr, &txd, &rxr, &rxd)) {
txr = na->num_tx_rings;
txd = na->num_tx_desc;
rxr = na->num_rx_rings;
rxd = na->num_rx_desc;
}
if (na->active_fds == 0) { // 更新收发队列和槽位的个数
na->num_tx_rings = txr;
na->num_tx_desc = txd;
na->num_rx_rings = rxr;
na->num_rx_desc = rxd;
return 0;
}
return 1;
}
netmap_linux_config
int netmap_linux_config(struct netmap_adapter *na,
u_int *txr, u_int *txd, u_int *rxr, u_int *rxd)
{
struct ifnet *ifp = na->ifp;
int error = 0;
rtnl_lock();
if (ifp == NULL) {
error = ENXIO;
goto out;
}
error = nm_os_generic_find_num_desc(ifp, txd, rxd); // 获取硬件网卡的槽位个数
if (error)
goto out;
nm_os_generic_find_num_queues(ifp, txr, rxr); // 获取硬件网卡的TX/RX个数
out:
rtnl_unlock();
return error;
}
nm_os_generic_find_num_desc
获取槽位
int nm_os_generic_find_num_desc(struct ifnet *ifp, unsigned int *tx, unsigned int *rx)
{
int error = EOPNOTSUPP;
#ifdef NETMAP_LINUX_HAVE_GET_RINGPARAM
struct ethtool_ringparam rp;
// 通过 ethtool_ops 获取槽位
if (ifp->ethtool_ops && ifp->ethtool_ops->get_ringparam) {
ifp->ethtool_ops->get_ringparam(ifp, &rp);
*tx = rp.tx_pending ? rp.tx_pending : rp.tx_max_pending;
*rx = rp.rx_pending ? rp.rx_pending : rp.rx_max_pending;
}
return error;
}
注意:下面这个 nm_os_generic_find_num_queues( ) 函数需要稍加注意一下
nm_os_generic_find_num_queues
我当时使用 netmap 环境:
- OS:RHEL6.5 x86_64
- Kernel:Linux 2.6.32-431
这里有一个问题,linux 2.6.32 的 ethtool_ops 中并没有get_channels
,这就导致 RX 被设置成了 1,那么会有什么问题呢?
比如我用 Intel 82599EB 10-Gigabit 测试,网卡是 8 TX 和 8 RX,不过 netmap 测试时,收包相当低,经过排查发现只使用了 1 个 RX。
更改方法见我之前的文章 使用 netmap 提升 ixgbe 性能(续),算是临时方案吧。
获取队列
void nm_os_generic_find_num_queues(struct ifnet *ifp, u_int *txq, u_int *rxq)
{
struct ethtool_channels ch;
memset(&ch, 0, sizeof(ch));
// 也是通过 ethtool_ops 获取队列信息
if (ifp->ethtool_ops && ifp->ethtool_ops->get_channels) {
ifp->ethtool_ops->get_channels(ifp, &ch);
*txq = ch.tx_count ? ch.tx_count : ch.combined_count;
*rxq = ch.rx_count ? ch.rx_count : ch.combined_count;
} else {
*rxq = 1;
}
}