BPF之前端工具BCC与bpftrace

news2024/9/20 14:32:26

BPF前端工具BCC与bpftrace

一、概述

BCC和bpftrace到底是什么,与BPF是什么关系呢?

经过上一篇的介绍,BPF是内核中的执行引擎,BCC和bpftrace则是两个前端工具,比如用户可以直接使用的命令行工具。

BCC与bpftrace又有何区别?

bpftrace是基于BPF和BCC开发出来的,bpftrace适合临时创造单行程序和短小脚本进行观测,而BCC更适合编写复杂的工具和守护进程。BCC提供的API分为内核态和用户态的,而bpftrace只有一种API即bpftrace编程语言。

BCC与bpftrace都是基于libbcc和libbpf库进行构建的。

二、BCC

BCC全称是BPF Compiler Collection,BPF编译器集合,简称BCC,它包含了构建BPF软件的编译器框架和库,是BPF的主要前端项目

组件

BCC组件可以用Brendan Gregg的一张图来表示:
在这里插入图片描述

安装

BPF组件是在内核4.1至4.9之间开发,最好选择内核4.9及以上的系统。

内核配置需要开启(一般默认开启了):

CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_EVENTS=y
CONFIG_BPF_JIT=y
CONFIG_HAVE_EBPF_JIT=y

Ubuntu

ubuntu上安装一般可能出现的问题较少,包名字叫bpfcc-tools

sudo apt install bpfcc-tools linux-headers-$(uname -r)
# 或者
sudo snap install gcc

RHEL

Centos7我试了很久,安装问题总是不断,不推荐。

sudo yum install bcc-tools
# 问题可能出在:
# 1.内核版本过低; 2. 缺少对应的kernel-devel(yum install kernel-devel-$(unamr -r); 3. 缺少kernel-headers)

工具

BCC工具有很多,单一用途或多用途工具都有,可以主要按以下分类:

功能点工具
调试手段trace、argdist、funccount、stackcount、opensnoop
CPU相关execsnoop、runqlat、runqlen、cpudist、profile、offcputime、syscount、softirq、hardirq
内存相关menleak
文件系统opensnoop、filelife、vfsstat、fileslower、cachestat、writeback、dcstat、xfsslower、xfsdist、ext4dist
磁盘I/Obiolatency、biosnoop、biotop、bitesize
网络相关tcpconnect、tcpaccept、tcplife、tcpretrans
安全相关capable

三、bpftrace

组件

在这里插入图片描述

所有的bpftrace工具都是以.bt作为文件后缀名。前端使用lex和yacc来对bpftrace语言进行词法和语法分析,使用Clang来解析结构体。后端则将bpftrace程序编译成LLVM中间表示形式,再通过LLVM库将其编译为BPF代码。

安装

Ubuntu

sudo apt update
sudo apt install bpftrace

工具

主要分类:

功能点工具
调试手段execsnoop.bt、threadsnoop.bt、opensnoop.bt、killsnoop.bt、signals.bt
CPU相关execsnoop.bt、runqlat.bt、runqlen.bt、cpuwalk.bt、offcputime.bt
内存相关oomkill.bt、failts.bt、vmscan.bt、swapin.bt
文件系统vfsstat.bt、filelife.bt、xfsdist.bt
存储I/Obiosnoop.bt、biolatency.bt、bitesize.bt、biostacks.bt、scsilatency.bt、nvmelatency.bt
网络相关tcpaccept.bt、tcpconnect.bt、tcpdrop.bt、tcpretrans.bt、gethostlatency.bt
安全相关ttysnoop.bt、elfsnoop.bt、setuids.bt
编程相关jnistacks.bt、javacalls.bt
应用相关threadsnoop.bt、pmheld.bt、naptime.bt、mysqld_qslower.bt
内核相关mlock.bt、mheld.bt、kmem.bt、kpages.bt、workq.bt
容器相关pidnss.bt、blkthrot.bt
虚拟机xenhyper.bt、cpustolen.bt、kvmexits.bt

bpftrace编程

基础语法

1. 程序结构

bpftrace程序结构是一系列探针加对应的动作

probes { actions }
probes { actions }
...

探针被激活时,相应动作会被执行。也可以在探针前设置一个可选的过滤表达式

probes /filter/ { actions }
/pattern/ { actions }
2. 注释

单行代码使用"//"来注释

// this is a comment

多行代码注释和C语言一样

/*
 * this is 
 * a muti-line
 * comment
 */
3. 探针格式

探针(probe)以类型名开始,然后是一系列冒号分隔的标识符

type:identifier1[:identifier2[...]]

标识符的组织形式由探针类型决定,比如

kprobe:vfs_read
uprobe:/bin/bash:readline
  • kprobe探针类型,内核态函数插桩,只需要一个标识符:内核函数名
  • uprobe探针类型,用户态函数插桩,需要两个标识符:二进制文件路径和函数名

可以使用逗号将多个探针并列,指向同一个执行动作,如

probe1,probe2,... { actions }

有两个特殊的探针类型不需要标识符:BEGIN和END,它们会在bpftrace程序启动和结束时触发(类似awk命令)

4. 探针通配符

有些探针接受通配符,比如下面这个,会对有的以"vfs_"开头的内核函数进行插桩

kprobe:vfs_*

但如果同时开启很多的函数插桩,对性能有影响。环境变量"BPFTRACE_MAX_PROBES"可以设置探针数量上限。

也可以插桩之前使用"bpftrace -l"命令进行统计有多少个探针数量比配,做个评估

bpftrace -l "kprobe:vfs_*" | wc -l
5. 过滤器

过滤器是一个布尔表达式,决定一个动作是否被执行

//过滤进程pid为123
/pid == 123/

//筛选pid为非零
/pid/

//过滤器也可使布尔运算组合
/pid > 100 && pid < 1000/
6. 执行动作

一个动作既可以是单条语句,也可以是分号分隔的多条语句

{ action one; action two; action three }

全部语句最后也可以加分号。语句采用bpftrace语法,类似C语言,可以操作变量和执行bpftrace函数调用

//创建变量x并赋值42,然后打印出来
{ $x = 42; printf("$x is %d", $x); }
7. 变量
变量类型含义举例
内置变量bpftrace预先定义好并提供pid,comm(进程名),nsecs(纳秒时间戳),curtask(当前线程task_struct结构体地址)
临时变量临时计算使用,"$"开头 x = 1 ; < b r / > x = 1;<br /> x=1;<br/>y = “hello”;
$z = (struct task_struct*) curtask;
映射表变量"@"前缀,用于存储对象@start[tid] = nsecs; //内置变量nsecs赋值给一个名为start、以tid为key的映射表,这允许每个线程存储自己的时间戳
@path[pid, f d ] = s t r ( a r g 0 ) ; / / 这是一个复合键的映射表,同时使用内置变量 p i d 和 fd] = str(arg0); //这是一个复合键的映射表,同时使用内置变量pid和 fd]=str(arg0);//这是一个复合键的映射表,同时使用内置变量pidfd组合key
8. 映射表函数

映射表可以通过特定的统计函数赋值,这些函数以特殊方式来存储和打印数据,这里使用了每个CPU独立的映射表

@x = count(); //对时间进行累计统计,打印时会答应出累计结果

下面这个语句也会对事件进行计数,但使用的是全局映射表,非每个CPU独立的映射表,@x的类型是整数

@x++;

其他举例:

@y = sum($x); //对$x求和,打印时打印出总数
@z = hist($x); //将$x保存在一个以2的幂为区间的直方图中
delete(@start[tid]); //从@start中删除key为tid的值
9. 实例

统计内核函数vfs_read()计时并以直方图打印

#!/usr/bin/bpftrace

kprobe:vfs_read
{
    @start[tid] = nsecs;
}

kretprobe:vfs_read
/@start[tid]/
{
    $duration_us = (nsecs - @start[tid]) / 1000;
    @us = hist($duration_us);
    delete(@start[tid]);
}

执行效果:
在这里插入图片描述

探针类型

类型缩写描述访问参数
tracepointt内核静态插桩点内置变量args
usdtU用户静态定义插桩点内置变量args
kprobek内核动态函数插桩arg0, arg1, …, argN
kretprobekr内核动态函数返回值插桩retval
uprobeu用户态动态函数插桩arg0, arg1, …, argN
uretprobeur用户态动态函数返回值插桩retval
softwares内核软件事件
hardwareh硬件基于计数器的插桩
profilep对全部CPU进行时间采样
intervali周期性报告(从一个CPU上)
BEGINbpftrace启动
ENDbpftrace推出
1. tracepoint

tracepoint会对内核跟踪点进行插桩,格式

tracepoint: tracepoint_name

tracepoint_name是跟踪点的全名,包括用来将跟踪点所在的类别和事件名字分隔开的冒号。如tracepoint:net:netif_rx是对net:netif_rx这个跟踪点进行插桩。

跟踪点通常带有参数,bpftrace可以通过内置变量args来访问这些参数。如net:netif_rx有一个代表数据包长度的参数len,可以通过args->len 来访问。可以通过"bpftrace -lv"来查看

root@pc:test# bpftrace -lv tracepoint:net:netif_rx
tracepoint:net:netif_rx
    void * skbaddr
    unsigned int len
    __data_loc char[] name

比如sys_enter_read是对系统调用read(2)的开始处插桩,man手册可以看到read(2)的帮助文档:

ssize_t read(int fd, void *buf, size_t count);

其有三个参数,那么对于sys_enter_read跟踪点来说,bpftrace中可以用args->fd, args->buf, args->count来访问这三个参数。

root@pc:test# bpftrace -lv tracepoint:syscalls:sys_enter_read
tracepoint:syscalls:sys_enter_read
    int __syscall_nr
    unsigned int fd
    char * buf
    size_t count

一个有趣的例子,对系统调用clone(2)的开始和推出进行插桩,test_sys_clone.bt代码

#!/usr/bin/bpftrace

tracepoint:syscalls:sys_enter_clone
{
    printf("\n-> clone() by %s PID %d\n", comm, pid);
}

tracepoint:syscalls:sys_exit_clone
{
    printf("<- clone() return %d, %s PID %d\n", args->ret, comm, pid);
}

执行,同时开启另一个terminal窗口

root@pc:test# ./sys_clone.bt 
Attaching 2 probes...

-> clone() by gnome-terminal- PID 3287
<- clone() return 76637, gnome-terminal- PID 3287
<- clone() return 0, gnome-terminal- PID 76637

-> clone() by bash PID 76637
<- clone() return 76639, bash PID 76637
<- clone() return 0, bash PID 76639

-> clone() by lesspipe PID 76639
<- clone() return 76640, lesspipe PID 76639
<- clone() return 0, lesspipe PID 76640

-> clone() by lesspipe PID 76639
<- clone() return 76641, lesspipe PID 76639
<- clone() return 0, lesspipe PID 76641

-> clone() by lesspipe PID 76641
<- clone() return 76642, lesspipe PID 76641
<- clone() return 0, lesspipe PID 76642

-> clone() by bash PID 76637
<- clone() return 76643, bash PID 76637
<- clone() return 0, bash PID 76643
^C

有趣的是clone(2)总是进入一次,返回两次,一次是父进程中返回子进程PID,一次是子进程中返回0

2. usdt

usdt探针是对用户态静态探针点进行插桩,格式为:

usdt:binary_path:probe_name
usdt:library_path:probe_name
usdt:binary_path:probe_namespace:probe_name
usdt:library_path:probe_namespace:probe_name

usdt可以对完整路径的二进制文件或者共享库进行插桩。probe_name为USDT的探针名字。

USDT探针的任意参数,都可以用bpftrace内置变量args访问

3. kprobe和kretprobe

内核态动态插桩,格式为

kprobe:function_name
kretprobe:function_name

kprobe的参数"arg0, arg1, … argN"是函数入参,都为64位无符号整型。

kretprobe的参数:内置的retval是函数的返回值。retval永远是64无符号整型,如果和函数返回值不一致,需要强制转化。

4. uprobe和uretprobe

用户态动态插桩,格式为

uprobe:binary_path:function_name
uprobe:library_path:function_name
uretprobe:binary_path:function_name
uretprobe:library_path:function_name

uprobe的参数:arg0,arg1,…,argN 是进入函数时的入参,类型均为64位无符号整型,如果是执行C结构体的指针,可以强制类型转换为对应结构体,目前还不支持BTF,将来可能会支持,那就可以像内核一样自己描述自己的结构体类型。

uretprobe的参数:内置变量retval是函数的返回值,retval恒为64位无符号整型,如果和函数返回值不一样,需要强制转换。

举例,一段简单的C代码,debugtest.c

root@pc:bpf-test# cat debugtest.c 
#include <stdio.h>

void test_func1(int num)
{
    printf("recv number %d\n", num);
}

int main(int argc, char *argv[])
{
    test_func1(2);
    return 0;
}

将debugtest.c编译,带debug编译

gcc -g debugtest.c -o debugtest

然后使用bpftrace跟踪test_func1()被调用时的输入参数num

bpftrace -e 'uprobe:./debugtest:test_func1 { printf("num is %d\n", arg0); }'

执行效果如下,在一个终端中跟踪,另一个终端中执行./debugtest
在这里插入图片描述

当然,bpftrace也可以直接跟踪地址,只要带"–unsafe"参数即可

root@pc:bpf-test# nm debugtest
000000000000038c r __abi_tag
0000000000004010 B __bss_start
0000000000004010 b completed.0
                 w __cxa_finalize@GLIBC_2.2.5
0000000000004000 D __data_start
0000000000004000 W data_start
0000000000001090 t deregister_tm_clones
0000000000001100 t __do_global_dtors_aux
0000000000003dc0 d __do_global_dtors_aux_fini_array_entry
0000000000004008 D __dso_handle
0000000000003dc8 d _DYNAMIC
0000000000004010 D _edata
0000000000004018 B _end
0000000000001198 T _fini
0000000000001140 t frame_dummy
0000000000003db8 d __frame_dummy_init_array_entry
0000000000002118 r __FRAME_END__
0000000000003fb8 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
0000000000002014 r __GNU_EH_FRAME_HDR
0000000000001000 T _init
0000000000002000 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 U __libc_start_main@GLIBC_2.34
0000000000001174 T main
                 U printf@GLIBC_2.2.5
00000000000010c0 t register_tm_clones
0000000000001060 T _start
0000000000001149 T test_func1
0000000000004010 D __TMC_END__

这里看到,debugtest中test_func1()函数地址在0x1149位置,同样一个终端跟踪0x1149这个地址,另一个终端执行./debugtest,
在这里插入图片描述

甚至,在strip debugtest删除debugsymbol之后,还可以使用0x1149这个地址来追踪test_func1()函数。

控制流

bpftrace中支持3中类型的测试:过滤器filter、ternary运算符和if语句

1. 过滤器
probe /filter/ { actions }
2. 三元操作符
test ? true_statement : false_statement
3. if语句
if (test) { true_statement }
if (test) { true_statement } else { false_statement }

目前还不支持else if语句。

运算符

bpftrace支持布尔运算,以及下面一些运算符

运算符含义
=赋值
+、-、*、/加减乘除
++、–加1、减1
&、|、^按位与、按位或、按位与或
!逻辑非
<<、>>向左位移、向右位移
+=、-=、*=、/=、%=、&=、^=、<<=、>>=复合运算符

变量

1. 内置变量

bpftrace中内置变量一般用于对信息的只读访问

内置变量类型含义
pidint进程ID
tidint线程ID
uidint用户ID
usernamestring用户名
nesecsint时间戳,纳秒级
elapsedint时间戳,纳秒,自bpftrace启动开始计时
cpuintCPU ID
commstring进程名
kstackstring内核调用栈
ustackstring用户态调用栈
arg0, …, argNint某些探针类型的参数
argsstruct某些探针类型的参数
retvalint某些探针类型的返回值
funcstring被跟踪函数名
probestring当前探针全名
curtaskint内核task_struct的地址,类型是64位无符号整型
cgroupintcgroup ID
$1, …, $Nint、chat*bpftrace程序的位置参数

所有的int型目前都是64位无符号整型

举例:

  • 使用pid、comm、uid来跟踪谁在调用setuid()这个系统调用

    bpftrace -e 't:syscalls:sys_enter_setuid { printf("setuid by PID %d (%s), UID %d\n", pid, comm, uid); }'
    bpftrace -e 't:syscalls:sys_enter_setuid { printf("setuid by %s returned %d\n", comm, args->ret); }'
    

在这里插入图片描述

  • 使用pid来跟踪指定pid的进程在哪个CPU上被进程调度切换掉给哪个进程

    //比如这里pid 79095是我运行的htop进程,查看其在哪个CPU上调度切换给哪个进程
    bpftrace -e 't:sched:sched_switch /pid == 79095/ { printf("cpu %d switch from %d(%s) to %d(%s)\n", cpu, args->prev_pid, args->prev_comm, args->next_pid, args->next_comm); }'
    

在这里插入图片描述

  • 使用kstack打印内核态调用栈,比如打印htop(pid为79182)的进程被切换时内核调用栈

    bpftrace -e 't:sched:sched_switch /pid == 79182/ { printf("sched switch call stack %s\n", kstack); }'
    

在这里插入图片描述

2. 临时变量

格式如下:

$name

这些变量可以在一个动作中进行临时计算。类型取决于第一次被赋值,可以是整型、字符串、结构体的指针或者结构体

3. 映射表变量

格式如下:

@name
@name[key]
@name[key1, key2[, ...]]

这些变量使用BPF映射表作为存储,BPF映射表是一种哈希表(关联数组),可以用于不同的存储类型。值可以用一个或多个键值来存储。映射表使用的键/值类型必须前后保持一致。与临时变量一样,映射表的类型也取决于第一次赋值

举例:

@start = nsecs;  //@start是整型,因为被赋值了内置变量纳秒时间戳nsecs
@last[tid] = nsecs;   //@last是整型,因为被赋值纳秒时间戳nsecs,同时要求键类型为整型,因为这里用了整数键tid
@bytes = hist(retval);   //@bytes是一个特殊类型:以2的幂为区间的直方图,会管理并打印直方图
@who[pid, comm] = count();  //@who映射表中有两个键,整数pid和字符串comm,它的值是一个统计函数count()

函数

bpftrace中提供了针对各种任务的函数,这里列出最重要的一些

函数描述
printf(char *fmt [, …])打印
time(char *fmt)格式化打印时间
join(char *arr[ ])打印字符串数组,以空格分隔
str(char *s [, int len])从指针s返回字符串,长度参数可选
kstack(int limit)返回一个深度最大为limit的内核态调用栈
ustack(int limit)返回一个深度最大为limit的用户态调用栈
ksym(void *p)分析内核地址,并返回字符串形式的符号
usym(void *p)分析用户空间地址,并返回字符串形式的符号
kaddr(char *name)将内核符号名字翻译为地址
uaddr(char)将用户空间符号翻译为地址
reg(char *name)将返回值存储到指定寄存器中
ntop([int af, ] int addr)返回一个字符串表示的IP地址
system(char *fmt [, …])执行一个shell命令
cat(char *filename)打印文件内容
exit()退出bpftrace

异步处理,上面一些函数是异步处理的:内核将事件加入队列,然后有用户态程序后面处理

printf()、time()、cat()、join()、system()是异步处理

kstack()、ustack()、ksym()、usym()会同步记录地址,但符号转义是异步处理。

映射表函数

bpftrace中一些重要的映射表函数:

函数描述
count()对出现次数进行统计计数
sum(int n)求和
avg(int n)求平均
min(int n)记录最小值
max(int n)记录最大值
stats(int n)返回事件次数、平均值和总和
hist(int n)打印2的幂次方直方图
lhist(int n, int min, int max, int step)打印线性直方图
delete(@m[key])删除映射表中的键/值对
print(@m [, top [, div] ])删除映射表,可带参数limit和除数
clear(@m)删除映射表中全部的键
zero(@m)将映射表中的所有值设置为0

异步处理的函数有:print()、clear()、zero(),所以这几个函数执行会有延迟。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/448270.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Stable Diffusion Lora模型训练详细教程

1. 介绍 通过Lora小模型可以控制很多特定场景的内容生成。 但是那些模型是别人训练好的&#xff0c;你肯定很好奇&#xff0c;我也想训练一个自己的专属模型&#xff08;也叫炼丹&#xff5e;_&#xff5e;&#xff09;。 甚至可以训练一个专属家庭版的模型&#xff08;fami…

JDK JRE JVM之间的关系

文章目录 1.从定义的角度解释JDK、JRE、JVM2、详细介绍JDK3、详细介绍JRE4、详细介绍JVM1、JVM内部区域划分 5、如何运行一个java程序&#xff1f; 本篇文章仅仅是个人片面观点&#xff0c;可能有错误或者表述不清的地方 1.从定义的角度解释JDK、JRE、JVM JDK&#xff1a;Java…

ArcGIS面要素最小外接矩形、外接圆的绘制方法

本文介绍在ArcMap软件中&#xff0c;基于一个面图层&#xff0c;绘制其中面要素的最小外接矩形、最小外接圆等的方法。 首先&#xff0c;我们来看一下本文需要实现的需求。现有一个面要素图层&#xff0c;其中包含多个面要素&#xff0c;如下图所示。我们希望绘制这个面要素图层…

【Redis】Redis十大数据类型—列表List

介绍 List列表是简单的字符串列表&#xff0c;按照插入顺序排序&#xff0c;可以从头部或尾部向List列表添加元素。 列表的最大长度是2^32-1&#xff0c;也就是每个列表支持超过40亿个元素。 实现 底层数据结构是由双向链表或压缩列表实现。 如果列表的元素个数小于 512 个…

Python获取某乎问答区计算机专业学生应聘保洁这一内容,看看为啥会有此事发生

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 不知道现在还有多少人玩知某乎&#xff0c; 今天刷话题的时候看到这么一个问答&#xff0c; 这么有趣的话题&#xff0c;咱们就对其问答内容进行采集一下&#xff1f;&#xff1f; 效果展示 可以看到&#xff0c;数据…

Linux 远程访问控制 SSH SCP SFTP TCP-Wrappers

SSH&#xff08;secure shell&#xff09;协议 一种安全通道协议&#xff0c;主要用来实现字符界面的远程登录、远程复制等功能。 协议对通信双方的数据传输进行了加密处理&#xff0c;其中包括用户登录时输入的用户口令 SSH客户端<-----------------网络------------------…

android 布局优化

1.绘制和布局加载原理 本文仅供个人学习记录&#xff0c;详细介绍可查看下面链接 Android布局优化&#xff0c;多套方案全面解析 布局优化的原因&#xff1a;布局嵌套过深&#xff0c;或者其他原因导致布局渲染性能不佳&#xff0c;可能会导致应用卡顿。 android绘制原理&am…

5.3 牛顿-科茨公式

学习目标&#xff1a; 理解微积分基础知识&#xff0c;例如导数和微分的概念。学习牛顿-科茨公式的推导过程。这个公式实际上是使用泰勒公式对被积函数进行展开&#xff0c;并使用微积分的基本原理进行简化得到的。学习如何使用牛顿-科茨公式进行数值积分。这通常涉及到将被积…

Ajax超详解(新手入门指南)

文章目录 1. AJAX简介2. 前后端交互3. XHR3.1 XMLHttpRequest对象3.2 获取模拟的后端数据3.3 获取网络数据3.4 使用json-server模拟服务器3.4.1 安装node.js3.4.2 安装并使用json-server 3.5 常见的请求方式3.5.1 GET请求3.5.2 POST请求3.5.3 PUT请求3.5.4 PATCH请求3.5.5 DELE…

【图像分割】Segment Anything(Meta AI)论文解读

文章目录 摘要一、引言二、segment anything任务1.任务2.预训练3.zero shot transfer4.相关任务5.讨论 三*、Segment Anything 模型四、Segment Anything 数据引擎五、Segment Anything 数据集六、Segment Anything RAI分析七、Zero-Shot Transfer 实验1.zero shot 单点有效掩模…

springboot本地local配置覆盖远程Apollo配置(含Apollo配置加载顺序说明)

手打不易&#xff0c;如果转摘&#xff0c;请注明出处&#xff01; 注明原文&#xff1a;https://zhangxiaofan.blog.csdn.net/article/details/130302692 目录 前言 Apollo配置加载顺序 步骤 第一步&#xff1a;Apollo创建properties 第二步&#xff1a;添加namespaces&…

js的dom事件流、事件委托和阻止绑定事件触发

主要讲解事件绑定和事件委托&#xff0c;onclick事件和addEventListener的区别 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge">&l…

IT项目管理计算题【太原理工大学】

计算题好像也没多少考点&#xff0c;主要就是记公式吧&#xff0c;其他的不想看了&#xff0c;直接考啥看啥&#xff0c;就看两个&#xff1a; ① 根据进度网络图写出时间参数表&#xff0c;ES、EF、LS、LF、TF 以及 FF&#xff0c;关键路径&#xff0c;总工期&#xff1b;② 挣…

volatile 保证内存变量可见性的实现原理解析

目录 volatile 的定义 可见性问题 JMM&#xff08;JavaMemoryModel&#xff09; 保证可见性 现代计算机的内存模型 MESI&#xff08;缓存一致性协议&#xff09; 嗅探 总线风暴 volatile 的两条实现原则 volatile 的定义 Java代码在编译后会编程 Java …

GD(兆易创新)系列FLASH进行FPGA和ZYNQ配置固化相操作

写在前面 本文主要针对使用GD&#xff08;兆易创新&#xff09;系列的FLASH做启动配置片时&#xff0c;遇到的相关问题进行简单整理复盘&#xff0c;避免后人踩坑。 本人操作固化芯片型号为&#xff1a;ZYNQ7045、690T&#xff08;复旦微替代型号V7 690T&#xff09;。 7系列…

02-waf绕过漏洞发现之代理池指纹被动探针

WAF绕过-漏洞发现之代理池指纹被动探针 思维导图 漏洞发现触发WAF点-针对xray工具&#xff0c;awvs工具等 1.扫描速度&#xff08;绕过方法&#xff1a;代理池&#xff0c;延迟&#xff0c;爬虫白名单&#xff09;2.工具指纹&#xff08;绕过方法&#xff1a;特征指纹&#x…

Qt Quick - Container

Qt Quick - Container使用总结 一、概述二、使用容器三、管理当前索引四、容器实现 一、概述 Container 提供容器通用功能的抽象基类。Container是类容器用户界面控件的基本类型&#xff0c;允许动态插入和删除Item。DialogButtonBox, MenuBar, SwipeView, 和 TabBar 都是继承…

测试工程师为什么要关注研发效能?

研发效能中的“研发”&#xff0c;指的是广义的研发团队&#xff0c;包含开发、测试、和研发团队内部的产品经理&#xff08;不包含业务部门的产品经理&#xff09;。测试工程师身处其中&#xff0c;作为研发团队的一员&#xff0c;对于整体的效能如何提升也应该了然于胸。这篇…

【论文写作】如何写科技论文?万能模板!!!(以IEEE会议论文为例)

0. 写在前面 常言道&#xff0c;科技论文犹如“八股文”&#xff0c;有固定的写作模式。本篇博客主要是针对工程方面的论文的结构以及写作链条的一些整理&#xff0c;并不是为了提高或者润色一篇论文的表达。基本上所有的论文&#xff0c;都需要先构思好一些点子&#xff0c;有…

「计算机控制系统」5. 模拟设计法

模拟控制器的离散化 数字PID控制器 Smith预估控制 文章目录 模拟控制器的离散化数值积分法一阶后向差分法一阶前向差分法双线性变换法&#xff08;Tustin&#xff09; 零极点匹配法其他方法 数字PID控制器模拟PID控制器的离散化数字PID的改进PID控制各环节的作用PID参数的整定扩…