Linux x86_64 dump_stack()函数基于FP栈回溯

文章目录

  • 前言
  • 一、dump_stack函数使用
  • 二、dump_stack函数源码解析
    • 2.1 show_stack
    • 2.2 show_stack_log_lvl
    • 2.3 show_trace_log_lvl
    • 2.4 dump_trace
    • 2.5 print_context_stack
  • 参考资料

前言

Linux x86_64
centos7
Linux:3.10.0

一、dump_stack函数使用

dump_stack函数用于打印当前任务的信息以及其堆栈跟踪,能够用来回溯打印调用栈信息。

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>void noinline func_c(void)
{dump_stack();	
}void noinline func_b(void)
{func_c();	
}void noinline func_a(void)
{func_b();
}//内核模块初始化函数
static int __init lkm_init(void)
{func_a();return 0;
}//内核模块退出函数
static void __exit lkm_exit(void)
{printk("Goodbye\n");
}module_init(lkm_init);
module_exit(lkm_exit);MODULE_LICENSE("GPL");

这里加了noinline修饰,否则会被优化成 inline 函数。

[1109990.858938] Call Trace:
[1109990.858952]  [<ffffffff8e781340>] dump_stack+0x19/0x1b
[1109990.858960]  [<ffffffffc0a3700e>] func_c+0xe/0x10 [helloworld]
[1109990.858968]  [<ffffffffc0a3701e>] func_b+0xe/0x10 [helloworld]
[1109990.858974]  [<ffffffffc0a3702e>] func_a+0xe/0x10 [helloworld]
[1109990.858981]  [<ffffffffc0153009>] lkm_init+0x9/0x1000 [helloworld]
[1109990.858990]  [<ffffffff8e00210a>] do_one_initcall+0xba/0x240
[1109990.858999]  [<ffffffff8e11e45a>] load_module+0x271a/0x2bb0
[1109990.859007]  [<ffffffff8e3b4290>] ? ddebug_proc_write+0x100/0x100
[1109990.859016]  [<ffffffff8e119fe3>] ? copy_module_from_fd.isra.44+0x53/0x150
[1109990.859024]  [<ffffffff8e11ead6>] SyS_finit_module+0xa6/0xd0
[1109990.859033]  [<ffffffff8e793f92>] system_call_fastpath+0x25/0x2a

Linux dump_stack 函数原理:
栈帧如下如图所示:callee的RBP寄存器的值保存caller的RBP寄存器地址,可以看作每个栈帧用单链表连接。

// linux-3.10/arch/x86/include/asm/stacktrace.h/* The form of the top of the frame on the stack */
struct stack_frame {struct stack_frame *next_frame;unsigned long return_address;
};

在这里插入图片描述

帧指针起到了历史上的作用。帧指针是一个寄存器,它始终包含着上一个堆栈指针的值。在 x86_64 架构中,通常使用的寄存器是 RBP。

由于帧指针寄存器的存在,堆栈现在成为了一个“堆栈帧”的链表,我们可以一直沿着链表向前遍历到开头。在任何时刻,我们只需查看当前帧指针寄存器的值,就可以获得先前的 RSP 值。由于先前的 RSP 值恰好是存储先前帧指针的位置,因此这就是一系列指针沿着堆栈向上爬行的过程。

通过遍历堆栈帧链表,我们可以逐个获取每个函数的返回地址、参数和局部变量等信息。这样,我们就可以按顺序打印每个函数的名称,实现堆栈跟踪。

帧指针寄存器的存在使得堆栈帧之间形成了链式结构,使得在堆栈跟踪过程中可以方便地从当前帧指针寄存器获取前一个堆栈帧的位置。通过这种方式,我们可以沿着堆栈链表一直向上遍历,获取所有函数的信息。

帧指针寄存器的使用使得堆栈跟踪变得更加直观和可靠,因为它提供了一种可靠的方式来遍历堆栈帧链表。但是需要注意的是,某些情况下,编译器可能会对帧指针进行优化或省略,因此在特定的编译器优化设置下,帧指针可能不可用或不准确。

centos 7 配置了CONFIG_FRAME_POINTER选项:

# cat /boot/config-3.10.0-1160.el7.x86_64 | grep CONFIG_FRAME_POINTER
CONFIG_FRAME_POINTER=y

(1)基于Frame Pointer - fp寄存器的栈回溯:
优点:栈回溯比较快,理解简单。相对较简单:基于Frame Pointer寄存器的栈回溯通常比解析unwind节更简单直接。
缺点:gcc添加了优化选项 -O 就会省略掉省略基指针。这样就不能都通过这种形式进行栈回溯了。
-fomit-frame-pointer编译标志进行优化:避免将%rbp用作栈帧指针,把FP当作一个通用寄存器,这样就提供了一个额外的通用寄存器,提高程序运行效率。

(1)func_c RBP寄存器的值存放了父函数func_b的RBP寄存器的地址。其返回地址 = func_cRBP寄存器地址+8。
(2)对函数func_b的RBP寄存器的地址取值获取func_b RBP寄存器的值,func_b RBP寄存器的值存放了父函数func_a的RBP寄存器的地址。其返回地址 = func_bRBP寄存器地址+8。
(3)对函数func_a的RBP寄存器的地址取值获取func_a RBP寄存器的值,func_a RBP寄存器的值存放了父函数lkm_init的RBP寄存器的地址。其返回地址 = func_aRBP寄存器地址+8。
这样一步步回溯就可以获取整个调用栈。

在 x86_64 架构中 rbp 指向当前栈帧的起始位置,这个位置保存着旧的 rbp的值。我们可以看到在旧的 rbp 保存的位置上方保存着返回地址(rbp + 8)。这个返回地址是调用者函数中 call 指令的下一条指令的地址,子函数执行完成后会返回,旧的 rbp 首先出栈并赋值给 rbp 寄存器,同时返回地址也要出栈并赋值给 pc。

上面的过程可以递归的用于多层函数调用上。

我们可以将 dump_stack 函数的栈帧看做 Current frame,当前 pc 的值保存的是 dump_stack 中的某条指令的地址,内核先根据这个地址查询 符号表 获取到 dump_stack 函数的名称与当前指令先相对于 dump_stack 函数起始位置的偏移量,然后通过访问 rbp 寄存器指向的旧 rbp 的值来获取到调用 dump_stack 函数的栈帧指针的值,有了这个值就可以不断的回溯上方的栈帧,一个栈帧就是一个调用层次。

同时返回地址的位置就在旧的 rbp 存储位置的上方,根据这样的特点 dump_stack 也就能回溯不同调用层次中返回地址的值。根据返回地址就可以获取到返回地址的上一条调用语句的地址,对该地址进行寻址,获取到指令的编码,就能够获取到调用函数的入口地址。这里可以使用如下公式:

call 指令调用函数的地址 = call 指令码后面的偏移量 + 返回地址

这之后使用入口地址查询 System-map 获取到函数的名称,同时计算出返回地址相对于函数入口的偏移量就准备好了打印的内容,调用打印函数打印信息,每个栈帧用单链表连接,然后继续重复这一过程直到找不到一个合法的栈帧为止。

二、dump_stack函数源码解析

centos 7 配置了CONFIG_FRAME_POINTER选项:

# cat /boot/config-3.10.0-1160.el7.x86_64 | grep CONFIG_FRAME_POINTER
CONFIG_FRAME_POINTER=y
// linux-3.10/lib/dump_stack.c/*** dump_stack - dump the current task information and its stack trace** Architectures can override this implementation by implementing its own.*/
void dump_stack(void)
{dump_stack_print_info(KERN_DEFAULT);show_stack(NULL, NULL);
}
EXPORT_SYMBOL(dump_stack);
dump_stack()-->show_stack()-->show_stack_log_lvl()-->show_trace_log_lvl()-->dump_trace()-->print_context_stack()

2.1 show_stack

// linux-3.10/arch/x86/include/asm/stacktrace.h#define STACKSLOTS_PER_LINE 4
#define get_bp(bp) asm("movq %%rbp, %0" : "=r" (bp) :)#ifdef CONFIG_FRAME_POINTER
static inline unsigned long
stack_frame(struct task_struct *task, struct pt_regs *regs)
{unsigned long bp;if (regs)return regs->bp;if (task == current) {/* Grab bp right from our regs */get_bp(bp);return bp;}/* bp is the last reg pushed by switch_to */return *(unsigned long *)task->thread.sp;
}

get_bp(bp)是一个宏定义,使用汇编语句获取当前函数的基址寄存器(rbp)的值,并将其保存在bp变量中。

stack_frame是一个内联函数,用于获取给定任务的栈帧指针。

(1)如果传入的regs参数非空,说明已经提供了寄存器上下文(pt_regs结构),则直接返回其中的基址寄存器(bp)的值。

(2)如果给定的任务结构体指针与当前任务相同(current表示当前任务),则直接使用get_bp宏获取当前函数的基址寄存器的值(rbp),并将其作为栈帧指针返回。

(3)如果以上条件都不满足,则假设bp是由switch_to函数推入的最后一个寄存器,从给定任务的线程结构体中获取栈指针(sp)所指向的地址,并将其解释为unsigned long类型的指针,以获取栈帧指针。

void show_stack(struct task_struct *task, unsigned long *sp)
{unsigned long bp = 0;unsigned long stack;/** Stack frames below this one aren't interesting.  Don't show them* if we're printing for %current.*/if (!sp && (!task || task == current)) {sp = &stack;bp = stack_frame(current, NULL);}show_stack_log_lvl(task, NULL, sp, bp, "");
}

该函数用于打印给定任务的堆栈跟踪信息。

2.2 show_stack_log_lvl

// linux-3.10/arch/x86/kernel/dumpstack_64.cvoid
show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs,unsigned long *sp, unsigned long bp, char *log_lvl)
{unsigned long *irq_stack_end;unsigned long *irq_stack;unsigned long *stack;int cpu;int i;preempt_disable();cpu = smp_processor_id();irq_stack_end	= (unsigned long *)(per_cpu(irq_stack_ptr, cpu));irq_stack	= (unsigned long *)(per_cpu(irq_stack_ptr, cpu) - IRQ_STACK_SIZE);/** Debugging aid: "show_stack(NULL, NULL);" prints the* back trace for this cpu:*/if (sp == NULL) {if (task)sp = (unsigned long *)task->thread.sp;elsesp = (unsigned long *)&sp;}stack = sp;for (i = 0; i < kstack_depth_to_print; i++) {if (stack >= irq_stack && stack <= irq_stack_end) {if (stack == irq_stack_end) {stack = (unsigned long *) (irq_stack_end[-1]);pr_cont(" <EOI> ");}} else {if (((long) stack & (THREAD_SIZE-1)) == 0)break;}if (i && ((i % STACKSLOTS_PER_LINE) == 0))pr_cont("\n");pr_cont(" %016lx", *stack++);touch_nmi_watchdog();}preempt_enable();pr_cont("\n");show_trace_log_lvl(task, regs, sp, bp, log_lvl);
}

show_stack_log_lvl函数用于打印给定任务的堆栈跟踪信息,并在日志级别上进行控制。

函数首先定义了一些局部变量,包括irq_stack_end、irq_stack、stack、cpu和i。

然后,禁用抢占(preempt_disable)并获取当前处理器的 ID(smp_processor_id)。

irq_stack_end表示中断堆栈的结束地址,irq_stack表示中断堆栈的起始地址(通过per_cpu宏和irq_stack_ptr变量计算得到)。

接下来,通过一系列条件判断,确定要打印的堆栈跟踪信息。

如果给定的sp参数为空,表示需要打印当前任务的堆栈跟踪信息。根据是否提供了任务结构体指针(task),确定要使用的栈指针(sp)。如果提供了任务结构体指针,则使用任务的线程结构体中的栈指针;否则,使用当前函数的栈指针。

接下来,通过循环遍历堆栈,打印堆栈上的地址。在遍历过程中,通过一系列条件判断确定是否处于中断堆栈范围内,并在特定情况下打印(End of Interrupt)标记。如果堆栈地址与线程栈的大小(THREAD_SIZE)对齐,则表示已经遍历到了栈的底部,循环结束。

在每次打印堆栈地址后,调用touch_nmi_watchdog函数,用于触发非屏蔽中断(NMI)看门狗,以确保系统不会因为长时间占用CPU而被认为是死锁。

最后,启用抢占(preempt_enable),打印换行符,然后调用show_trace_log_lvl函数,将任务结构体指针、寄存器上下文、栈指针、栈帧指针和日志级别作为参数传递,继续打印堆栈跟踪信息。

2.3 show_trace_log_lvl

// linux-3.10/arch/x86/kernel/dumpstack.cvoid
show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs,unsigned long *stack, unsigned long bp, char *log_lvl)
{printk("%sCall Trace:\n", log_lvl);dump_trace(task, regs, stack, bp, &print_trace_ops, log_lvl);
}
Call Trace:
[1119269.645012]  [<ffffffff8e781340>] dump_stack+0x19/0x1b
[1119269.645021]  [<ffffffffc0a5000e>] func_c+0xe/0x10 [helloworld]
[1119269.645028]  [<ffffffffc0a5001e>] func_b+0xe/0x10 [helloworld]
[1119269.645034]  [<ffffffffc0a5002e>] func_a+0xe/0x10 [helloworld]
[1119269.645041]  [<ffffffffc0153009>] lkm_init+0x9/0x1000 [helloworld]
[1119269.645049]  [<ffffffff8e00210a>] do_one_initcall+0xba/0x240
[1119269.645059]  [<ffffffff8e11e45a>] load_module+0x271a/0x2bb0
[1119269.645066]  [<ffffffff8e3b4290>] ? ddebug_proc_write+0x100/0x100
[1119269.645075]  [<ffffffff8e119fe3>] ? copy_module_from_fd.isra.44+0x53/0x150
[1119269.645083]  [<ffffffff8e11ead6>] SyS_finit_module+0xa6/0xd0
[1119269.645093]  [<ffffffff8e793f92>] system_call_fastpath+0x25/0x2a

2.4 dump_trace

(1)

// linux-3.10/arch/x86/kernel/dumpstack_64.c/** x86-64 can have up to three kernel stacks:* process stack* interrupt stack* severe exception (double fault, nmi, stack fault, debug, mce) hardware stack*/void dump_trace(struct task_struct *task, struct pt_regs *regs,unsigned long *stack, unsigned long bp,const struct stacktrace_ops *ops, void *data)
{const unsigned cpu = get_cpu();unsigned long *irq_stack_end =(unsigned long *)per_cpu(irq_stack_ptr, cpu);unsigned used = 0;struct thread_info *tinfo;int graph = 0;unsigned long dummy;if (!task)task = current;if (!stack) {if (regs)stack = (unsigned long *)regs->sp;else if (task != current)stack = (unsigned long *)task->thread.sp;elsestack = &dummy;}if (!bp)bp = stack_frame(task, regs);/** Print function call entries in all stacks, starting at the* current stack address. If the stacks consist of nested* exceptions*/tinfo = task_thread_info(task);for (;;) {char *id;unsigned long *estack_end;estack_end = in_exception_stack(cpu, (unsigned long)stack,&used, &id);if (estack_end) {if (ops->stack(data, id) < 0)break;bp = ops->walk_stack(tinfo, stack, bp, ops,data, estack_end, &graph);ops->stack(data, "<EOE>");/** We link to the next stack via the* second-to-last pointer (index -2 to end) in the* exception stack:*/stack = (unsigned long *) estack_end[-2];continue;}if (irq_stack_end) {unsigned long *irq_stack;irq_stack = irq_stack_end -(IRQ_STACK_SIZE - 64) / sizeof(*irq_stack);if (in_irq_stack(stack, irq_stack, irq_stack_end)) {if (ops->stack(data, "IRQ") < 0)break;bp = ops->walk_stack(tinfo, stack, bp,ops, data, irq_stack_end, &graph);/** We link to the next stack (which would be* the process stack normally) the last* pointer (index -1 to end) in the IRQ stack:*/stack = (unsigned long *) (irq_stack_end[-1]);irq_stack_end = NULL;ops->stack(data, "EOI");continue;}}break;}/** This handles the process stack:*/bp = ops->walk_stack(tinfo, stack, bp, ops, data, NULL, &graph);put_cpu();
}
EXPORT_SYMBOL(dump_trace);

dump_trace函数用于在给定任务的堆栈上进行跟踪,并通过提供的回调函数执行相应的操作。

函数首先定义了一些局部变量,包括cpu、irq_stack_end、used、tinfo和graph,以及一个dummy变量。

然后,根据情况,确定要跟踪的任务和堆栈的起始地址。如果没有给定任务,则默认使用当前任务。如果没有给定堆栈地址,则根据情况选择使用寄存器上下文的栈指针、任务的线程结构体中的栈指针,或者一个临时变量作为栈指针。

接下来,如果没有给定基指针(bp),则通过调用stack_frame函数计算基指针。

在一个无限循环中,函数根据堆栈的类型进行处理。首先,通过调用in_exception_stack函数检查堆栈是否属于异常堆栈(如双重故障、NMI、堆栈故障、调试、MCE等),并获取异常堆栈的结束地址(estack_end)以及用于标识堆栈的字符串(id)。

如果堆栈属于异常堆栈(接上文)

如果堆栈属于异常堆栈,将调用回调函数ops->stack(data, id)打印堆栈标识符,并通过调用ops->walk_stack函数执行堆栈的遍历操作。然后,再次调用ops->stack(data, “”)打印异常堆栈的结束标识符。之后,通过异常堆栈的倒数第二个指针(索引为-2)获取下一个堆栈的起始地址,并继续下一轮循环。

如果堆栈不属于异常堆栈,将检查是否存在中断堆栈(IRQ stack)。如果存在中断堆栈,将通过调用in_irq_stack函数判断当前堆栈是否属于中断堆栈,并获取中断堆栈的起始地址。如果当前堆栈属于中断堆栈,则与异常堆栈类似,调用回调函数打印中断标识符,并通过ops->walk_stack函数执行中断堆栈的遍历操作。然后,通过中断堆栈的最后一个指针(索引为-1)获取下一个堆栈的起始地址,并继续下一轮循环。

如果既不是异常堆栈也不是中断堆栈,表示已经遍历完所有堆栈,退出循环。

最后,通过调用ops->walk_stack函数处理进程堆栈,并完成整个跟踪过程。最后,调用put_cpu()释放当前CPU的引用计数。

该函数使用了一些其他函数和数据结构,例如task_thread_info函数用于获取线程信息,stack_frame函数用于计算基指针,in_exception_stack和in_irq_stack函数用于判断堆栈类型。回调函数ops->stack用于打印堆栈标识符,回调函数ops->walk_stack用于执行堆栈的遍历操作。

(2)

// linux-3.10/arch/x86/include/asm/stacktrace.h/* Generic stack tracer with callbacks */struct stacktrace_ops {void (*address)(void *data, unsigned long address, int reliable);/* On negative return stop dumping */int (*stack)(void *data, char *name);walk_stack_t	walk_stack;
};
/** x86-64 can have up to three kernel stacks:* process stack* interrupt stack* severe exception (double fault, nmi, stack fault, debug, mce) hardware stack*/static inline int valid_stack_ptr(struct thread_info *tinfo,void *p, unsigned int size, void *end)
{void *t = tinfo;if (end) {if (p < end && p >= (end-THREAD_SIZE))return 1;elsereturn 0;}return p > t && p < t + THREAD_SIZE - size;
}unsigned long
print_context_stack(struct thread_info *tinfo,unsigned long *stack, unsigned long bp,const struct stacktrace_ops *ops, void *data,unsigned long *end, int *graph)
{struct stack_frame *frame = (struct stack_frame *)bp;while (valid_stack_ptr(tinfo, stack, sizeof(*stack), end)) {unsigned long addr;addr = *stack;if (__kernel_text_address(addr)) {if ((unsigned long) stack == bp + sizeof(long)) {ops->address(data, addr, 1);frame = frame->next_frame;bp = (unsigned long) frame;} else {ops->address(data, addr, 0);}print_ftrace_graph_addr(addr, data, ops, tinfo, graph);}stack++;}return bp;
}
EXPORT_SYMBOL_GPL(print_context_stack);static int print_trace_stack(void *data, char *name)
{printk("%s <%s> ", (char *)data, name);return 0;
}void printk_address(unsigned long address, int reliable)
{pr_cont(" [<%p>] %s%pB\n",(void *)address, reliable ? "" : "? ", (void *)address);
}/** Print one address/symbol entries per line.*/
static void print_trace_address(void *data, unsigned long addr, int reliable)
{touch_nmi_watchdog();printk(data);printk_address(addr, reliable);
}static const struct stacktrace_ops print_trace_ops = {.stack			= print_trace_stack,.address		= print_trace_address,.walk_stack		= print_context_stack,
};

2.5 print_context_stack

/* The form of the top of the frame on the stack */
struct stack_frame {struct stack_frame *next_frame;unsigned long return_address;
};
/** x86-64 can have up to three kernel stacks:* process stack* interrupt stack* severe exception (double fault, nmi, stack fault, debug, mce) hardware stack*/static inline int valid_stack_ptr(struct thread_info *tinfo,void *p, unsigned int size, void *end)
{void *t = tinfo;if (end) {if (p < end && p >= (end-THREAD_SIZE))return 1;elsereturn 0;}return p > t && p < t + THREAD_SIZE - size;
}unsigned long
print_context_stack(struct thread_info *tinfo,unsigned long *stack, unsigned long bp,const struct stacktrace_ops *ops, void *data,unsigned long *end, int *graph)
{struct stack_frame *frame = (struct stack_frame *)bp;while (valid_stack_ptr(tinfo, stack, sizeof(*stack), end)) {unsigned long addr;addr = *stack;if (__kernel_text_address(addr)) {if ((unsigned long) stack == bp + sizeof(long)) {ops->address(data, addr, 1);frame = frame->next_frame;bp = (unsigned long) frame;} else {ops->address(data, addr, 0);}print_ftrace_graph_addr(addr, data, ops, tinfo, graph);}stack++;}return bp;
}
EXPORT_SYMBOL_GPL(print_context_stack);

print_context_stack函数用于在给定线程的堆栈上打印函数调用的地址,并通过提供的回调函数执行相应的操作。

函数首先定义了局部变量frame,它是一个指向struct stack_frame类型的指针,用于表示帧结构。

然后,使用一个循环遍历堆栈中的每个地址。在每次循环迭代中,函数检查堆栈指针是否有效,并获取当前堆栈指针处的地址。

如果地址属于内核文本空间(通过__kernel_text_address函数判断),则进行以下操作:

  1. 如果当前堆栈指针等于基指针加上一个long大小,表示该地址是当前函数调用的返回地址。在这种情况下,将调用回调函数ops->address(data, addr, 1)打印地址,并更新帧结构和基指针,使其指向上一帧的基指针。
  2. 如果当前堆栈指针不等于基指针加上一个long大小,表示该地址是普通的函数调用地址。在这种情况下,将调用回调函数ops->address(data, addr, 0)打印地址。
  3. 最后,调用print_ftrace_graph_addr函数打印与地址相关的ftrace图形信息。

在每次循环迭代后,将堆栈指针指向下一个地址。

最后,函数返回更新后的基指针。

参考资料

Linux 3.10.0

https://blogs.oracle.com/linux/post/unwinding-stack-frame-pointers-and-orc
https://blog.csdn.net/Longyu_wlz/article/details/103327538

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大模型prompt实例:知识库信息质量校验模块

大模型相关目录 大模型&#xff0c;包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步&#xff0c;扬帆起航。 大模型应用向开发路径&#xff1a;AI代理工作流大模型应用开发实用开源项目汇总大模…

DRF 纯净版创建使用

【一】介绍 &#xff08;1&#xff09;使用原因 在Django中&#xff0c;contrib 包包含了许多内置的app和中间件&#xff0c;如auth、sessions、admin等&#xff0c;这些app在创建新的Django项目时默认是包含在内的。然而&#xff0c;在开发RESTful API时&#xff0c;可能不需…

linux性能监控之lsof

lsof&#xff1a;list open files&#xff0c;显示所有打开的文件以及进程信息&#xff0c;我们通常用来检查特定的文件被哪些进程打开 [rootk8s-master ~]# lsof --help lsof: illegal option character: - lsof: -e not followed by a file system path: "lp" lso…

网络传输,请每次都开启 TCP_NODELAY

原文&#xff1a;Marc Brooker - 2024.05.09 &#xff08;注&#xff1a;不必过于担心这个问题&#xff0c;大部分现代库&#xff0c;语言&#xff08;如 Go&#xff09;&#xff0c;代理&#xff08;如 Envoy&#xff09;&#xff0c;都默认设置了 TCP_NODELAY。如果遇到网络…

光耦 IS314W中文资料 IS314W引脚图及功能说明

IS314W是一款IGBT/MOSFET输出型光耦&#xff0c;由Isocom公司制造。它主要用于驱动用于电机控制和电源系统变频器的功率IGBT和MOSFET。以下是该产品的部分功能和参数&#xff1a; - 两个独立的光耦输出通道 - 轨对轨输出电压 - 最大峰值输出电流&#xff1a;1.0A - 最小峰值输…

DDoS攻击揭秘与网站防护策略

DDoS攻击&#xff08;分布式拒绝服务攻击&#xff09;是一种利用大量被控制的计算机或智能设备&#xff08;如僵尸网络&#xff09;对目标网站或服务器发起大量无效请求或数据流量&#xff0c;从而导致目标系统资源耗尽、服务崩溃或无法处理正常请求的攻击方式。这种攻击通常是…

【cpp】并发多线程 Unique

1. unique_lock 何时锁定资源。 unique_lock lock1 时候&#xff0c;还没有锁住资源。 实际是后面&#xff0c;显式的出发&#xff1a; 比如&#xff0c; lock.lock, 或 std::lock(lk1,lk2), 或者条件变量CV.wait(mtx, []{!re})。 #include <iostream> #include <mu…

《大数据分析-数据仓库项目实战》学习笔记

目录 基本概念 数据仓库 数据仓库整体技术架构 数据仓库主题 数据集市 数据仓库的血缘关系 数据仓库元数据管理 数据仓库的指标 数据仓库维度概念 HDFS Flume Hadoop Kafka 数据仓库分层模型 Superset 即席查询 Sqoop Atlas元数据管理 项目需求描述 系统目标…

python 对矩阵与矩阵之间对应位置的元素,做softmax操作,代码实战

1.对矩阵中对应位置的元素&#xff0c;做softmax 对于一个向量&#xff0c;softmax函数会对其中每一个元素进行指数运算&#xff0c;然后除以所有元素指数和的结果。当将其应用到多个矩阵的相应位置上时&#xff0c;我们实际上是在对每个位置的一组数&#xff08;从各个矩阵的同…

pyqt学习过程中的问题

1&#xff0c; 2. 3.传递参数&#xff1a; 第二个函数缺少参数self, 第三种方法&#xff1a;可以使用 lambda 表达式 # 连接按钮的点击信号到槽函数&#xff0c;传递一个参数 self.button.clicked.connect(lambda: self.onButtonClicked(10))def onButtonClicked(self, value)…

基于ChatGPT 和 OpenAI 模型的现代生成式 AI

书籍&#xff1a;Modern Generative AI with ChatGPT and OpenAI Models: Leverage the capabilities of OpenAIs LLM for productivity and innovation with GPT3 and GPT4 作者&#xff1a;Valentina Alto 出版&#xff1a;Packt Publishing 书籍下载-《基于ChatGPT 和 Op…

Taro 快速开始

大家好我是苏麟 , 今天聊聊Trao. 官网 : Taro 介绍 | Taro 文档 (jd.com) 点击快速开始 全局安装 CLI 初始化一个项目 选择配置 : 根据自己需求选择 安装失败先不用管 , 用前端工具打开项目 npm install 安装 , 显示安装失败 怎么解决 ? : 查看报错信息 百度 , 问 AI 工具 运…

【Docker系列】Linux部署Docker Compose

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

前端无样式id或者class等来定位标签

目录&#xff1a; 1、使用背景2、代码处理 1、使用背景 客户使用我们产品组件&#xff0c;发现替换文件&#xff0c;每次替换都会新增如下的样式&#xff0c;造就样式错乱&#xff0c;是组件的文件&#xff0c;目前临时处理的话就是替换文件时删除新增的样式&#xff0c;但是发…

【C++11】std::async函数介绍及问题梳理

目录 &#x1f31e;1. std::async 简介 &#x1f31e;2. 问题梳理 &#x1f30a;2.1 std::async 到 future get 直接调用会如何抛异常 &#x1f30a;2.2 std::async 如果通过劫持让 new 内存不够&#xff0c;有没有可能抛异常 &#x1f30a;2.3 std::async 如果系统线程不…

电商平台接口自动化框架实践||电商API数据采集接口

电商数据采集接口 语言&#xff1a;python 接口自动化实现流程 红色为可实现/尚未完成 绿色为需要人工干预部分 自动生成测试用例模板&#xff08;俩种方式二选一&#xff09;&#xff1a; mimproxy&#xff0c;通过浏览器代理抓包方式&#xff0c;访问 H5 或者 web 页面&a…

万物生长大会 | 创邻科技再登杭州准独角兽榜单

近日&#xff0c;由民建中央、中国科协指导&#xff0c;民建浙江省委会、中国投资发展促进会联合办的第八届万物生长大会在杭州举办。 在这场创新创业领域一年一度的盛会上&#xff0c;杭州市创业投资协会联合微链共同发布《2024杭州独角兽&准独角兽企业榜单》。榜单显示&…

【强化学习-Mode-Free DRL】深度强化学习如何选择合适的算法?DQN、DDPG、A3C等经典算法Mode-Free DRL算法的四个核心改进方向

【强化学习-DRL】深度强化学习如何选择合适的算法&#xff1f; 引言&#xff1a;本文第一节先对DRL的脉络进行简要介绍&#xff0c;引出Mode-Free DRL。第二节对Mode-Free DRL的两种分类进行简要介绍&#xff0c;并对三种经典的DQL算法给出其交叉分类情况&#xff1b;第三节对…

Tomcat的实现

在一台电脑上启动tomcat&#xff0c;tomcat是server&#xff0c;即服务器。服务器只会被实例化一次&#xff0c;tomcat这只猫就是服务器。服务器下包含多个子节点服务&#xff0c;即service&#xff0c;顾名思义就是对外提供服务。服务器通常只有一个服务&#xff0c;默认是卡特…

申请免费的域名证书

免费域名证书主要是由一些证书颁发机构&#xff08;CA&#xff09;提供的&#xff0c;用于为网站启用HTTPS加密的数字证书&#xff0c;目的是保障网站数据传输的安全性。这些证书的特点和获取途径如下&#xff1a; 功能与目的&#xff1a;免费域名证书能够帮助网站实现基本的加…