@@ -7,6 +7,7 @@
> eBPF 是一种内核的网络程序。它可以在内核空间中执行, 并且可以实时地监控内核的行为。eunomia-bpf 是一个工具,可以用来编写 eBPF 程序。它提供了一个简单的接口,使用户可以编写 eBPF 程序来实现特定的目标。例如, eBPF 程序可以用来监控内核网络流量,或者实现虚拟机监控等功能。
> eBPF 是一种内核的网络程序。它可以在内核空间中执行, 并且可以实时地监控内核的行为。eunomia-bpf 是一个工具,可以用来编写 eBPF 程序。它提供了一个简单的接口,使用户可以编写 eBPF 程序来实现特定的目标。例如, eBPF 程序可以用来监控内核网络流量,或者实现虚拟机监控等功能。
这是一个使用 eunomia-bpf 的例子:
这是一个使用 eunomia-bpf 的例子:
```c
```c
#include "vmlinux.h"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_helpers.h>
@@ -169,6 +170,7 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe
<...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: write system call from PID 3840345.
<...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: write system call from PID 3840345.
<...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: write system call from PID 3840345.
<...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: write system call from PID 3840345.
```
```
请告诉我如何编译运行实时监控内核中 open 和 openat 系统调用的 eBPF 程序。
请告诉我如何编译运行实时监控内核中 open 和 openat 系统调用的 eBPF 程序。
> 要编译和运行实时监控内核中 open 和 openat 系统调用的 eBPF 程序,可以按照下面的步骤进行:
> 要编译和运行实时监控内核中 open 和 openat 系统调用的 eBPF 程序,可以按照下面的步骤进行:
@@ -215,7 +217,6 @@ Runing eBPF program...
> 查看 /sys/kernel/debug
> 查看 /sys/kernel/debug
请继续。查看 /sys/kernel/debug
请继续。查看 /sys/kernel/debug
> 运行 eBPF 程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出。例如,如果有进程在调用 open 或 openat 系统调用,那么可以看到类似下面的输出:
> 运行 eBPF 程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出。例如,如果有进程在调用 open 或 openat 系统调用,那么可以看到类似下面的输出:
@@ -229,7 +230,6 @@ sys_enter_openat: pid = 456
> 这些输出表明,在运行 eBPF 程序的过程中, 有两个进程( PID 为 123 和 456) 分别调用了 open 和 openat 系统调用。对于每个系统调用, eBPF 程序都会记录调用进程的 ID, 并在内核日志中输出。
> 这些输出表明,在运行 eBPF 程序的过程中, 有两个进程( PID 为 123 和 456) 分别调用了 open 和 openat 系统调用。对于每个系统调用, eBPF 程序都会记录调用进程的 ID, 并在内核日志中输出。
> 需要注意的是,如果内核中有多个进程同时调用 open 或 openat 系统调用,那么输出结果可能会有所不同。例如,可能会有多个进程的 ID 同时出现在内核日志中。这取决于系统的具体情况。
> 需要注意的是,如果内核中有多个进程同时调用 open 或 openat 系统调用,那么输出结果可能会有所不同。例如,可能会有多个进程的 ID 同时出现在内核日志中。这取决于系统的具体情况。
```c
```c
#include <vmlinux.h>
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_helpers.h>
@@ -462,6 +462,7 @@ int sys_exit_stat(struct trace_event_raw_sys_exit *ctx)
请继续。
请继续。
> 编译运行 eBPF 程序的方法与之前给出的方法类似,首先使用 ecc 编译程序:
> 编译运行 eBPF 程序的方法与之前给出的方法类似,首先使用 ecc 编译程序:
```console
```console
$ ecc stat.bpf.c
$ ecc stat.bpf.c
Compiling bpf object...
Compiling bpf object...
@@ -480,6 +481,7 @@ docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest
$ sudo ecli run ./package.json
$ sudo ecli run ./package.json
Runing eBPF program...
Runing eBPF program...
```
```
> 运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出:
> 运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出:
```console
```console
@@ -497,6 +499,7 @@ Running 'record' for 'bpf_printk' (5 secs):
[ perf record: Woken up 1 times to write data ]
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.178 MB perf.data (~1623 samples) ]
[ perf record: Captured and wrote 0.178 MB perf.data (~1623 samples) ]
```
```
> 使用 perf script 命令来查看 perf.data 中的输出:
> 使用 perf script 命令来查看 perf.data 中的输出:
```
```
@@ -538,6 +541,7 @@ int BPF_KRETPROBE(do_syscall_64_exit, long ret)
return 0;
return 0;
}
}
```
```
> 要编译这段代码,可以使用 ecc 工具:
> 要编译这段代码,可以使用 ecc 工具:
```
```
@@ -840,12 +844,15 @@ int handle_tp(void *ctx)
char _license[] SEC("license") = "GPL";
char _license[] SEC("license") = "GPL";
```
```
> 这个 eBPF 程序通过定义一个 handle_tp 函数并使用 SEC 宏把它附加到 sys_enter_write tracepoint。在进入 write 系统调用时, handle_tp 函数会被执行。该函数会使用 bpf_get_current_pid_tgid 函数获取调用 write 系统调用的进程 ID, 然后使用 bpf_printk 函数在内核日志中打印出来。
> 这个 eBPF 程序通过定义一个 handle_tp 函数并使用 SEC 宏把它附加到 sys_enter_write tracepoint。在进入 write 系统调用时, handle_tp 函数会被执行。该函数会使用 bpf_get_current_pid_tgid 函数获取调用 write 系统调用的进程 ID, 然后使用 bpf_printk 函数在内核日志中打印出来。
>
>
> eBPF 程序的输出结果将是内核日志中打印的调用 write 系统调用的进程的 PID。例如, 如果进程 42 调用了 write 系统调用,则输出结果将是:
> eBPF 程序的输出结果将是内核日志中打印的调用 write 系统调用的进程的 PID。例如, 如果进程 42 调用了 write 系统调用,则输出结果将是:
```
```
PID of process calling write: 42
PID of process calling write: 42
```
```
> 需要注意的是,在使用此 eBPF 程序之前,需要编译它并将其加载到内核中。此外,由于 eBPF 程序在内核态运行,因此它可能需要特殊权限才能加载和使用。
> 需要注意的是,在使用此 eBPF 程序之前,需要编译它并将其加载到内核中。此外,由于 eBPF 程序在内核态运行,因此它可能需要特殊权限才能加载和使用。
```c
```c
@@ -871,6 +878,7 @@ int handle_tp(void *ctx)
return 0;
return 0;
}
}
```
```
不。代码应该是这样的。
不。代码应该是这样的。
> 您的代码看起来正确。它定义了一个 handle_tp 函数,并使用 SEC 宏把它附加到 sys_enter_write tracepoint。在进入 write 系统调用时, handle_tp 函数会被执行。该函数会使用 bpf_get_current_pid_tgid 函数获取调用 write 系统调用的进程 ID, 然后使用 bpf_printk 函数在内核日志中打印出来。如果需要,还可以使用 pid_filter 变量来过滤进程。如果您需要进一步改进您的 eBPF 程序,请告诉我您的需求,我可以帮助您实现它。
> 您的代码看起来正确。它定义了一个 handle_tp 函数,并使用 SEC 宏把它附加到 sys_enter_write tracepoint。在进入 write 系统调用时, handle_tp 函数会被执行。该函数会使用 bpf_get_current_pid_tgid 函数获取调用 write 系统调用的进程 ID, 然后使用 bpf_printk 函数在内核日志中打印出来。如果需要,还可以使用 pid_filter 变量来过滤进程。如果您需要进一步改进您的 eBPF 程序,请告诉我您的需求,我可以帮助您实现它。
@@ -907,9 +915,11 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe
> 您的描述是正确的。eBPF 程序需要编译才能运行。可以使用 ecc 工具来编译 eBPF 程序,也可以使用 docker 镜像进行编译。运行 eBPF 程序的方法有多种,可以使用 ecli 命令运行编译后的程序,也可以使用其他工具。输出结果将在内核日志中打印,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看。
> 您的描述是正确的。eBPF 程序需要编译才能运行。可以使用 ecc 工具来编译 eBPF 程序,也可以使用 docker 镜像进行编译。运行 eBPF 程序的方法有多种,可以使用 ecli 命令运行编译后的程序,也可以使用其他工具。输出结果将在内核日志中打印,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看。
>
>
> 请注意,使用 ecc 编译程序后,会生成一个 package.json 文件,其中包含编译后的 eBPF 程序和配置信息。运行 eBPF 程序时,需要指定该文件,以便 ecli 命令能够找到并运行 eBPF 程序。例如,可以使用以下命令运行 eBPF 程序:
> 请注意,使用 ecc 编译程序后,会生成一个 package.json 文件,其中包含编译后的 eBPF 程序和配置信息。运行 eBPF 程序时,需要指定该文件,以便 ecli 命令能够找到并运行 eBPF 程序。例如,可以使用以下命令运行 eBPF 程序:
```
```
$ sudo ecli run ./package.json
sudo ecli run ./package.json
```
```
> 如果您还有任何问题,请随时告诉我,我很乐意帮助您解决问题。
> 如果您还有任何问题,请随时告诉我,我很乐意帮助您解决问题。
```c
```c
@@ -956,6 +966,7 @@ clang -O2 -target bpf -c my_bpf_prog.c -o my_bpf_prog.o
# 运行 eBPF 程序
# 运行 eBPF 程序
sudo ./bcc.py run my_bpf_prog.o
sudo ./bcc.py run my_bpf_prog.o
```
```
> 这样,就会编译并运行您的 eBPF 程序。在运行 eBPF 程序时,会在内核日志中打
> 这样,就会编译并运行您的 eBPF 程序。在运行 eBPF 程序时,会在内核日志中打
不对,使用 eunomia-bpf 工具编译。要编译这个程序,请使用 ecc 工具:
不对,使用 eunomia-bpf 工具编译。要编译这个程序,请使用 ecc 工具:
@@ -1025,6 +1036,7 @@ int BPF_PROG(do_unlinkat_exit, int dfd, struct filename *name, long ret)
```
```
sudo apt install bcc-tools libbcc-examples linux-headers-$(uname -r)
sudo apt install bcc-tools libbcc-examples linux-headers-$(uname -r)
```
```
> 然后,在终端中输入以下命令来编译您的 eBPF 程序:
> 然后,在终端中输入以下命令来编译您的 eBPF 程序:
我不希望你继续使用 BCC 工具集,请使用 eunomia-bpf
我不希望你继续使用 BCC 工具集,请使用 eunomia-bpf
@@ -1034,13 +1046,17 @@ sudo apt install bcc-tools libbcc-examples linux-headers-$(uname -r)
sudo npm install -g eunomia-bpf
sudo npm install -g eunomia-bpf
> 然后,在终端中输入以下命令来编译您的 eBPF 程序:
> 然后,在终端中输入以下命令来编译您的 eBPF 程序:
```
```
ecc kprobe-link.bpf.c
ecc kprobe-link.bpf.c
```
```
> 编译后,您会得到一个 package.json 文件。您可以使用 ecli 命令运行该程序:
> 编译后,您会得到一个 package.json 文件。您可以使用 ecli 命令运行该程序:
```
```
sudo ecli run package.json
sudo ecli run package.json
```
```
> 运行该程序后,您可以查看 /sys/kernel/debug/tracing/trace_pipe 文件,以查看程序的输出。请注意,编译和运行 eBPF 程序的步骤可能会有所不同,具体取决于使用的工具和系统环境。如果您在编
> 运行该程序后,您可以查看 /sys/kernel/debug/tracing/trace_pipe 文件,以查看程序的输出。请注意,编译和运行 eBPF 程序的步骤可能会有所不同,具体取决于使用的工具和系统环境。如果您在编
我们需要编写一段 eBPF 程序来捕获进程打开文件的系统调用,具体实现如下:
我们需要编写一段 eBPF 程序来捕获进程打开文件的系统调用,具体实现如下:
@@ -1102,6 +1118,7 @@ Packing ebpf object and config into package.json...
```
```
sudo ecli run package.json
sudo ecli run package.json
```
```
> 在 /sys/kernel/debug/tracing/trace_pipe 文件中,应该能看到类似下面的输出:
> 在 /sys/kernel/debug/tracing/trace_pipe 文件中,应该能看到类似下面的输出:
```
```
@@ -1185,6 +1202,7 @@ Packing ebpf object and config into package.json...
$ sudo ecli run ./package.json
$ sudo ecli run ./package.json
Runing eBPF program...
Runing eBPF program...
```
```
> 在 /sys/kernel/debug/tracing/trace_pipe 文件中,应该能看到类似下面的输出:
> 在 /sys/kernel/debug/tracing/trace_pipe 文件中,应该能看到类似下面的输出:
```
```
@@ -1193,6 +1211,7 @@ PID 1234 (bash) read: some command line input
PID 1234 (bash) read: another command line input
PID 1234 (bash) read: another command line input
...
...
```
```
> 这些输出表示有一个进程 ID 为 1234, 名称为 bash 的进程在调用 readline 函数时输入了相应的命令行输入。
> 这些输出表示有一个进程 ID 为 1234, 名称为 bash 的进程在调用 readline 函数时输入了相应的命令行输入。
```c
```c
@@ -1359,6 +1378,7 @@ char LICENSE[] SEC("license") = "GPL";
使用这段代码,我们就可以捕获 Linux 内核中进程执行的事件。我们可以通过工具(例如 eunomia-bpf) 来查看这些事件, 并分析进程的执行情况。
使用这段代码,我们就可以捕获 Linux 内核中进程执行的事件。我们可以通过工具(例如 eunomia-bpf) 来查看这些事件, 并分析进程的执行情况。
> 是的,这段代码通过定义两个 eBPF 程序来捕获进程执行 execve 系统调用的入口和出口事件。在入口程序中,我们获取了当前进程的进程 ID 和用户 ID, 并通过 bpf_get_current_task 函数获取了当前进程的 task_struct 结构体,并通过 bpf_probe_read_str 函数读取了进程名称。在出口程序中,我们获取了当前进程的进程 ID 和用户 ID, 并获取了 execve 系统调用的返回值。最后,在
> 是的,这段代码通过定义两个 eBPF 程序来捕获进程执行 execve 系统调用的入口和出口事件。在入口程序中,我们获取了当前进程的进程 ID 和用户 ID, 并通过 bpf_get_current_task 函数获取了当前进程的 task_struct 结构体,并通过 bpf_probe_read_str 函数读取了进程名称。在出口程序中,我们获取了当前进程的进程 ID 和用户 ID, 并获取了 execve 系统调用的返回值。最后,在
```
```
#include <vmlinux.h>
#include <vmlinux.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_endian.h>
@@ -1400,10 +1420,11 @@ char __license[] SEC("license") = "GPL";
需要注意的是,我们在代码中使用了一些 BPF 库函数,例如 bpf_htons 和 bpf_ntohs 函数,它们用于进行网络字节序和主机字节序之间的转换。此外,我们还使用了一些注释来为 TC 提供附加点和选项信息。例如,在这段代码的开头,我们使用了以下注释:
需要注意的是,我们在代码中使用了一些 BPF 库函数,例如 bpf_htons 和 bpf_ntohs 函数,它们用于进行网络字节序和主机字节序之间的转换。此外,我们还使用了一些注释来为 TC 提供附加点和选项信息。例如,在这段代码的开头,我们使用了以下注释:
```
```c
/// @tchook {"ifindex":1, "attach_point":"BPF_TC_INGRESS"}
/// @tchook {"ifindex":1, "attach_point":"BPF_TC_INGRESS"}
/// @tcopts {"handle":1, "priority":1}
/// @tcopts {"handle":1, "priority":1}
```
```
> 这些注释告诉 TC 将 eBPF 程序附加到网络接口的 ingress 附加点,并指定了 handle 和 priority 选项的值。
> 这些注释告诉 TC 将 eBPF 程序附加到网络接口的 ingress 附加点,并指定了 handle 和 priority 选项的值。
>
>
> 总之,这段代码实现了一个简单的 eBPF 程序,用于捕获数据包并打印出它们的信息。
> 总之,这段代码实现了一个简单的 eBPF 程序,用于捕获数据包并打印出它们的信息。
@@ -1432,7 +1453,7 @@ Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe`
The `tc` output in `/sys/kernel/debug/tracing/trace_pipe` should look
The `tc` output in `/sys/kernel/debug/tracing/trace_pipe` should look
something like this:
something like this:
```
```console
$ sudo cat /sys/kernel/debug/tracing/trace_pipe
$ sudo cat /sys/kernel/debug/tracing/trace_pipe
node-1254811 [007] ..s1 8737831.671074: 0: Got IP packet: tot_len: 79, ttl: 64
node-1254811 [007] ..s1 8737831.671074: 0: Got IP packet: tot_len: 79, ttl: 64
sshd-1254728 [006] ..s1 8737831.674334: 0: Got IP packet: tot_len: 79, ttl: 64
sshd-1254728 [006] ..s1 8737831.674334: 0: Got IP packet: tot_len: 79, ttl: 64
@@ -1444,7 +1465,7 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe
这段代码还使用了 @tchook 和 @tcopts 注释,用于指定该 eBPF 程序的挂载点和处理方式。例如,@tchook 注释中的 ifindex 参数指定了该 eBPF 程序
这段代码还使用了 @tchook 和 @tcopts 注释,用于指定该 eBPF 程序的挂载点和处理方式。例如,@tchook 注释中的 ifindex 参数指定了该 eBPF 程序
```
```c
const volatile __u64 min_us = 0;
const volatile __u64 min_us = 0;
const volatile pid_t targ_pid = 0;
const volatile pid_t targ_pid = 0;
const volatile pid_t targ_tgid = 0;
const volatile pid_t targ_tgid = 0;
@@ -1550,6 +1571,7 @@ int BPF_PROG(handle_sched_switch, bool preempt, struct task_struct *prev, struct
return handle_switch(ctx, prev, next);
return handle_switch(ctx, prev, next);
}
}
```
```
> 这段代码定义了一个 eBPF 程序,该程序用于跟踪进程在运行队列中的等待时间。它通过使用 tracepoint 和 perf event 输出来实现。
> 这段代码定义了一个 eBPF 程序,该程序用于跟踪进程在运行队列中的等待时间。它通过使用 tracepoint 和 perf event 输出来实现。
>
>
> 程序首先定义了两个 BPF 内核映射: start 映射用于存储每个进程在被调度运行之前的时间戳, events 映射用于存储 perf 事件。
> 程序首先定义了两个 BPF 内核映射: start 映射用于存储每个进程在被调度运行之前的时间戳, events 映射用于存储 perf 事件。
@@ -1690,6 +1712,7 @@ char LICENSE[] SEC("license") = "GPL";
> 这个 BPF 程序用于监控进程的调度时间。它使用了 tracepoints 和 raw tracepoints, 用于监控进程的唤醒和切换事件, 并记录进程调度的时间。它还使用了 BPF 的统计功能,以便对时间进行统计,以查看统计信息。这个程序可以按照进程、进程组或进程命名空间来统计信息,并可以针对特定进程或进程组进行过滤。
> 这个 BPF 程序用于监控进程的调度时间。它使用了 tracepoints 和 raw tracepoints, 用于监控进程的唤醒和切换事件, 并记录进程调度的时间。它还使用了 BPF 的统计功能,以便对时间进行统计,以查看统计信息。这个程序可以按照进程、进程组或进程命名空间来统计信息,并可以针对特定进程或进程组进行过滤。
它的输出结果类似这样:
它的输出结果类似这样:
```console
```console
$ sudo ecli run examples/bpftools/runqlat/package.json
$ sudo ecli run examples/bpftools/runqlat/package.json
key = 4294967295
key = 4294967295
@@ -1722,6 +1745,7 @@ comm = rcu_preempt
> 最后,程序通过 BPF perf events 把执行时间的统计信息输出到标准输出。可以通过工具 bpftrace 或者 ecli 来查看统计结果。
> 最后,程序通过 BPF perf events 把执行时间的统计信息输出到标准输出。可以通过工具 bpftrace 或者 ecli 来查看统计结果。
不是 BPF perf events 。通过 sample hash map 来输出直方图。
不是 BPF perf events 。通过 sample hash map 来输出直方图。
```
```
/// @sample {"interval": 1000, "type" : "log2_hist"}
/// @sample {"interval": 1000, "type" : "log2_hist"}
struct {
struct {
@@ -1731,12 +1755,13 @@ struct {
__type(value, struct hist);
__type(value, struct hist);
} hists SEC(".maps");
} hists SEC(".maps");
```
```
> 正确的。该程序使用 BPF hash map 来记录每个线程的执行时间,并使用 BPF sample hash map 来定期输出每个线程的直方图。您可以通过在 /sys/kernel/debug/tracing/ 文件系统中执行 "cat runqslat" 来查看输出。
> 正确的。该程序使用 BPF hash map 来记录每个线程的执行时间,并使用 BPF sample hash map 来定期输出每个线程的直方图。您可以通过在 /sys/kernel/debug/tracing/ 文件系统中执行 "cat runqslat" 来查看输出。
不,应该通过 sudo ecli run examples/bpftools/runqlat/package.json 来查看输出。
不,应该通过 sudo ecli run examples/bpftools/runqlat/package.json 来查看输出。
> 对,这样就可以看到输出的直方图了。例如:
> 对,这样就可以看到输出的直方图了。例如:
```
```
$ sudo ecli run examples/bpftools/runqlat/package.json
$ sudo ecli run examples/bpftools/runqlat/package.json
key = 4294967295
key = 4294967295
@@ -1760,6 +1785,7 @@ Copy code
8192 -> 16383 : 0 | |
8192 -> 16383 : 0 | |
16384 -> 32767 : 1 | |
16384 -> 32767 : 1 | |
```
```
> 这个示例程序使用了 BPF 的 sample hash map 功能,每隔一定时间间隔(默认为 1 秒)就会输出一次直方图,展示这段时间内各个进程的运行时间分布情况。每一行的前半部分是时间间隔,以及在这段时间内运行的次数,后半部分是一个用星号表示的分布情况。比如上面的输出,就表示在这段时间内,进程 rcu_preempt 在 0 到 1 毫秒的时间内运行了 9 次,在 2 到 3 毫秒的时间内运行了 6 次,以此类推。
> 这个示例程序使用了 BPF 的 sample hash map 功能,每隔一定时间间隔(默认为 1 秒)就会输出一次直方图,展示这段时间内各个进程的运行时间分布情况。每一行的前半部分是时间间隔,以及在这段时间内运行的次数,后半部分是一个用星号表示的分布情况。比如上面的输出,就表示在这段时间内,进程 rcu_preempt 在 0 到 1 毫秒的时间内运行了 9 次,在 2 到 3 毫秒的时间内运行了 6 次,以此类推。
>
>
> 可以通过参数 -m 来指定时间间隔,例如使用 -m 5 就可以让程序每隔 5 秒输出一次直方图。此外,还可以通过 -P 来指
> 可以通过参数 -m 来指定时间间隔,例如使用 -m 5 就可以让程序每隔 5 秒输出一次直方图。此外,还可以通过 -P 来指
@@ -1819,8 +1845,8 @@ int trace_return(struct pt_regs *ctx)
return 0;
return 0;
}
}
```
```
编译运行方法如下:
编译运行方法如下:
不。使用 eunomia-bpf 来完成上述操作。
不。使用 eunomia-bpf 来完成上述操作。