Linux内核 eBPF基础 ftrace基础:过滤函数和开启追踪

荣涛 2021年5月12日

本文相关注释代码:https://github.com/Rtoax/linux-5.10.13
上篇文章:Linux内核 eBPF基础:ftrace基础
推荐Presentation:《Ftrace Kernel Hooks: More than just tracing》

1. 从set_ftrace_filter讲起

/sys/kernel/debug/tracing/tracing_on

su
cd /sys/kernel/debug/tracing/
echo 1 > tracing_on
echo "schedule" > set_ftrace_filter
echo function_graph > current_tracer
cat trace

部分输出:

   1) ! 66730.47 us |  }------------------------------------------1)  contain-2264  =>  contain-2147 ------------------------------------------1) ! 66717.12 us |  } /* schedule */1)   3.683 us    |  schedule();1)               |  schedule() {------------------------------------------1)  contain-2147  =>  contain-2175 ------------------------------------------

那么以上流程在内核中都经历了什么呢?让我们细细道来。

2. echo schedule > set_ftrace_filter

在内核中kernel\trace\ftrace.c代码有:

 trace_create_file("set_ftrace_filter", 0644, parent,ops, &ftrace_filter_fops);

看一下文件操作符ftrace_filter_fops

static const struct file_operations ftrace_filter_fops = {.open = ftrace_filter_open,.read = seq_read,.write = ftrace_filter_write,.llseek = tracing_lseek,.release = ftrace_regex_release,
};

显然,这里对应ftrace_filter_write

2.1. ftrace_filter_write函数

该函数很简单:

ssize_t /* echo schedule > set_ftrace_filter */
ftrace_filter_write(struct file *file, const char __user *ubuf,size_t cnt, loff_t *ppos)
{return ftrace_regex_write(file, ubuf, cnt, ppos, 1);
}

调用了ftrace_regex_write,首先从打开的文件私有数据中获取struct ftrace_iterator结构,

 if (file->f_mode & FMODE_READ) {struct seq_file *m = file->private_data;iter = m->private;} elseiter = file->private_data;

这个结构中包含了太多有用的信息:

struct ftrace_iterator {loff_t               pos;loff_t              func_pos;loff_t             mod_pos;struct ftrace_page      *pg;struct dyn_ftrace       *func;struct ftrace_func_probe  *probe;struct ftrace_func_entry *probe_entry;struct trace_parser        parser;struct ftrace_hash       *hash;struct ftrace_ops     *ops;struct trace_array     *tr;struct list_head        *mod_list;int               pidx;int                idx;unsigned            flags;
};

首先使用函数trace_get_user将用户字符串写入struct trace_parser结构中。接着执行下面代码:

 if (read >= 0 && trace_parser_loaded(parser) &&!trace_parser_cont(parser)) {ret = ftrace_process_regex(iter, parser->buffer,parser->idx, enable);trace_parser_clear(parser);if (ret < 0)goto out;}

我们直接看ftrace_process_regex,此时enable=1

2.2. ftrace_process_regex函数

函数首先将获取func,在我们的例子中,func="schedul",然后会调用下面的代码:

 func = strsep(&next, ":");if (!next) {ret = ftrace_match_records(hash, func, len);if (!ret)ret = -EINVAL;if (ret < 0)return ret;return 0;}

ftrace_match_records进一步会调用match_records,这里可以得知,match_records的入参为:

match_records(hash, "schedul", len, NULL);

2.3. match_records函数

首选调用filter_parse_regex进行正则表达式匹配,匹配的类型包括:

enum regex_type {MATCH_FULL = 0,MATCH_FRONT_ONLY,MATCH_MIDDLE_ONLY,MATCH_END_ONLY,MATCH_GLOB,MATCH_INDEX,
};

因为我们的例子是func="schedul",所以类型为MATCH_FULL。接着运行下面的代码:

 do_for_each_ftrace_rec(pg, rec) {if (rec->flags & FTRACE_FL_DISABLED)continue;if (ftrace_match_record(rec, &func_g, mod_match, exclude_mod)) {ret = enter_record(hash, rec, clear_filter);if (ret < 0) {found = ret;goto out_unlock;}found = 1;}} while_for_each_ftrace_rec();

首先是两个宏定义:

#define do_for_each_ftrace_rec(pg, rec)                  \for (pg = ftrace_pages_start; pg; pg = pg->next) {        \int _____i;                        \for (_____i = 0; _____i < pg->index; _____i++) {  \rec = &pg->records[_____i];#define while_for_each_ftrace_rec()     \}              \}

最重要的是全局变量ftrace_pages_start,我们需要细致讲解一下:

static struct ftrace_page    *ftrace_pages_start;    /* ftrace 起始地址 *//* 初始化位置 ftrace_process_locs() */
static struct ftrace_page   *ftrace_pages;          /* 同上,用于定位链表中最后一个 pg */

2.4. ftrace_pages_startftrace_pages

这两个全局变量是在ftrace_process_locs中初始化的,他是在ftrace_init中被调用,这在《Linux内核 eBPF基础:ftrace基础-ftrace_init初始化》有详细介绍,他的调用如下:

 ret = ftrace_process_locs(NULL,__start_mcount_loc,__stop_mcount_loc);

在我的系统中为:

# cat /proc/kallsyms | grep mcount_loc
ffffffffaf0d75d0 T __start_mcount_loc
ffffffffaf1110e0 T __stop_mcount_loc

page大小可以计算得出。

在遍历过程中,每一项用struct dyn_ftrace标识,在循环中调用ftrace_match_record

2.5. ftrace_match_record函数

他的入参为:

ftrace_match_record((struct dyn_ftrace*)rec,(struct ftrace_glob*) {.search = "schedule",.type = MATCH_FULL,.len = strlen("schedule"),},(struct ftrace_glob*)NULL,0
)

该函数首先调用:

 kallsyms_lookup(rec->ip, NULL, NULL, &modname, str);

他的操作如下,即获取如下信息:

# cat /proc/kallsyms | grep " schedule"
[...]
ffffffffae97f1a0 T schedule
[...]

步骤如下:

  • 使用is_ksym_addr判断是否在内核中;
  • 使用kallsyms_expand_symbol获取ip对应的函数名(str);
  • 因为不在模块中,所以if (mod_g)分支不执行;
  • 执行ftrace_match函数;

2.6. ftrace_match函数

他的入参为:

ftrace_match("schedule",(struct ftrace_glob*) {.search = "schedule",.type = MATCH_FULL,.len = strlen("schedule"),},
)

因为我没有使用正则表达式,所以我们的type为MATCH_FULL,也就是字符串完全匹配("schedule")。所以我匹配的代码是:

 case MATCH_FULL:if (strcmp(str, g->search) == 0)matched = 1;break;

也就是说匹配上了。

这时,ftrace_match_record返回匹配结果1。然后调用enter_record函数。

2.7. enter_record函数

他的入参为:

enter_record((struct ftrace_hash *)hash,(struct dyn_ftrace *)rec,0
)

这里的hash对应struct ftrace_iterator *iter中的struct ftrace_hash *hash结构(iter为file文件的私有数据iter = file->private_data;)。

首先使用ftrace_lookup_ip从哈希结构中查找到这个ip对应的entry结构,他的结构为:

struct ftrace_func_entry {struct hlist_node hlist;unsigned long ip;unsigned long direct; /* for direct lookup only */
};

如果存在,直接退出:

 entry = ftrace_lookup_ip(hash, rec->ip);if (clear_filter) {/* Do nothing if it doesn't exist */if (!entry)return 0;free_hash_entry(hash, entry);} else {/* Do nothing if it exists */if (entry)return 0;ret = add_hash_entry(hash, rec->ip);}

至此,该函数返回,导致

     if (ftrace_match_record(rec, &func_g, mod_match, exclude_mod)) {ret = enter_record(hash, rec, clear_filter);if (ret < 0) {found = ret;goto out_unlock;}found = 1;}

在遍历所有page之后,match_records返回1,所以ftrace_match_records返回1,在函数ftrace_process_regex中,

     ret = ftrace_match_records(hash, func, len);if (!ret)ret = -EINVAL;if (ret < 0)return ret;return 0;

返回0

至此,ftrace_regex_write函数返回,也就是说,我们在中断中执行的

echo schedule > set_ftrace_filter

返回了。

那么,关于schedule的ftrace是如何使用的呢?

3. echo function_graph > current_tracer

为什么是function_graph?请使用下面的命令查看

cat available_tracers
hwlat blk function_graph wakeup_dl wakeup_rt wakeup function nop

在内核中kernel\trace\trace.c代码有:

    /* /sys/kernel/debug/tracing/current_tracer */trace_create_file("current_tracer", 0644, d_tracer,tr, &set_tracer_fops);

也就是他对应的文件操作符为:

static const struct file_operations set_tracer_fops = {.open        = tracing_open_generic,.read       = tracing_set_trace_read,.write        = tracing_set_trace_write,.llseek      = generic_file_llseek,
};

那我们就要看tracing_set_trace_write函数了。

3.1. tracing_set_trace_write函数

函数原型为:

static ssize_t
tracing_set_trace_write(struct file *filp, const char __user *ubuf,size_t cnt, loff_t *ppos)

首先将用户字符串拷贝到内核copy_from_user(buf, ubuf, cnt),然后取出空格

 for (i = cnt - 1; i > 0 && isspace(buf[i]); i--)buf[i] = 0;

然后调用tracing_set_tracer函数。

3.2. tracing_set_tracer函数

struct trace_array *tr = filp->private_data;

他的入参为:

tracing_set_tracer((struct trace_array *)tr,"function_graph"
)

struct trace_array可以认为是一块缓冲区,当ftrace时间发生时需要对这段ring-buffer读写。

而对于function_graph

cat available_tracers
hwlat blk function_graph wakeup_dl wakeup_rt wakeup function nop

以上每个类型对应一个struct tracer结构,他的定义如下(部分):

struct tracer { /*  */const char     *name;int               (*init)(struct trace_array *tr);void            (*reset)(struct trace_array *tr);void           (*start)(struct trace_array *tr);

在函数中或作这样的查找:

 for (t = trace_types; t; t = t->next) {if (strcmp(t->name, buf) == 0)break;}

也就是找到function_graph对应的struct tracer结构,他是在源文件kernel/trace/trace_functions_graph.c中定义的:

static struct tracer __tracer_data graph_trace  = {.name        = "function_graph",.update_thresh    = graph_trace_update_thresh,.open      = graph_trace_open,.pipe_open  = graph_trace_open,.close      = graph_trace_close,.pipe_close    = graph_trace_close,.init      = graph_trace_init,.reset      = graph_trace_reset,.print_line    = print_graph_function,.print_header   = print_graph_headers,.flags       = &tracer_flags,.set_flag  = func_graph_set_flag,
#ifdef CONFIG_FTRACE_SELFTEST.selftest  = trace_selftest_startup_function_graph,
#endif
};

这里我将跳过很多判断和鉴权,仅仅对函数调用感兴趣。

首先使用reset将之前的tracer重置

 if (tr->current_trace->reset)tr->current_trace->reset(tr);

加入上次使用function_graph,本次使用function,那么此处就会调用function_graph对应的reset函数,即graph_trace_reset,简单看下它做了什么:

static void graph_trace_reset(struct trace_array *tr)
{tracing_stop_cmdline_record();if (tracing_thresh)unregister_ftrace_graph(&funcgraph_thresh_ops);elseunregister_ftrace_graph(&funcgraph_ops);
}

简言之,就是注销一系列的东西(断点,进程调度相关内容等)。

然后就是使用初始化函数初始化struct tracer

 if (t->init) {ret = tracer_init(t, tr);if (ret)goto out;}

3.3. tracer_init函数

入参:

tracer_init((struct tracer*)t,(struct trace_array *)tr
)

函数实现很简单:

int tracer_init(struct tracer *t, struct trace_array *tr)
{tracing_reset_online_cpus(&tr->array_buffer);return t->init(tr);
}

首先使用tracing_reset_online_cpus重置buffer,然后调用tracer对应init函数,function_graph对应的init为graph_trace_init

3.4. graph_trace_init函数

static int graph_trace_init(struct trace_array *tr)
{int ret;set_graph_array(tr);if (tracing_thresh)ret = register_ftrace_graph(&funcgraph_thresh_ops);elseret = register_ftrace_graph(&funcgraph_ops);if (ret)return ret;tracing_start_cmdline_record();return 0;
}

首先初始化array

void set_graph_array(struct trace_array *tr)
{graph_array = tr;/* Make graph_array visible before we start tracing */smp_mb();
}

内存屏障smp_mb();的作用:让graph_array在开始tracing之前可见。

然后调用register_ftrace_graph,函数中设置了对应的全局函数指针:

ftrace_graph_return = trace_graph_return;
__ftrace_graph_entry = trace_graph_entry;
ftrace_graph_entry = ftrace_graph_entry_test;

使用register_pm_notifier注册电源管理同质量,这里不讨论。

调用start_graph_tracing函数。

3.5. start_graph_tracing函数

 ftrace_graph_active++;ret = start_graph_tracing();if (ret) {ftrace_graph_active--;goto out;}

首先申请存放栈的内存空间:

 ret_stack_list = kmalloc_array(FTRACE_RETSTACK_ALLOC_SIZE,sizeof(struct ftrace_ret_stack *),GFP_KERNEL);

默认大小为:

#define FTRACE_RETFUNC_DEPTH 50
#define FTRACE_RETSTACK_ALLOC_SIZE 32

然后是对idle进程的设置:

 /* The cpu_boot init_task->ret_stack will never be freed */for_each_online_cpu(cpu) {if (!idle_task(cpu)->ret_stack)ftrace_graph_init_idle_task(idle_task(cpu), cpu);}

然后尝试在FTRACE_RETSTACK_ALLOC_SIZE任务上分配一个返回堆栈数组。

 do {ret = alloc_retstack_tasklist(ret_stack_list);} while (ret == -EAGAIN);

然后激活tracepoint

     ret = register_trace_sched_switch(ftrace_graph_probe_sched_switch, NULL);if (ret)pr_info("ftrace_graph: Couldn't activate tracepoint"" probe to kernel_sched_switch\n");

这里可参见上面提到的tracing_set_tracer函数:

 if (tr->current_trace->reset)tr->current_trace->reset(tr);

然后就进入了 ftrace_startup函数。

3.6. ftrace_startup函数

register_ftrace_function函数内部会调用这个函数。

他的入参为:

ftrace_startup(&graph_ops, FTRACE_START_FUNC_RET)

其中graph_ops为:

static struct ftrace_ops graph_ops = {.func         = ftrace_stub,.flags           = FTRACE_OPS_FL_RECURSION_SAFE |FTRACE_OPS_FL_INITIALIZED |FTRACE_OPS_FL_PID |FTRACE_OPS_FL_STUB,
#ifdef FTRACE_GRAPH_TRAMP_ADDR.trampoline       = FTRACE_GRAPH_TRAMP_ADDR,/* trampoline_size is only needed for dynamically allocated tramps */
#endifASSIGN_OPS_HASH(graph_ops, &global_ops.local_hash)
};

该函数首先调用__register_ftrace_function函数。

3.6.1. __register_ftrace_function函数

函数入参为graph_ops

首先调用函数add_ftrace_ops(&ftrace_ops_list, ops)ftrace_ops_list

struct ftrace_ops __rcu __read_mostly *ftrace_ops_list  = &ftrace_list_end;

ftrace_list_end为:

struct ftrace_ops __read_mostly ftrace_list_end  = {.func       = ftrace_stub,.flags       = FTRACE_OPS_FL_RECURSION_SAFE | FTRACE_OPS_FL_STUB,INIT_OPS_HASH(ftrace_list_end)
};

最终,生成的链表为(graph_opsftrace_ops_list是一个节点):

ftrace_ops_list->graph_ops->ftrace_list_end

add_ftrace_ops函数刚刚运行结束后,func是这样的:

  链表头|
graph_ops->func = ftrace_stub|
ftrace_list_end->func = ftrace_stub|链表尾

也就是目前他们的func均为ftrace_stub

3.6.2. ftrace_update_trampoline函数

大致流程为

  • 调用arch_ftrace_update_trampoline架构相关的函数创建蹦床结构;

    • 调用create_trampoline创建蹦床并赋值;
    • 调用calc_trampoline_call_offset计算指令偏移;
    • 调用ftrace_call_replace生成并替换指令;
    • 调用text_poke_bp进行指令替换

下面对arch_ftrace_update_trampoline函数内部流程进行详述。

3.6.2.1. create_trampoline函数

为了简化流程,我们默认用这段代码分支:

 start_offset = (unsigned long)ftrace_caller;end_offset = (unsigned long)ftrace_caller_end;op_offset = (unsigned long)ftrace_caller_op_ptr;call_offset = (unsigned long)ftrace_call;jmp_offset = 0;

在我的系统中为:

ffffffffae98f840 T ftrace_caller
ffffffffae98f89c T ftrace_call

然后申请蹦床需要的内存:

trampoline = alloc_tramp(size + RET_SIZE + sizeof(void *));

这里的大小非常讲究size + RET_SIZE + sizeof(void *),我将在下文中解释:

+--------+    start_offset=ftrace_caller
|        |
|        |
|  size  |
|        |
+--------+    end_offset=ftrace_caller_end
|RET_SIZE|
+--------+
| void * |
+--------+

然后将蹦床函数拷贝过去:

ret = copy_from_kernel_nofault(trampoline, (void *)start_offset, size);

用ip指向end之后:

ip = trampoline + size;

也就是:

+--------+    start_offset=ftrace_caller
|        |
|        |
|  size  |
|        |
+--------+    end_offset=ftrace_caller_end
|RET_SIZE|    <--ip
+--------+
| void * |
+--------+

然后将ftrace_stub拷贝到ip

 retq = (unsigned long)ftrace_stub;ret = copy_from_kernel_nofault(ip, (void *)retq, RET_SIZE);
+--------+    start_offset=ftrace_caller
|        |
|        |
|  size  |
|        |
+--------+    end_offset=ftrace_caller_end
|RET_SIZE|    ftrace_stub    <--ip
+--------+
| void * | ---> graph_ops
+--------+

接着,计算void *位置,并将ops赋值,

 ptr = (unsigned long *)(trampoline + size + RET_SIZE);*ptr = (unsigned long)ops;

已知在源文件中:

SYM_FUNC_START(ftrace_caller)/* save_mcount_regs fills in first two parameters */save_mcount_regsSYM_INNER_LABEL(ftrace_caller_op_ptr, SYM_L_GLOBAL)/* Load the ftrace_ops into the 3rd parameter */movq function_trace_op(%rip), %rdx/* regs go into 4th parameter (but make it NULL) */movq $0, %rcxSYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL)call ftrace_stubrestore_mcount_regs/** The code up to this label is copied into trampolines so* think twice before adding any new code or changing the* layout here.*/
SYM_INNER_LABEL(ftrace_caller_end, SYM_L_GLOBAL)jmp ftrace_epilogue
SYM_FUNC_END(ftrace_caller);

也就是说现在是这样的:

+--------+    ftrace_caller(start_offset)
|        |
|        |    ftrace_caller_op_ptr(op_offset)
|        |
|  size  |    ftrace_call(call_offset)
|        |
+--------+    ftrace_caller_end(end_offset)
|RET_SIZE|    ftrace_stub    <--ip
+--------+
| void * | ---> graph_ops
+--------+

也就是说,对于本文的schedule是这样的:

<schedule>:
push %rbp
mov %rsp,%rbp
call ftrace_caller
pop %rbp
[…]
ftrace_caller:save regsload args
ftrace_call:call funcrestore regs
ftrace_stub:retq

关于函数ftrace_update_trampoline我们就讲到这,需要说明的是,如果要细讲,还有提多的东西需要说明。

3.6.3. update_ftrace_function函数

该函数会替换func函数,这里我们姑且认为它是func = ftrace_ops_list_func;
同时,该函数中最终会调用追踪函数(链表):

op->func(ip, parent_ip, op, regs);

至此,__register_ftrace_function返回。

接下来就是一系列的使能:

  • ftrace_hash_ipmodify_enable
  • ftrace_hash_rec_enable
  • ftrace_startup_enable

至此,开始返回到用户空间:

  • ftrace_startup返回,
  • register_ftrace_graph返回,
  • graph_trace_init返回,
  • tracer_init返回,
  • tracing_set_tracer返回,
  • tracing_set_trace_write返回,
  • 指令echo function_graph > current_tracer返回。

4. cat trace命令

在内核中kernel\trace\trace.c代码有:

 trace_create_file("trace", 0644, d_tracer,tr, &tracing_fops);

文件操作符为:

static const struct file_operations tracing_fops = {.open       = tracing_open,.read       = seq_read,.write      = tracing_write_stub,.llseek       = tracing_lseek,.release   = tracing_release,
};

2021年5月14日19:02:35,不要意思,要下班了,我要早点下班去,怕有长进着急。有时间在继续完成下面的部分,会在另一篇文章中继续讨论。

5. 蹦床trampoline函数

TODO

5.1. ftrace_stub函数

5.2. ftrace_caller函数

5.3. ftrace_caller_end函数

5.4. ftrace_caller_op_ptr函数

5.5. ftrace_call函数

5.6. ftrace_ops_list_func函数

6. 参考和相关链接

  • 内核注释版代码:https://github.com/Rtoax/linux-5.10.13
  • 《Linux内核 eBPF基础:kprobe原理源码分析:基本介绍与使用》
  • Linux内核 eBPF基础:kprobe原理源码分析:源码分析
  • 《Linux内核:kprobe机制-探测点》
  • 《Linux eBPF:bcc 用法和原理初探之 kprobes 注入》
  • 《Linux内核调试技术——kprobe使用与实现》
  • 《Linux kprobe调试技术使用》
  • linux-5.10.13/Documentation/trace/kprobes.rst
  • https://github.com/tinyclub/markdown-lab/tree/clk-2016-ftrace/slides
  • FTRACE: vmlinux __mcount_loc section
  • ftrace(一)原理简介
  • Linux内核 eBPF基础:ftrace基础
  • GCC(-pg) profile mcount | ftrace基础原理
  • 《ftrace-kernel-hooks-2014-More than just tracing.pdf》
  • 《Ftrace Kernel Hooks: More than just tracing》

附录

参考路径

  • kernel/include/asm-generic/vmlinux.lds.h
  • /sys/kernel/debug/tracing/

register_ftrace_function内核路径

//kernel/kprobes.c
enable_kprobearm_kprobearm_kprobe_ftrace__arm_kprobe_ftraceregister_ftrace_function
//kernel/trace/trace_events.c
// late_initcall(event_trace_self_tests_init);
event_trace_self_tests_initevent_trace_self_test_with_functionregister_ftrace_function(&trace_ops)
//kernel/trace/trace_event_perf.c
perf_ftrace_event_registerperf_ftrace_function_registerregister_ftrace_function
function_trace_inittracing_start_function_traceregister_ftrace_function
func_set_flagregister_ftrace_function
init_irqsoff_tracer() {register_tracer(&irqsoff_tracer);
}
core_initcall(init_irqsoff_tracer);
static struct tracer irqsoff_tracer  = {....init       = irqsoff_tracer_init,...
};
irqsoff_tracer_init__irqsoff_tracer_initstart_irqsoff_tracerregister_irqsoff_functionregister_ftrace_function
stack_trace_sysctlregister_ftrace_function
stack_trace_initregister_ftrace_functiondevice_initcall(stack_trace_init);

Linux内核 eBPF基础:ftrace源码分析:过滤函数和开启追踪相关推荐

  1. linux 内核字符驱动char_dev源码分析

    内核模块中字符驱动代码写的相当精简,提供了字符驱动的各种管理功能主要代码位于fs\char_dev.c文件中,分析该模块代码可以主要从三个方面入手: 字符设备号管理:主要提供设备号注册申请等功能 st ...

  2. Linux内核 eBPF基础:ftrace基础-ftrace_init初始化

    Linux内核 eBPF基础 ftrace基础:ftrace_init初始化 荣涛 2021年5月12日 本文相关注释代码:https://github.com/Rtoax/linux-5.10.13 ...

  3. Linux内核 eBPF基础:kprobe原理源码分析:源码分析

    Linux内核 eBPF基础 kprobe原理源码分析:源码分析 荣涛 2021年5月11日 在 <Linux内核 eBPF基础:kprobe原理源码分析:基本介绍与使用>中已经介绍了kp ...

  4. Linux内核 eBPF基础:kprobe原理源码分析:基本介绍与使用示例

    Linux内核 eBPF基础 kprobe原理源码分析:基本介绍与使用示例 荣涛 2021年5月11日 kprobe调试技术是为了便于跟踪内核函数执行状态所设计的一种轻量级内核调试技术. 利用kpro ...

  5. Linux内核 eBPF基础:Tracepoint原理源码分析

    Linux内核 eBPF基础 Tracepoint原理源码分析 荣涛 2021年5月10日 1. 基本原理 需要注意的几点: 本文将从sched_switch相关的tracepoint展开: 关于st ...

  6. Linux内核 eBPF基础:perf(4)perf_event_open系统调用与用户手册详解

    Linux内核 eBPF基础 perf(4)perf_event_open系统调用与用户手册详解 荣涛 2021年5月19日 本文相关注释代码:https://github.com/Rtoax/lin ...

  7. Linux内核 eBPF基础:perf(1):perf_event在内核中的初始化

    Linux内核 eBPF基础 perf(1):perf_event在内核中的初始化 荣涛 2021年5月12日 本文相关注释代码:https://github.com/Rtoax/linux-5.10 ...

  8. Linux内核 eBPF基础:perf(2):perf性能管理单元PMU的注册

    Linux内核 eBPF基础 perf(2):性能管理单元PMU的注册 荣涛 2021年5月18日 本文相关注释代码:https://github.com/Rtoax/linux-5.10.13 Li ...

  9. Linux下USB suspend/resume源码分析【转】

    转自:http://blog.csdn.net/aaronychen/article/details/3928479 Linux下USB suspend/resume源码分析 Author:aaron ...

最新文章

  1. Spring Cloud 第十一篇:docker部署spring cloud项目
  2. Linux编译动态链接库
  3. Linux服务器安装cuda,cudnn,显卡驱动和pytorch超详细流程
  4. OJ1013: 求两点间距离
  5. APP搜索框的样式素材模板,可临摹的好素材
  6. python将十进制转为二进制_如何用Python将十进制数字转为二进制,以及将二进制转为十六进制?...
  7. html怎么制作表单,HTML如何制作表单
  8. TCRT5000红外反射传感器
  9. 华为无线路由器信道怎么测试软件,华为路由WS5200怎么修改wifi信道
  10. java 省市联动_Java 地区字典之省市区三级联动 (一)
  11. Cisco 3550交换机IOS备份(真实设备演示)
  12. 一步一步实现中后台管理平台模板-13-解决IE浏览器兼容性问题
  13. C++学习45 流成员函数put输出单个字符 cin输入流详解 get()函数读入一个字符
  14. JDK8安装error 1335
  15. 5G/NR SSB与PRACH occasion如何关联?
  16. Win10自动拨号上网设置方法
  17. 《下班后开始新的一天》阅读笔记
  18. 英雄埋骨无人问,戏子家事天下知!
  19. 一座教学楼内的计算机网络系统属于,2006—2007学年第一学期期末考试(计算机网络技术试卷》A...
  20. 前端七十二变之jquery高级

热门文章

  1. VS2010 VS2012版最常用的快捷键
  2. ExecuteNonQuery()方法发即:是指执行非查询SQL命令,如:增、删、改等
  3. websocket替代方案_WebSocket 有没有可能取代 AJAX ?
  4. 它在计算机房的旁边英文,计算机房设备搬迁协议 (中英文)
  5. linux安装Git依赖的包出错,Centos6.7安装编译安装最新Git2.10.1
  6. 为何要进行软件维护?维护的种类及目标?
  7. Solr 新增、更新、删除索引
  8. VisualSVN Server 的安装(windows版本)
  9. Pycharm文档模板变量
  10. Android(java)学习笔记114:Service生命周期