当前位置: 首页 > news >正文

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编译修改名字

最近&#xff0c;我用Rust重写了一个2W行C代码的linux内核模块。在此记录一点经验。我此前没写过内核模块&#xff0c;认识比较疏浅&#xff0c;有错误欢迎指正。 为什么要重写&#xff1f; 这个模块2W行代码量看起来不多&#xff0c;却在线上时常故障&#xff0c;永远改不完。…...

BS5852英国家具防火安全条款主要包括哪几个方面呢?

什么是BS5852检测&#xff1f; BS5852是英国针对家用家具的强制性安全要求&#xff0c;主要测试家具在受到燃烧香烟和火柴等火源时的可燃性。这个标准通常分为四个部分进行测试&#xff0c;但实际应用中主要测试第一部分和第二部分&#xff0c;包括烟头测试和利用乙炔火焰模拟…...

【运维】源码编译安装cmake

背景&#xff1a; 已经在本地源码编译安装gcc/g&#xff0c;现在源码安装cmake 下载源码 下载地址&#xff1a;CMake - Upgrade Your Software Build System 安装步骤&#xff1a; ./bootstrap --prefix/usr/local/cmake make make install 错误处理 1、提示找不到libmpc.…...

检测网络安全漏洞 工具

实验一的名称为信息收集和漏洞扫描 实验环境&#xff1a;VMware下的kali linux2021和Windows7 32&#xff0c;网络设置均为NAT&#xff0c;这样子两台机器就在一个网络下。攻击的机器为kali,被攻击的机器为Windows 7。 理论知识记录&#xff1a; 1.信息收集的步骤 2.ping命令…...

frameworks 之 Activity添加View

frameworks 之 Activity添加View 1 LaunchActivityItem1.1 Activity 创建1.2 PhoneWindow 创建1.3 DecorView 创建 2 ResumeActivityItem 讲解 Activity加载View的时机和流程 涉及到的类如下 frameworks/base/core/java/android/app/Activity.javaframeworks/base/services/cor…...

UWB技术中的两种调制方式:PPM与PAM

Ultra-Wideband (UWB) 技术以其低功耗、宽频谱和高精度定位的特点&#xff0c;广泛应用于物联网&#xff08;IoT&#xff09;、智能家居、资产追踪和无线通信等领域。在UWB中&#xff0c;信号的调制方式对于数据传输的效率和精度起着至关重要的作用。本文将深入探讨UWB中常用的…...

达梦:用户和模式

目录标题 数据库管理系统与用户权限管理**四权分立****用户管理与权限划分****用户管理界面与权限控制****用户创建与管理****实操**1. **默认创建用户与模式**&#xff1a;2. **用户权限和角色分配**&#xff1a;3. **命令行管理用户与角色**&#xff1a;4. 模式也可以创建 **…...

23. AI-大语言模型-DeepSeek

文章目录 前言一、DeepSeek是什么1. 简介2. 产品版本3. 特征4. 地址链接5. 三种访问方式1. 网页端和APP2. DeepSeek API 二、DeepSeek可以做什么1. 应用场景2. 文本生成1. 文本创作2. 摘要与改写3. 结构化生成 3. 自然语言理解与分析1. 语义分析2. 文本分类3. 知识推理 4. 编程…...

Spring-GPT智谱清言AI项目(附源码)

一、项目介绍 本项目是Spring AI第三方调用整合智谱请言&#xff08;官网是&#xff1a;https://open.bigmodel.cn&#xff09;的案例&#xff0c;回答响应流式输出显示&#xff0c;这里使用的是免费模型&#xff0c;需要其他模型可以去 https://www.bigmodel.cn/pricing 切换…...

计算机网络(涵盖OSI,TCP/IP,交换机,路由器,局域网)

一、网络通信基础 &#xff08;一&#xff09;网络通信的概念 网络通信是指终端设备之间通过计算机网络进行的信息传递与交流。它类似于现实生活中的物品传递过程&#xff1a;数据&#xff08;物品&#xff09;被封装成报文&#xff08;包裹&#xff09;&#xff0c;通过网络…...

云计算架构学习之Ansible-playbook实战、Ansible-流程控制、Ansible-字典循环-roles角色

一、Ansible-playbook实战 1.Ansible-playbook安装软件 bash #编写yml [rootansible ansible]# cat wget.yml - hosts: backup tasks: - name: Install wget yum: name: wget state: present #检查playbook的语法 [rootansible ansible]…...

《运维工程师如何利用DeepSeek实现智能运维:分级实战指南》

目录 智能运维革命:DeepSeek带来的范式转变DeepSeek核心运维能力全景解析分级实战场景与解决方案 3.1 初级工程师:自动化运维入门3.2 中级工程师:复杂系统诊断与优化3.3 高级工程师:架构级智能运维典型项目案例深度剖析 4.1 金融系统全链路监控体系构建4.2 电商大促资源弹性…...

windows事件倒计时器与提醒组件

widgets 这是桌面组件前端开源组件&#xff0c;作者称&#xff1a;项目还在持续完善中&#xff0c;目前包含键盘演示、抖音热榜、喝水提醒、生日列表、待办事项、倒计时、灵动通知、打工进度等多个组件 有vue编程能力的可以自己做组件 百度网盘 夸克网盘 桌面组件 | Ca…...

Mac OS JAVA_HOME设置

个人博客地址&#xff1a;Mac OS JAVA_HOME设置 | 一张假钞的真实世界 在MacOS上使用DMG文件安装了Jdk8 之后&#xff0c;在默认路径下找不到JDK的HOME路径&#xff1a; $ which java /usr/bin/java $ ls -l /usr/bin/java lrwxr-xr-x 1 root wheel 74 12 6 2015 /usr/b…...

6.3 DBMS的功能和特征

文章目录 DBMS的6大功能DBMS的3个特征DBMS的分类 DBMS的6大功能 DBMS包含数据定义&#xff0c;数据库操作&#xff08;检索、插入、修改、删除&#xff09;&#xff0c;数据库运行管理&#xff08;保证多用户环境下正常运行&#xff09;&#xff0c;数据组织、存储、管理&…...

C# ConcurrentQueue 使用详解

总目录 前言 在C#多线程编程中&#xff0c;数据共享如同走钢丝——稍有不慎就会引发竞态条件&#xff08;Race Condition&#xff09;或死锁。传统Queue<T>在并发场景下需要手动加锁&#xff0c;而ConcurrentQueue<T>作为.NET Framework 4.0 引入的线程安全集合&a…...

python脚本文件设置进程优先级(在.py文件中实现)

在 Python 代码中可以直接通过 psutil 模块或 系统调用 来设置进程优先级&#xff0c;无需依赖终端命令。以下是具体方法和示例&#xff1a; 1. 使用 psutil 模块&#xff08;跨平台推荐&#xff09; psutil 是一个跨平台库&#xff0c;支持 Windows、Linux 和 macOS。通过其 …...

基于Django快递物流管理可视化分析系统(完整系统源码+数据库+详细开发文档+万字详细论文+答辩PPT+详细部署教程等资料)

文章目录 基于Django快递物流管理可视化分析系统&#xff08;完整系统源码数据库详细开发文档万字详细论文答辩PPT详细部署教程等资料&#xff09;一、项目概述二、项目说明三、研究意义四、系统设计技术架构 五、功能实现六、完整系统源码数据库详细开发文档万字详细论文答辩P…...

el-table树状表格,默认展开第一个节点的每一层

效果如图 <template><el-table:data"tableData"style"width: 100%":tree-props"{ children: children, hasChildren: hasChildren }":expand-row-keys"expandRowKeys"row-key"id"expand-change"handleExpan…...

【雅思博客05】New Guy in Town

Daily Life ‐ New Guy in Town 原文&#xff1a; A: Oh, I don’t know if you heard, but someone moved into that old house down the road. B: Yeah, I know. I met the owner of the house yesterday as he was moving in. His name is Armand. A: Really? What’s h…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

Yolov8 目标检测蒸馏学习记录

yolov8系列模型蒸馏基本流程&#xff0c;代码下载&#xff1a;这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中&#xff0c;**知识蒸馏&#xff08;Knowledge Distillation&#xff09;**被广泛应用&#xff0c;作为提升模型…...

vulnyx Blogger writeup

信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面&#xff0c;gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress&#xff0c;说明目标所使用的cms是wordpress&#xff0c;访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)

题目 做法 启动靶机&#xff0c;点进去 点进去 查看URL&#xff0c;有 ?fileflag.php说明存在文件包含&#xff0c;原理是php://filter 协议 当它与包含函数结合时&#xff0c;php://filter流会被当作php文件执行。 用php://filter加编码&#xff0c;能让PHP把文件内容…...

比较数据迁移后MySQL数据库和OceanBase数据仓库中的表

设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

windows系统MySQL安装文档

概览&#xff1a;本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容&#xff0c;为学习者提供全面的操作指导。关键要点包括&#xff1a; 解压 &#xff1a;下载完成后解压压缩包&#xff0c;得到MySQL 8.…...