Linux内核超级装备eBPF技术详细研究

news2024/11/27 12:40:32

定义一(http://ebpf.io的定义)

eBPF (which is no longer an acronym for anything) is a revolutionary technology with origins in the Linux kernel that can run sandboxed programs in a privileged context such as the operating system kernel.

定义二(Cilium的定义)

BPF is a highly flexible and efficient virtual machine-like construct in the Linux kernel allowing to execute bytecode at various hook points in a safe manner.

内核大神Brendan用了比喻的说法,更加形象地介绍了这个技术。eBPF对于Linux就相当于JavaScript对于HTML一样。JavaScript给静态的HTML网站带了了动态的内容和效果,已经丰富的交互能力。JavaScript程序运行在一个虚拟机的安全沙盒中。eBPF也对Linxu内核提供了类似的功能,程序员可以通过编写字节码,从而让程序工作在内核的沙盒环境中。eBPF更像是内核中JavaScript的V8虚拟机引擎。直接编写eBPF非常困难,因此一般都会采用bcc,bpftrace这些框架来编写eBPF程序。如今,也可以采用Golang,Rust和Python的框架进行eBPF编程。

eBPF的应用

eBPF是一个非常重要的将内核态功能映射到用户态的技术,最早是用于对内核中的程序进行监控产生的。如今已经被广泛应用于各类需要挂入内核以提供内核监控,以及提高性能的应用中。主要应用于以下几类应用领域:

  1. Security 通过直接提取和解析内核中socket的数据报文,可以提供新型的安全预警和过滤机制。通过eBPF可以构建一个全面透明和可控的操作系统内核级的安全系统,提高非常高级别的安全防护机制。
  2. Tracing & Profiling 只是BPF技术最早被发明出来时的使用场景和用途。通过eBPF可以将探针插入内核态以及用户态的应用中去。从而追踪和分析应用程序的运行时信息和状态,提供深度的程序运行情况的洞悉。通过这个技术,我们能够更加深入分析系统的性能,提供调优和缺陷查找能力。
  3. Networking 结合了可编程性和高性能的特点,eBPF天生适合对网络数据报文进行预处理和分析,能够在内核层级对网络协议和数据进行高效处理和分发,提高网络应用的性能和可靠性。
  4. Observability & Monitoring 通过eBPF可以不依赖操作系统提供的静态计数器和监控机制。直接获取内核中的定制化的度量指标,并能够通过事件驱动的方式通过广泛的数据来源进行系统监控。

img

一个采用eBPF跟踪所有TCP连接的例子

tcplifebcc工具中一个专门用来追踪Linux系统中所有TCP连接情况的程序,采用了eBPF技术实现。运行tcplife后的结果如下:

# tcplife
PID   COMM       LADDR           LPORT RADDR           RPORT TX_KB RX_KB MS
22597 recordProg 127.0.0.1       46644 127.0.0.1       28527     0     0 0.23
3277  redis-serv 127.0.0.1       28527 127.0.0.1       46644     0     0 0.28
22598 curl       100.66.3.172    61620 52.205.89.26    80        0     1 91.79
22604 curl       100.66.3.172    44400 52.204.43.121   80        0     1 121.38
22624 recordProg 127.0.0.1       46648 127.0.0.1       28527     0     0 0.22
3277  redis-serv 127.0.0.1       28527 127.0.0.1       46648     0     0 0.27
22647 recordProg 127.0.0.1       46650 127.0.0.1       28527     0     0 0.21
3277  redis-serv 127.0.0.1       28527 127.0.0.1       46650     0     0 0.26
[...]

PID是进程ID,发送和接收字节数(TX_KB,RX_KB),持续时间(MS)。这样的程序也可以用一般的内核代码编写,但是如果是这样的话可能永远不可能达到如此的性能和安全性。首先原生内核程序会过滤每一个网络报文,而不是只是TCP协议报文,这就会增加大量额外开销。同时抓取所有的数据包也对安全性产生了一定的隐患。

了解基于eBPF工具bcc

bcc本身提供了70多个可以直接使用的基于eBPF技术的工具,涵盖了内核层的很多功能和追踪。这些工具如下图所示:这些工具的用法可以通过https://github.com/iovisor/bcc查看

bcc_tracing_tools_2019.png

基于eBPF的工具bpftrace

Brendan Gregg提供的bpftrace教程:The bpftrace One-Liner Tutorial。写的非常详细。在这里选取一部分进行解释和学习参考用。

  1. 查询Probe
bpftrace -l 'tracepoint:syscalls:sys_enter_*'

-l用于根据查询条件列出所有的应用程序探针点(Probes),其中探针点(Probes)是指的一个可以抓取时间数据的测量点

  1. 插入触发点
bpftrace -e 'BEGIN { printf("hello world\n"); }'
Attaching 1 probe...
hello world
^C

如上面的命令所示,BEGIN就是在程序的前面加入一个probe触发点,可以再此处定义变量或者输出一些文字。这里是输出一个hello world字符串。

  1. 打开文件
bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'
Attaching 1 probe...
snmp-pass /proc/cpuinfo
snmp-pass /proc/stat
snmpd /proc/net/dev
snmpd /proc/net/if_inet6
^C

上面的命令在呼叫系统调用openst的时候触发追踪事件,将进程名称comm和系统调用中的文件名参数filename打印出来。运行后,我们可以看到所有调用了打开文件openat系统调用的程序都被追踪并输出了文件名。除了comm,我们也可以获得pid(进程ID)和tid(线程ID)。

  1. 统计进程数量
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
Attaching 1 probe...
^C
@[bpftrace]: 6
@[systemd]: 24
@[snmp-pass]: 96
@[sshd]: 125

上述代码运行后会显示目前运行的某个进程名称的总数。

@用于设定一个map,@后可以跟一个map名称,这里没有设定名称。中括号[]里面的变量作为map的key值。count()是一个map的方法,用于统计某个map中某个key出现的次数。

  1. 输出read()的bytes数分布直方图
bpftrace -e 'tracepoint:syscalls:sys_exit_read /pid == 18644/ { @bytes = hist(args->ret); }'
Attaching 1 probe...
^C

@bytes:
[0, 1]                12 |@@@@@@@@@@@@@@@@@@@@                                |
[2, 4)                18 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                     |
[4, 8)                 0 |                                                    |
[8, 16)                0 |                                                    |
[16, 32)               0 |                                                    |
[32, 64)              30 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[64, 128)             19 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                    |
[128, 256)             1 |@

/.../这个是一个过滤器,用于过滤查询条件,限制范围。这里直接设置了进程的ID。通过对read结束时ret参数的统计,来获得最终读取的字节数。然后通过map的直方图方法hist对数据进行统计。除了hist以外,map方法还有前面用过的count(),lhist()-线性直方图,sum(),avg(),min()以及max()

...剩下的部分可以直接参考原文。

image-20220906165426164

性能火焰图,上述的火焰图广泛用于系统性能评估和调优,也是通过eBPF技术实现的。上述是Mysql的CPU火焰图,显示了各个层级的stack的CPU占用。

Golang的eBPF库

目前基于Go的eBPF框架有Dropbox、Cilium、Aqua和Calico这几个库,通常这些库完成的工作也是将eBPF程序和Map载入内核,通过文件描述符和Map进行关联。并且可以和eBPF Map进行交互(CRUD)操作。不同的库实现形式,使用范围不尽相同。

  1. Calico用bpftool和IProute实现的CLI做了一个Go的包装
  2. Aqua实现了对libbpf这个库的Go包装
  3. Drobox只支持了一部分功能,API比较简洁
  4. IO Visor开发的gobpf是bcc的Go语言版本。
  5. Cilium维护了一个纯Go语言的BPF库,将eBPF系统调用抽象为Go接口

eBPF程序的种类

更具《Linux内核观测技术》中的说法,主要更具功能将BPF程序分成两种类型,分别是跟踪网络

跟踪程序

这类程序用于更好的了解系统和应用程序当前的状态和行为。主要能够提供实时的系统行为和硬件的直接信息。另一方面可以访问特定应用程序内存区域,跟踪并提取运行进程中的信息。此外,还可以直接访问为每一个进程分配的资源,其中包括文件描述符,CPU和内存等信息。

网络

网络络包追踪和过滤是BPF程序最开始被设计出来时的应用方向。通过BPF我们可以对网络流量数据包处理的各个阶段进行监控和处理,能够在内核层级实现对于数据包的流量控制,安全检查,过滤,甚至可以跳过全部或者部分内核网络协议栈,直接处理网络数据并转发。提供了对于网络底层的强大追踪和控制能力。

上面是根据主要功能类型将BPF程序进行了分类,下面会根据eBPF程序的具体功能更详细的分类介绍

详细分类介绍

  1. socket过滤器程序
  2. Kprobe程序
  3. 跟踪点程序
  4. XDP程序
  5. Perf事件程序
  6. cgroup套接字程序
  7. cgroup打开套接字程序
  8. socket选项程序
  9. socket映射程序
  10. cgroup设备程序
  11. socket消息传递程序
  12. 原始跟踪点程序
  13. cgroup套接字地址程序
  14. socket重用端口程序
  15. 流量解析程序
  16. 其他BPF程序

全部程序种类列表如下:

enum bpf_prog_type {
    BPF_PROG_TYPE_UNSPEC,
    BPF_PROG_TYPE_SOCKET_FILTER,
    BPF_PROG_TYPE_KPROBE,
    BPF_PROG_TYPE_SCHED_CLS,
    BPF_PROG_TYPE_SCHED_ACT,
    BPF_PROG_TYPE_TRACEPOINT,
    BPF_PROG_TYPE_XDP,
    BPF_PROG_TYPE_PERF_EVENT,
    BPF_PROG_TYPE_CGROUP_SKB,
    BPF_PROG_TYPE_CGROUP_SOCK,
    BPF_PROG_TYPE_LWT_IN,
    BPF_PROG_TYPE_LWT_OUT,
    BPF_PROG_TYPE_LWT_XMIT,
    BPF_PROG_TYPE_SOCK_OPS,
    BPF_PROG_TYPE_SK_SKB,
    BPF_PROG_TYPE_CGROUP_DEVICE,
    BPF_PROG_TYPE_SK_MSG,
    BPF_PROG_TYPE_RAW_TRACEPOINT,
    BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
    BPF_PROG_TYPE_LWT_SEG6LOCAL,
    BPF_PROG_TYPE_LIRC_MODE2,
    BPF_PROG_TYPE_SK_REUSEPORT,
    BPF_PROG_TYPE_FLOW_DISSECTOR,
    BPF_PROG_TYPE_CGROUP_SYSCTL,
    BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE,
    BPF_PROG_TYPE_CGROUP_SOCKOPT,
    BPF_PROG_TYPE_TRACING,
    BPF_PROG_TYPE_STRUCT_OPS,
    BPF_PROG_TYPE_EXT,
    BPF_PROG_TYPE_LSM,
    BPF_PROG_TYPE_SK_LOOKUP,
    BPF_PROG_TYPE_SYSCALL, /* a program that can execute syscalls */
};

eBPF执行流程

上图简要描述了eBPF的执行流程,BPF需要用C语言进行编写,通过LLVM/Clang编译为BPF字节码,运行时通过用户空间中的程序(C/Golang/Rust/Python...)将字节码注入内核态的BPF虚拟机中,首先会通过BPF验证器(Verifier)检查代码的正确性和安全性,通过后才能够给到JIT进行编译,生成本地可以执行代码。这些代码是通过BPF的挂载点和某个内核函数绑定,并在内核函数调用的时候被启动执行。BPF和用户态数据的传递是依靠BPF Maps机制执行的,这个Map支持多种数据类型,通过Map可以将监控数据实时传输到用户态程序,用户空间程序也可以通过Map将运行时的参数等传递给EPF程序。

BPF最神奇之处就是它可以通过通过消息传递的方式,监控内核态程序的状态以及影响和改变内核态程序的执行过程,而不需要修改内核程序本身。这是一个非常强大的工具,因此被称为Linux内核中最神奇的技术,没有之一。

另外一点需要强调的是,BPF程序运行在内核中,而且每次运行必然是事件驱动的。例如:

  • 在网络设备或者驱动的数据入口挂载的BPF程序,会在每次有数据包接收时被运行。
  • kprobe探针类型的BPF程序会在每次某个挂载函数地址运行时被触发,并运行。

BPF程序的资源

一个eBPF程序可以使用的资源包括11个64位寄存器(可以分成32位子寄存器),一个程序计数器以及一个512byte的BPF栈空间。其中寄存器名称为r0~r10。其中r10寄存器是只读寄存器,储存一个帧地址指针,用于访问BPF的栈空间。其余四个寄存器都是通用可读写的寄存器。

内核资料领取,Linux内核源码学习地址。

BPF程序可以调用内核中预定义的帮助函数,这些帮助函数只会定义在内核(core kernel)中,而不可能存在与任何模块(modules)里。调用帮助函数是各个寄存器功能设定如下:

  • r0 储存帮助函数的返回值
  • r1~r5 保存帮助函数的调用参数
  • r6~r9 帮助函数预留的寄存器

BPF设计的寄存器配置能够被目前主流的任何CPU架构满足,因此,通常BPF寄存器都是和实际的硬件寄存器一一对应的。JIT只需要处理功能调用指令,而不需要关心参数和返回值的存放地址(总是在固定的寄存器中)。这使得运行BPF帮助函数的性能和效率非常高。不过这也造成帮助函数的限制,那就是不支持6个和6个以上参数。

r0中保存的返回值定义,会由于BPF程序类型的不同而有区别。在执行BPF程序前,r1寄存器通常保存的是程序的上下文信息(context),context指的是BPF程序的运行参数(类似C语言中argc/argv运行参数对)。BPF只能有一个context,context是由程序类型决定的。例如网络程序的context是内核网络数据报文包(skb)作为入参。

BPF程序都是基于64位处理的,主要是为了兼容主流的64位架构以及64位数表示的指针类型。BPF程序支持尾部调用(tail call)用于从一个BPF程序跳转到另外一个BPF程序执行,这种内联上线为33个调用。这个功能通常用于将BPF程序分成不同的执行阶段。

需要注意的一些限制

早期的cBPF程序指令数量限制在4096条,从5.1版内核开始,eBPF程序的指令数量提升到一百万条。内核中的BPF检查器通常会拒绝含有循环操作的字节码,这是由于BPF程序是运行于内核中的,检查器必须要确保内核运行的稳定性和安全性。

BPF程序的spilling/filling

由于寄存器数量的限制,有时候需要将r~r5寄存器中保存的参数复制到BPF栈空间中,这个操作称为spilling,而将栈空间的参数写回参数寄存器中的操作称为filling

BPF程序指令

目前为止,BPF拥有87条不同的指令。每条指令都有相同的长度和结构。大数端系统的指令结构如下:

bpf_r3_c2_r1_c2

其中offimm都是有符号类型的数据。op定义了实际的操作,dst_reg和 src_reg提供了额外的寄存器使用信息,off在某些指令中用于设置地址或者栈空间的偏移量(offset)或者是跳转指令的位置。imm设置常量或者直接使用的值。op本身也有不同部分构成,前四为是指令code,一位source标志和三位指令类型代码。

目前BPF支持的系统架构包括:x86_64,arm64,ppc64,s390x,mips64,sparc64和arm。不同架构都有专门的内核eBPF JIT编译器。所有的BPF操作都通过bpf()系统调用完成。包括加载BPF字节码程序到内核以及操作Maps进行数据交互。

BPF帮助函数(helper functions)

内核中定义了很多帮助函数,用于获取/加载数据到内核。不同的BPF程序类型通常只能调用一部分对应的帮助函数。根据使用的参数数量不同,内核中提供了不同的帮助函数的宏定义BPF_CALL_0()~BPF_CALL_5()。更新Map元素的帮助函数定义如下:

BPF_CALL_4(bpf_map_update_elem, struct bpf_map *, map, void *, key,
           void *, value, u64, flags)
{
    WARN_ON_ONCE(!rcu_read_lock_held());
    return map->ops->map_update_elem(map, key, value, flags);
}

const struct bpf_func_proto bpf_map_update_elem_proto = {
    .func           = bpf_map_update_elem,
    .gpl_only       = false,
    .ret_type       = RET_INTEGER,
    .arg1_type      = ARG_CONST_MAP_PTR,
    .arg2_type      = ARG_PTR_TO_MAP_KEY,
    .arg3_type      = ARG_PTR_TO_MAP_VALUE,
    .arg4_type      = ARG_ANYTHING,
};

帮助函数和系统调用类似,函数标准定义如下:

u64 fn(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)

环境准备

eBPF程序一般有两个部分组成

  1. 内核中运行的eBPF程序,用C语言编写,编译为内核文件。通常需要使用clang/llvm编译为elf格式的文件。
  2. Go语言编写的用于加载和测试eBPF的程序,在用户态运行,用于配置和读取eBPF程序数据。

我们需要安装以下一些工具:

最近一些年,由于eBPF的发展比较快,Linux内核的更新速度也非常快,eBPF在不同的内核版本中还是有不少的兼容性和功能上的区别,建议最好是使用比较新的内核版本v5.10以上。我们在编写和测试eBPF程序前应该确认当前的内核版本,笔者的开发系统:

  1. 安装llvm编译器,clang 9.0以上, 下面的代码是安装llvm和clang
sudo apt update -y
sudo apt install -y llvm
sudo apt install -y clang

安装完整的开发环境如下:

$ sudo apt-get install -y make gcc libssl-dev bc libelf-dev libcap-dev \
  clang gcc-multilib llvm libncurses5-dev git pkg-config libmnl-dev bison flex \
  graphviz

系统调用探针BPF程序示例

编译和运行程序

在示例程序中我们采用Cilium开源的ebpf-go库进行程序生成和内核加载。我们可以从https://github.com/cilium/ebpf下载这个项目。实际开发并不需要完整的项目代码,将examples文件夹中kprobe文件夹中的内容拷贝到一个测试用的文件夹中。我将Kprobe探针程序放到~/testbpf/kprobe这个文件夹中,另外需要将headers文件夹拷贝到~/testbpf文件夹中,所有c的bpf库调用都要依赖头文件,BPF程序不允许引用任何外部库和模块。我们在kprobe文件夹中只需要保留main.go和kprobe.c这两个文件。首先设置编译工具clang:

export BPF_CLANG=clang

然后运行go mod init,生成mod文件并下载所需的go的ebpf库

go mod init kprobe

生成字节码和辅助的go程序,并编译和执行

$ go generate
$ go build

我们可以看到通过generate我们生成了BPF程序的字节码文件和辅助的go程序文件。编译后生成kprobe程序,运行后,效果如下:

xxxx@ubuntu:~/testbpf/kprobe$ sudo ./kprobe
2022/10/06 15:59:11 Waiting for events..
2022/10/06 15:59:13 sys_execve called 16 times
2022/10/06 15:59:15 sys_execve called 32 times
2022/10/06 15:59:17 sys_execve called 32 times
2022/10/06 15:59:19 sys_execve called 43 times
2022/10/06 15:59:21 sys_execve called 47 times
2022/10/06 15:59:23 sys_execve called 47 times
2022/10/06 15:59:25 sys_execve called 62 times
2022/10/06 15:59:27 sys_execve called 62 times
2022/10/06 15:59:29 sys_execve called 73 times
2022/10/06 15:59:31 sys_execve called 77 times

这个程序就是每两秒显示一次execve系统程序调用的总次数。

代码分析

eBPF程序的代码如下:

// +build ignore

#include "common.h"

char __license[] SEC("license") = "Dual MIT/GPL";

struct bpf_map_def SEC("maps") kprobe_map = {
    .type        = BPF_MAP_TYPE_ARRAY,
    .key_size    = sizeof(u32),
    .value_size  = sizeof(u64),
    .max_entries = 1,
};

SEC("kprobe/sys_execve")
int kprobe_execve() {
    u32 key     = 0;
    u64 initval = 1, *valp;

    valp = bpf_map_lookup_elem(&kprobe_map, &key);
    if (!valp) {
        bpf_map_update_elem(&kprobe_map, &key, &initval, BPF_ANY);
        return 0;
    }
    __sync_fetch_and_add(valp, 1);

    return 0;
}

代码主要是创建了一个map对象kprobe_map,用于内核于用户空间程序数据交换,这个map是BPF_MAP_TYPE_ARRAY类型的,最大元素1个。程序非常简单,就是每次系统调用被调用,探针BPF程序就能够触发并将map中的值+1。

main.go程序如下:

// This program demonstrates attaching an eBPF program to a kernel symbol.
// The eBPF program will be attached to the start of the sys_execve
// kernel function and prints out the number of times it has been called
// every second.
package main

import (
    "log"
    "time"

    "github.com/cilium/ebpf/link"
    "github.com/cilium/ebpf/rlimit"
)

// $BPF_CLANG and $BPF_CFLAGS are set by the Makefile.
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS bpf kprobe.c -- -I../headers

const mapKey uint32 = 0

func main() {

    // Name of the kernel function to trace.
    fn := "sys_execve"
    //fn := "sys_bind"

    // Allow the current process to lock memory for eBPF resources.
    if err := rlimit.RemoveMemlock(); err != nil {
        log.Fatal(err)
    }

    // Load pre-compiled programs and maps into the kernel.
    objs := bpfObjects{}
    if err := loadBpfObjects(&objs, nil); err != nil {
        log.Fatalf("loading objects: %v", err)
    }
    defer objs.Close()

    // Open a Kprobe at the entry point of the kernel function and attach the
    // pre-compiled program. Each time the kernel function enters, the program
    // will increment the execution counter by 1. The read loop below polls this
    // map value once per second.
    kp, err := link.Kprobe(fn, objs.KprobeExecve, nil)
    if err != nil {
        log.Fatalf("opening kprobe: %s", err)
    }
    defer kp.Close()

    // Read loop reporting the total amount of times the kernel
    // function was entered, once per second.
    ticker := time.NewTicker(2 * time.Second)
    defer ticker.Stop()

    log.Println("Waiting for events..")

    for range ticker.C {
        var value uint64
        if err := objs.KprobeMap.Lookup(mapKey, &value); err != nil {
            log.Fatalf("reading map: %v", err)
        }
        log.Printf("%s called %d times\n", fn, value)
    }
}

参考文献

  1. eBPF Documentation
  2. BPF and XDP Reference Guide
  3. Learn eBPF Tracing: Tutorial and Examples
  4. BPF helper functions

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

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

相关文章

物联网通信之串口服务器,RS485/RS232双串口并行、远程虚拟串口调试

随着现代工业信息技术发展,串口服务器在工业应用中越来越常见,那么什么是串口服务器呢,今天智联物联小编就与大家分享一下物联网通信中的串口服务器。 为帮助大家理解,智联物联小编从串口服务器的接口为大家开始讲解,一…

高分子PEG:mPEG-Maleimide MW:3400,甲氧基-聚乙二醇—马来酰亚胺,常用作聚合物试剂

【产品描述】 陕西新研博美生物科技有限公司供应的​mPEG-Maleimide属于高分子PEG,马来酰亚胺和巯基的偶合是蛋白和多肽偶联中的一个非常有用的反应。mPEG-MAL被用来合成具有确定结构和生物活性的PEG-蛋白质偶合物。mPEG-MAL也常用作聚合物试剂来选择性诱捕含巯基的…

易基因:小檗碱通过介导m6A mRNA甲基化调控斑马鱼肝细胞氧化应激、凋亡和自噬|科研进展

大家好,这里是专注表观组学十余年,领跑多组学科研服务的易基因。 中药小檗碱(Berberine,BBR,化学式C20H18NO4)是从几种药用植物中分离出的一种异喹啉季生物碱,包括小檗(Berberis ar…

让你不再好奇怎样同声传译

众所周知,同声传译技术在国际交流和商务领域发挥着重要的作用,它能够帮助人们跨越语言障碍,促进人们之间的有效沟通。那么,你知道如何同声传译吗?接下来我将教你三个方法,帮助你更好的进行同声传译操作。 方…

springboot+vue餐厅点餐系统在线点餐系统(含源码+数据库)

1.系统分析 系统用例图如下所示。 从用户、餐厅等方面进行需求分析如下。 1.用户需求:系统应该提供简单易用的用户界面,用户可以浏览餐厅菜单,选择菜品,下订单。此外,应该允许用户管理个人信息和查看历史订单。 2.餐…

SQL-多表查询-事务

SQL-多表查询-事务 多表查询顾名思义就是从多张表中一次性的查询出我们想要的数据 前期表准备 DROP TABLE IF EXISTS emp; DROP TABLE IF EXISTS dept;# 创建部门表CREATE TABLE dept(did INT PRIMARY KEY AUTO_INCREMENT,dname VARCHAR(20));# 创建员工表CREATE TABLE emp (i…

一文3000字从0到1用Python做安全测试攻击实战(建议收藏)

在本文中,我们将使用Python进行一次安全测试的实战演练,目标是找出并利用应用程序的安全漏洞。请注意,这个演练仅用于教育和研究目的,切勿将这些技术用于非法活动。 注意:未经授权的攻击是违法的。确保你在拥有明确权…

准实时刷新集群中各节点本地缓存的解决方案

目录 背景 Redis发布订阅 MQ广播消息 配置中心Nacos,Zookeeper监听 注册中心获取服务节点ip端口接口调用 本地定时任务兜底 背景 我们在系统开发过程中,为了减少数据库和redis缓存的查询以提升接口性能,有时候会把一些常用的、变动不是…

[数据结构 -- C语言] 堆实现Top-K问题,原来王者荣耀的排名是这样实现的,又涨知识了

目录 1、什么是Top-K问题? 1.1 Top-K基本思路 2、Top-K问题逻辑分析 2.1 建堆,大小为K的小堆 2.2 将剩余的N - K 个元素依次与堆顶元素比较,大于就替换 2.3 打印堆 3、TopK实现代码 4、Top-K问题完整代码 结果展示: TopK…

做IT运维的,哪有人不疯的

网飞最新的剧集《怒呛人生》大受欢迎的一大原因就是:发疯。 在2023年,发疯已经从一种人身攻击,拯救语言匮乏的恶评转移成一个中性词,在某些语境下,等同于冒犯、破罐子破摔。连快乐都不敢的东亚人,为啥发疯…

C++:智能指针

目录 一. 智能指针的概念及原理 1.1 什么是智能指针 1.2 智能指针的原理 二. 智能指针的拷贝问题 三. auto_ptr 3.1 auto_ptr的拷贝构造和赋值问题 3.2 auto_ptr的模拟实现 四. unique_ptr 五. shared_ptr 5.1 shared_ptr的常用接口 5.2 shared_ptr的拷贝构造和赋值…

软件工程导论(四)软件编码测试与维护

一、软件编程 1.1良好的编程习惯 变量命名有意义并且使用统一的命名规则 编写自文档代码(序言性注释 or 行内注释) 提前进行可维护性考量(可以用常量的方式存在的数值最好以变量的方式存在) 良好的视觉安排可以提高代码的可读性(…

ChatGPT训练一次要耗多少电?

如果开个玩笑:问ChatGPT最大的贡献是什么? “我觉得它对全球变暖是有一定贡献的。”知名自然语言处理专家、计算机科学家吴军在4月接受某媒体采访时如是说。 随着ChatGPT引爆AIGC,国内外巨头纷纷推出自己的AI大模型,大家为人工智…

2023 开放原子全球开源峰会“开发者之夜”高能剧透!

开发者之夜~即将高燃启动 最潮!最嗨!最青春! 肆意!亲切!嗨 FUN 派! 这是一场面向开发者的线下狂欢! 也是一场精心准备的答谢盛宴! 更是一场开源圈的老友聚会! 开发者之夜…

IP地址中的子网掩码和CIDR

将常规的子网掩码转换为二进制,将发现子网掩格式为连续的二进制1跟连续0,其中子网掩码中为1的部分表示网络ID,子网掩中为0的表示主机ID。比如255.255.0.0转换为二进制为11111111 11111111 00000000 00000000。 ​ 在前面所举的例子中为什么不…

Yakit: 集成化单兵安全能力平台使用教程·Web Fuzzer篇

Yakit: 集成化单兵安全能力平台使用教程Web Fuzzer篇 1.数据包共享2.数据包扫描3.使用Web Fuzzer进行模糊测试4.常用 fuzz 标签5.热加载Fuzz1.数据包共享 分享/导入功能可用于信息分享,分享可以设置有效时长和分享密码,凭分享id和密码可以导入分享者的请求包 注意:数据包是…

uni-app 自定义组件之星级评价分数

效果图&#xff1a; 1.自定义组件starsRating.vue文件&#xff08;放在components文件夹内&#xff09; 代码截图&#xff1a; 对应的代码&#xff1a; <image click“btnStars1” class“starsicon” :src“starsObject[0]” mode“widthFix”> <image click“…

redis基础-----安装及使用场景基础操作

需要使用的网址 Redis中文网 Download | Redis 数据库及缓存架构选型网址&#xff1a; DB-Engines Ranking - popularity ranking of database management systems 常识&#xff1a; 存储方面&#xff1a; 磁盘&#xff1a; 1&#xff0c;寻址&#xff1a;ms 2&#xff…

达梦数据库读写分离集群异常测试(⾼可⽤)及双主(类似脑裂)问题处理

目录 测试前准备... 4 断电测试... 4 一、备库204断电... 4 二、断电数据新增测试... 5 1、备库204断电... 5 2、主库200新增数据&#xff0c;203备库查询正常... 5 3、204服务器启动并启动守护进程&#xff0c;测试&#xff0c;正常... 6 三、主库断电测试... 6 1、主…

python使用requests+excel进行接口自动化测试(建议收藏)

前言 在当今的互联网时代中&#xff0c;接口自动化测试越来越成为软件测试的重要组成部分。Python是一种简单易学&#xff0c;高效且可扩展的语言&#xff0c;自然而然地成为了开发人员的首选开发语言。而requests和xlwt这两个常用的Python标准库&#xff0c;能够帮助我们轻松…