编辑
2025-03-03
记录知识
0

在使用shell环境的时候,难免会遇到对文本进行操作的情况,shell提供了一些功能强大的文本操作命令,这里做了简单汇总,有助于了解和学习这些工具,提高工作中处理文本的效率

一:grep

grep是经常使用的文本查找工具,下面列举grep常用的一些命令。当然,也有比grep更好的工具ripgrep。这个可以后续探索

1.1 查找不以#开头的文本内容,并去掉空格的行

grep -vE "^#|^$" /etc/ssh/sshd_config

-v是选中不匹配的行,-E是明确使用正则 ^$ 是没有数据的行 ^#是以#开头的行

1.2 查找指定匹配字符(忽略大小写)前后5行的内容

dmesg | grep -i cfg80211 -A 5 dmesg | grep -i cfg80211 -B 5 dmesg | grep -i cfg80211 -C 5

A after 之后的5行, B before 之前的5行, C 是A+B的合计,前后5行

-i 忽略大小写

二:cut

cut是一个字符截取命令,其命令在处理一些字符串上,可以方便的对一些字符串直接截取。如果文本内容格式比较规律的话,也可以用来筛选指定分隔符下的内容

2.1 通过ifconfig找到所有的网卡设备名

ifconfig | grep -vE "^ |^$" | cut -d ' ' -f 1 | cut -d ":" -f 1

-d 是指定分隔符, -f 是指定某个域。

-d ' ' -f 1 是以单个空格为分隔符找到第一个域

cut -d ":" -f 1 是以冒号为分隔符找到第一个域

2.2 只看内核的时间戳

dmesg | cut -b -15

三:sed

sed是重要的字符替换工具,也就流编辑器,可以方便的直接替换一些文本内容,从而不用打开文本去处理,这使得在shell内对文本替换来说十分方便

-e :添加一个命令,多个sed命令可以用这个 -i :直接修改文件内容 -n :取消自动打印,只打印模式匹配的行 -E :支持扩展表达式;

3.1 替换

cat filename | sed -e“s/text/replace/g”-e“s/text/replace/g”

-e 允许运行多个命令,s///g是替换的语法, s指的搜索,g指的全局。也就是搜索全局,然后统一替换

3.2 插入

cat filename | sed -e "4a help"

在文本第4行后添加help作为新的一行

cat filename | sed -e "4i help"

在文本第4行前添加help作为新的一行

3.2 删除

cat filename | sed -e "/^$/d"

删除空格行

cat filename | sed "s/[a-z]\|[A-Z]/[&]/g"

对文本中的所有字符加上[] 。&指的是上一次搜索到的值。 | 是或,意思可以多条规则匹配

3.3 子串匹配

echo a b | sed -E -e "s/([a-z]) ([a-z])/\2 \1/"

()可以声明这是一个字串,()声明的字串,可以通过\1 \2等通配来引用。

-E 代表使用正则,如果不加-E 则使用()的时候需要转换成 () 那么匹配一个a-z的字符需要这样表示 ([a-z])

那么不加-E 的效果如下

echo a b c | sed -e "s/\([a-z]\) \([a-z]\) \(c\)/\3 \2 \1/"

四:sort

sort是重要的字符和数字排序工具,可以直接对文本进行升序和降序操作

4.1 对任意文件按照行数反序

grep -n '.' /etc/os-release | sort -t ':' -k1 -nru

-t 指定分隔符

-k 指定分隔符划分的域

-n 指定排序方式为数字(默认为字符)

-r 指定反序

4.2 以=为分隔符对文本的第一个域的第2个字符排序,并去掉重复的行

cat /etc/os-release | sort -t '=' -k 1.2 -u

-u 去掉重复的排序行

五:uniq

uniq是找重复的工具,可以快速的识别出文本中完全重复的行,并记录次数

5.1 找到文本中重复的行,忽略大小写,并打印重复次数

cat Readme.md | uniq -dci

5.2 找到文本中所有不重复的行

cat Readme.md | uniq -u

-d 是找到完全重复的行

-c 是统计重复次数

-i 是忽略大小写

-u 是打印不重复的行

六:awk

awk模式是模式扫描及处理语言,也就是说文本处理规则太多了,我定义一个语言,你们想怎么匹配就怎么匹配

NF 一条记录的字段的数目 NR 行号,当前处理的文本行的行号 FS 定义间隔符 OFS 定义输出字段分隔符,默认空格  RS 输入记录分隔符,默认换行

awk默认格式为

awk '{pattern + action}' {filenames}

这里'{}'里面就是代码域,用于编写awk代码的地方。

最简单的打印可以如下

echo | awk '{print "Hello World"}'

直接打印hello world

echo Hello World | awk '{print $1" "$2}'

打印传过来的参数

6.1 BEGIN和END宏

cat /etc/passwd | awk 'BEGIN{count=0;print "begin is:" count} {count=count+1;print count} END{print "end is:"count}'

这里统计了passwd的文件行数

awk不仅默认{}里面填写代码,也可以在BEGIN和END宏里面填写代码,BEGIN填写的代码,在{}前运行,END填写的代码在{}后运行

ll |awk 'BEGIN {byte=0;print "统计当前目录占用字节数"} {byte=byte+$5;} END{print "当前目录字节数为:",byte}'

这里统计了当前目录的字节数,不包括下级目录

6.2 if语句

cat /etc/passwd | awk '{if(NR>=10 && NR<=20) print $1}'

这里取passwd文件的第10行到20行内容

'{}'的if语句可以和C语言一样,print可以和python语言一样。

6.3 for语句

cat /etc/passwd | awk -F '' 'BEGIN{count=0} {for(i=1; i<=NF; i++) count++} END{print count}'

这里对每个字符做计数,最后打印存在的字符总数。

6.4 while语句

echo abcdefg | awk -F '' '{i=1; while(i<=length($0)) {print $i;i++}}'

这里对一行abcdefg转换成一个字母多行

6.5 分隔符

echo "1 2,3:4" | awk -F [" ",:] '{print $1$2$3$4}'

以空格,逗号,冒号作为分隔符打印第1,2,3,4个匹配的内容

echo "1 2,3:4" | awk BEGIN{'FS="[ ,:]"} {print $1$2$3$4}'

FS宏也可以代替-F参数,需要注意的是,FS参数需要在后面加上"" 双引号

七:总结

这里介绍了grep sort uniq awk sed cut一共六个命令,每个命令都可以搭配起来使用。这里简要介绍一下在哪些情况下可以用哪些命令

1.搜索一些匹配单词的时候,可以用grep,例如 grep -nr text filename 2.面临排序问题的时候,可以用sort, 例如 sort -r 3.去重复行的时候,可以用uniq, 例如 uniq -u 4.批量替换的时候,可以用sed,例如 sed -i “s/text/replace/g” 5.对特定格式字符串进行截取的时候,可以用cut ,例如 cut b 1-3 6.在对一些列的域做划分的时候,可以用awk , 例如 awk -F ‘:’ '{print $1}' 7.对文本进行高级编辑操作的时候,awk都能完成.
编辑
2025-03-03
记录知识
0

RK3588的实时内核,这里测试验证其指标,用于后续评估判断RK3588相关的实时指标作为参考借鉴。测试结果或有偏差,最终以实际测试为准

一:编译内核

RT内核两笔补丁需要修改,如下:

diff --git a/include/trace/hooks/dtask.h b/include/trace/hooks/dtask.h index 9890bfe5c41d..fc2a51ce547e 100644 --- a/include/trace/hooks/dtask.h +++ b/include/trace/hooks/dtask.h @@ -58,7 +58,7 @@ DECLARE_HOOK(android_vh_sched_show_task, TP_ARGS(task)); DECLARE_HOOK(android_vh_alter_mutex_list_add, TP_PROTO(struct mutex *lock, - struct mutex_waiter *waiter, + struct rt_mutex_waiter *waiter, struct list_head *list, bool *already_on_list), TP_ARGS(lock, waiter, list, already_on_list)); diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index a2506b92e0c1..8d794db5e4d6 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -67,6 +67,7 @@ #ifdef CONFIG_PRINTK_TIME_FROM_ARM_ARCH_TIMER #include <clocksource/arm_arch_timer.h> +#if 0 static u64 get_local_clock(void) { u64 ns; @@ -76,6 +77,7 @@ static u64 get_local_clock(void) return ns; } +#endif #else static inline u64 get_local_clock(void) {

编译命令:

export ARCH=arm64 export CROSS_COMPILE=aarch64-none-linux-gnu- export PATH=$PATH:/path/to/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin make rockchip_linux_defconfig rockchip_rt.config make kylin.img -j123

这里项目有kylin.dts,故编译为kylin.img的内核,其他项目自行修改

二:测试任务上下文切换时间

2.1 通过mqueue来测试

#ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <stdlib.h> #include <string.h> #include <inttypes.h> #include <unistd.h> #include <stdio.h> #include <limits.h> #include <sched.h> #include <signal.h> #include <netinet/if_ether.h> #include <netinet/ip.h> #include <stdbool.h> #include <pthread.h> //#include <lock.h> #include <fcntl.h> #include <time.h> #include <mqueue.h> #include <semaphore.h> #include <stdarg.h> #include <sys/mman.h> #define TESTMQ_NAME "/testmsg" #define MSG_SIZE 8 char *testmsg = "Testing"; static int print_mode, TestCount; mqd_t sendsyncmq, recvsyncmq, testmq; struct timespec tv_s[10], tv_e[10]; static int over; struct mq_attr mqstat; pthread_t send_tid, recv_tid, send_recv_tid; static int tracemark_fd = -1; #define FILE_TRACEMARK "/sys/kernel/debug/tracing/trace_marker" static void open_tracemark() { if ((tracemark_fd = open(FILE_TRACEMARK, O_WRONLY)) == -1) printf("unable to open trace_marker file: %s\n", FILE_TRACEMARK); } static void close_tracemark() { close(tracemark_fd); } #define TRACEBUFSIZ 1024 static __thread char tracebuf[TRACEBUFSIZ]; static void tracemark(char *fmt, ...) __attribute__((format(printf, 1, 2))); static void tracemark(char *fmt, ...) { va_list ap; int len; /* bail out if we're not tracing */ /* or if the kernel doesn't support trace_mark */ if (tracemark_fd < 0) return; va_start(ap, fmt); len = vsnprintf(tracebuf, TRACEBUFSIZ, fmt, ap); va_end(ap); write(tracemark_fd, tracebuf, len); } /* * * * 终止ftrace,这样可以在ftrace的ringbuffer的底部保留问题现场 * * */ static int trace_fd = -1; #define FILE_TRACEON "/sys/kernel/debug/tracing/tracing_on" static void open_tracingon() { if ((trace_fd = open(FILE_TRACEON, O_WRONLY)) == -1) printf("unable to open trace_on file: %s\n", FILE_TRACEON); } static void close_tracingon() { close(trace_fd); } static void disable_trace() { if (trace_fd < 0) return; write(trace_fd, "0", 1); } pthread_t test_task_create(char *name, void *thread_func, size_t stack_size, int prio, int core) { int ret; struct sched_param param; pthread_attr_t attr; pthread_t thread_id; cpu_set_t mask; ret = pthread_attr_init(&attr); if (ret) { printf("ERROR: pthread_attr_init failed\n"); return -1; } ret = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); if (ret) { printf("ERROR: pthread_attr_setscope failed\n"); goto out; } ret = pthread_attr_setstacksize(&attr, stack_size); if (ret) { printf("ERROR: pthread_attr_setstacksize failed\n"); goto out; } ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); if (ret) { printf("ERROR: pthread_attr_setinheritsched failed\n"); goto out; } /* ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (ret) { printf("ERROR: pthread_attr_setdetachstate failed\n"); goto out; } */ ret = pthread_attr_setschedpolicy(&attr, SCHED_FIFO); if (ret) { printf("ERROR: pthread_attr_setschedpolicy failed\n"); goto out; } param.sched_priority = prio; ret = pthread_attr_setschedparam(&attr, &param); if (ret) { printf("ERROR: pthread_attr_setschedparam failed\n"); goto out; } ret = pthread_create(&thread_id, &attr, thread_func, NULL); if (ret) { printf("ERROR: pthread_create %s failed\n", name); goto out; } CPU_ZERO(&mask); CPU_SET(core, &mask); if (pthread_setaffinity_np(thread_id, sizeof(mask), &mask) == -1) printf("ERROR: %s pthread_setaffinity_np failed, cpu_core = %d\n", name, core); return thread_id; out: pthread_attr_destroy(&attr); return -1; } struct timespec send_tv, recv_tv; bool flag = false; bool flag_send = false; void send_task_pthread(int core) { int i, ret; struct timespec tv1, tv2; unsigned int send_diff, send_sum; unsigned int send_mindiff, send_avg, send_maxdiff; char recvtestmsg[MSG_SIZE]; send_sum = 0; send_avg =0; send_mindiff = 100000000; send_maxdiff = 0; for (i = 0; i < TestCount; i++) { if (over) break; /* * send the test massege and calculate the cost time */ if (i > 1) { send_diff = (recv_tv.tv_sec * 1000000000 + recv_tv.tv_nsec) - (send_tv.tv_sec * 1000000000 + send_tv.tv_nsec); send_sum += send_diff; send_avg = send_sum / (i + 1); if (send_diff < send_mindiff) { send_mindiff = send_diff; } /* * 从第三次开始计入最大时间 * 原因:第二次发送消息时,如果接受线程未创建成功,mq_send阻塞, * 等待接受线程创建,第二次计算时间并不准确。 * 所以等接收线程接收一次消息后,开始统计最大时间 */ if ((send_diff > send_maxdiff) && (i > 1)) { send_maxdiff = send_diff; } // if (((i % 10) == 0)) printf("Task Switch Time, loop: %d, Min: %4d ns, Cur: %4d ns, Avg: %4d ns, Max: %4d ns\n\n", i, send_mindiff, send_diff, send_avg, send_maxdiff); } flag_send = false; ret = mq_send(testmq, testmsg, strlen(testmsg), 1); clock_gettime(CLOCK_REALTIME, &send_tv); flag_send = true; flag = false; while (!flag) { usleep(10000); } // if (!flag){ // usleep(10000); // flag = true; // printf("ERROR: send test message failed\n"); // sleep(1); // goto retry; // } } over = 1; printf("mq_send test count: %d \n", i); printf("Final result, Min: %4d ns, Cur: %4d ns, Avg: %4d ns, Max: %4d ns\n\n", send_mindiff, send_diff, send_avg, send_maxdiff); /* close_tracemark(); close_tracingon(); */ pthread_kill(recv_tid, SIGTERM); return; } void recv_task_pthread(int core) { int i, ret; ssize_t recv_length; struct timespec tv1, tv2; unsigned int recv_diff, recv_sum; unsigned int recv_mindiff, recv_avg, recv_maxdiff; char recvsyncmsg[MSG_SIZE]; char recvtestmsg[MSG_SIZE]; while (!over) { /* * receive the test massege and calculate the cost time */ recv_length = mq_receive(testmq, recvtestmsg, MSG_SIZE, NULL); while (!flag_send) { } clock_gettime(CLOCK_REALTIME, &recv_tv); flag = true; if ( recv_length != strlen(testmsg)) { perror("could not receive test message"); over = 1; } } return; } void send_recv_pthread(int core) { int i, ret; ssize_t recv_length; struct timespec tv1, tv2; unsigned int recv_diff, recv_sum; unsigned int recv_mindiff, recv_avg, recv_maxdiff; char recvtestmsg[MSG_SIZE]; recv_sum = 0; recv_avg =0; // recv_mindiff = UINT_MAX; recv_mindiff = 100000000; recv_maxdiff = 0; for (i = 0; i < TestCount; i++) { /* * send the test massege */ ret = mq_send(testmq, testmsg, strlen(testmsg), 1); if (ret < 0) { printf("ERROR: send test message failed\n"); over = 1; } usleep(200); /* * receive the test massege and calculate the cost time */ clock_gettime(CLOCK_REALTIME, &tv1); recv_length = mq_receive(testmq, recvtestmsg, MSG_SIZE, NULL); clock_gettime(CLOCK_REALTIME, &tv2); if ( recv_length != strlen(testmsg)) { perror("could not receive test message"); over = 1; } recv_diff = (tv2.tv_sec * 1000000000 + tv2.tv_nsec) - (tv1.tv_sec * 1000000000 + tv1.tv_nsec); recv_sum += recv_diff; recv_avg = recv_sum / (i + 1); if (recv_diff < recv_mindiff) { recv_mindiff = recv_diff; } if ((recv_diff > recv_maxdiff) && (i > 0)) { recv_maxdiff = recv_diff; } if (((i % 10) == 0)) printf("MQ Receive Time,loop: %d, Min: %4d ns, Cur: %4d ns, Avg: %4d ns, Max: %4d ns\n\n", i, recv_mindiff, recv_diff, recv_avg, recv_maxdiff); usleep(100); } printf("mq_receive test count: %d \n", i); printf("Final result, Min: %4d ns, Cur: %4d ns, Avg: %4d ns, Max: %4d ns\n\n", recv_mindiff, recv_diff, recv_avg, recv_maxdiff); return; } int main(int argc, char **argv) { int SendCore, SendThreadPrio, RecvCore, RecvThreadPrio, mode; if (argc != 7) { printf("Wrong CMD, check the parameters, please!\n"); printf("./mq_task_switch 1 90 2 90 10000 1\n"); return -1; } if (mlockall(MCL_CURRENT|MCL_FUTURE) == -1) { perror("mlockall"); return -1; } SendCore = atoi(argv[1]); SendThreadPrio = atoi(argv[2]); RecvCore = atoi(argv[3]); RecvThreadPrio = atoi(argv[4]); TestCount = atoi(argv[5]); mode = atoi(argv[6]); over = 0; memset(&mqstat, 0, sizeof(mqstat)); mqstat.mq_maxmsg = 1; mqstat.mq_msgsize = 8; mqstat.mq_flags = 0; mq_unlink(TESTMQ_NAME); testmq = mq_open(TESTMQ_NAME, O_RDWR | O_CREAT, 0666, &mqstat); if (testmq < 0) { printf("open test message queue failed\n"); return -1; } if (mode == 0) { send_recv_tid = test_task_create("SndRECVTask", send_recv_pthread, 262144, SendThreadPrio, SendCore); if (send_recv_tid < 0) { printf("ERROR: Create send_recv_pthread failed!\n"); return -1; } pthread_join(send_recv_tid, NULL); } else { send_tid = test_task_create("sndTask", send_task_pthread, 262144, SendThreadPrio, SendCore); if (recv_tid < 0) { printf("ERROR: Create send_task_pthread failed!\n"); return -1; } usleep(10); recv_tid = test_task_create("RcvTask", recv_task_pthread, 262144, RecvThreadPrio, RecvCore); if (recv_tid < 0) { printf("ERROR: Create recv_task_pthread failed!\n"); return -1; } pthread_join(recv_tid, NULL); pthread_join(send_tid, NULL); } munlockall(); return 0; }

编译得到二进制mq_task_switch,开始测试

./mq_task_switch 1 90 2 90 10000 1

输出结果

Final result, Min: 10208 ns, Cur: 11084 ns, Avg: 11113 ns, Max: 24791 ns

这里可以确定最大值是24791ns ,平时值是11113ns.

2.2 通过管道传递令牌

#include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <time.h> #include <sched.h> #include <sys/types.h> #include <unistd.h> //pipe() int main() { int x, i, fd[2], p[2]; int tv[2]; //用于进程间数据通信 char send = 's'; char receive; pipe(fd); pipe(p); pipe(tv); struct timeval tv_start,tv_end; struct sched_param param; param.sched_priority = 0; while ((x = fork()) == -1); if (x==0) { //子进程 sched_setscheduler(getpid(), SCHED_FIFO, &param); gettimeofday(&tv_start, NULL); write(tv[1], &tv_start, sizeof(tv_start));//保存数据,以便传递到父进程中进行计算 for (i = 0; i < 10000; i++) { read(fd[0], &receive, 1); //printf("Child read!\n"); write(p[1], &send, 1); //printf("Child write!\n"); } exit(0); } else { //父进程 sched_setscheduler(getpid(), SCHED_FIFO, &param); for (i = 0; i < 10000; i++) { write(fd[1], &send, 1); //printf("Parent write!\n"); read(p[0], &receive, 1); //printf("Parent read!\n"); } gettimeofday(&tv_end, NULL); } read(tv[0], &tv_start, sizeof(tv_start)); printf("Task Switch Time: %f us\n", (1000000*(tv_end.tv_sec-tv_start.tv_sec) + tv_end.tv_usec - tv_start.tv_usec)/20000.0); return 0; }

编译:

gcc task_switch_pipe.c -o task_switch_pipe

运行:

root@kylin:~# while((1));do ./task_switch_pipe ; done Task Switch Time: 7.925000 us Task Switch Time: 7.978100 us Task Switch Time: 7.542050 us Task Switch Time: 9.470300 us Task Switch Time: 7.518850 us Task Switch Time: 8.346700 us Task Switch Time: 8.005150 us Task Switch Time: 7.560750 us Task Switch Time: 7.625400 us Task Switch Time: 8.143850 us Task Switch Time: 6.504850 us Task Switch Time: 8.422350 us Task Switch Time: 8.029650 us Task Switch Time: 7.612400 us Task Switch Time: 7.706200 us Task Switch Time: 6.831750 us Task Switch Time: 6.637150 us Task Switch Time: 9.136150 us Task Switch Time: 7.259700 us Task Switch Time: 9.774700 us Task Switch Time: 6.781400 us Task Switch Time: 7.425350 us Task Switch Time: 7.738900 us Task Switch Time: 8.812100 us Task Switch Time: 7.271150 us Task Switch Time: 8.677250 us Task Switch Time: 7.514600 us Task Switch Time: 6.683600 us Task Switch Time: 7.421900 us Task Switch Time: 8.019550 us Task Switch Time: 8.250450 us Task Switch Time: 8.571800 us Task Switch Time: 7.957400 us Task Switch Time: 7.837050 us Task Switch Time: 7.161250 us Task Switch Time: 8.998750 us Task Switch Time: 7.160500 us Task Switch Time: 6.629200 us Task Switch Time: 7.082150 us Task Switch Time: 7.570450 us Task Switch Time: 8.423500 us Task Switch Time: 7.531950 us Task Switch Time: 7.856900 us Task Switch Time: 8.980750 us Task Switch Time: 7.072300 us Task Switch Time: 9.112650 us Task Switch Time: 7.871100 us Task Switch Time: 7.168500 us Task Switch Time: 7.774850 us Task Switch Time: 7.308050 us Task Switch Time: 6.532000 us Task Switch Time: 6.994950 us Task Switch Time: 7.805700 us Task Switch Time: 7.980750 us Task Switch Time: 7.990150 us Task Switch Time: 8.017550 us Task Switch Time: 6.781300 us Task Switch Time: 6.755500 us Task Switch Time: 6.418300 us Task Switch Time: 8.239600 us Task Switch Time: 8.661350 us Task Switch Time: 8.688850 us Task Switch Time: 7.245000 us Task Switch Time: 8.793250 us Task Switch Time: 7.628300 us Task Switch Time: 6.593100 us Task Switch Time: 8.685100 us Task Switch Time: 6.575550 us Task Switch Time: 8.380500 us Task Switch Time: 8.607750 us Task Switch Time: 6.581850 us Task Switch Time: 6.862200 us Task Switch Time: 6.401400 us Task Switch Time: 6.690150 us Task Switch Time: 7.945100 us Task Switch Time: 7.820750 us Task Switch Time: 7.935500 us Task Switch Time: 8.156100 us Task Switch Time: 7.198700 us Task Switch Time: 7.230950 us Task Switch Time: 7.623400 us Task Switch Time: 7.438650 us Task Switch Time: 7.172000 us Task Switch Time: 8.439000 us Task Switch Time: 6.927050 us Task Switch Time: 8.215500 us Task Switch Time: 7.361300 us Task Switch Time: 7.231800 us Task Switch Time: 7.303450 us Task Switch Time: 8.140700 us Task Switch Time: 8.331800 us Task Switch Time: 7.962950 us Task Switch Time: 6.757600 us Task Switch Time: 7.251000 us

这里测试最大延迟为9.774700 us

三:测试消息传递延迟时间

安装测试包

apt install rt-tests

测试命令:

pmqtest -p 90 -t 2 -i 1000 -l 7200000

测试结果

root@kylin:~# pmqtest -p 90 -t 2 -i 1000 -l 7200000 #0: ID2607, P90, CPU5, I1000; #1: ID2608, P90, CPU4, TO 0, Cycles 782505 #2: ID2609, P89, CPU6, I1500; #3: ID2610, P89, CPU7, TO 0, Cycles 524798 #1 -> #0, Min 3, Cur 6, Avg 6, Max 27 #3 -> #2, Min 3, Cur 6, Avg 7, Max 36

这里可以确定最小值3us,平均值7us,最大值36us

四:中断响应时间

测试命令:

cyclictest -p 90 -m -c 0 -i 1000 -t 4 -l 7200000

测试结果:

root@kylin:~# cyclictest -p 90 -m -c 0 -i 1000 -t 4 -l 7200000 policy: fifo: loadavg: 2.61 1.48 0.68 1/288 257policy: fifo: loadavg: 2.91 2.71 2.14 1/285 2595 # /dev/cpu_dma_latency set to 0us T: 0 ( 2567) P:90 I:1000 C:1217830 Min: 4 Act: 4 Avg: 4 Max: 19 T: 1 ( 2568) P:90 I:1500 C: 811877 Min: 4 Act: 4 Avg: 4 Max: 15 T: 2 ( 2569) P:90 I:2000 C: 608904 Min: 4 Act: 5 Avg: 4 Max: 14 T: 3 ( 2570) P:90 I:2500 C: 487120 Min: 4 Act: 4 Avg: 4 Max: 13

这里可以确定最大延迟19us,平均延迟4us

五:结论

当前测试结论如下:

3588任务上下文切换最大时间为:24.791us,通过pipe令牌测试最大延迟为9.774700 us

3588消息传递最大延迟为:36us

3588中断响应最大延迟为:19us

当前测试结果不代表实际测试结果,并且测试结果仅供参考,具体情况根据系统负载,硬件情况决定。

编辑
2025-03-03
记录知识
0

RK最近实现了从上层处理的硬件光标层,仓库地址为 https://github.com/JeffyCN/drm-cursor 。以往的如RK3399的硬件光标都是走的libdrm的通用接口,但在新的平台例如RK3568和RK3588上,内核貌似没有单独设置鼠标层。

如果需要在平台上使用硬件光标,可以使用这个库实现。

这里实现的drm curosr 鼠标指针,是通过hook的方式,把drm设置鼠标的相关函数通过EGL的接口实现,避免通过libdrm走到内核上面去。

关于什么是光标,这里给出了解释:

Cursors Similar to planes, many hardware also supports cursors. A cursor is a very small buffer with an image that is blended over the CRTC framebuffer. You can set a different cursor for each CRTC with drmModeSetCursor(3) and move it on the screen with drmModeMoveCursor(3). This allows to move the cursor on the screen without rerendering. If no hardware cursors are supported, you need to rerender for each frame the cursor is moved.

一:标准光标函数实现

1.1 libdrm函数实现

drmModeSetCursor xf86drmMode.c +412 drm_public int drmModeSetCursor(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height) { struct drm_mode_cursor arg; memclear(arg); arg.flags = DRM_MODE_CURSOR_BO; arg.crtc_id = crtcId; arg.width = width; arg.height = height; arg.handle = bo_handle; return DRM_IOCTL(fd, DRM_IOCTL_MODE_CURSOR, &arg); }
DRM_IOCTL_DEF(DRM_IOCTL_MODE_CURSOR, drm_mode_cursor_ioctl, DRM_MASTER),
drmModeMoveCursor drm_public int drmModeMoveCursor(int fd, uint32_t crtcId, int x, int y) { struct drm_mode_cursor arg; memclear(arg); arg.flags = DRM_MODE_CURSOR_MOVE; arg.crtc_id = crtcId; arg.x = x; arg.y = y; return DRM_IOCTL(fd, DRM_IOCTL_MODE_CURSOR, &arg); }
DRM_IOCTL_DEF(DRM_IOCTL_MODE_CURSOR, drm_mode_cursor_ioctl, DRM_MASTER),
drmModeSetCursor2 drm_public int drmModeSetCursor2(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height, int32_t hot_x, int32_t hot_y) { struct drm_mode_cursor2 arg; memclear(arg); arg.flags = DRM_MODE_CURSOR_BO; arg.crtc_id = crtcId; arg.width = width; arg.height = height; arg.handle = bo_handle; arg.hot_x = hot_x; arg.hot_y = hot_y; return DRM_IOCTL(fd, DRM_IOCTL_MODE_CURSOR2, &arg); }
DRM_IOCTL_DEF(DRM_IOCTL_MODE_CURSOR2, drm_mode_cursor2_ioctl, DRM_MASTER|DRM_UNLOCKED)

1.2 Xorg函数实现

对应Xorg中实现光标的地方如下

hw/xfree86/drivers/modesetting/drmmode_display.c
drmmode_set_cursor_position
static void drmmode_set_cursor_position(xf86CrtcPtr crtc, int x, int y) { drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; drmmode_ptr drmmode = drmmode_crtc->drmmode; drmModeMoveCursor(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id, x, y); }
drmmode_show_cursor
static Bool drmmode_show_cursor(xf86CrtcPtr crtc) { drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; drmmode_crtc->cursor_up = TRUE; return drmmode_set_cursor(crtc); }

1.3 是否使用硬件光标

ret = drmModeSetCursor2(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id, handle, ms->cursor_width, ms->cursor_height, cursor->bits->xhot, cursor->bits->yhot); /* -EINVAL can mean that an old kernel supports drmModeSetCursor but * not drmModeSetCursor2, though it can mean other things too. */ if (ret == -EINVAL) ret = drmModeSetCursor(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id, handle, ms->cursor_width, ms->cursor_height); /* -ENXIO normally means that the current drm driver supports neither * cursor_set nor cursor_set2. Disable hardware cursor support for * the rest of the session in that case. */ if (ret == -ENXIO) { xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(crtc->scrn); xf86CursorInfoPtr cursor_info = xf86_config->cursor_info; cursor_info->MaxWidth = cursor_info->MaxHeight = 0; drmmode_crtc->drmmode->sw_cursor = TRUE; }

如果内核的IOCTL为 -ENXIO 则Xorg实现软光标

drmmode_crtc->drmmode->sw_cursor = TRUE;

二:RK的光标的钩子实现

2.1 函数实现

int drmModeSetCursor(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height) { /* Init log file */ drm_get_ctx(fd); return drm_set_cursor(fd, crtcId, bo_handle, width, height); } int drmModeMoveCursor(int fd, uint32_t crtcId, int x, int y) { return drm_move_cursor(fd, crtcId, x, y); } int drmModeSetCursor2(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height, int32_t hot_x, int32_t hot_y) { return -EINVAL; }

2.2 钩子的处理

这个仓库最终生成了 libdrm-cursor.so.1.0.0 的库文件,但是光标的实现函数在libdrm的so里面。这里RK使用了ld的preload的配置

方式是在debian/libdrm-cursor.postinst 内添加 echo /usr/lib/*/libdrm-cursor.so.1 >> /etc/ld.so.preload

Xorg的链接情况如下,说明钩子已生效

kylin@kylin:~$ ldd /usr/lib/xorg/Xorg | grep drm /usr/lib/aarch64-linux-gnu/libdrm-cursor.so.1 (0x0000007fb9a31000) libdrm.so.2 => /lib/aarch64-linux-gnu/libdrm.so.2 (0x0000007fb97d1000)

2.3 drm-cursor运行日志

drm_get_ctx(485) atomic drm API enabled drm_get_ctx(518) max fps: 60 drm_get_ctx(559) found 0 CRTC: 64(0) (1920x1200) prefer plane: 0 drm_get_ctx(559) found 1 CRTC: 83(1) (0x0) prefer plane: 0 drm_get_ctx(567) found 2 CRTCs drm_get_ctx(616) found plane: 58[primary] crtcs: 0x1 (ARGB) drm_get_ctx(616) found plane: 61[cursor ] crtcs: 0x1 (ARGB) drm_get_ctx(616) found plane: 65[overlay] crtcs: 0x1 (ARGB) drm_get_ctx(616) found plane: 68[overlay] crtcs: 0x1 (ARGB) drm_get_ctx(616) found plane: 80[primary] crtcs: 0x2 (ARGB) drm_get_ctx(616) found plane: 84[overlay] crtcs: 0x2 (ARGB) drm_get_ctx(625) using libdrm-cursor (1.3.0~20211201) drmModeSetCursor(1185) fd: 11 crtc: 64 handle: 0 size: 0x0 drm_crtc_bind_plane(692) CRTC[64]: using cursor plane drm_crtc_bind_plane(700) CRTC[64]: bind plane: 61 drm_set_cursor(1097) CRTC[64]: request setting new cursor 0 (0x0) drm_crtc_thread_fn(866) CRTC[64]: thread started drm_crtc_thread_fn(924) CRTC[64]: set new cursor 0 (0x0) drmModeMoveCursor(1192) fd: 11 crtc: 64 position: 956,596 drm_move_cursor(1154) CRTC[64]: request moving cursor to (956,596) in (1920x1200)

可以看出的信息为,1.解析配置文件,2.找到drm支持的crtc,3.找到对应的plane,4.将配置指定的plane配置为光标使用的plane

2.4 内核运行日志

查看内核drm光标这块的日志,需要打开drm相关的配置宏

drm的sys节点为: /sys/module/drm/parameters/debug

对应的可选值如下:

Bit 0 (0x01) will enable CORE messages (drm core code) Bit 1 (0x02) will enable DRIVER messages (drm controller code) Bit 2 (0x04) will enable KMS messages (modesetting code) Bit 3 (0x08) will enable PRIME messages (prime code) Bit 4 (0x10) will enable ATOMIC messages (atomic code) Bit 5 (0x20) will enable VBL messages (vblank code) (int)

因为光标通过atomic的api实现的,通过借助本身调试atomic的debug,可以提供一点有用的信息。

echo 0x10 > /sys/module/drm/parameters/debug 打开drm ATOMIC 调试信息

日志如下:

[drm:drm_atomic_state_init] Allocated atomic state 000000009ea89924 [drm:drm_atomic_get_plane_state] Added [PLANE:71:Cluster0-win0] 000000001d2516b5 state to 000000009ea89924 [drm:drm_atomic_get_crtc_state] Added [CRTC:85:video_port0] 000000002526233c state to 000000009ea89924 [drm:drm_atomic_set_fb_for_plane] Set [FB:202] for [PLANE:71:Cluster0-win0] state 000000001d2516b5 [drm:drm_atomic_check_only] checking 000000009ea89924 [drm:drm_atomic_commit] committing 000000009ea89924 [drm:drm_atomic_state_default_clear] Clearing atomic state 000000009ea89924 [drm:__drm_atomic_state_free] Freeing atomic state 000000009ea89924

可以发现,默认使用plane 71,crtc 85,fb202

查看sys下的信息,是否正常对应

1.cat /sys/kernel/debug/dri/0/framebuffer | grep drm-cursor -B 1
framebuffer[202]: allocated by = drm-cursor[85]
2.cat /sys/kernel/debug/dri/0/state | grep drm-cursor -B 3
plane[71]: Cluster0-win0 crtc=video_port0 fb=208 allocated by = drm-cursor[85]
3.cat /sys/kernel/debug/dri/0/summary
Cluster0-win0: ACTIVE win_id: 4 format: AB24 little-endian (0x34324241)[AFBC] SDR[0] color_space[0] glb_alpha[0xff] rotate: xmirror: 0 ymirror: 0 rotate_90: 0 rotate_270: 0 csc: y2r[0] r2y[1] csc mode[1] zpos: 7 src: pos[0, 0] rect[64 x 64] dst: pos[0, 0] rect[64 x 64] buf[0]: addr: 0x0000000001825000 pitch: 256 offset: 0

由此,可以确定,硬件光标正常启动,大小为64x64

三:测试和调试

3.1 配置文件 /etc/drm-cursor.conf

# debug=1 # log-file= # hide=1 # hide cursors # atomic=0 # disable atomic drm API # max-fps=60 # allow-overlay=1 # allowing overlay planes # prefer-afbc=0 # prefer plane with AFBC modifier supported # num-surfaces=8 # num of egl surfaces to avoid edge moving corruption # prefer-plane=65 # prefer-planes=61,65 # crtc-blocklist=64,83
debug=1 默认会将log输出在 /var/log/drm-cursor.log log-file=/tmp/drm-cursor.log 手动指定log文件 hide=1 是否隐藏鼠标 atomic=1 这一项没什么用,drm cursor默认使用atomic 的api max-fps=60 光标指针的显示帧数。 allow-overlay=1 允许使用overlay层。如果不设置,则默认不使用overlay,也就是只使用cursor层 prefer-afbc=0 是否使用arm的 afbc功能的overlay层 num-surfaces=8 eglCreateWindowSurface 创建的离屏缓冲区的数量 prefer-plane=65 默认设置的plane prefer-planes=61,65 可选的多个plane crtc-blocklist=83 阻止在哪些crtc上允许光标层

3.2 如何选择plane和crtc

安装libdrm-tests

modeprint rockchip Crtc id : 64 x : 0 y : 0 width : 1920 height : 1200 mode : 0x55ad634f8c gamma size : 1024

可以看到当前显示挂的crtc在64上

modetest -M rockchip -p 查看plane 61 64 117 0,0 0,0 0 0x00000001 formats: XR24 AR24 XB24 AB24 RG24 BG24 RG16 BG16 props: 7 type: flags: immutable enum enums: Overlay=0 Primary=1 Cursor=2 value: 2 8 SHARE_ID: flags: immutable range values: 0 4294967295 value: 61 63 rotation: flags: bitmask values: rotate-0=0x1 reflect-y=0x20 value: 1 56 FEATURE: flags: immutable bitmask values: scale=0x1 alpha=0x2 hdr2sdr=0x4 sdr2hdr=0x8 afbdc=0x10 pdaf_pos=0x20 value: 18

可以看到plane 61 是挂载crtc64上的cursor层

测试硬件光标验证:

systemctl stop lightdm modetest -M rockchip -s 91@64:1920x1200 -C

如果出现颜色图,则说明libdrm能够正常使用硬件光标

指定plane

modetest -M rockchip -P 61@64:1920x1200

如果出现颜色图,则说明libdrm能够正常指定plane

四:具体应用

首先需要在内核打开硬件光标层,找到你需要配置硬光标的vp,添加 cursor-win-id,例如我的显示是在vp2上面

image.png

image.png

然后需要安装libdrm-cursor软件和libdrm-tests软件

首先使用libdrm-tests软件查看实际使用中的crtc层

命令:modetest

image.png

image.png

image.png

image.png 这里可以看到68、85、102是未使用的crtc,实际连接到fb正在使用的crtc是133

所以我们需要在软件中配置屏蔽掉前面三个crtc层,让鼠标层能够正确匹配到133上面

命令:vim /etc/drm-cursor.conf

修改前

image.png 修改后

image.png

这里我们把硬光标层打开了,所以就不需要overlay层,把它注释掉,然后打开crtc-blocklist选项,把其他不用的ctrc层屏蔽掉,重启机器就能正常使用硬件光标了。crtc层具体的id需要根据实际情况使用modetest命令确定。

五:参考链接

《Linux DRM Developer's Guide》https://landley.net/kdocs/htmldocs/drm.html

《Linux Kernel Doc》 https://www.kernel.org/doc/

《DRM-wikipedia》 https://en.wikipedia.org/wiki/Direct_Rendering_Manager 《Linux GPU Driver Developer's Guide》https://01.org/linuxgraphics/gfx-docs/drm/gpu/index.html

《DRM-KMS Doc》 https://kernel.readthedocs.io/en/latest/gpu/drm-kms.html#mode-setting 《KMS简介》 https://events.static.linuxfound.org/sites/events/files/slides/brezillon-drm-kms.pdf

《EGL Reference Pages》https://www.khronos.org/registry/EGL/sdk/docs/man/

《Linux User and Programmer's Manual》https://www.systutorials.com/docs/linux/man/3-drmModeGetResources/

《Nvidia drm api参考》https://docs.nvidia.com/jetson/l4t-graphics/group__direct__rendering__manager.html

《Linux How to的KMS简介》https://www.linuxhowtos.org/manpages/7/drm-kms.htm

《freedesktop.org的kms文档》https://dri.freedesktop.org/docs/drm/gpu/drm-kms.html

编辑
2025-03-03
记录知识
0

在RK的sdk中有制作update的工具二进制,但是这些二进制是只能运行在x86平台上。而我们的工作环境大部分情况下是在arm64上的,本篇文章提供了在arm64下能够打包update的方法。

一:获取源码

https://gitlab2.kylin.com/shanghai-team/create-rootfs/-/tree/kybuild/tool/src

如上是工具的源码,可以通过make的方式在各个平台上编译生成afptool和img_maker两个二进制

二:使用说明

目录附带了一个mkupdate.sh脚本,通过此脚本可以自动打包固件,如下为脚本内容

#!/bin/bash # Self Test CHIP=$1 usage() { echo "Usage: ./$(basename $0) rk3399 ./$(basename $0) rk3568 ./$(basename $0) rk3588" } if ! [ "${CHIP}" = "rk3399" ] && ! [ "${CHIP}" = "rk3568" ] && ! [ "${CHIP}" = "rk3588" ] then echo -e "\e[32mUnsupported Chip\e[0m" usage exit fi [ ! -d ./Image ] && echo "Cannot found Image directory" && exit ./afptool -pack ./ firmware_afp.img <<! # rk3399 ./img_maker -rk3399 ./Image/MiniLoaderAll.bin firmware_afp.img update.img # rk3568 ./img_maker -rk3588 ./Image/MiniLoaderAll.bin firmware_afp.img update.img # rk3588 ./img_maker -rk3568 ./Image/MiniLoaderAll.bin firmware_afp.img update.img ! ./img_maker -${CHIP} ./Image/MiniLoaderAll.bin firmware_afp.img update.img

2.1 打包文件package-file

在mkupdate.sh的当前目录,需要存在一个打包文件,里面描述了需要打包固件的名字和路径,该文件有自行编写,示例如下:

package-file package-file bootloader Image/MiniLoaderAll.bin parameter Image/parameter.txt trust Image/trust.img uboot Image/uboot.img boot Image/boot.img rootfs Image/rootfs.img

上述描述了需要打包的内容如,自身package-file,bootloader,分区表parameter,trust固件,uboot固件,内核固件boot,根文件系统rootfs

2.2 Image目录

mkupdate.sh需要一个Image目录,其中存放打包中需要的文件,我们要将需要打包的二进制手动拷贝到当前目录的Image目录内后方可运行测试脚本,其文件对应package-file的指定内容

2.3 parameter文件

分区文件主要指定了固件的分区细节,该文件根据平台自定义,rk3588的分区文件示例如下

FIRMWARE_VER: 1.0 MACHINE_MODEL: RK3588 MACHINE_ID: 007 MANUFACTURER: RK3588 MAGIC: 0x5041524B ATAG: 0x00200800 MACHINE: 0xffffffff CHECK_MASK: 0x80 PWR_HLD: 0,0,A,0,1 TYPE: GPT CMDLINE: mtdparts=rk29xxnand:0x00002000@0x00004000(uboot),0x00040000@0x00008000(boot:bootable),0x01508000@0x00048000(rootfs),0x00600000@0x01550000(data),0x00600000@0x01b50000(work),-@0x02150000(userdata:grow) uuid:rootfs=614e0000-0000-4b53-8000-1d28000054a9 uuid:boot=7A3F0000-0000-446A-8000-702F00006273

一般情况下只修改分区文件的cmdline中的分区地址,其他无需改变

2.4 输出文件

mkupdate.sh会在本地目录生成一个update.img,该文件即rk平台的固件格式,可以通过RK的工具进行整包烧录

2.5 烧录

https://gitlab2.kylin.com/shanghai-team/create-rootfs/-/tree/kybuild/tool/src/windows-tool

上述提供了2.92版本的烧录工具,我们自行开发的arm64的打包工具在此版本正常工作,其他版本尚未验证,无法保障。因为工具为自行修改,和RK的实现不一致,故可能出现windows烧录工具的版本不一样导致出现的烧录异常问题。

如RK更新了超过2.92版本的烧录工具,后续我们会再自行验证和兼容。

编辑
2025-03-03
记录知识
0

随着AI智能的发展,继传统的CPU和GPU之后,又推出了许多更适用于AI学习的芯片,本文就NPU芯片如何在RK平台提供的SDK基础上开发做一个介绍。

一、NPU芯片简介

NPU全称为Neural network Processing Unit,即神经网络处理器。

二、RKNN使用方法

1.SDK下载及内容介绍

可以在110服务器获取 /home/develop/rk/project/Rockchip_RK356X_RKNN_SDK_V0.7.0_20210402

这里 RKNN_API_for_RK356X 里面主要包含了linux和android系统对于如何使用模型文件调用NPU进行AI运算的demo。

rknn-toolkit 是用于将其他神经网络训练架构模型如何转化为 rknn 架构模型的demo,它分为了docker和nodocker版本,建议使用docker版,因为nodeocker版本对于python-3和pip软件版本的要求比较严格,环境不太好搭建。

2.rknn-toolkit 工具使用

这里我使用含docker版本举例。

  • 首先进入rknn-toolkit2-0.7.0/docker/ 文件夹
  • 可以看到 rknn-toolkit2-0.7.0-docker.tar.gz 镜像文件
  • 使用 sudo docker load --input rknn-toolkit2-0.7.0-docker.tar.gz 命令加载镜像
  • 加载成功后,可以使用 sudo docker images 命令确认。

正常加载后,就可以进入docker环境 如下,

docker run -t -i --privileged -v /home/kylin/:/home/kylin/ rknn-toolkit2:0.7.0 /bin/bash

执行后命令行的前缀会改变。

进入docker环境后,进入 rknn-toolkit2-0.7.0/examples/ 目录下,可以看到包含了一些以比较常见的神经网络训练框架命名的文件夹。

这里我们以caffe为例,将 caffemodule 转换为 rknnmodule。

进入 caffe/vgg-ssd/ 文件夹,其是转换 caffemodule 的一个demo 。里面使用了 deploy_rm_detection_output.prototxt 文件中提供的图层模型,把 caffemodule 通过 rknn.api 转换为了 rknnmodule 。重点包含以下几个函数:

image.png

image.png 可以看到流程和RK给出的文档中一致,按照步骤操作即可。

这里会需要一个 caffemodule 的训练模型,可以通过

https://eyun.baidu.com/s/3jJhPRzo , password is rknn

进行下载。

然后执行 python test.py 运行脚本,执行成功后就可以看到文件夹中多了 .rknn 为后缀的文件,它就是rk平台的模型。 这里就完成了模型的转换,过程中可能会根据模型效率/准确度的需求修改一些参数,可以参考SDK中的文档进行调整。

3.RKNN模型使用

上面我们已经得到了后缀为 .rknn 的模型文件,现在我们就来讲解如何在RK平台使用模型通过NPU进行图像的解析操作。其实上面在转换模型时已经使用python脚本进行了图像解析的示例,本章介绍demo中通过C语言调用RKNN接口的过程。

进入 RKNN_API_for_RK356X_v0.7_20210402/examples/rknn_ssd_demo/ 文件夹,可以看到 我们先看看源码调用流程

image.png 跟文档流程一致,其中多了一步 postProcessSSD ,这是将结果中的值转换为图片中的坐标点,用于后面画框所用。

image.png 看完了源代码我们可以编译应用。这里的环境是rk3568开发板+麒麟v10系统,所以直接执行 build-linux.sh 脚本。

image.png 完成后可以看到生成了 rknn_ssd_demo 的二进制文件,接下来我们使用它看看效果。

image.png 执行

./build/build_linux_aarch64/rknn_ssd_demo ./model/ssd_inception_v2.rknn ./model/road.bmp

第一个参数是我们生成的二进制文件
第二个参数是 .rknn 为后缀的module模型文件
第三个参数是要被检测的图片。执行后在本地文件夹中生成了 out.jpg 图片
输出如下 image.png

image.png 可以看到,模型在上图中成功识别到了行人和车辆的位置。

三、NPU硬件信息查看

如何判断是否调用到了NPU去处理的上面图像呢。我们可以通过中断:

image.png 如果调用到NPU,中断号会增加。

内核中还提供了调整NPU频率的接口:

image.png 查看NPU支持的模式,默认为 simple_ondemand

image.png 查看NPU当前的频率

echo performance > /sys/devices/platform/fde40000.npu/devfreq/fde40000.npu/governor

调整NPU的工作模式为性能模式(保持最大频率)

image.png 查看NPU当前工作状态,@之前为占用率,之后为当前频率

image.png 根据内核log,可以判断NPU是否被加载成功,从中也可以看出NPU包含了iommu模块,对内存读写进行了优化。