netmap源码涉及的技术非常多,本人以前从未做过驱动开发或者内核开发,阅读代码的过程中深感吃力,不过,不管怎样,还是有些收获,这些知识如果不加整理记录,后面再回忆起来可能又要重新思考一次,很不明智;况且,目前只是梳理了个框架,细节方面还需要日后再继续研究整理,是以为记。
首先要说明的是,netmap的代码质量很高,在FreeBSD中已经与内核代码一起发布,且注释非常清晰,里面有大段的注释,相当于设计文档,大家要细细研究。
其次,我个人觉得,要彻底弄清楚实现机理,还需要对内核原理、驱动开发有相当的了解,否则看起来会比较吃力,我就属于这种。
以下是我的一部分阅读笔记:
/
1. 数据结构
文件: netmap.h
描述: netmap把一个netmap_slot结构称为"buffer descriptor",这点一定要记住,netmap
源码中,有很多处出现descriptor,大家要能够明白所指之物。当然,作者这样称呼
的原因是因为netmap_slot结构本身并不包含slot内存,它只是"描述"slot的属性。
我们最关心的slot所指内存使用buf_idx表示,其真实所在其实是在内存池中。
作用: 报文(收/发)存储的场所
创建: when - 注册netmap设备时。
who - 由内核创建,并且mmap到用户空间。
where - 使用kmalloc申请,处于内核空间。
struct netmap_slot {
uint32_t buf_idx;/* buffer index */
uint16_t len; /* length for this slot */
uint16_t flags;/* buf changed, etc. */
uint64_t ptr; /* pointer for indirect buffers */
};
文件: netmap.h
描述: 顾名思义,此结构代表的是netmap的一个ring,简单来讲就是一个环形缓冲区,基本上可以这样认为。slot[0]成员指示的就是ring的组成成分——netmap_slot,这只是一 个占位符,真正申请内存的时候,这后面会跟着一堆netmap_slot。提醒一下,前面我们讲过,netmap_slot只是一个描述符,里面并不包含真正拷贝报文所需内存。
作用: 表示一个环形缓冲区
创建: when - 注册netmap设备时。
who - 由内核创建,并且mmap到用户空间。
where - 使用kmalloc申请,处于内核空间。
struct netmap_ring
{
// buffer在内存池中的偏移,主要用于宏计算
const int64_t buf_ofs;
// 此ring包含多少个slot
const uint32_tnum_slots;
// 每一个slot的内存大小
const uint32_tnr_buf_size;
// 当前netmap_ring索引(可能分配多个netmap_ring)
const uint16_tringid;
// 标识此ring是用于发送还是接受,0: tx, 1: rx
const uint16_tdir;
//
uint32_t head;/* (u) first user slot */
uint32_t cur;/* (u) wakeup point */
uint32_t tail; /* (k) first kernel slot */
uint32_t flags;
struct timevalts; /* (k) time of last *sync() */
uint8_t sem[128] __attribute__((__aligned__(NM_CACHE_ALIGN)));
// 一堆netmap_slot
struct netmap_slot slot[0];/* array of slots. */
};
文件: netmap.h
描述: 代表一个网络接口及接口上的队列。此结构在绑定一个文件描述符到
一个端口的时候进行初始化,对于用户程序来说是只读的,内核不使用
此结构。
作用:
创建: when - 注册netmap设备时。
who - 内核模块创建。
where - 用户空间使用。
struct netmap_if {
char ni_name[IFNAMSIZ];/* name of the interface. */
const uint32_tni_version; /* API version, currently unused */
const uint32_tni_flags; /* properties */
const uint32_tni_tx_rings; /* number of HW tx rings */
const uint32_tni_rx_rings; /* number of HW rx rings */
uint32_t ni_bufs_head; /* head index for extra bufs */
uint32_t ni_spare1[5];
const ssize_t ring_ofs[0];
};
所在文件: netmap.h
struct nmreq {
char nr_name[IFNAMSIZ];
uint32_t nr_version; /* API version */
uint32_t nr_offset; /* nifp offset in the shared region */
uint32_t nr_memsize; /* size of the shared region */
uint32_t nr_tx_slots; /* slots in tx rings */
uint32_t nr_rx_slots; /* slots in rx rings */
uint16_t nr_tx_rings; /* number of tx rings */
uint16_t nr_rx_rings; /* number of rx rings */
uint16_t nr_ringid; /* ring(s) we care about */
uint16_t nr_cmd;
uint16_t nr_arg1; /* reserve extra rings in NIOCREGIF */
uint16_t nr_arg2;
uint32_t nr_arg3; /* req. extra buffers in NIOCREGIF */
uint32_t nr_flags;
uint32_t spare2[1]; /* various modes, extends nr_ringid */
};
文件: netmap.h
描述: 与ixgbe_adapter对应,代表逻辑上的一个netmap适配器。
作用:
创建: when - 加载驱动时创建。
who - 内核模块创建。
where - 内核使用。
struct netmap_adapter {
uint32_t magic;
uint32_t na_flags;/* enabled, and other flags */
int active_fds;
u_int num_rx_rings; /* number of adapter receive rings */
u_int num_tx_rings; /* number of adapter transmit rings */
u_int num_tx_desc; /* number of descriptor in each queue */
u_int num_rx_desc;
struct netmap_kring *tx_rings; /* array of TX rings. */
struct netmap_kring *rx_rings; /* array of RX rings. */
void *tailroom; /* space below the rings array */
NM_SELINFO_T tx_si, rx_si;/* global wait queues */
int tx_si_users, rx_si_users;
int (*if_transmit)(struct ifnet *, struct mbuf *);
void (*if_input)(struct ifnet *, struct mbuf *);
struct ifnet *ifp; /* adapter is ifp->if_softc */
/*---- callbacks for this netmap adapter -----*/
void (*nm_dtor)(struct netmap_adapter *);
int (*nm_register)(struct netmap_adapter *, int onoff);
int (*nm_txsync)(struct netmap_kring *kring, int flags);
int (*nm_rxsync)(struct netmap_kring *kring, int flags);
int (*nm_config)(struct netmap_adapter *, u_int *txr, u_int *txd, u_int *rxr, u_int *rxd);
int (*nm_krings_create)(struct netmap_adapter *);
void (*nm_krings_delete)(struct netmap_adapter *);
int (*nm_notify)(struct netmap_adapter *, u_int ring, enum txrx, int flags);
int na_refcount;
struct netmap_mem_d *nm_mem;
struct lut_entry *na_lut;
uint32_t na_lut_objtotal;/* max buffer index */
void *na_private;
#ifdef WITH_PIPES
struct netmap_pipe_adapter **na_pipes;
int na_next_pipe;
int na_max_pipes;
#endif /* WITH_PIPES */
};
1) 用户空间可见的数据结构,都是由内核申请内存,然后mmap到用户空间的。
2) netmap中大量使用内存偏移或者索引来代替指针,这样更容易在内核与用户之间使用。(?)
2. 处理流程
1) 加载netmap_lin.ko
a) 初始化全局锁(全局锁干啥用的?)
b) 初始化内存池的信号量(?)
c) 使用misc_register创建/dev/netmap设备。这个操作同时也注册了netmap支持的设备操作接口,
后续,对netmap的操作就可以像访问普通设备一样了。
static struct file_operations netmap_fops = {
.owner = THIS_MODULE,
.open = linux_netmap_open,
.mmap = linux_netmap_mmap,
.ioctl = linux_netmap_ioctl,
.poll = linux_netmap_poll,
.release = linux_netmap_release,
};
d) VALE初始化
2) 加载ixgbe.ko
module_init -> ixgbe_init_module -> pci_register_driver(dca_register_notify)
static struct pci_driver ixgbe_driver = {
.name = ixgbe_driver_name,
.id_table = ixgbe_pci_tbl,
.probe = ixgbe_probe,
.remove = __devexit_p(ixgbe_remove),
#ifdef CONFIG_PM
.suspend = ixgbe_suspend,
.resume = ixgbe_resume,
#endif
.shutdown = ixgbe_shutdown,
.err_handler = &ixgbe_err_handler
};
简单来讲就是注册PCI设备,不过,这里需要注意,由于ixgbe驱动被修改适配netmap框架,
ixgbe.ko依赖于netmap_lin.ko,换句话说ixgbe.ko需要使用netmap_lin.ko中的导出符号,
所以,加载顺序必须是先加载netmap_lin.ko再加载ixgbe.ko。
内核注册完驱动后,就会调用其初始化函数ixgbe_probe,此函数会做一堆初始化,这里
摘取出几项比较重要的分析下:
a) 设置ixgbe netdev的回调函数:netdev->netdev_ops = &ixgbe_netdev_ops;
b) 设置ixgbe ethtool的回调函数:ixgbe_set_ethtool_ops(netdev);
c) ixgbe_init_interrupt_scheme:设置RSS等的队列个数和申请中断向量。
这里面还会设置NAPI的回调接口为ixgbe_poll,这样报文接收通道就构建好了。
d) ixgbe_netmap_attach:创建netmap_adapter赋值到net_device的ax25_ptr成员,
这样,netmap与ixgbe就建立一种联系,可以说是netmap attach到ixgbe了。
3) up/open事件(以RX为例)
ixgbe_up/ixgbe_open --> ixgbe_configure -> ixgbe_configure_rx
-> ixgbe_configure_rx_ring -> ixgbe_netmap_configure_rx_ring
a) DMA资源和中断资源都是在这个流程申请的。而加载驱动时只是设置DMA工作参数等。
b) 每一个ring都需要申请独立的dma资源
4) 使用netmap设备
nm_open -> open("/dev/netmap", O_RDWR) -> ioctl(d->fd, NIOCREGIF, &d->req) -> mmap
a) 用户使用的API都在netmap_user.h中声明。
b) 调用nm_open会以netmap方式打开网卡设备,内部包含一系列动作。
c) open操作最终会调用之前加载的内核模块netmap_lin.ko中的linux_netmap_open。
d) ioctl操作会在内核申请
e) mmap操作将内核空间映射到用户空间来,
5) 接收报文
NAPI -> ixgbe_poll -> ixgbe_clean_rx_irq -> netmap_rx_irq -> netmap_common_irq -> netmap_notify
a) netmap底层也是使用NAPI来接收报文的。
b) ixgbe_poll是遍历所有ring来逐个通知上层的。
c) netmap_rx_irq劫持了ixgbe驱动的报文接收,从这个函数往下,报文接收就完全由netmap接管了。