DPDK :中断,rte_epoll, 时钟,定时器的解析

说明

       这一篇文章将介绍DPDK的rte_interrupt, rte_epoll, rte_timer, rte_alarm,这些功能是网卡驱动,pci以及其他的lib的基础。
       由于个人水平所限,若所写的博文中存在错误,希望大家能帮忙指出。

一,简单介绍

       这一篇文章主要介绍DPDK中的以下四种功能:

	1,rte_interrupt,用于可以注册需要监听的中断信号以及对应的回调函数(Callback)。
	2,rte_epoll允许用户通过epoll对象监听指定的fd, 并指定对应的回调函数。
	3,rte_timer是DPDK的计时器,提供了delay, sleep等功能。
	4,rte_alarm用于设置alarm,即设置一个定时器用于在未来的某个时间点触发一个alarm中断信号。

二,背景知识

       这部分的代码可能需要两个背景知识,IO多路复用和self-pipe trick。
       若引用以上的链接侵犯了原作者的权利,请告知本人,将即刻删除。

三,相关结构体的说明

*******************rte_eal_interrupts.h*******************
struct rte_epoll_data {
	//指定了epoll对象的event
    uint32_t event;               /**< event type */
	void *data;                   /**< User data */
	//事件发生后需要调用的callback及其参数
    rte_intr_event_cb_t cb_fun;   /**< IN: callback fun */
    void *cb_arg;                 /**< IN: callback arg */
};

/** interrupt epoll event obj, taken by epoll_event.ptr */
struct rte_epoll_event {
	 // 指定epoll的相关状态信息 : EXEC表示繁忙(正在处理中断信号), VALID表示空闲, INVALID表示无效
	volatile uint32_t status;  /**< OUT: event status */
	// 需要监听的file descriptor
	int fd;                    /**< OUT: event fd */
	// epoll对象对应的文件描述符
    int epfd;       /**< OUT: epoll instance the ev associated with */
    struct rte_epoll_data epdata;
};

/** Handle for interrupts. */
struct rte_intr_handle {
    RTE_STD_C11
    // 对于UIO而言,对应于/sys/class/uio/uio%u/device/config, 即PCI配置空间文件
    // 对于VFIO而言,对应于设备的vfio_dev_fd, 其作用是作为参数用于ioctl()
    union {
        int vfio_dev_fd;  /**< VFIO device file descriptor */
        int uio_cfg_fd;  /**< UIO cfg file desc for uio_pci_generic */
	};
	//对于UIO而言,对应于/dev/uioX; 对于VFIO而言,对应于eventfd; 对于ALARM而言,对应于timefd;
    int fd;  /**< interrupt event file descriptor */ 
    enum rte_intr_handle_type type;  /**< handle type */
    uint32_t max_intr;             /**< max interrupt requested */
    uint32_t nb_efd;               /**< number of available efd(event fd) */
	uint8_t efd_counter_size;      /**< size of efd counter, used for vdev */
	//每一个efds都会与一个elist相对应
    int efds[RTE_MAX_RXTX_INTR_VEC_ID];  /**< intr vectors/efds mapping */
    struct rte_epoll_event elist[RTE_MAX_RXTX_INTR_VEC_ID];
    			/**< intr vector epoll event */
    int *intr_vec;                 /**< intr vector number array */
};

*******************eal_alarm.c*******************
struct alarm_entry {
	LIST_ENTRY(alarm_entry) next;
	//alarm发生的时间点
	struct timeval time;
	
	//对应的callback函数以及参数
    rte_eal_alarm_callback cb_fn;
	void *cb_arg;
	
	//指明alarm是否正在执行
	volatile uint8_t executing;
	//执行alarm的线程的id
    volatile pthread_t executing_id;
};

四,rte_interrupt

       rte_interrupt的使用 : 程序可以通过 rte_intr_callback_register() 注册一个中断源以及对应的callback,注册完成后,中断控制线程会对其信号进行监听,若有中断信号产生,则会调用对应的callback函数。
       还可以通过 rte_intr_callback_unregister() 注销一个中断源的callback, 此时中断控制线程会停止对中断信号进行监听。

       rte_interrupt的实现 : DPDK会维持一个关于中断源的list,list的每一个元素保存了中断的相关信息及其相应的callback list。中断控制线程对应的例程为eal_intr_thread_main(),其使用了 epoll_create() 创建了一个epoll对象,然后使用 epoll_ctl() 将intr_pipe的读端的fd,以及中断源list中的对应的fd加入到epoll对象的fd interesting list中。然后使用 epoll_wait() 监听epoll对象的fd interesting list。一旦有某些file descriptor有中断信号发生,则会对于每一个中断信号,调用其对应的callback函数。

       上面提到的 intr_pipe 是作为一个self-pipe, 程序调用 rte_intr_callback_register() 注册一个新的中断源时,会向intr_pipe的写端写入一个字符,此时中断控制线程的epoll会监听到intr_pipe有I/O信号,说明中断源list发送了变化,则会重新构造epoll对象,将新的中断源加入到epoll对象的fd interesting list中,使用epoll_wait()继续监听epoll对象。
       并且epoll对象采用了水平触发(level-triggered), 所以intr_pipe和其他fd都可以I/O的情况下,在重新构造epoll后,其他fd的I/O会使得epoll_wait()立即返回,继续处理其他fd的I/O。

五,rte_epoll

       DPDK还提供了rte_epoll, 主要包括了rte_epoll_ctl() , rte_epoll_wait(),。其底层也是使用了epoll进行实现。
       rte_epoll_ctl() : 将某个fd加入到epoll对象(可由用户指定,若没有指定则由系统创建)中。
       rte_epoll_wait() : 开始监听epoll对象的fd interesting list。

六,rte_timer

        rte_timer的使用:,给程序提供了rte_delay_us_block, rte_delay_us_sleep等功能,主要作用是使得线程休眠一段时间;其底层是使用Linux系统的timer机制进行实现的。(使用time.h中的clock_gettime, nanosleep, sleep进行实现)
       DPDK初始化时会调用 rte_eal_timer_init() 初始化timer的相关信息(比如时钟频率)
       (默认使用了Time Stamp Counter, 缩写为TSC, 系统也提供了HPET的初始化,对应于rte_eal_hpet_init(),可通过配置文件启动)

七,rte_alarm

        DPDK提供的rte_alarm,包括设置一个alarm, 取消alarm等。其底层是使用了timefd.h, time.h, rte_interrupt进行实现。程序可以通过rte_eal_alarm_set()设置一个定时器,或者rte_eal_alarm_cancel()关闭某个定时器。

        rte_alarm的实现: DPDK调用rte_eal_alarm_init()初始化一个类型为ALARM的中断源。并且维护了一个alarm list。每当用户调用 rte_eal_alarm_set() 时,会创建一个alarm_entry, 并且存放用户指定的callback函数;然后调用rte_intr_callback_register() 注册ALARM的中断源,然后使用 timerfd_settime() 设置定时器的时间,使得在对应的时间点会产生ALARM中断信号。最后信号发生时,由中断控制线程进行处理。rte_eal_alarm_cancel() 会将相对应的alarm从alarm list中移除即可。

八,总结

       1,本文简单地介绍了rte_interrupt, rte_epoll, rte_timer, rte_alarm, 这四个功能的实现比较简单,代码也比较少。
       2,rte_interrupt 和 rte_epoll 两者的区别在于,rte_interrupt会委托中断控制线程进行监听,而rte_epoll则是由调用rte_epoll_wait()的线程进行监听。
       3,rte_timer的功能主要是获取时钟相关的信息和对线程进行休眠操作,而rte_alarm主要是设置一个定时器,指定在未来的某个时间点内产生一个ALARM中断信号。

九,函数调用图

       下面给出的是rte_interrupt, rte_epoll, rte_timer, rte_alarm相关的代码的函数调用图(所阅读的代码是来自DPDK官方最新的代码https://github.com/DPDK/dpdk, 如果官方代码更新,文章也会尽快地更新)
       1,rte_alarm : http://naotu.baidu.com/file/5f502fb27ab9b95a237c541c108928f7?token=77e5d32f728698af
       2,rte_timer : http://naotu.baidu.com/file/b0b4cdde1c182f4986a7d79f030e882c?token=aed4e01a4c885851
       3,rte_interrupt 和 rte_epoll : http://naotu.baidu.com/file/92c83461a325cec7744d97c2c41216fc?token=c477de8bad81c87c

你可能感兴趣的:(DPDK源代码的解析)