ebpf的bootstrap代码介绍+运行情况,代码层代码和用户层代码的源码介绍+语法解释

目录

bootstrap

介绍

运行情况 

选项

代码解释

.bpf.c

源码(handle_exec)

语法 

struct {      __uint(type, BPF_MAP_TYPE_HASH);    __uint(max_entries, 8192);    __type(key, pid_t);    __type(value, u64);} exec_start SEC(".maps"); 

__uint

环形缓冲区

__type

SEC("tp/sched/sched_process_exec") 

tp(tracepoint)

sched

sched_process_exec

("tp/sched/sched_process_exec") 

trace_event_raw_sched_process_exec

event 

 bpf_ktime_get_ns()

 bpf_map_update_elem()

bpf_ringbuf_reserve()

bpf_get_current_task()

BPF_CORE_READ()

bpf_get_current_comm()

fname_off = ctx->__data_loc_filename & 0xFFFF

bpf_probe_read_str(&e->filename, sizeof(e->filename), (void *)ctx + fname_off)

bpf_ringbuf_submit(e, 0)

源码(handle_exit)

语法 

SEC("tp/sched/sched_process_exit")

bpf_map_lookup_elem()

duration_ns = bpf_ktime_get_ns() - *start_ts

bpf_map_delete_elem()

e->exit_code = (BPF_CORE_READ(task, exit_code) >> 8) & 0xff;

.c 

源码

语法

strtol()

argp_usage()

struct argp

LIBBPF_DEBUG

struct tm

​编辑

strftime()

ring_buffer系列函数

skel->rodata->min_duration_ns = env.min_duration_ms * 1000000ULL 

ring_buffer__poll(rb, 100 /* timeout, ms */)


下面介绍的,是libbpf-bootstrap库中example下的bootstrap代码:

bootstrap

介绍

是一个基于ebpf的Linux 内核跟踪程序

  • 它跟踪了每一个进程的启动时间,运行时间等执行事件,并将相关信息记录到环形缓冲区中,以便后续的处理和分析

运行情况 

会监视:

进程的启动时间,记录启动/退出事件,进程的pid,进程ppid,进程文件名,退出码,持续运行时间

选项

  • -v 显示详细信息
  • -d + 设置最小运行时间(如果设置了最短持续时间,将不会触发exec事件)

(但我感觉有点不合理,所以我修改了一下 -- 设置了最短持续时间后,会过滤掉持续时间未达到该阈值的进程):

代码解释

.bpf.c

源码(handle_exec)

// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2020 Facebook */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include "bootstrap.h"

char LICENSE[] SEC("license") = "Dual BSD/GPL";

struct {  
	__uint(type, BPF_MAP_TYPE_HASH);
	__uint(max_entries, 8192);
	__type(key, pid_t);
	__type(value, u64);
} exec_start SEC(".maps"); //存储进程启动时的时间戳

struct {
	__uint(type, BPF_MAP_TYPE_RINGBUF);
	__uint(max_entries, 256 * 1024);
} rb SEC(".maps"); //环形缓冲区

const volatile unsigned long long min_duration_ns = 0; //最小持续时间

SEC("tp/sched/sched_process_exec")
int handle_exec(struct trace_event_raw_sched_process_exec *ctx) //一个新的进程被执行时,就会触发此事件。该函数会从事件上下文中获取相关信息,并将其放入环形缓冲区以供后续处理
{
	struct task_struct *task;
	unsigned fname_off; //存储文件名的偏移量
	struct event *e;
	pid_t pid;
	u64 ts; //时间戳

	/* remember time exec() was executed for this PID */
	pid = bpf_get_current_pid_tgid() >> 32;
	ts = bpf_ktime_get_ns();
	bpf_map_update_elem(&exec_start, &pid, &ts, BPF_ANY);

	/* don't emit exec events when minimum duration is specified */
	if (min_duration_ns) 
		return 0;

	/* reserve sample from BPF ringbuf */
	e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
	if (!e)
		return 0;

	/* fill out the sample with data */
	task = (struct task_struct *)bpf_get_current_task();

	e->exit_event = false;
	e->pid = pid;
	e->ppid = BPF_CORE_READ(task, real_parent, tgid);
	bpf_get_current_comm(&e->comm, sizeof(e->comm));

	fname_off = ctx->__data_loc_filename & 0xFFFF;
	bpf_probe_read_str(&e->filename, sizeof(e->filename), (void *)ctx + fname_off);

	/* successfully submit it to user-space for post-processing */
	bpf_ringbuf_submit(e, 0);
	return 0;
}

总之,就是在进程启动时,记录当前有关信息,并打包成结构体event的形式,交给环形缓冲区里

语法 

struct {  
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 8192);
    __type(key, pid_t);
    __type(value, u64);
} exec_start SEC(".maps"); 

定义了一个bpf哈希映射

__uint
  • 是一个编译器宏,用于指定 BPF Map 的类型以及其他属性
  • type -- 指定了哈希映射的类型
  • BPF_MAP_TYPE_HASH -- 表示是哈希表类型的bpf映射
  • BPF_MAP_TYPE_RINGBUF -- 表示环形缓冲区
  • max_entries -- 定义了哈希映射的最大条目数
环形缓冲区
  • 它是一种特殊的数据结构,用于在内核中跟踪事件并将其传递给用户空间的程序进行处理
  • 具有固定大小的缓冲区,新的数据可以不断地写入缓冲区的尾部,而读取操作则可以从缓冲区的头部进行
  • 当缓冲区满时,最旧的数据将会被覆盖,从而实现了循环利用
__type
  • 是一个编译器宏,用于指定 BPF Map 中key和value的类型

SEC("tp/sched/sched_process_exec") 

指示编译器将该函数映射到特定的 BPF 事件点上

tp(tracepoint)
  • 是内核中预定义的一些关键点(内核开发人员事先定义并注册的事件点或跟踪点)
  • 它们允许用户在这些点上执行BPF程序,从而监视、记录或操作系统的各种活动和事件
sched
  • 代表调度(scheduler)子系统,负责管理和调度系统中的进程和线程
  • 调度子系统中包括了一些跟踪点,用于跟踪进程的调度活动,比如进程的创建、执行、休眠、唤醒和退出等
sched_process_exec
  • 表示在调度子系统中的进程执行事件
("tp/sched/sched_process_exec") 
  • 当一个进程开始执行时,内核会触发这个tracepoint,从而允许BPF程序捕获并处理相关事件
trace_event_raw_sched_process_exec

用于表示跟踪事件的上下文,结构体包含了与进程执行相关的各种信息

  • 例如进程的 PID、父进程的 PID、进程的命令名称等
  • 通过这个参数,可以获取事件中的各种字段,从而进行进一步的分析、记录或处理
  • 可以把它当做pt_regs结构体指针一样的作用(之前在kprobe中使用的,用于提供寄存器的值)
event 

用于描述特定事件的数据结构

  • 包含事件相关的各种信息
  • 例如进程/线程标识符,退出码,运行时间,进程名,文件名,事件类型(启动/退出)
 bpf_ktime_get_ns()

在内核中获取当前时间的纳秒级别时间戳

 bpf_map_update_elem()

用于在内核层代码中更新或插入一个键值对到一个特定的 eBPF map 中

  • int bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags)
bpf_ringbuf_reserve()

从一个环形缓冲区中预留空间以存储事件数据

  • int bpf_ringbuf_reserve(struct ringbuf *ringbuf, u64 size, u64 flags)
  • flags的值:

bpf_get_current_task()

获取当前正在执行的任务的tast_struct指针

BPF_CORE_READ()

表示正在进行核心级别的读取操作

bpf_get_current_comm()

是 eBPF 的一个 helper 函数,用于获取当前任务(进程)的命令名称

  • char *bpf_get_current_comm(void *buf, int size_of_buf);
    
fname_off = ctx->__data_loc_filename & 0xFFFF

ctx指向的结构体里存放了文件名的偏移量,表示文件名在 eBPF 上下文结构体中的位置

  • 这里只保留低 16 位
bpf_probe_read_str(&e->filename, sizeof(e->filename), (void *)ctx + fname_off)

用于从内核空间复制一个以 null 结尾的字符串到用户空间

  • int bpf_probe_read_str(void *dst, u32 size, const void *unsafe_ptr);
    
  • 如果字符串的长度超过了size,只会复制size-1个字符
bpf_ringbuf_submit(e, 0)

用于将先前预留的事件数据提交到环形缓冲区中(也就是提交到用户空间),以便进行后续处理

  • int bpf_ringbuf_submit(void *data,u64 flags);
    
  • flags的值:

源码(handle_exit)

SEC("tp/sched/sched_process_exit")
int handle_exit(struct trace_event_raw_sched_process_template *ctx) //当一个进程退出时,内核会触发此事件。该函数会计算进程的生命周期持续时间,并将相关信息放入环形缓冲区中
{
	struct task_struct *task;
	struct event *e;
	pid_t pid, tid;
	u64 id, ts, *start_ts, duration_ns = 0;

	/* get PID and TID of exiting thread/process */
	id = bpf_get_current_pid_tgid();
	pid = id >> 32;
	tid = (u32)id;

	/* ignore thread exits */
	if (pid != tid)
		return 0;

	/* if we recorded start of the process, calculate lifetime duration */
	start_ts = bpf_map_lookup_elem(&exec_start, &pid);
	if (start_ts)
		duration_ns = bpf_ktime_get_ns() - *start_ts;
	else if (min_duration_ns)
		return 0;
	bpf_map_delete_elem(&exec_start, &pid);

	/* if process didn't live long enough, return early */
	if (min_duration_ns && duration_ns < min_duration_ns)
		return 0;

	/* reserve sample from BPF ringbuf */
	e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
	if (!e)
		return 0;

	/* fill out the sample with data */
	task = (struct task_struct *)bpf_get_current_task();

	e->exit_event = true;
	e->duration_ns = duration_ns;
	e->pid = pid;
	e->ppid = BPF_CORE_READ(task, real_parent, tgid);
	e->exit_code = (BPF_CORE_READ(task, exit_code) >> 8) & 0xff;
	bpf_get_current_comm(&e->comm, sizeof(e->comm));

	/* send data to user-space for post-processing */
	bpf_ringbuf_submit(e, 0);
	return 0;
}

和上面那个类似,只不过是在进程退出时才会触发该事件

  • 退出前会将运行时间+其他相关信息打包成event事件,发送到环形缓冲区

语法 

SEC("tp/sched/sched_process_exit")

声明要捕获 Linux 内核中进程退出时的相关信息

  • 当一个进程退出时,内核会触发这个tracepoint,从而允许BPF程序捕获并处理相关事件
bpf_map_lookup_elem()

在内核层代码中,从一个 eBPF map 中查找与给定键关联的值

  • void *bpf_map_lookup_elem(void *map, const void *key);
    
  • 通过提供的map指针和key值指针,可以拿到对应的value值
duration_ns = bpf_ktime_get_ns() - *start_ts
  • 计算从进程开始执行到当前时间点的持续时间
  • bpf_ktime_get_ns获取当前时间的纳秒级别的时间戳,用该值-开始时间,即为持续时间
bpf_map_delete_elem()

从一个 eBPF map 中删除指定的键值对

  • int bpf_map_delete_elem(void *map, const void *key);
    
  • 因为进程已经退出了,之前在map中记录的进程启动时间就没有用了,所以要删掉
e->exit_code = (BPF_CORE_READ(task, exit_code) >> 8) & 0xff;

提取进程的退出码

.c 

源码

/ SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2020 Facebook */

#include <argp.h> //解析命令行参数
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "bootstrap.h"
#include "bootstrap.skel.h"

static struct env { //环境变量
	bool verbose; //是否启用详细输出
	long min_duration_ms; //最小持续时间
} env;

const char *argp_program_version = "bootstrap 0.0"; //版本号
const char *argp_program_bug_address = "<bpf@vger.kernel.org>"; //存储程序的Bug报告地址
const char argp_program_doc[] = "BPF bootstrap demo application.\n"  //存储程序的文档字符串,包括程序的简要说明和用法
				"\n"
				"It traces process start and exits and shows associated \n"
				"information (filename, process duration, PID and PPID, etc).\n"
				"\n"
				"USAGE: ./bootstrap [-d <min-duration-ms>] [-v]\n";

static const struct argp_option opts[] = { //用于存储命令行参数的选项信息
	{ "verbose", 'v', NULL, 0, "Verbose debug output" },
	{ "duration", 'd', "DURATION-MS", 0, "Minimum process duration (ms) to report" },
	{},
};

static error_t parse_arg(int key, char *arg, struct argp_state *state) //解析命令行参数
{
	switch (key) {
	case 'v':
		env.verbose = true;
		break;
	case 'd':
		errno = 0;
		env.min_duration_ms = strtol(arg, NULL, 10);
		if (errno || env.min_duration_ms <= 0) {
			fprintf(stderr, "Invalid duration: %s\n", arg);
			argp_usage(state);
		}
		break;
	case ARGP_KEY_ARG:
		argp_usage(state);
		break;
	default:
		return ARGP_ERR_UNKNOWN;
	}
	return 0;
}

static const struct argp argp = { //包含了命令行参数的选项信息、解析函数和程序文档
	.options = opts,
	.parser = parse_arg,
	.doc = argp_program_doc,
};

static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) 
{
	if (level == LIBBPF_DEBUG && !env.verbose)
		return 0;
	return vfprintf(stderr, format, args);
}

static volatile bool exiting = false;

static void sig_handler(int sig)
{
	exiting = true;
}

static int handle_event(void *ctx, void *data, size_t data_sz) //处理跟踪到的进程事件,并打印相关信息
{
	const struct event *e = data;
	struct tm *tm;
	char ts[32];
	time_t t;

	time(&t);
	tm = localtime(&t);
	strftime(ts, sizeof(ts), "%H:%M:%S", tm);

	if (e->exit_event) {
		printf("%-8s %-5s %-16s %-7d %-7d [%u]", ts, "EXIT", e->comm, e->pid, e->ppid,
		       e->exit_code);
		if (e->duration_ns)
			printf(" (%llums)", e->duration_ns / 1000000);
		printf("\n");
	} else {
		printf("%-8s %-5s %-16s %-7d %-7d %s\n", ts, "EXEC", e->comm, e->pid, e->ppid,
		       e->filename);
	}

	return 0;
}

int main(int argc, char **argv)
{
	struct ring_buffer *rb = NULL;
	struct bootstrap_bpf *skel;
	int err;

	/* Parse command line arguments */
	err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
	if (err)
		return err;

	/* Set up libbpf errors and debug info callback */
	libbpf_set_print(libbpf_print_fn);

	/* Cleaner handling of Ctrl-C */
	signal(SIGINT, sig_handler);
	signal(SIGTERM, sig_handler);

	/* Load and verify BPF application */
	skel = bootstrap_bpf__open();
	if (!skel) {
		fprintf(stderr, "Failed to open and load BPF skeleton\n");
		return 1;
	}

	/* Parameterize BPF code with minimum duration parameter */
	skel->rodata->min_duration_ns = env.min_duration_ms * 1000000ULL; //设置了 BPF 程序的参数,即最小持续时间,单位为纳秒

	/* Load & verify BPF programs */
	err = bootstrap_bpf__load(skel);
	if (err) {
		fprintf(stderr, "Failed to load and verify BPF skeleton\n");
		goto cleanup;
	}

	/* Attach tracepoints */
	err = bootstrap_bpf__attach(skel);
	if (err) {
		fprintf(stderr, "Failed to attach BPF skeleton\n");
		goto cleanup;
	}

	/* Set up ring buffer polling */
	rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL); //创建一个环形缓冲区,并设置好缓冲区的回调函数
	if (!rb) {
		err = -1;
		fprintf(stderr, "Failed to create ring buffer\n");
		goto cleanup;
	}

	/* Process events */
	printf("%-8s %-5s %-16s %-7s %-7s %s\n", "TIME", "EVENT", "COMM", "PID", "PPID",
	       "FILENAME/EXIT CODE");
	while (!exiting) { //不断地轮询环形缓冲区以获取事件
		err = ring_buffer__poll(rb, 100 /* timeout, ms */);
		/* Ctrl-C will cause -EINTR */
		if (err == -EINTR) {
			err = 0;
			break;
		}
		if (err < 0) {
			printf("Error polling perf buffer: %d\n", err);
			break;
		}
	}

cleanup:
	/* Clean up */
	ring_buffer__free(rb);
	bootstrap_bpf__destroy(skel);

	return err < 0 ? -err : 0;
}

语法

strtol()

字符串转换为长整型数值

  • long strtol(const char *nptr, char **endptr, int base);
    
argp_usage()

输出标准的用法消息

  • * Possibly output the standard usage message for ARGP to stderr and exit.  */
    extern void argp_usage (const struct argp_state *__state);
    extern void __argp_usage (const struct argp_state *__state);
    
struct argp

是 GNU Argp 参数解析库中的一个结构体,用于定义参数解析的规则和选项

LIBBPF_DEBUG

是 libbpf 库中定义的一个枚举常量,用于表示调试级别的日志输出

struct tm

用于表示日期和时间的各个组成部分

  • struct tm
    {
      int tm_sec;			/* Seconds.	[0-60] (1 leap second) */
      int tm_min;			/* Minutes.	[0-59] */
      int tm_hour;			/* Hours.	[0-23] */
      int tm_mday;			/* Day.		[1-31] */
      int tm_mon;			/* Month.	[0-11] */
      int tm_year;			/* Year	- 1900.  */
      int tm_wday;			/* Day of week.	[0-6] */
      int tm_yday;			/* Days in year.[0-365]	*/
      int tm_isdst;			/* DST.		[-1/0/1]*/
    
    # ifdef	__USE_MISC
      long int tm_gmtoff;		/* Seconds east of UTC.  */
      const char *tm_zone;		/* Timezone abbreviation.  */
    # else
      long int __tm_gmtoff;		/* Seconds east of UTC.  */
      const char *__tm_zone;	/* Timezone abbreviation.  */
    # endif
    };

localtime()

将一个时间戳转换为本地时间,并将结果存储在tm结构体中

  • struct tm *localtime(const time_t *timep);
    
strftime()

将tm结构体中的时间信息格式化为字符串,并将结果存储在指定缓冲区

size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr);

ring_buffer系列函数

  • 其中,__new用于创建并初始化一个新的环形缓冲区
  • 后面两个参数可以设置为null
skel->rodata->min_duration_ns = env.min_duration_ms * 1000000ULL 

将环形缓冲区的只读数据(rodata)的最小持续时间赋值

  • 是环境变量中的最小持续时间(也就是-d选项设置的数值)由毫秒转换为纳秒
ring_buffer__poll(rb, 100 /* timeout, ms */)

在环形缓冲区上进行轮询,等待有新的数据可用

  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个使用 Bootstrap、JS 和 CSS 的示例网站的代码示例: HTML 代码: ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My Music Library</title> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> <!-- Custom CSS --> <link rel="stylesheet" href="style.css"> </head> <body> <!-- Navigation Bar --> <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <a class="navbar-brand" href="#">My Music Library</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav"> <li class="nav-item active"> <a class="nav-link" href="#">Home</a> </li> <li class="nav-item"> <a class="nav-link" href="#">Songs</a> </li> <li class="nav-item"> <a class="nav-link" href="#">Artists</a> </li> <li class="nav-item"> <a class="nav-link" href="#">About Us</a> </li> </ul> </div> </nav> <!-- Main Content --> <div class="container my-5"> <h1>Welcome to My Music Library</h1> <p>Discover the latest music albums and songs from your favorite artists. Search for your favorite songs and download them for free.</p> </div> <!-- Footer --> <footer class="bg-dark text-white py-3"> <div class="container"> <p>© 2021 My Music Library. All Rights Reserved.</p> </div> </footer> <!-- jQuery --> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script> <!-- Bootstrap JS --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"></script> </body> </html> ``` CSS 代码: ``` body { font-family: Arial, sans-serif; } .navbar { border-radius: 0; } .container { text-align: center; } footer { margin-top: 50px; } ``` 希望这个代码示例能够帮助您更好地理解 Bootstrap、JS 和 CSS 的应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值