關鍵詞:DMA、sync、async、SIGIO、F_SETSIG。cookie
DMA自己用於減輕CPU負擔,進行CPU off-load搬運工做。併發
在DMA驅動內部實現有同步和異步模式,異步模式使用dma_async_issue_pending(),而後在callback()中發送SIGIO信號,用戶空間收到SIGIO進行handler處理視爲一個週期完成。app
同步模式,採用dma_sync_wait()進行等待,期間並無釋放CPU給其餘進程使用。異步
在一個項目中,發現DMA相關佔用率高的問題,後來發現是由於其使用了同步模式。而後,將其改爲異步模式,並對其進行詳細的分析,記錄以下。async
那麼當初爲何沒有使用async IO模式呢?ide
原來是由於同一進程中其餘線程也是用了async IO設備,因爲kill_fasync()發送SIGIO信號,在一個進程內沒法多個handler。函數
權宜措施使用了同步DMA,形成佔用率高。性能
而後經過fcntl(fd, F_SETSIG, sig);解決了kill_fasync()發送相同SIGIO信號的衝突問題。測試
抓數據命令:this
perf record -a -e cpu-clock -- sleep 60 perf report perf report -s comm
不一樣數據量,不一樣CMA處理方式下的top和perf結果:
Item | 1 cma | per cma | ||
top | perf | top | perf | |
4K 25fps | 15% | 84.43% swapper [kernel.kallsyms] [k] __sched_text_end 11.51% main [kernel.kallsyms] [k] dma_cookie_status 2.81% main [kernel.kallsyms] [k] dma_sync_wait 0.20% main [kernel.kallsyms] [k] uart_write 0.13% swapper [kernel.kallsyms] [k] cache_op_range 0.12% swapper [kernel.kallsyms] [k] __dma_tx_complete 0.06% swapper [kernel.kallsyms] [k] dw_dma_tasklet 0.04% ksoftirqd/0 [kernel.kallsyms] [k] finish_task_switch 0.03% perf [kernel.kallsyms] [k] raw_copy_from_user 0.02% swapper [kernel.kallsyms] [k] __softirqentry_text_start |
61% | 45.93% main [kernel.kallsyms] [k] dcache_wb_line 36.79% swapper [kernel.kallsyms] [k] __sched_text_end 4.86% main [kernel.kallsyms] [k] dma_cookie_status 4.06% main [kernel.kallsyms] [k] free_hot_cold_page 1.28% main [kernel.kallsyms] [k] dma_sync_wait 1.28% main [kernel.kallsyms] [k] skip_ftrace 0.66% main [kernel.kallsyms] [k] _mcount 0.58% main [kernel.kallsyms] [k] unset_migratetype_isolate 0.51% main [kernel.kallsyms] [k] __free_pages 0.41% main [kernel.kallsyms] [k] start_isolate_page_range |
1080p 50fps | 9% | 89.82% swapper [kernel.kallsyms] [k] __sched_text_end 5.63% main [kernel.kallsyms] [k] dma_cookie_status 1.48% main [kernel.kallsyms] [k] dma_sync_wait 0.49% ksoftirqd/0 [kernel.kallsyms] [k] finish_task_switch 0.24% swapper [kernel.kallsyms] [k] dw_dma_tasklet 0.13% swapper [kernel.kallsyms] [k] __dma_tx_complete 0.10% swapper [kernel.kallsyms] [k] tasklet_action 0.10% main [kernel.kallsyms] [k] do_futex 0.06% main [kernel.kallsyms] [k] raw_copy_from_user 0.06% main [kernel.kallsyms] [k] restore_from_user_fp |
44% | 53.49% swapper [kernel.kallsyms] [k] __sched_text_end 30.73% main [kernel.kallsyms] [k] dcache_wb_line 3.65% main [kernel.kallsyms] [k] free_hot_cold_page 3.36% main [kernel.kallsyms] [k] dma_cookie_status 1.03% main [kernel.kallsyms] [k] skip_ftrace 0.85% main [kernel.kallsyms] [k] dma_sync_wait 0.56% main [kernel.kallsyms] [k] _mcount 0.48% main [kernel.kallsyms] [k] unset_migratetype_isolate 0.38% main [kernel.kallsyms] [k] __free_pages 0.33% main [kernel.kallsyms] [k] isolate_migratepages_range |
問題的分析:
1. skip_trace和_mcount兩個是由於ftrace引入的負荷,不合理。此時不該該有這些。----須要推進csky修改爲Dynamic function/function_graph。
2. dcache_wb_line/free_hot_cold_page是CMA操做引發的。-----------------------------------這裏須要修改處理方式,CMA不須要重複申請釋放。
3. dma_cookie_status/dma_sync_wait是DMA操做引發的。------------------------------------這裏能夠經過修改DMA異步信號觸發來下降佔用率。
分析總結:
1. 從上面的測試結果看,應該儘可能避免內存申請釋放。csky內存處理效率很低。
2. 同步模式效率很是低,4K單路佔用率達到15%,若是4K雙路就無法使用了。
因此必需要使用異步模式,這也是使用DMA的初衷。
多大的傳輸使用DMA得到的收益最高呢?作了個實驗,結果以下,橫軸單位是B,縱軸單位是MB/s、
能夠看出,DMA size大小越大效率越高,size接近3MB的時候,以及之後吞吐率就比較穩定了。
int axidma_memcpy(dma_addr_t src, dma_addr_t dst, unsigned int len) { struct dma_async_tx_descriptor *tx = NULL; dma_cookie_t cookie; unsigned long flags; bool sync_wait = false;-------------------------------------------------------------------true表示同步等待模式;false表示異步模式,和callback()配合。 int err = 0; flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; tx = xxxxxx->dma.chan->device->device_prep_dma_memcpy(xxxxxx->dma.chan, dst, src, len, flags); if (!tx) { pr_err("Fail to prepare memcpy.\n"); return -1; } tx->callback = axidma_callback; tx->callback_param = xxxxxx; cookie = tx->tx_submit(tx); if (dma_submit_error(cookie)) { pr_err("Fail to submit axi dma.\n"); return -1; } if (!sync_wait) { dma_async_issue_pending(dma_dev->dma.chan);------------------------------------------發送DMA傳輸請求,而後退出。這裏不會等待操做結果。 } else { if (dma_sync_wait(dma_dev->dma.chan, cookie) == DMA_COMPLETE) { err = 0; } else { err = -EIO; } } return err; } enum dma_status dma_sync_wait(struct dma_chan *chan, dma_cookie_t cookie) { enum dma_status status; unsigned long dma_sync_wait_timeout = jiffies + msecs_to_jiffies(5000);-------------------超時5000ms,足夠大了。 dma_async_issue_pending(chan);------------------------------------------------------------和以前一樣功能,發送DMA傳輸請求。只是下面會進行等待,並有超時動做。 do { status = dma_async_is_tx_complete(chan, cookie, NULL, NULL);--------------------------pool DMA傳輸狀態。 if (time_after_eq(jiffies, dma_sync_wait_timeout)) { dev_err(chan->device->dev, "%s: timeout!\n", __func__); return DMA_ERROR;-----------------------------------------------------------------超時退出。 } if (status != DMA_IN_PROGRESS) break; cpu_relax();--------------------------------------------------------------------------讓出CPU執行。 } while (1); return status; } static inline enum dma_status dma_async_is_tx_complete(struct dma_chan *chan, dma_cookie_t cookie, dma_cookie_t *last, dma_cookie_t *used) { struct dma_tx_state state; enum dma_status status; status = chan->device->device_tx_status(chan, cookie, &state); if (last) *last = state.last; if (used) *used = state.used; return status; }
而後在callback()中發送SIGIO信號:
static void axidma_callback(void *arg) { if(dma_dev->async) { pr_debug("axidma callback: chan=%s.\n", dma_chan_name(dma_dev->dma.chan)); pr_debug("axidma_callback: magic=0x%08x pid_type=%d\n", dma_dev->async->magic, dma_dev->async->fa_file->f_owner.pid_type); kill_fasync(&dma_dev->async, SIGIO, POLL_IN);--------------------------------在傳輸完成後,異步發送SIGIO信號。 } }
爲了排除其餘進程影響,單獨構造3個試用例:1. 4K 25fps;2. 1080p 2路 25fps;3. 1080p 4路 25fps。
而後改爲DMA異步模式,CPU佔用率是否明顯降低?
測試程序以下:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <poll.h> #include <signal.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> #include <time.h> #include <pthread.h> #include <sys/prctl.h> #include <sys/syscall.h> #include <sys/ioctl.h> #include "IFMS_MemCMA_API.h" int fd; unsigned int handle_count = 0; volatile unsigned int dmatest_exit = 0; volatile sigio_received = 0; #define LOOP_COUNT 10000 #define DMA_MEMCPY 0 #define DMA_FILE "/dev/dpeye1000_axidma" #define DPEYE1000_AXIDMA_IOC_MAGIC 'd' #define DPEYE1000_AXIDMA_REQUEST_CHNN _IOW(DPEYE1000_AXIDMA_IOC_MAGIC, 1, int) //Request channel. #define DPEYE1000_AXIDMA_RELEASE_CHNN _IOW(DPEYE1000_AXIDMA_IOC_MAGIC, 2, int) //Release channel. #define DPEYE1000_AXIDMA_MEMCPY _IOW(DPEYE1000_AXIDMA_IOC_MAGIC, 3, int) //Do 1d memcpy. //#define PERFORMANCE_DEBUG struct axidma_dma { struct dma_chan *chan; unsigned long src; unsigned long dst; // for memcpy unsigned int len; unsigned int n_channels; }; struct thread_para { unsigned int size; unsigned int expries; struct cma_block cma_block1; struct cma_block cma_block2; }; static struct axidma_dma axidma_dma; struct thread_para t_para; int dmacopy_tid = 0; void trigger_dma_copy(void) { int ret; #ifdef PERFORMANCE_DEBUG struct timespec time_0, time_1; unsigned long duration; float throughput = 0.0; clock_gettime(CLOCK_REALTIME, &time_0); #endif axidma_dma.src = (unsigned long)t_para.cma_block1.addr_p; axidma_dma.dst = (unsigned long)t_para.cma_block2.addr_p; axidma_dma.len = t_para.size; ret = ioctl(fd, DPEYE1000_AXIDMA_MEMCPY, &axidma_dma); if(ret < 0) { printf("fail to ioctl DPEYE1000_AXIDMA_MEMCPY!!!\n"); } #ifdef PERFORMANCE_DEBUG clock_gettime(CLOCK_REALTIME, &time_1); duration = (time_1.tv_sec-time_0.tv_sec)*1000000000 + (time_1.tv_nsec-time_0.tv_nsec); throughput = (float)t_para.size/1048576*1000000000/duration; printf("%ld.%ld %d %d, %ld, %f\n", time_1.tv_sec, time_1.tv_nsec, handle_count, t_para.size, duration, throughput); #endif } void sigint_handler(int sig) { if(sig == SIGINT) { printf("%s SIGINT\n", __func__); dmatest_exit = 1; } } void sigio_handler(int sig) { if(sig == SIGIO) { sigio_received = 1; } } static void pthread_func(void *arg) { int ret; int Oflags; struct f_owner_ex owner_ex; struct timespec time1, time2; struct sigaction sa, sa2; sigset_t set, oldset; long long duration; unsigned int mode = DMA_MEMCPY; ret = IFMS_MemCMAAlloc(t_para.size, &t_para.cma_block1); if(ret<0) { printf("CMA alloc failed.\n"); return -1; } ret=IFMS_MemCMAAlloc(t_para.size, &t_para.cma_block2); if(ret<0) { printf("CMA alloc failed.\n"); return -1; } //=========================================================================================== //Set thread name. prctl(PR_SET_NAME,"sigio"); dmacopy_tid = syscall(SYS_gettid); printf("sigio thread tid=%ld %ld.\n", syscall(SYS_gettid), getpid()); //Set SIGIO actiong. memset(&sa, 0, sizeof(sa)); sa.sa_handler = sigio_handler; sa.sa_flags |= SA_RESTART; sigaction(SIGIO, &sa, NULL); //Set proc mask. sigemptyset(&set); sigprocmask(SIG_SETMASK, &set, NULL); sigaddset(&set, SIGIO); fd = open(DMA_FILE, O_RDWR); if (fd < 0) { printf("Can't open %s!\n", DMA_FILE); } //If set F_SETOWN_EX, SIGIO will send to this thread only. owner_ex.pid = syscall(SYS_gettid); owner_ex.type = F_OWNER_TID; fcntl(fd, F_SETOWN_EX, &owner_ex); Oflags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, Oflags | FASYNC); clock_gettime(CLOCK_REALTIME, &time1); ret = ioctl(fd, DPEYE1000_AXIDMA_REQUEST_CHNN, &mode); if(ret < 0) { printf("fail to ioctl DPEYE1000_AXIDMA_REQUEST_CHNN!!!\n"); } while(!dmatest_exit) { if(t_para.expries > 1) usleep(t_para.expries); trigger_dma_copy(); //Send DMA copy request. while(!sigio_received) while(!sigio_received) { pthread_sigmask(SIG_BLOCK, &set, &oldset); //sigprocmask(SIG_BLOCK, &set, &oldset); sigsuspend(&oldset); //Will pause here, and will be waked up by SIGIO. pthread_sigmask(SIG_UNBLOCK, &set, NULL); //sigprocmask(SIG_UNBLOCK, &set, NULL); handle_count++; } sigio_received = 0; } clock_gettime(CLOCK_REALTIME, &time2); duration = (long long)(time2.tv_sec-time1.tv_sec)*1000000000 + (time2.tv_nsec-time1.tv_nsec); sleep(1); printf("End time %lld.%09lld count=%d fps=%lld.\n", duration/1000000000, duration%1000000000, handle_count, (long long)handle_count*1000000000/duration); ioctl(fd, DPEYE1000_AXIDMA_RELEASE_CHNN, NULL); if(ret < 0) { printf("fail to ioctl DPEYE1000_AXIDMA_RELEASE_CHNN!!!\n"); } close(fd); //=========================================================================================== ret=IFMS_MemCMAFree(t_para.cma_block1); if(ret<0) { printf("CMA free failed.\n"); return -1; } ret=IFMS_MemCMAFree(t_para.cma_block2); if(ret<0) { printf("CMA free failed.\n"); return -1; } pthread_exit(0); } void main(int argc, char **argv) { pthread_t tidp; sigset_t set; unsigned int size = 0, expries = 0; struct sigaction sa; if(argc != 3) { printf("Usage: %s size(B) expries(us).\n", argv[0]); return -1; } size = atoi(argv[1]); if(!size) { printf("Please input right size.\n"); return -1; } expries = atoi(argv[2]); if(!expries) { printf("Please input right expries time.\n"); return -1; } t_para.size = size; t_para.expries = expries; memset(&sa, 0, sizeof(sa)); sa.sa_handler = sigint_handler; sa.sa_flags |= SA_RESTART; sigaction(SIGINT, &sa, NULL); sigemptyset(&set); sigaddset(&set, SIGIO); sigprocmask(SIG_BLOCK, &set, NULL); if(pthread_create(&tidp, NULL, pthread_func, NULL) == -1) { printf("Create pthread error.\n"); return; } if(pthread_join(tidp, NULL)) { printf("Join pthread error.\n"); return; } printf("Main exit.\n"); return; }
dma_thread測試不一樣分辨率、不一樣幀率下的性能對比:
Case | 同步DMA | 異步DMA | ||
top -d 5 | perf record -a -e cpu-clock -- sleep 60 |
top -d 5 | perf record -a -e cpu-clock -- sleep 60 | |
1080p 50fps dma_thread 3110400 20000 |
7.5% 單次耗時:1.6ms |
91.12% swapper [kernel.kallsyms] [k] __sched_text_end 91.22% swapper 8.04% main0.43% perf 0.11% jbd2/mmcblk1p2- 0.08% mmcqd/1 |
dma_sigio 3110400 20000 0.3% 單次耗時:135us |
98.57% swapper |
1080p 100fps dma_thread 3110400 10000 |
14.1% 單次耗時:1.6ms |
84.27% swapper [kernel.kallsyms] [k] __sched_text_end 84.30% swapper |
dma_sigio 3110400 10000 0.5% 單次耗時:135us |
98.48% swapper |
4k 25fps dma_thread 13271040 40000 |
14.5% 單次耗時:6.7ms |
84.71% swapper [kernel.kallsyms] [k] __sched_text_end 84.75% swapper |
dma_sigio 13271040 40000 0.2% 單次耗時:135us |
98.51% swapper |
分析總結:
1. 同步模式下,CPU佔用率跟數據量大小強相關,基本成正比;影響CPU佔用率的最大因素是DMA傳輸同步等待,即上面dma_sync_wait()和dma_cookie_status()兩個函數。
2. 異步模式下,請求發送後,交出CPU,在收到信號後繼續下一次發送,期間不會佔用CPU。CPU佔用率跟DMA請求次數強相關,主要是發送請求,以及sigsuspend()和SIGIO信號處理佔用。
3. 幀的吞吐率受DMA傳輸的幀大小影響。
明顯DMA的異步極限幀率,一樣受限於DMA傳輸效率,並不會增大吞吐率。
那麼看看不一樣幀率下的CPU狀況:
Case | 異步DMA | |
top -d 5 | perf record -a -e cpu-clock -- sleep 60 | |
1080p 550 fps (max) |
2.6% |
96.50% swapper |
1080p 375 fps | 1.8 | 98.44% swapper |
1080p 273 fps | 1.2% | 98.43% swapper |
4K 145 fps (max) |
0.8% | 98.45% swapper |
因此DMA極限幀率,主要受DMA傳輸大小和傳輸速度影響。
分別看看上面3個場景下同步模式下,kernelshark輸出能夠看出1080p執行時間是1.67ms,4k時間是6.88ms;每次時間間隔跟fps設置也對應。
1080p 50fps、1080p 100fps、4k 25fps三種佔用率應該是1.67/21.67=7.7%、1.67/11.67=14.3%、6.88/46.88=14.7%。
再來看一下異步狀況下的輸出,這時候越是大尺寸DMA傳輸CPU佔用率的收益越大。4k的時候
下面以1080p和4k對比看一下異步的收益。
1080p的DMA傳輸佔用時間從1.65,降到了1.65-1.57=0.08,收益率95%。
能夠看出4k狀況下異步DMA的CPU佔用時間從6.70,降到6.70-6.64=0.06,收益率達到99%。
當測試經過,進入方案的時候遇到SIGIO沒法接收到的問題。
檢查得知,原來是存在多個設備kill_fasync()。而一個進程範圍內,SIGIO只能有一個handler。
經過fcntl()設置F_SETSIG能夠定義sig代提SIGIO發送信號。
操做以下:
#define SIGDMA (SIGRTMIN+1)--------------------------定義一個實時信號 fcntl(fd, F_SETSIG, SIGDMA);---------------------使用SIGDMA代提SIGIO做爲async信號。 memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = sigdma_handler;----------------必定要修改成sa_sigaction,對應的sigdma_handler參數也須要修改。 sa.sa_flags = SA_RESTART | SA_SIGINFO;-----------必定要增長SA_SIGINFO。 sigaction(SIGDMA, &sa, NULL);
除了有上面的好處以外,實時信號還能排隊,這就比非實時信號更不會丟失。除非隊列溢出。
在實際場景中,每40ms來一幀數據進行DMA搬運,
那麼這段時間內,整個線程佔用多少時間呢?2.303-0.225=2.078ms,對應的CPU佔用率應該是5.2%。
再看看異步DMA實際效果如何?能夠看出copy線程,中間調度出去的時間增大不小。
那麼此時CPU佔用率多少呢?2.288-1.177-0.431=0.68ms,對應的CPU佔用率應該是1.7%。
從計算來看CPU佔用率能下降3%左右。
1. 使用AXI DMA兩通道,可否提升DMA吞吐率?至關於DMA併發,copy雙線程?------------硬件雙通道,如何構造同時觸發的雙通道case?
2. 如何標識每一次DMA傳輸,經過netlink port端口?--------------------------------------------------修改異步觸發方式,port和channel綁定