fix the link to tutorial

This commit is contained in:
yunwei37
2023-06-03 03:38:15 +08:00
committed by 云微
parent dea38b0d80
commit d995c6a6f8
16 changed files with 20 additions and 340 deletions

View File

@@ -390,7 +390,7 @@ cnt = 0
8192 -> 16383 : 4 |********** | 8192 -> 16383 : 4 |********** |
``` ```
源代码: 完整源代码:
- <https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/14-tcpstates> - <https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/14-tcpstates>
@@ -404,6 +404,6 @@ cnt = 0
通过本篇 eBPF 入门实践教程我们学习了如何使用tcpstates和tcprtt这两个 eBPF 示例程序,监控和分析 TCP 的连接状态和往返时间。我们了解了tcpstates和tcprtt的工作原理和实现方式包括如何使用 BPF map 存储数据,如何在 eBPF 程序中获取和处理 TCP 连接信息,以及如何在用户态应用程序中解析和显示 eBPF 程序收集的数据。这些知识和技巧对于使用 eBPF 进行网络和性能分析是非常重要的。 通过本篇 eBPF 入门实践教程我们学习了如何使用tcpstates和tcprtt这两个 eBPF 示例程序,监控和分析 TCP 的连接状态和往返时间。我们了解了tcpstates和tcprtt的工作原理和实现方式包括如何使用 BPF map 存储数据,如何在 eBPF 程序中获取和处理 TCP 连接信息,以及如何在用户态应用程序中解析和显示 eBPF 程序收集的数据。这些知识和技巧对于使用 eBPF 进行网络和性能分析是非常重要的。
如果您希望学习更多关于 eBPF 的知识和实践,请查阅 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf> 。您还可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 以获取更多示例和完整的教程。 如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 以获取更多示例和完整的教程。
接下来的教程将进一步探讨 eBPF 的高级特性,我们会继续分享更多有关 eBPF 开发实践的内容。 接下来的教程将进一步探讨 eBPF 的高级特性,我们会继续分享更多有关 eBPF 开发实践的内容。

View File

@@ -129,11 +129,8 @@ $(patsubst %,$(OUTPUT)/%.o,$(BZS_APPS)): $(LIBBLAZESYM_HEADER)
$(BZS_APPS): $(LIBBLAZESYM_OBJ) $(BZS_APPS): $(LIBBLAZESYM_OBJ)
uprobe_helpers.o: uprobe_helpers.c
$(call msg,CC,$@)
$(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@
# Build application binary # Build application binary
$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) uprobe_helpers.o | $(OUTPUT) $(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT)
$(call msg,BINARY,$@) $(call msg,BINARY,$@)
$(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@ $(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@

View File

@@ -307,7 +307,7 @@ TIME CPU PID GC TIME
10:00:05 11% 12345 50ms 10:00:05 11% 12345 50ms
``` ```
源代码: 完整源代码:
- <https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/15-javagc> - <https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/15-javagc>
@@ -322,4 +322,4 @@ TIME CPU PID GC TIME
此外,我们也介绍了一些关于 Java GC、USDT 和 eBPF 的基础知识和实践技巧,这些知识和技巧对于想要在网络和系统性能分析领域深入研究的开发者来说是非常有价值的。 此外,我们也介绍了一些关于 Java GC、USDT 和 eBPF 的基础知识和实践技巧,这些知识和技巧对于想要在网络和系统性能分析领域深入研究的开发者来说是非常有价值的。
如果您希望学习更多关于 eBPF 的知识和实践,请查阅 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf> 。您还可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 以获取更多示例和完整的教程。 如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 以获取更多示例和完整的教程。

View File

@@ -1,294 +0,0 @@
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2021 Google LLC. */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <gelf.h>
#define warn(...) fprintf(stderr, __VA_ARGS__)
/*
* Returns 0 on success; -1 on failure. On sucess, returns via `path` the full
* path to the program for pid.
*/
int get_pid_binary_path(pid_t pid, char *path, size_t path_sz)
{
ssize_t ret;
char proc_pid_exe[32];
if (snprintf(proc_pid_exe, sizeof(proc_pid_exe), "/proc/%d/exe", pid)
>= sizeof(proc_pid_exe)) {
warn("snprintf /proc/PID/exe failed");
return -1;
}
ret = readlink(proc_pid_exe, path, path_sz);
if (ret < 0) {
warn("No such pid %d\n", pid);
return -1;
}
if (ret >= path_sz) {
warn("readlink truncation");
return -1;
}
path[ret] = '\0';
return 0;
}
/*
* Returns 0 on success; -1 on failure. On success, returns via `path` the full
* path to a library matching the name `lib` that is loaded into pid's address
* space.
*/
int get_pid_lib_path(pid_t pid, const char *lib, char *path, size_t path_sz)
{
FILE *maps;
char *p;
char proc_pid_maps[32];
char line_buf[1024];
char path_buf[1024];
if (snprintf(proc_pid_maps, sizeof(proc_pid_maps), "/proc/%d/maps", pid)
>= sizeof(proc_pid_maps)) {
warn("snprintf /proc/PID/maps failed");
return -1;
}
maps = fopen(proc_pid_maps, "r");
if (!maps) {
warn("No such pid %d\n", pid);
return -1;
}
while (fgets(line_buf, sizeof(line_buf), maps)) {
if (sscanf(line_buf, "%*x-%*x %*s %*x %*s %*u %s", path_buf) != 1)
continue;
/* e.g. /usr/lib/x86_64-linux-gnu/libc-2.31.so */
p = strrchr(path_buf, '/');
if (!p)
continue;
if (strncmp(p, "/lib", 4))
continue;
p += 4;
if (strncmp(lib, p, strlen(lib)))
continue;
p += strlen(lib);
/* libraries can have - or . after the name */
if (*p != '.' && *p != '-')
continue;
if (strnlen(path_buf, 1024) >= path_sz) {
warn("path size too small\n");
return -1;
}
strcpy(path, path_buf);
fclose(maps);
return 0;
}
warn("Cannot find library %s\n", lib);
fclose(maps);
return -1;
}
/*
* Returns 0 on success; -1 on failure. On success, returns via `path` the full
* path to the program.
*/
static int which_program(const char *prog, char *path, size_t path_sz)
{
FILE *which;
char cmd[100];
if (snprintf(cmd, sizeof(cmd), "which %s", prog) >= sizeof(cmd)) {
warn("snprintf which prog failed");
return -1;
}
which = popen(cmd, "r");
if (!which) {
warn("which failed");
return -1;
}
if (!fgets(path, path_sz, which)) {
warn("fgets which failed");
pclose(which);
return -1;
}
/* which has a \n at the end of the string */
path[strlen(path) - 1] = '\0';
pclose(which);
return 0;
}
/*
* Returns 0 on success; -1 on failure. On success, returns via `path` the full
* path to the binary for the given pid.
* 1) pid == x, binary == "" : returns the path to x's program
* 2) pid == x, binary == "foo" : returns the path to libfoo linked in x
* 3) pid == 0, binary == "" : failure: need a pid or a binary
* 4) pid == 0, binary == "bar" : returns the path to `which bar`
*
* For case 4), ideally we'd like to search for libbar too, but we don't support
* that yet.
*/
int resolve_binary_path(const char *binary, pid_t pid, char *path, size_t path_sz)
{
if (!strcmp(binary, "")) {
if (!pid) {
warn("Uprobes need a pid or a binary\n");
return -1;
}
return get_pid_binary_path(pid, path, path_sz);
}
if (pid)
return get_pid_lib_path(pid, binary, path, path_sz);
if (which_program(binary, path, path_sz)) {
/*
* If the user is tracing a program by name, we can find it.
* But we can't find a library by name yet. We'd need to parse
* ld.so.cache or something similar.
*/
warn("Can't find %s (Need a PID if this is a library)\n", binary);
return -1;
}
return 0;
}
/*
* Opens an elf at `path` of kind ELF_K_ELF. Returns NULL on failure. On
* success, close with close_elf(e, fd_close).
*/
Elf *open_elf(const char *path, int *fd_close)
{
int fd;
Elf *e;
if (elf_version(EV_CURRENT) == EV_NONE) {
warn("elf init failed\n");
return NULL;
}
fd = open(path, O_RDONLY);
if (fd < 0) {
warn("Could not open %s\n", path);
return NULL;
}
e = elf_begin(fd, ELF_C_READ, NULL);
if (!e) {
warn("elf_begin failed: %s\n", elf_errmsg(-1));
close(fd);
return NULL;
}
if (elf_kind(e) != ELF_K_ELF) {
warn("elf kind %d is not ELF_K_ELF\n", elf_kind(e));
elf_end(e);
close(fd);
return NULL;
}
*fd_close = fd;
return e;
}
Elf *open_elf_by_fd(int fd)
{
Elf *e;
if (elf_version(EV_CURRENT) == EV_NONE) {
warn("elf init failed\n");
return NULL;
}
e = elf_begin(fd, ELF_C_READ, NULL);
if (!e) {
warn("elf_begin failed: %s\n", elf_errmsg(-1));
close(fd);
return NULL;
}
if (elf_kind(e) != ELF_K_ELF) {
warn("elf kind %d is not ELF_K_ELF\n", elf_kind(e));
elf_end(e);
close(fd);
return NULL;
}
return e;
}
void close_elf(Elf *e, int fd_close)
{
elf_end(e);
close(fd_close);
}
/* Returns the offset of a function in the elf file `path`, or -1 on failure. */
off_t get_elf_func_offset(const char *path, const char *func)
{
off_t ret = -1;
int i, fd = -1;
Elf *e;
Elf_Scn *scn;
Elf_Data *data;
GElf_Ehdr ehdr;
GElf_Shdr shdr[1];
GElf_Phdr phdr;
GElf_Sym sym[1];
size_t shstrndx, nhdrs;
char *n;
e = open_elf(path, &fd);
if (!gelf_getehdr(e, &ehdr))
goto out;
if (elf_getshdrstrndx(e, &shstrndx) != 0)
goto out;
scn = NULL;
while ((scn = elf_nextscn(e, scn))) {
if (!gelf_getshdr(scn, shdr))
continue;
if (!(shdr->sh_type == SHT_SYMTAB || shdr->sh_type == SHT_DYNSYM))
continue;
data = NULL;
while ((data = elf_getdata(scn, data))) {
for (i = 0; gelf_getsym(data, i, sym); i++) {
n = elf_strptr(e, shdr->sh_link, sym->st_name);
if (!n)
continue;
if (GELF_ST_TYPE(sym->st_info) != STT_FUNC)
continue;
if (!strcmp(n, func)) {
ret = sym->st_value;
goto check;
}
}
}
}
check:
if (ehdr.e_type == ET_EXEC || ehdr.e_type == ET_DYN) {
if (elf_getphdrnum(e, &nhdrs) != 0) {
ret = -1;
goto out;
}
for (i = 0; i < (int)nhdrs; i++) {
if (!gelf_getphdr(e, i, &phdr))
continue;
if (phdr.p_type != PT_LOAD || !(phdr.p_flags & PF_X))
continue;
if (phdr.p_vaddr <= ret && ret < (phdr.p_vaddr + phdr.p_memsz)) {
ret = ret - phdr.p_vaddr + phdr.p_offset;
goto out;
}
}
ret = -1;
}
out:
close_elf(e, fd);
return ret;
}

View File

@@ -1,18 +0,0 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* Copyright (c) 2021 Google LLC. */
#ifndef __UPROBE_HELPERS_H
#define __UPROBE_HELPERS_H
#include <sys/types.h>
#include <unistd.h>
#include <gelf.h>
int get_pid_binary_path(pid_t pid, char *path, size_t path_sz);
int get_pid_lib_path(pid_t pid, const char *lib, char *path, size_t path_sz);
int resolve_binary_path(const char *binary, pid_t pid, char *path, size_t path_sz);
off_t get_elf_func_offset(const char *path, const char *func);
Elf *open_elf(const char *path, int *fd_close);
Elf *open_elf_by_fd(int fd);
void close_elf(Elf *e, int fd_close);
#endif /* __UPROBE_HELPERS_H */

View File

@@ -1063,6 +1063,5 @@ int attach_uprobes(struct memleak_bpf *skel)
ATTACH_UPROBE(skel, aligned_alloc, aligned_alloc_enter); ATTACH_UPROBE(skel, aligned_alloc, aligned_alloc_enter);
ATTACH_URETPROBE(skel, aligned_alloc, aligned_alloc_exit); ATTACH_URETPROBE(skel, aligned_alloc, aligned_alloc_exit);
return 0; return 0;
} }

View File

@@ -1,5 +1,7 @@
# eBPF 入门实践教程:使用 LSM 进行安全检测防御 # eBPF 入门实践教程:使用 LSM 进行安全检测防御
eBPF (扩展的伯克利数据包过滤器) 是一项强大的网络和性能分析工具,被广泛应用在 Linux 内核上。eBPF 使得开发者能够动态地加载、更新和运行用户定义的代码,而无需重启内核或更改内核源代码。这个特性使得 eBPF 能够提供极高的灵活性和性能,使其在网络和系统性能分析方面具有广泛的应用。安全方面的 eBPF 应用也是如此,本文将介绍如何使用 eBPF LSMLinux Security Modules机制实现一个简单的安全检查程序。
## 背景 ## 背景
LSM 从 Linux 2.6 开始成为官方内核的一个安全框架,基于此的安全实现包括 SELinux 和 AppArmor 等。在 Linux 5.7 引入 BPF LSM 后,系统开发人员已经能够自由地实现函数粒度的安全检查能力,本文就提供了这样一个案例:限制通过 socket connect 函数对特定 IPv4 地址进行访问的 BPF LSM 程序。(可见其控制精度是很高的) LSM 从 Linux 2.6 开始成为官方内核的一个安全框架,基于此的安全实现包括 SELinux 和 AppArmor 等。在 Linux 5.7 引入 BPF LSM 后,系统开发人员已经能够自由地实现函数粒度的安全检查能力,本文就提供了这样一个案例:限制通过 socket connect 函数对特定 IPv4 地址进行访问的 BPF LSM 程序。(可见其控制精度是很高的)
@@ -157,9 +159,7 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe
本文介绍了如何使用 BPF LSM 来限制通过 socket 对特定 IPv4 地址的访问。我们可以通过修改 GRUB 配置文件来开启 LSM 的 BPF 挂载点。在 eBPF 程序中,我们通过 `BPF_PROG` 宏定义函数,并通过 `SEC` 宏指定挂载点;在函数实现上,遵循 LSM 安全检查模块中 "cannot override a denial" 的原则,并根据 socket 连接请求的目的地址对该请求进行限制。 本文介绍了如何使用 BPF LSM 来限制通过 socket 对特定 IPv4 地址的访问。我们可以通过修改 GRUB 配置文件来开启 LSM 的 BPF 挂载点。在 eBPF 程序中,我们通过 `BPF_PROG` 宏定义函数,并通过 `SEC` 宏指定挂载点;在函数实现上,遵循 LSM 安全检查模块中 "cannot override a denial" 的原则,并根据 socket 连接请求的目的地址对该请求进行限制。
更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf> 如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 以获取更多示例和完整的教程。
完整的教程和源代码已经全部开源,可以在 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 中查看。
## 参考 ## 参考

View File

@@ -148,4 +148,4 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe
本文是 eBPF 入门开发实践教程的第二篇。下一篇文章将介绍如何在 eBPF 中使用 fentry 监测捕获 unlink 系统调用。 本文是 eBPF 入门开发实践教程的第二篇。下一篇文章将介绍如何在 eBPF 中使用 fentry 监测捕获 unlink 系统调用。
完整的教程和源代码已经全部开源,可以在 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 中查看 如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 以获取更多示例和完整的教程

View File

@@ -1,4 +1,4 @@
# eBPF 入门实践教程:使用 eBPF 进行 tc 流量控制 # eBPF 入门实践教程二十:使用 eBPF 进行 tc 流量控制
## 背景 ## 背景
@@ -100,9 +100,7 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe
本文介绍了如何向 TC 流量控制子系统挂载 eBPF 类型的 filter 来实现对链路层数据包的排队处理。基于 eunomia-bpf 提供的通过注释向 libbpf 传递参数的方案,我们可以将自己编写的 tc BPF 程序以指定选项挂载到目标网络设备,并借助内核的 sk_buff 结构对数据包进行过滤处理。 本文介绍了如何向 TC 流量控制子系统挂载 eBPF 类型的 filter 来实现对链路层数据包的排队处理。基于 eunomia-bpf 提供的通过注释向 libbpf 传递参数的方案,我们可以将自己编写的 tc BPF 程序以指定选项挂载到目标网络设备,并借助内核的 sk_buff 结构对数据包进行过滤处理。
更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf> 如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 以获取更多示例和完整的教程。
完整的教程和源代码已经全部开源,可以在 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 中查看。
## 参考 ## 参考

View File

@@ -1,4 +1,4 @@
# eBPF 入门实践教程:使用 xdp 实现可编程包处理 # eBPF 入门实践教程二十一:使用 xdp 实现可编程包处理
## 背景 ## 背景
@@ -93,11 +93,9 @@ $ sudo cat /sys/kernel/tracing/trace_pipe
本文介绍了如何使用 xdp 来处理经过特定网络设备的包,基于 eunomia-bpf 提供的通过注释向 libbpf 传递参数的方案,我们可以将自己编写的 xdp BPF 程序以指定选项挂载到目标设备,并在网络包进入内核网络协议栈之前就对其进行处理,从而获取高性能的可编程包处理能力。 本文介绍了如何使用 xdp 来处理经过特定网络设备的包,基于 eunomia-bpf 提供的通过注释向 libbpf 传递参数的方案,我们可以将自己编写的 xdp BPF 程序以指定选项挂载到目标设备,并在网络包进入内核网络协议栈之前就对其进行处理,从而获取高性能的可编程包处理能力。
更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf> 如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 以获取更多示例和完整的教程。
完整的教程和源代码已经全部开源,可以在 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 中查看。 ## 参考资料
## 参考
+ <http://arthurchiao.art/blog/xdp-paper-acm-2018-zh/> + <http://arthurchiao.art/blog/xdp-paper-acm-2018-zh/>
+ <http://arthurchiao.art/blog/linux-net-stack-implementation-rx-zh/> + <http://arthurchiao.art/blog/linux-net-stack-implementation-rx-zh/>

View File

@@ -86,4 +86,4 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe
编译这个程序可以使用 ecc 工具,运行时可以使用 ecli 命令,并通过查看 /sys/kernel/debug/tracing/trace_pipe 文件查看 eBPF 程序的输出。更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf> 编译这个程序可以使用 ecc 工具,运行时可以使用 ecli 命令,并通过查看 /sys/kernel/debug/tracing/trace_pipe 文件查看 eBPF 程序的输出。更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf>
完整的教程和源代码已经全部开源,可以在 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 中查看 如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 以获取更多示例和完整的教程

View File

@@ -120,4 +120,4 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe
更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf> 更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf>
完整的教程和源代码已经全部开源,可以在 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 中查看 如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 以获取更多示例和完整的教程

View File

@@ -133,4 +133,4 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe
更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf> 更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf>
完整的教程和源代码已经全部开源,可以在 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 中查看 如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 以获取更多示例和完整的教程

View File

@@ -138,4 +138,4 @@ struct {
更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf> 更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf>
完整的教程和源代码已经全部开源,可以在 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 中查看 如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 以获取更多示例和完整的教程

View File

@@ -122,4 +122,4 @@ struct {
更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf> 更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf>
完整的教程和源代码已经全部开源,可以在 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 中查看 如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 以获取更多示例和完整的教程

View File

@@ -448,4 +448,4 @@ runqlat 是一种用于监控Linux内核中进程调度延迟的工具。它可
更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf> 更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<https://github.com/eunomia-bpf/eunomia-bpf>
完整的教程和源代码已经全部开源,可以在 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 中查看 如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 以获取更多示例和完整的教程