rustdesk编译修改名字
最近,我用Rust重写了一个2W+行C代码的linux内核模块。在此记录一点经验。我此前没写过内核模块,认识比较疏浅,有错误欢迎指正。
为什么要重写?
这个模块2W+行代码量看起来不多,却在线上时常故障,永远改不完。十多年的老代码,经手了无数程序员,没人能解决其中的内存安全问题。拿过来一看,代码中的确有不少会产生UB的写法,线上的故障从core来看都飘得太远,难以定位根本原因在哪里。所以我没有把握(没有能力)在原代码基础上能将所有线上故障修复。 而Rust是一个现代的、高性能、无GC、内存安全的编程语言,我想它非常适合用来重写这个内核模块。
Hello World
首先来介绍下如何用Rust写linux内核模块吧。也可以参考这里, 该项目正在尝试写一个safe的rust内核框架,目前的状态还不实用,我没使用该框架,仅参考了其基本编译配置。
基本思路就是分别建立一个linux内核c工程和rust的hello world工程,把它们放到一块儿(不放到一块儿也行),文件分布如下:
├── Cargo.toml
├── Makefile
├── mydriver.c
└── src└── lib.rs
然后在linux内核模块的入口和出口函数分别调用rust中实现的入口和出口函数,rust中将入口、出口函数标记为extern "C",所有业务逻辑在Rust中完成。
// mydriver.c
// ... include headersextern int my_drv_init(void); // defined in rust
extern void my_drv_exit(void); // defined in ruststatic int _my_drv_init(void)
{printk("loading my drivern");return my_drv_init();
}static void _my_drv_exit(void)
{printk("exiting my drivern");my_drv_exit();
}module_init(_my_drv_init);
module_exit(_my_drv_exit);
// lib.rs
#[no_mangle]
pub extern "C" fn my_drv_init() -> i32 {KLogger::install();info!("loading my driver in rust");0
}#[no_mangle]
pub extern "C" fn my_drv_exit() {info!("exiting my driver in rust");
}
Cargo.toml中需要配置输出staticlib:
[lib]
name = "mydriver"
crate-type = ["staticlib", "rlib"]
模块的Makefile调用cargo编译rust库,然后将其和c一块儿链接成ko,大概这个样子:
MODNAME = mydriverKDIR ?= /lib/modules/$(shell uname -r)/build
BUILD_TYPE = release
LIB_DIR = target/$(ARCH)-linux-kernel/$(BUILD_TYPE)all:$(MAKE) -C $(KDIR) M=$(CURDIR)clean:$(MAKE) -C $(KDIR) M=$(CURDIR) cleanrm -rf targetrlib:# 目前需要nightly才能编译core和alloc.cargo +nightly build --$(BUILD_TYPE) -Z features=dev_dep,build_dep -Z build-std=core,alloc --target=$(ARCH)-linux-kernelobj-m := $(MODNAME).o$(MODNAME)-objs := mydriver.o mydriver.rust.o.PHONY: $(src)/lib$(MODNAME).a
$(src)/lib$(MODNAME).a:cd $(src); make rlibcd $(src); cp $(LIB_DIR)/lib$(MODNAME).a .%.rust.o: lib%.a$(LD) -r -o $@.tmp --whole-archive $<$(src)/plt2pc.py $@.tmp $@
可行性评估
用Rust写linux内核模块还是有些担忧,目前还没看到Rust内核模块相关的严肃开源项目,Demo倒是有两个。动手之前,咱们还是尽可能评估一下可行性。之前有了解到有工具C2Rust可以将C代码转换成Rust代码,所以,我的想法是先用C2Rust将原有C代码转成Rust,看能不能编译跑起来,各功能是否正常,看看有没有什么硬伤。如果能正常使用,则可以在转出的代码的基础上逐渐将unsafe rust重构为safe rust。
C2Rust工作流
按照C2Rust相关文档操作下来,遇到几个问题:
转换时内核头文件的时候报错。
/usr/src/kernels/.../arch/x86/include/asm/jump_label.h:16:2: error: 'asm goto' constructs are not supported yetasm_volatile_goto("1:"^
include/linux/compiler-gcc4.h:79:43: note: expanded from macro 'asm_volatile_goto'
# define asm_volatile_goto(x...) do { asm goto(x); asm (""); } while (0)
据C2Rust文档介绍,需要最新的libclang才能支持此语法。
2. 转换后的代码编译报错。
编译错误大致分为memcpy宏、内联汇编错误、依赖libc crate几类。
以上错误中,libc的依赖仅仅使用了libc中定义的一些C语言基本类型,因此,可以写一个简单的libc crate替代。其它错误均通过临时修改内核头文件,将不支持的语法define成其他替代品规避。
3. 编译成功后的ko文件加载报错。
加载ko报如下错误:
insmod: ERROR: could not insert module mp.ko: Invalid module format
dmesg显示:
Unknown rela relocation: 4
这是由于Rust编译器(LLVM)生成的二进制中对于extern “C”函数的访问,采用的是R_X86_64_PLT32标记重定位,Linux4.15内核开始支持此标记,而我们使用的3.x内核仅支持R_X86_64_PC32标记。内核中相应提交可以看出内核对这两个标记是无区别对待的:
"PLT32 relocation is used as marker for PC-relative branches. Becauseof EBX, it looks odd to generate PLT32 relocation on i386 when EBXdoesn't have GOT.As for symbol resolution, PLT32 and PC32 relocations are almostinterchangeable. But when linker sees PLT32 relocation against aprotected symbol, it can resolved locally at link-time since it isused on a branch instruction. Linker can't do that for PC32relocation"but for the kernel use, the two are basically the same, and thiscommit gets things building and working with the current binutilsmaster - Linus
因此,我们可以简单地将编译出的二进制文件中的PLT32标记替换为PC32就能解决此问题。readelf命令可以帮我们找出这些标记都在什么位置,故甚至都不需要了解elf文件结构,可以写脚本完成替换:
#!/usr/bin/env pythonimport sys
import os
import repy3 = sys.version_info.major >= 3def get_relocs(filename):"""readelf output:Relocation section '.rela.text' at offset 0x1e8 contains 1 entry:Offset Info Type Sym. Value Sym. Name + Addend
00000000000a 000a00000002 R_X86_64_PC32 0000000000000000 hello - 4Relocation section '.rela.eh_frame' at offset 0x200 contains 1 entry:Offset Info Type Sym. Value Sym. Name + Addend
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0"""relocs = []sec = ''idx = 0os.environ["LANG"] = ''f = os.popen('readelf -r "%s"' % filename)while True:line = f.readline()if not line:breakif line.startswith('Relocation section'):arr = re.findall(r'0x[0-9a-f]*', line)sec = int(arr[0], base=16)idx = 0f.readline()continueoff = idx * 24 + 8idx += 1arr = line.strip().split()[:4]if len(arr) != 4:continueoffset, info, typ, val = arrif typ != 'R_X86_64_PLT32':continuerelocs.append((sec, off, val))return relocsdef main():PLT32 = 4 if py3 else 'x04'PC32 = 2 if py3 else 'x02'infile = sys.argv[1]outfile = sys.argv[2] if len(sys.argv) == 3 else infileobj = list(open(infile, 'rb').read())for sec, offset, val in get_relocs(infile):goff = sec + offsetassert obj[goff] == PLT32obj[goff] = PC32out_bin = bytes(obj) if py3 else ''.join(obj)open(outfile, 'wb').write(out_bin)if __name__ == '__main__':main()
解决了reloc问题后模块就能正常加载了,且经测试,各项功能均和原版相同,连bug都一样。至此,我们用C2Rust完成了一个和原模块等效的Rust版本。如此顺利且真的等效有些出乎意料,相比其他语言中类似的工具(往往需要大量修改转换后源代码才能编译且很难做到等效),C2Rust还是很给力的(用C2Rust转换的代码包含2W+行模块主体代码和8W行的第三方库)。
用Rust重写
重构unsafe的痛
正如预期,用C2Rust转出来rust没有safe代码,一律unsafe。我们需要将其重构为safe代码。简短地实践下来,发现重构转换出的代码非常痛苦。
例1,C中的宏调用会被展开,大部分宏展开的结果非常难看,这也直接导致生成的代码行数膨胀为原版的3-4倍。如,原版代码是这样:
do_something(ntohl(info->port), ntohl(info->event));
转换后变成这样:
do_something(if 0 != 0 {(((*info).port &0xff as libc::c_ulong as __u32) <<24 as libc::c_int |((*info).port &0xff00 as libc::c_ulong as __u32)<< 8 as libc::c_int |((*info).port &0xff0000 as libc::c_ulong as__u32) >> 8 as libc::c_int) |((*info).port &0xff000000 as libc::c_ulong as__u32) >> 24 as libc::c_int} else { __fswab32((*info).port) },if 0 != 0 {(((*info).event &0xff as libc::c_ulong as __u32) <<24 as libc::c_int |((*info).event &0xff00 as libc::c_ulong as __u32)<< 8 as libc::c_int |((*info).event &0xff0000 as libc::c_ulong as__u32) >> 8 as libc::c_int) |((*info).event &0xff000000 as libc::c_ulong as__u32) >> 24 as libc::c_int} else { __fswab32((*info).event) });
例2, 大量的类型强转,让人看不清代码逻辑。如:
Temp0 = do_something(Koeff0, Vk1_0 << 1 as libc::c_int) - Vk2_0 +*arraySamples.offset(ii as isize) as libc::c_int;
Temp1 = Temp1 as __s16 as libc::c_int * Vk2_1 as __s16 as libc::c_int;
每去除一个强转,都要去斟酌一下是不是和原版等效的(c2rust之所以这么写,是为了和C中默认的类型提升规则等效)。
例3,每个c文件对应转换出一个独立的rs文件,包括C中引用的头文件中的各种声明和类型定义,都独立地在每个rs文件中重复、乱序地定义一份,难以整合。
例4,Rust不支持goto语句,于是c2rust用许多的if/else来模拟c中goto语句,我是比较佩服这么机智的处理方法,但是要重构它就难以看清了。
......
当然,c2rust有个refactor命令,里面许多实验性的工具来帮助减轻重构的负担,包括上面遇到的问题,不过使用下来感觉这些工具都不成熟,比较难用。于是,还是决定参照原版功能逻辑,重写一个吧。
垫脚层
rust程序要在内核工作少不了要和内核交互,这就需要ffi调用内核的一些“API”来完成特定工作。内核的API都声明在内核头文件中,理论上我们可以用rust-bindgen直接输出kernel-bindings.rs来使用这些API。
实践中,一方面,有少部分的类型bind后无法编译;另一方面,由于内核头文件有大量的参数宏和static inline函数,这些API目前无法通过rust-bindgen完成绑定,使得rust-bindgen的意义大大缩减。c2rust倒是可以处理static inline函数,但是c2rust目前绑死到了特定nightly版本上才能用。因此,我还是决定对要用到的内核函数封装一个垫脚层ksys.c中转一下,使用rust-bindgen绑定ksys.h,这样会比较简单稳定。例如,memcpy的绑定:
原始定义:
#define memcpy(dst, src, len)
({ size_t __len = (len); void *__ret; if (__builtin_constant_p(len) && __len >= 64) __ret = __memcpy((dst), (src), __len); else __ret = __builtin_memcpy((dst), (src), __len); __ret;
ksys.h中:
void *ksys_memcpy(void *dest, const void *src, size_t n);
ksys.c中:
void *ksys_memcpy(void *dst, const void *src, size_t n) {return memcpy(dst, src, n);
}
binding结果:
extern "C" {pub fn ksys_memcpy(dest: *mut c_types::c_void,src: *const c_types::c_void,n: usize,) -> *mut c_types::c_void;
这样实现会导致Rust编译器不能inline这些函数,从而对性能有一定影响,后续等rust-bindgen完善了再切换过去。
造轮子
内核态写rust没有标准库可用,因此,需要造一些基础设施的轮子,以及内核API函数的安全封装。包括lock、channel、fs、net、thread、timer、logger等。当然,不造这些轮子也能实现功能,需要的地方直接调用内核API来完成相关功能就好了...这样的话,干嘛还用Rust呢?造轮子是常规操作,有大量crate可参考,就不细说了,channel部分遇到一个小坑,后文讲述。
栈溢出
程序写完运行起来遇到的第一个坑是栈溢出,Linux内核线程的栈很小(x86上16KB),容易溢出。debug编译模式就不说了,一句带格式的log就能把栈爆掉。我就只讲一下release模式,release编译的程序编译器会尽可能地优化栈空间的使用,也正是因为编译器的优化的存在,我们要从代码中肉眼找出栈空间使用的最深路径变得困难。幸运的是嵌入式工作组的老大@japaric开发了一个不起眼的工具cargo-call-stack专门用来分析栈空间的使用情况,效果如下图:
cargo call-stack 输出
利用该工具,我们可以一瞬间找出栈使用最深点和量,然后顺腾摸瓜在代码中逐个优化掉。
至于哪些写法会影响编译器对栈的优化,我没有太细致的总结,就简短写一点吧。不用cargo-call-stack我们可以按照类似下面这样写来分析各种写法对编译优化的影响:
#![feature(test)]
#![feature(box_syntax)]use std::hint::black_box;static mut BOTTOM: usize = 0;#[inline(never)]
fn anchor_bottom() {let mut v = 0;unsafe { BOTTOM = (&mut v) as *mut i32 as _ };
}#[inline(never)]
fn depth() -> usize {let mut v = 0;unsafe { BOTTOM - ((&mut v) as *mut i32 as usize) }
}fn main() {anchor_bottom(); // 标定栈底test_entry();
}#[inline(never)]
fn test_entry() {// 在这里测试各种写法的影响let mut msg = Message::new();println!("stack size = {}", depth());black_box(&msg); // 防止编译器认为msg无用而整体优化掉了。
}struct Message {id: usize,data: [u8; 1000],
}impl Message {// inline影响探针的功能,禁掉#[inline(never)]fn new() -> Self {let mut msg = Self::default();println!("stack size in new = {}", depth());msg}
}
执行上面的代码执行结果:
// debug编译:
stack size in new = 2320
stack size = 1152
// release编译:
stack size in new = 1200
stack size = 1104
说明release下new里面的msg变量栈使用被优化了,Self::default()的返回值直接放到了test_entry这帧的msg里面。
这里主要想说两点:
Box::new(value)会先把value放到栈上,然后copy进堆里面,使用unstable的box关键字可以解决。
fn test_entry() {let mut v = Box::new(Message::new());println!("stack size = {}", depth());black_box(&v);
}
// output:
// stack size = 1056
换成box:
fn test_entry() {let mut v = box Message::new();println!("stack size = {}", depth());black_box(&v);
}
// output:
// stack size = 96
把栈变量的地址传给ffi函数会阻止编译器优化该变量,例如,上面的new改成:
fn new() -> Self {let mut msg = Self::default();black_box(&msg);println!("stack size in new = {}", depth());msg}
则会变成:
stack size in new = 2224
cargo-call-stack番外
cargo-call-stack并不能拿来即用,安装一执行便报一行30MB的错误(没错,一行,30M):
Failure(("define internal fastcc void @_ZN3std10sys_common9backtrace28__rust_begin_short_backtrace17ha028a22ae68de0a6E(i8* ......
这是由于call-stack通过分析llvm IR来获得所有函数的调用关系,从而构图计算评估栈空间。而有些IR语法它并不能识别(工具太小众了照顾不全),只好自己动手添加不识别的语法支持,对于我遇到的几个不支持的语法,我已添加并提交了PR。
修完语法问题后就能输出call-stack图了,然而并没有得到其主页介绍的那美美的图片,得到的是这样:
实践中cargo call-stack的输出
节点太多,根本无法动弹,换了几个软件均没有理想的查看效果。那就自己动手吧,给call-stack添加一个tui前端,这样浏览起来就方便多了:
添加的cargo call-stack的tui前端
Rust的函数没有颜色
在支持类协程(如Rust的async/await)编程语言中存在这样一个问题:协程(async)函数中要避免调用阻塞函数,否则会影响协程的调度。而实践中编译器往往没有做到编译时检查出协程中调用阻塞而给出提示,完全依靠人小心避免。Rust社区有尝试从各种角度解决此问题,比如这里,这里,还有这里,目前没有什么进展。有人用函数的颜色来描述讨论此问题。
而到了内核里,类似的问题就更加凸显出来。
例如,在内核态,在中断上下文、获得spinlock等场景下不允许程序休眠(放弃CPU),否则会导致死锁或影响系统性能。和用户态的区别是用户态用错了影响一个服务的性能,而内核里用错了会整个系统垮掉。中断和spinlock都是写内核态程序常常要面对的,而内核的API中会sleep的函数里遍地都是,并且不会像用户态的libc有清晰规范的文档,这就导致完全依靠人为小心避免变得更困难。如果rust有某种机制,在编译时禁止或提示这类危险上下文调用某种颜色的函数是不是会更好呢?
又例如,这次我踩到的一个坑:我一开始便使用spin这个crate实现了一个channel用于线程间通信,使用前还专门看了issue,安全审计团队对这个crate的安全性审计过了,因此比较放心。我把这个channel用在了定时器中给一个服务线程发消息,程序跑起来后就发现时而卡死(死锁)。看内核文档得知spinlock用于中断上下文是有文章的,道理很简单,内核态一个线程随时可能被中断服务程序中断了,去处理更紧急的事情,但如果被中断的线程正拿着一个锁,而此时中断服务也试图去获取同一个锁就会导致死锁。内核文档的描述:
The reasons you mustn't use these versions if you have interrupts that
play with the spinlock is that you can get deadlocks:spin_lock(&lock);...<- interrupt comes in:spin_lock(&lock);
解决办法就是如果中断程序里面要获取一个锁,则所有获取该锁的代码都要先屏蔽中断,然后再去拿锁。内核中因此将spinlock的api分为了几组:
void spin_lock(spinlock_t *lock);
void spin_lock_irq(spinlock_t *lock);
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
其中后两个是会屏蔽中断的,而Rust的spin crate并不会屏蔽中断,因此导致死锁。因此,放弃spin crate,封装一个内核版本spin解决了此问题。如果rust有某种机制,在编译时禁止或提示中断上下文中获取没有屏蔽中断的锁是不是会更好呢?
有些语言中有Effect System来解决这类问题,例如nim语言允许我们对函数标记额外的副作用:
type IO = object # 定义IO副作用
proc readLine(): string {.tags: [IO].} = discard # 标记readLine函数具有IO副作用proc no_IO_please() {.tags: [].} = # 标记此函数不允许IO副作用# 编译器将拒绝此行代码let x = readLine()
避免感觉语法怪异,我将其翻译为rust风格的伪代码:
struct IO; // 定义IO副作用
#[tags([IO])] // 标记readline函数具有IO副作用
fn readline() -> String {todo!()
} #[tags([])] // 标记此函数不允许IO副作用
fn no_IO_please() {let x = readline(); //编译器将拒绝此行代码...
}
目前Rust里面函数只有safe/unsafe两种颜色,没有更多色深,感觉有些单调。Rust大佬们的讨论中也提到了此特性,但目前的情况看,应该短期不会有进展。
不过好在实践(我的)过程中,无论是中断还是spinlock上下文,代码都会非常简短,影响没那么大。只要脑子里知道这个知识点,一般就不会再出差错了。
多姿的内存分配函数
内核中为了提高效率,有各式各样堆内存分配函数选择,大块的/小块的、是否保证物理连续、是否会sleep、是否触碰文件系统......。不同的场景需要使用不同的API来分配堆内存。来瞧一瞧:
void *kmalloc(size_t size, gfp_t flags);
void *kcalloc(size_t n, size_t size, unsigned int __nocast gfp_flags);
void *kzalloc(size_t size, unsigned int __nocast gfp_flags);
void *vmalloc(unsigned long size);
void *kvmalloc(size_t size, gfp_t flags);
void *kvzalloc(size_t size, gfp_t flags);
void *kvmalloc_node(size_t size, gfp_t flags, int node);
void *kvzalloc_node(size_t size, gfp_t flags, int node);
其中flags又有这些选择:
#define GFP_ATOMIC (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
#define GFP_KERNEL (__GFP_RECLAIM | __GFP_IO | __GFP_FS)
#define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT)
#define GFP_NOWAIT (__GFP_KSWAPD_RECLAIM)
#define GFP_NOIO (__GFP_RECLAIM)
#define GFP_NOFS (__GFP_RECLAIM | __GFP_IO)
#define GFP_USER (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
#define GFP_DMA __GFP_DMA
#define GFP_DMA32 __GFP_DMA32
#define GFP_HIGHUSER (GFP_USER | __GFP_HIGHMEM)
#define GFP_HIGHUSER_MOVABLE (GFP_HIGHUSER | __GFP_MOVABLE)
#define GFP_TRANSHUGE_LIGHT ((GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NOWARN) & ~__GFP_RECLAIM)
#define GFP_TRANSHUGE (GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM)
/* Convert GFP flags to their corresponding migrate type */
#define GFP_MOVABLE_MASK (__GFP_RECLAIMABLE|__GFP_MOVABLE)
#define GFP_MOVABLE_SHIFT 3
突然明白了为什么zig语言设计成处处调用需要手动传入一个allocator。
而Rust的alloc crate只有一个自定义接口,这就导致只能选择一种,并且需要人为避免在不合适的场景触发Rust的alloc导致的堆内存分配,其它场景的分配恐怕就要绕过alloc crate另外实现了。目前,为兼容大部分场景,暂且这样实现分配器:
use crate::ffi;
use core::alloc::{GlobalAlloc, Layout};pub struct KernelAllocator;unsafe impl GlobalAlloc for KernelAllocator {unsafe fn alloc(&self, layout: Layout) -> *mut u8 {// FIXME: kernel does not support custom alignment。// kmalloc has some sort of guarantee.// See: https://lwn.net/Articles/787740/let size = layout.size();if size <= PAGE_SIZE {return ffi::kmalloc(size, GFP_KERNEL);} else {return ffi::vmalloc(size);}}unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {if layout.size() <= PAGE_SIZE {return ffi::kfree(ptr);} else {return ffi::vfree(ptr);}}
}
这就需要人为避免在中断、spinlock等场景触发Rust的alloc crate中的内存分配。好在实践过程中没有遇到这些场景下需要分配堆内存的情况。
结语
虽然遇到一些小坑,但瑕不掩瑜,使用Rust最大的好处就是内存安全,写完这种安心的感觉会让人觉得上述那些过程中的坑、额外的工作都是小事儿。只要把好ffi这关,今后因为各队友的疏忽而引入各种难查的UB将难以再发生。
相关文章:
rustdesk编译修改名字
最近,我用Rust重写了一个2W行C代码的linux内核模块。在此记录一点经验。我此前没写过内核模块,认识比较疏浅,有错误欢迎指正。 为什么要重写? 这个模块2W行代码量看起来不多,却在线上时常故障,永远改不完。…...
MySQL 窗口函数:功能、使用场景与性能优化
MySQL 8.0 引入了一个强大的新特性——**窗口函数(Window Functions)**。它为数据分析和复杂查询提供了极大的便利,但同时也可能带来性能问题。本文将带你快速了解窗口函数的功能、使用场景以及如何优化性能。 --- ## **什么是窗口函数&#…...
数据权限校验实践
数据权限控制实践 最近在实习中为公司项目完成一个文件数据权限校验代码的转换重构,写这篇博客来记录前后两种权限校验的实现方案与相关概念 原实现方案:RBAC-基于角色的访问控制 RBAC(Role-Based Access Control) RBAC 是一种常…...
spring boot对接clerk 实现用户信息获取
在现代Web应用中,用户身份验证和管理是一个关键的功能。Clerk是一个提供身份验证和用户管理的服务,可以帮助开发者快速集成这些功能。在本文中,我们将介绍如何使用Spring Boot对接Clerk,以实现用户信息的获取。 1.介绍 Clerk提供…...
公网远程家里局域网电脑过程详细记录,包含设置路由器。
由于从校内迁居小区,校内需要远程控制访问小区内个人电脑,于是早些时间刚好自己是电信宽带,可以申请公网ipv4不需要花钱,所以就打电话直接申请即可,申请成功后访问光猫设备管理界面192.168.1.1,输入用户名密码登录超管(密码是网上查下就有了)设置了光猫为桥接模式,然后…...
自制简单的图片查看器()
图片格式:支持常见的图片格式(JPG、PNG、BMP、GIF)。 import os import tkinter as tk from tkinter import filedialog, messagebox from PIL import Image, ImageTkclass ImageViewer:def __init__(self, root):self.root rootself.root.…...
25/2/17 <嵌入式笔记> 桌宠代码解析
这个寒假跟着做了一个开源的桌宠,我们来解析下代码,加深理解。 代码中有开源作者的名字。可以去B站搜着跟着做。 首先看下main代码 #include "stm32f10x.h" // Device header #include "Delay.h" #include &quo…...
Kafka偏移量管理全攻略:从基础概念到高级操作实战
#作者:猎人 文章目录 前言:概念剖析kafka的两种位移消费位移消息的位移位移的提交自动提交手动提交 1、使用--to-earliest重置消费组消费指定topic进度2、使用--to-offset重置消费offset3、使用--to-datetime策略指定时间重置offset4、使用--to-current…...
python中使用日期和时间差:datetime模块
datetime模块的表示时间的有 datetime.datetime #时间包含年月日时分秒毫秒 datetime.date #时间只包含年月日 datetime.time #只包含时分秒 获取当前时间 import datetime now datetime.datetime.now() print(now)得到 atetime中的年月日时分秒可以分别取出来 import da…...
申论对策建议类【2022江苏B卷第一题“如何开展网络直播”】
材料: 近年来,公安交管部门通过网络直播,将执法过程和执法细节以视频形式呈现在公众面前,吸引“围观”、组织点评,让执法过程变成一堂生动的法治公开课。 “各位网友,大家好!这里是‘全国交通…...
Blazor-父子组件传递任意参数
在我们从父组件传参数给子组件时,可以通过子组件定义的[Parameter]特性的公开属性进行传值,但是当我们需要传递多个值的时候,就需要通过[Parameter]特性定义多个属性,有没有更简便的方式? 我们可以使用定义 IDictionar…...
Python的那些事第二十三篇:Express(Node.js)与 Python:一场跨语言的浪漫邂逅
摘要 在当今的编程世界里,Node.js 和 Python 像是两个性格迥异的超级英雄,一个以速度和灵活性著称,另一个则以强大和优雅闻名。本文将探讨如何通过 Express 框架将 Node.js 和 Python 结合起来,打造出一个高效、有趣的 Web 应用。我们将通过一系列幽默风趣的实例和表格,展…...
win11安装wsl报错:无法解析服务器的名称或地址(启用wsl2)
1. 启用wsl报错如下 # 查看可安装的 wsl --install wsl --list --online此原因是因为没有开启DNS的原因,所以需要我们手动开启DNS。 2. 按照如下配置即可 Google的DNS(8.8.8.8和8.8.4.4) 全国通用DNS地址 (114.114.114.114) 3. 运行以下命令来重启 WSL…...
【设计模式】【结构型模式】桥接模式(Bridge)
👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD 🔥 2025本人正在沉淀中… 博客更新速度 👍 欢迎点赞、收藏、关注,跟上我的更新节奏 🎵 当你的天空突…...
1997-2019年各省进出口总额数据
1997-2019年各省进出口总额数据 1、时间:1997-2020年 2、来源:国家统计局、各省年鉴 3、指标:进出口总额 4、范围:31省 5、指标解释:进出口总额是指以货币表示的一定时期内一国实际进出口商品的总金额ÿ…...
AI前端开发效率革命:拥抱AI,开启前端开发新纪元
前端开发行业竞争日益激烈,项目交付周期不断缩短,对开发效率的要求也越来越高。在这种高压环境下,开发者们常常面临着巨大的压力。而近年来,人工智能技术的飞速发展,特别是AI写代码工具的出现,为前端开发带…...
Rust编程语言入门教程(一)安装Rust
目录 引言一、为什么要用 Rust?二、与其他语言比较三、Rust 特别擅长的领域四、Rust 与 Firefox五、Rust 的用户和案例六、Rust 的优缺点七、安装 Rust1、访问官网下载 Rust2、下载完成,执行exe文件 八、Rust 安装验证九、开发工具结束语 引言 在当今快…...
Kubernetes控制平面组件:Kubernetes如何使用etcd
云原生学习路线导航页(持续更新中) kubernetes学习系列快捷链接 Kubernetes架构原则和对象设计(一)Kubernetes架构原则和对象设计(二)Kubernetes架构原则和对象设计(三)Kubernetes控…...
2025年-G4-Lc78--121. 买卖股票的最佳时机--(java版)
1.题目描述 2.思路 思路1: 做两轮排序,第一轮排序找到最小的那个数,然后再判断最小的那个数之后还有其他数吗,如果有在进行排序,选出最大的那个数,然后值相减。 问题要点: (1)你需要…...
LabVIEW 中的 3dgraph.llb 库
3dgraph.llb 库位于 C:\Program Files (x86)\National Instruments\LabVIEW 2019\vi.lib\Platform 目录下,是 LabVIEW 系统中用于 3D 图形相关操作的重要库。它为 LabVIEW 用户提供了丰富的功能,能在应用程序中创建、显示和交互各种 3D 图形,…...
通过VSCode直接连接使用 GPT的编程助手
GPT的编程助手在VSC上可以直接使用 选择相应的版本都可以正常使用。每个月可以使用40条,超过限制要付费。 如下图对应的4o和claude3.5等模型都可以使用。VSC直接连接即可。 配置步骤如下: 安装VSCODE 直接,官网下载就行 https://code.vis…...
[LeetCode力扣hot100]-C++常用数据结构
0.Vector 1.Set-常用滑动窗口 set<char> ans;//根据类型定义,像vector ans.count()//检查某个元素是否在set里,1在0不在 ans.insert();//插入元素 ans.erase()//删除某个指定元素 2.栈 3.树 树是一种特殊的数据结构,力扣二叉树相…...
2-安装YIUI
YIUI框架:GitHub - LiShengYang-yiyi/YIUI: Unity3D UGUI Framework, 基于UI数据事件绑定为核心 数据驱动的UGUI框架, ETUI框架, ET框架官方推荐UI框架 ET框架:egametang/ET: Unity3D Client And C# Server Framework (github.com) 1 - 安装YIUI框架&a…...
16-使用QtChart创建动态图表:入门指南
QtChart是Qt框架中的一个强大模块,用于创建各种类型的图表,如折线图、柱状图、饼图等。它提供了丰富的API和灵活的配置选项,使得开发者能够轻松地将数据可视化集成到应用程序中。本文将介绍如何使用QtChart创建一个简单的动态折线图ÿ…...
蓝耘智算携手DeepSeek,共创AI未来
🌟 各位看官号,我是egoist2023! 🌍 种一棵树最好是十年前,其次是现在! 🚀 今天来学习如何通过蓝耘智算使用DeepSeek R1模型 👍 如果觉得这篇文章有帮助,欢迎您一键三连&a…...
具身智能在智能巡检机器人中的应用——以开关柜带电操作机器人为例
随着机器人技术和人工智能的迅速发展,具身智能在各行业的应用日益广泛,尤其是在电力行业中的智能巡检领域。传统的电力巡检和维护工作通常需要人工操作,存在着高温、高压、强电磁场等危险环境,且效率较低。开关柜带电操作机器人作…...
【第4章:循环神经网络(RNN)与长短时记忆网络(LSTM)— 4.6 RNN与LSTM的变体与发展趋势】
引言:时间序列的魔法钥匙 在时间的长河中,信息如同涓涓细流,绵延不绝。而如何在这无尽的数据流中捕捉、理解和预测,正是循环神经网络(RNN)及其变体长短时记忆网络(LSTM)所擅长的。今天,我们就来一场深度探索,揭开RNN与LSTM的神秘面纱,看看它们如何在时间序列的海洋…...
【R语言】回归分析与判别分析
一、线性回归分析 1、lm()函数 lm()函数是用于拟合线性模型(Linear Models)的主要函数。线性模型是一种统计方法,用于描述一个或多个自变量(预测变量、解释变量)与因变量(响应变量)之间的关系…...
git开发流程以及github社区企业版
常规开发流程 1、将仓库 clone 到本地,已经 clone 的要 fetch & pull,保证本地 master 分支已经更新到最新状态 2、在 master 最新分支的基础上 checkout 一个开发分支,分支命名要求规范,如带用户名、日期、bug id 等关键信…...
DeepSeek + Vue实战开发
利用DeepSeek V3模型、siliconflow大模型一站式云服务平台以及vue3.0实现一个在线人工智能客服对话系统。 因为deepseek官网的api密钥使用起来比较缓慢,所以可以使用第三方的,具体操作请自行查阅资料。 siliconflow官网 SiliconFlow, Accelerate AGI …...
从安装软件到flask框架搭建可视化大屏(二)——创建一个flask页面,搭建可视化大屏,零基础也可以学会
附录:所有文件的完整代码 models.py # models/models.py from flask_sqlalchemy import SQLAlchemydb SQLAlchemy()class User(db.Model):__tablename__ user # 显式指定表名为 userid db.Column(db.Integer, primary_keyTrue)username db.Column(db.String(…...
Python编程中,async/await/asyncio分别是干啥的?
在Python异步编程中,async、await和asyncio是三个核心概念。它们共同构成了Python处理高并发I/O密集型任务的解决方案。本文将通过代码实例解析它们的作用和用法。 一、异步编程基础 1.1 同步 vs 异步 同步编程:代码按顺序执行,遇到I/O操作(如网络请求、文件读写)时会阻塞…...
vue非组件的初学笔记
1.创建Vue实例,初始化渲染的核心 准备容器引包创建Vue实例new Vue() el用来指定控制的盒子data提供数据 2.插值表达式 作用利用表达式插值,将数据渲染到页面中 格式{{表达式}} 注意点 表达式的数据要在data中存在表达式是可计算结果的语句插值表达式…...
4.3 学习UVM中的“run_phase“,将其应用到具体案例分为几步?
文章目录 前言1. run_phase 的作用与执行特点2. 关键组件的 run_phase 实现2.1 Driver 的 run_phase:驱动事务2.2 Monitor 的 run_phase:捕获事务2.3 Scoreboard 的 run_phase:数据比对 3. 同步与 Objection 管理3.1 控制仿真结束3.2 多组件协…...
[Python人工智能] 五十.PyTorch入门 (5)快速搭建神经网络及模型保存
从本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关知识。前文讲解PyTorch构建分类神经网络。这篇文章将介绍如何利用PyTorch快速构建神经网络,之前的代码比较复杂,通过自定义Net类实现,本文通过Torch函数定义神经网络。前面我们的Python人工智能主要以Tens…...
【C语言】有序数组的平方
文章目录 给你一个按非递减顺序排序的整数数组 nums,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。 #include<stdio.h>/*** brief 计算一个整数数组的平方,并按非递减顺序存放结果* * 该函数接受一个整数数组arr和其长度le…...
osgearth视点坐标及鼠标交点坐标的信息显示(七)
核心函数如下: void COSGObject::addViewPointLabel() {//mRoot->addChild(osgEarth::Util::Controls::ControlCanvas::get(mViewer));//放开这句,球就卡住了。 为什么,shitosgEarth::Util::Controls::ControlCanvas* canvas = osgEarth::Util::Controls::ControlCanvas…...
【096】基于51单片机红外线人数统计系统【Proteus仿真+Keil程序+报告+原理图】
☆、设计硬件组成:51单片机最小系统LCD1602液晶显示两路E18-D80NK红外线传感器DS1302时钟芯片AT24C02存储芯片蜂鸣器LED灯按键设置。 1、设计采用STC89C52、AT89C52、AT89S52作为主控芯片; 2、采用DS1302时钟芯片实现对日期和时间的计时,并…...
【ENSP】链路聚合的两种模式
【ENSP】链路聚合的两种模式 1、背景介绍2、链路聚合的使用场景3、配置过程1、手工模式Eth-Trunk配置2、静态LACP模式Eth-Trunk 4、总结 1、背景介绍 随着网络规模的不断扩大,人们对骨干链路的带宽吞吐量和可靠性提出了越来越高的要求。在传统方案中,为…...
机器学习_17 K近邻算法知识点总结
K近邻算法(K-Nearest Neighbors,KNN)是一种简单而直观的机器学习算法,广泛应用于分类和回归任务。它通过寻找训练集中与新样本最接近的K个样本(近邻)来进行预测。今天,我们就来深入探讨K近邻算法…...
Web 后端 请求与响应
一 请求响应 1. 请求(Request) 客户端向服务器发送的HTTP请求,通常包含以下内容: 请求行:HTTP方法(GET/POST等)、请求的URL、协议版本。 请求头(Headers):…...
网络工程师 (44)ARP协议
前言 ARP协议,即地址解析协议(Address Resolution Protocol),是一种网络协议,主要用于将网络层的IPv4地址(逻辑地址)解析为链路层的物理地址(通常是MAC地址)。 一、基本概…...
使用verilog 实现 cordic 算法 ----- 旋转模式
1-设计流程 ● 了解cordic 算法原理,公式,模式,伸缩因子,旋转方向等,推荐以下链接视频了解 cordic 算法。哔哩哔哩-cordic算法原理讲解 ● 用matlab 或者 c 实现一遍算法 ● 在FPGA中用 verilog 实现,注意…...
搜狗浏览器卸载教程
需求背景 今天发现geek居然无法卸载搜狗浏览器,作为一个老司机,这是不允许的。如果你使用geek或者windows的卸载,或者直接在它的安装包的Uninstall.exe中卸载,他走到100%就一直不动了。那玩意是假的。 卸载教程 结束 -----华丽的…...
ES7 (ES2016) 新特性
目录 Array.prototype.includes()指数运算符与 ES6 的对比实际应用场景最佳实践 Array.includes() 基本语法 array.includes(searchElement[, fromIndex])特点 返回布尔值可以检测 NaN支持可选的 fromIndex 参数比 indexOf() 更语义化 使用示例 const numbers [1, 2, 3…...
设计模式13:职责链模式
系列总链接:《大话设计模式》学习记录_net 大话设计-CSDN博客 1.概述 职责链模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许将请求沿着处理者链传递,直到有一个处理者能够处理该请求。这种模式通过…...
MongoDB between ... and ... 操作
个人博客地址:MongoDB between ... and ... 操作 | 一张假钞的真实世界 MongoDB中类似SQL的between and操作可以采用如下语法: db.collection.find( { field: { $gt: value1, $lt: value2 } } );...
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_alloc函数
ngx_alloc 声明在 src\os\unix\ngx_alloc.h 中: void *ngx_alloc(size_t size, ngx_log_t *log); 定义在 src\os\unix\ngx_alloc.c 中: void * ngx_alloc(size_t size, ngx_log_t *log) {void *p;p malloc(size);if (p NULL) {ngx_log_error(NGX_LOG_…...
总结:Helm 命令详解
文章目录 1. Helm 概述2. Helm 的安装与配置2.1 安装 Helm2.2 验证安装 3、Helm 的常用命令3.1 查看帮助3.2 查看 Chart 列表3.3 安装 Chart3.4 卸载 Chart3.5 升级 Chart3.6 回滚 Chart3.7 查看 Chart 详细信息3.8 查看 Chart 的模板3.9 查看 Chart 的值3.10 管理仓库 4. Helm…...
通俗诠释 DeepSeek-V3 模型的 “671B” ,“37B”与 “128K”,用生活比喻帮你理解模型的秘密!
欢迎来到涛涛聊AI。 在DeepSeek-V3模型的参数描述中,你可能会看到类似“671B 37B 128K”这样的标记。这些字母和数字的组合看起来像密码,但其实它们揭示了模型的“大脑容量”和“工作方式”。我们用日常生活的比喻来解释: 一、数字含义&…...