DPDK通過在線程中使用epoll模型,監聽UIO設備的事件,來模擬操作系統的中斷處理。
一、中斷初始化
在rte_eal_intr_init()函數中初始化中斷。具體如下:
1、首先初始化intr_sources鏈表。所有UIO設備的中斷都掛在這個鏈表上,中斷處理線程通過遍歷這個鏈表,來執行設備的中斷。
2、創建intr_pipe管道,用於epoll模型的消息通知。
3、創建線程intr_thread,線程的執行體是eal_intr_thread_main()函數,創建epoll模型,遍歷intr_sources鏈表,監聽已注冊的所有UIO設備的中斷事件,並調用對應UIO設備的中斷處理函數。
1 int 2 rte_eal_intr_init(void) 3 { 4 int ret = 0; 5 6 /* init the global interrupt source head */ 7 TAILQ_INIT(&intr_sources); 8 9 /** 10 * create a pipe which will be waited by epoll and notified to 11 * rebuild the wait list of epoll. 12 */ 13 if (pipe(intr_pipe.pipefd) < 0) 14 return -1; 15 16 /* create the host thread to wait/handle the interrupt */ 17 ret = pthread_create(&intr_thread, NULL, 18 eal_intr_thread_main, NULL); 19 if (ret != 0) 20 RTE_LOG(ERR, EAL, 21 "Failed to create thread for interrupt handling\n"); 22 23 return -ret; 24 }
中斷線程執行主體eal_intr_thread_main()函數具體如下:
1、epoll_create()創建epoll模型。
2、將intr_pipe管道加入到epoll中。
3、遍歷intr_sources鏈表,將所有UIO設備加入到epoll中。
4、在eal_intr_handle_interrupts()函數中,在一個for(;;)死循環中,調用epoll_wait()阻塞模式監聽事件。如果有事件發生,則調用eal_intr_process_interrupts()函數,最終會調用到相應UIO設備注冊的中斷處理函數。
1 static __attribute__((noreturn)) void * 2 eal_intr_thread_main(__rte_unused void *arg) 3 { 4 struct epoll_event ev; 5 6 /* host thread, never break out */ 7 for (;;) { 8 /* build up the epoll fd with all descriptors we are to 9 * wait on then pass it to the handle_interrupts function 10 */ 11 static struct epoll_event pipe_event = { 12 .events = EPOLLIN | EPOLLPRI, 13 }; 14 struct rte_intr_source *src; 15 unsigned numfds = 0; 16 17 /* create epoll fd */ 18 int pfd = epoll_create(1); 19 if (pfd < 0) 20 rte_panic("Cannot create epoll instance\n"); 21 22 pipe_event.data.fd = intr_pipe.readfd; 23 /** 24 * add pipe fd into wait list, this pipe is used to 25 * rebuild the wait list. 26 */ 27 if (epoll_ctl(pfd, EPOLL_CTL_ADD, intr_pipe.readfd, 28 &pipe_event) < 0) { 29 rte_panic("Error adding fd to %d epoll_ctl, %s\n", 30 intr_pipe.readfd, strerror(errno)); 31 } 32 numfds++; 33 34 rte_spinlock_lock(&intr_lock); 35 36 TAILQ_FOREACH(src, &intr_sources, next) { 37 if (src->callbacks.tqh_first == NULL) 38 continue; /* skip those with no callbacks */ 39 ev.events = EPOLLIN | EPOLLPRI; 40 ev.data.fd = src->intr_handle.fd; 41 42 /** 43 * add all the uio device file descriptor 44 * into wait list. 45 */ 46 if (epoll_ctl(pfd, EPOLL_CTL_ADD, 47 src->intr_handle.fd, &ev) < 0){ 48 rte_panic("Error adding fd %d epoll_ctl, %s\n", 49 src->intr_handle.fd, strerror(errno)); 50 } 51 else 52 numfds++; 53 } 54 rte_spinlock_unlock(&intr_lock); 55 /* serve the interrupt */ 56 eal_intr_handle_interrupts(pfd, numfds); 57 58 /** 59 * when we return, we need to rebuild the 60 * list of fds to monitor. 61 */ 62 close(pfd); 63 } 64 }
二、中斷注冊
以e1000網卡為例說明。在網卡初始化的時候,會調用rte_eth_dev_init()--->eth_igb_dev_init()--->rte_intr_callback_register()注冊中斷處理函數。
1 rte_intr_callback_register(&(pci_dev->intr_handle), 2 eth_igb_interrupt_handler, (void *)eth_dev);
rte_intr_callback_register()函數,主要工作如下:
1、首先申請一個struct rte_intr_source變量。
1 struct rte_intr_source { 2 TAILQ_ENTRY(rte_intr_source) next; 3 struct rte_intr_handle intr_handle; /**< interrupt handle */ 4 struct rte_intr_cb_list callbacks; /**< user callbacks */ 5 uint32_t active; 6 };
2、將中斷處理函數eth_igb_interrupt_handler,添加到rte_intr_source->callbacks鏈表中。
3、再將該rte_intr_source掛到全局intr_sources鏈表中,方便中斷處理線程遍歷調用。
錯誤之處,歡迎指出。