【xv6操作系统】系统调用与traps机制解析及实验设计
【xv6操作系统】系统调用与traps机制解析及实验设计
- 系统调用
- 相关理论
- 系统调用追溯
- 系统调用实验设计
- Sysinfo
- 🚩系统调用总结(结合trap机制)
- trap
- trap机制
- trap代码流程
- Backtrace实验
- alarm实验
系统调用
相关理论
隔离性(isolation): 类似的,操作系统某种程度上为所有的应用程序服务。当你的应用程序出现问题时,你会希望操作系统不会因此而崩溃。比如说你向操作系统传递了一些奇怪的参数,你会希望操作系统仍然能够很好的处理它们(能较好的处理异常情况)。所以,你也需要在应用程序和操作系统之间有强隔离性。
使用操作系统的一个原因,甚至可以说是主要原因就是为了实现multiplexing(CPU在多进程同分时复用)和内存隔离。如果你不使用操作系统,并且应用程序直接与硬件交互,就很难实现这两点。
- 应用程序不能直接与CPU交互,只能与进程交互
- 操作系统内核会完成不同进程在CPU上的切换
- 操作系统不是直接将CPU提供给应用程序,而是向应用程序提供“进程”,进程抽象了CPU,这样操作系统才能在多个应用程序之间复用一个或者多个CPU。
硬件对于强隔离的支持
当用户程序执行系统调用,会通过ECALL(详情结合下章trap)触发一个软中断(software interrupt),软中断会查询操作系统预先设定的中断向量表,并执行中断向量表中包含的中断处理程序。中断处理程序在内核中,这样就完成了user mode到kernel mode的切换,并执行用户程序想要执行的特殊权限指令。
每一个进程都会有自己独立的page table,这样的话,每一个进程只能访问出现在自己page table中的物理内存。操作系统会设置page table,使得每一个进程都有不重合的物理内存,这样一个进程就不能访问其他进程的物理内存,因为其他进程的物理内存都不在它的page table中。
编译运行kernel:
Makefile会为所有内核文件做相同的操作,比如说pipe.c,会按照同样的套路,先经过gcc编译成pipe.s,再通过汇编解释器生成pipe.o。之后,系统加载器(Loader)会收集所有的.o文件,将它们链接在一起,并生成内核文件。

User/Kernel mode切换:
-
用户的应用程序执行系统调用的唯一方法就是通过这里的ECALL指令。
-
调用ECALL指令,并将fork对应的数字作为参数传给ECALL
-
这里的数字参数代表了应用程序想要调用的System Call。
-
可以通过系统调用或者说ECALL指令,将控制权从应用程序转到操作系统中
进程虚拟地址空间
-
使用符号
p->xxx
来指代proc
结构中的元素;struct proc
在 kernel/proc.h 文件第 86 行定义。 -
每个进程有两个栈:一个用户栈
user stack
和一个内核栈kernel stack
( p->kstack )enum procstate { UNUSED, USED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE }; // Per-process state struct proc {struct spinlock lock; // 锁// p->lock must be held when using these:enum procstate state; // Process statevoid *chan; // If non-zero, sleeping on chanint killed; // If non-zero, have been killedint xstate; // Exit status to be returned to parent's waitint pid; // Process ID// wait_lock must be held when using this:struct proc *parent; // Parent process// these are private to the process, so p->lock need not be held.uint64 kstack; // Virtual address of kernel stackuint64 sz; // Size of process memory (bytes)pagetable_t pagetable; // User page tablestruct trapframe *trapframe; // data page for trampoline.Sstruct context context; // swtch() here to run processstruct file *ofile[NOFILE]; // Open filesstruct inode *cwd; // Current directorychar name[16]; // Process name (debugging) };
在内核态中如何获取用户参数,如sleep(10),涉及一个拷贝
系统调用追溯
1. 实验要求: 添加一个trace system call调用,可以实现跟踪system call。此函数入参为一个数字,可以控制跟踪哪些system call。
如:
- trace(1<<SYS_fork),trace(10b),trace(2)表示跟踪fork调用;
- trace(1<<SYS_read),trace(10 0000b),trace(32),表示跟踪read调用;
2. 一些理论基础
-
initcode.S
将 exec 的参数放置在寄存器 a0 和 a1,将系统调用编号放在a7
中.# exec(init, argv) .globl start start:la a0, initla a1, argvli a7, SYS_exececall
-
sys_call返回值: 当
sys_exec
返回时,系统调用会将其返回值记录在p->trapframe->a0
中。如果系统调用编号无效,系统调用将打印错误并返回 −1
system call调用链路
1)在user/user.h做函数声明
2)Makefile调用usys.pl(perl脚本)生成usys.S,里面写了具体实现,通过ecall进入kernel,通过设置寄存器a7的值,表明调用哪个system call
3)ecall表示一种特殊的trap,转到kernel/syscall.c:syscall执行
4)syscall.c中有个函数指针数组,即一个数组中存放了所有指向system call实现函数的指针,通过寄存器a7的值定位到某个函数指针,通过函数指针调用函数
系统调用实验设计
核心1:内核函数调用过程
entry进入内核,调用syscall函数,通过a7获取函数指针数组的数值(即sys_call的入口地址)

核心2:内核获取用户参数
调用 argint
,argaddr
函数,本质是从进程中获取

实验设计:
(1)链路配置:
-
user.h声明 trace函数
int trace(int);
-
user.pl加入跳板函数
entry("trace");
-
在 syscalls 函数指针数组添加
sys_trace
数组,并在syscall.h中宏定义SYS_trace
// syscall.c [SYS_trace] sys_trace,// syscall.h #define SYS_trace 22
-
在syscall中外部函数声明,并在sys_call在sysproc.c中定义函数原型
sys_trace
//sys_call.c extern uint64 sys_trace(void);//sysproc.c //Add a sys_trace() function uint64 sys_trace(void)
(2)函数实现: 根据实验要求设计
- 在 sys_trace 中获取用户系统调用参数
mask
uint64 sys_trace(void) {int mask;if(argint(0, &mask) < 0)return -1; }
- 通过在 proc 结构的新变量中记住其参数来实现新的系统调用,即
proc
结构体(proc.h)中添加新的变量mask
,以便子进程继承//Add a sys_trace() function uint64 sys_trace(void) {int mask;if(argint(0, &mask) < 0)return -1;//获取当前进程struct proc *p = myproc();//需要在proc.h结构体中添加新的成员变量 maskp->trace_mask = mask; return 0; }
- 修改 fork() (kernel/proc.c) 以将跟踪掩码从父进程复制到子进程
//copy the trace mask from the parent to the child process. np->trace_mask = p->trace_mask;
- 修改 kernel/syscall.c 中的 syscall()函数,以打印跟踪输出

Sysinfo
- 个人认为本题重点在于如何实现用户态与内核态的信息交互
(调用、参数、返回值等)
添加一个sysinfo system call调用,可以实现打印可用空间(字节)、可用进程数
首先阅读测试案例:user/sysinfotest.c:main,需结合程序
testcall
:测试 sysinfo调用失败,用户传递非法内存地址,因为Risc-v只支持39位 在下一章页表中涉及testmem
:使用sbrk分配物理内存页,测试剩余分配量,释放测试testproc
:测试有多少进程未使用,测试fork,使用 + 1,未使用 -1
实验设计: 构建 sysinfo 系统调用链路,略,参考System call tracing 中的实验设计的第一步:链路配置
程序设计:
1. 获取用户sysinfo参数
uint64 addr; // user pointer to struct stat
// step1: copy a struct sysinfo back to user space
if(argaddr(0, &addr) < 0)return -1;
2. 获取未使用的空间和进程数存入 struct sysinfo
结构体
struct sysinfo info; // sysinfo struct// step2 get freemen
info.freemem = acquire_freemen(); //kalloc.c
// step3 get unused number of processes
info.nproc = acquire_npro(); //proc.c
3. 功能函数实现
(1) 获取未使用的空间,参考 kalloc.c: kalloc,遍历空页表,计算页数,返回 页数 * 每一页的字节数,这里涉及到一点下一章的页表知识
// 参考 kalloc
uint64 acquire_freemen(void)
{struct run *r; //listuint64 cnt = 0;acquire(&kmem.lock); r = kmem.freelist;//遍历链表求长度 页表while(r){r = r->next;cnt++;}release(&kmem.lock);return cnt * PGSIZE; // 页表页数 * 每一页的字节数
}
(2) 获取未使用的进程数,遍历进程,判断进程状态
uint64 acquire_npro(void)
{struct proc *p; // 进程指针int cnt = 0;// 遍历所有进程for(p = proc; p < &proc[NPROC]; p++) {acquire(&p->lock); // get lock//进程状态为使用if(p->state == UNUSED) {cnt++;} release(&p->lock); // release lcok }return cnt;
}
4. 内核数据拷贝给用户,利用 copyout
函数将内核地址的数据拷贝给用户态
函数定义:
int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
数据拷贝实现
struct proc *p = myproc(); // process p
copyout(p->pagetable, addr, (char *)&info, sizeof(info));
5. 完整 sys_sysinfo
函数实现:
//Add a sys_sysinfo() function
uint64
sys_sysinfo(void)
{struct sysinfo info; // sysinfo structuint64 addr; // user pointer to struct statstruct proc *p = myproc(); // process p// step2 get parainfo.freemem = acquire_freemen();// step3 get number of processesinfo.nproc = acquire_npro();// step1: copy a struct sysinfo back to user spaceif(argaddr(0, &addr) < 0)return -1;if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)return -1;//printf("call sys_sysinfo Hi\n");return 0;
}
🚩系统调用总结(结合trap机制)
- 这里在学习页表、trap后总结

trap
大佬认为: 本部分主要内容其实都在lecture里(lecture 5、lecture 6),实验不是非常复杂但是以理解概念为重,trap机制、trampoline作用、函数calling convention、调用栈、特权模式、riscv汇编,这些即使都不知道可能依然能完成 lab。但是不代表这些不重要,相反这些才是主要内容,
参考大佬的博文:[mit6.s081] 笔记 Lab4: Traps | 中断陷阱
trap机制
参考博文: Mit6.S081学习笔记
- 程序执行系统调用
- 程序出现了类似page fault、运算时除以0的错误
- 一个设备触发了中断使得当前程序运行需要响应内核设备驱动
都会发生用户空间和内核空间的切换,这种切换通常被称为trap

对比 (二)ARM寄存器组织与异常处理 中的异常处理学习,这是ARM寄存器组织异常处理过程:
- 拷贝之前的模式及状态
- 切换模式 用户模式 -> 异常模式
- 禁止相应的中断
- 修改模式位
- 修改状态位
- 保存返回地址
- 设置
PC
为相应的中断异常向量表的值
先认识一些内核模式下的寄存器,留个印象:
- scause: trap类型(sys_call, page fault)
- sststus: 保存状态模式(User / supervisor mode)
- scratch: trapframe地址
- sepc: 保存当前PC的值
- stvec: trap handler地址
- csr: 控制状态寄存器,csrr,csrw,csrrw指令
用户下的内存块:
- trampoline:存放切换代码的地方 (内核下也有)
- trapframe:类似于上下文保存的结构 (内核下没有)
需要清楚如何让程序的运行,从只拥有user权限并且位于用户空间的程序,切换到拥有supervisor权限的内核。在这个过程中,硬件的状态将会非常重要,因为很多的工作都是将硬件从适合运行用户应用程序的状态,改变到适合运行内核代码的状态。
用户应用程序可以使用全部的32个寄存器,很多寄存器都有特殊的作用。其中:
- 程序计数寄存器PC(Program Counter Register)
- 表明当前mode的标志位,这个标志位表明了当前是
supervisor mode
还是user mode
- 还有一堆控制CPU工作方式的寄存器,比如
SATP
(Supervisor Address Translation andProtection)寄存器,包含了指向page table的物理内存地址 STVEC
(Supervisor Trap Vector Base Address Register)寄存器,指向了内核中处理trap的指令的起始地址SEPC
(Supervisor Exception Program Counter)寄存器,在trap的过程中保存程序计数器的值- SSCRATCH(Supervisor Scratch Register)寄存器,这也是个非常重要的寄存器
这些寄存器表明了执行系统调用时计算机的状态。
trap代码流程
用户程序执行系统调用函数(实际上通过执行ECALL指令来执行系统调用)
用户程序→ ECALL→uservec(在trampoline中)→usertrap(在trap.c中)→ syscall → sys xxx(对应的系统调用)一执行结果返回给syscall→usertrapret(在trap.c中)→userret(在trampoline中)→系统调用完成,返回到用户空间,恢复ECALL之后的用户程序的执行

一、当一个trap来临((ecall指令)RISC-V硬件做了什么
- 如果是设备中断,并且状态SIE位(sststus中的标志位)被清空,不执行以下操作
- 清除SIE以禁用中断(disable interrupts by clearing SlE)(ECALL禁用,防止切换到其他进程,并重新trap,覆盖sepc)
- 保存
pc
的值到sepc
(epc = PC) - 保存当前所处模式到SPP位(sststus中的标志位) (user mode -> supervisor mode
- 设置
scause
表明 trap类型(Set scause to reflect the trap’s cause) - 切换到
supervisor
模式(Set the mode to supervisor) - 将
stvec
寄存器里面的值复制到pc
(Copy the stvec to the pc) - 从
pc
上取值执行(Start executing at the new pc)
所以现在,ecall在硬件上帮我们做了一点点工作,但是实际上我们离执行内核中的C代码还差的很远。接下来:
- 我们需要保存32个用户寄存器的内容,这样当我们想要恢复用户代码执行时,我们才能恢复这些寄存器的内容。(保存现场)
- 因为现在我们还在user page table,我们需要切换到kernel page table
- 我们需要创建或者找到一个kernel stack,并将Stack Pointer寄存器的内容指向那个kernel stack。这样才能给C代码提供栈
- 我们还需要跳转到内核中C代码的某些合理的位置。
ecall并不会为我们做这里的任何一件事。
GDB过程:




二、uservec函数
- 保存现场(32个通用寄存器)
- 把内核的page table,内核的stack、当前执行该进程的CPU号装载到寄存器里
- 跳转到usertrap继续执行

三、usertrap函数
-
分情况,执行系统调用/中断/异常的处理逻辑
scause == 8
-> syscall ->num = p->trapframe->a7
-> syscalls表驱动 -> sys_write(16)
-
修改了stvec的值,还可能会修改sepc的值
void usertrap(void) {int which_dev = 0;if((r_sstatus() & SSTATUS_SPP) != 0)panic("usertrap: not from user mode");// send interrupts and exceptions to kerneltrap(),// since we're now in the kernel.w_stvec((uint64)kernelvec);// save user program counter.p->trapframe->epc = r_sepc();if(r_scause() == 8) {// system call// sepc points to the ecall instruction,// but we want to return to the next instruction.p->trapframe->epc += 4;// an interrupt will change sstatus &c registers,// so don't enable until done with those registers.intr_on();syscall();} // give up the CPU if this is a timer interrupt.if(which_dev == 2) {yield();}usertrapret(); }
四、usertrapret函数
- 填入了trapframe的内容,这样下一次从用户空间转换到内核空间时可以用到这些数据
- 存储
kernel page table
的指针 - 存储当前用户进程的
kernel stack
- 存储
usertrap
函数的指针,这样trampoline代码才能跳转到这个函数 - 从tp寄存器中读取当前的
CPU核编号
,并存储在trapframe中 - 恢复stvec、sepc的值(supervisor mode register)
- 存储
五、userret函数
-
恢复现场
-
把用户空间的
page table
、用户空间的stack
装载到寄存器里 -
执行sret指令(相对于ecall指令)
六、sret指令
- 程序会切换回user mode
- SEPC寄存器的数值会被拷贝到PC寄存器(程序计数器)
- 重新打开中断

Backtrace实验
添加 backtrace 功能,打印出调用栈,用于调试
实验步骤:
-
在 defs.h 中添加声明
backtrace
,并在 print 中定义
-
在 riscv.h 中添加获取当前 fp(frame pointer)寄存器的方法:
static inline uint64 r_fp() {uint64 x;asm volatile("mv %0, s0" : "=r" (x) );return x; }
-
获取当前栈帧指针
void backtrace(void) {//并在 Backtrace 中调用r_fp以读取当前帧指针//此函数使用内联汇编来读取 s0。uint64 fp = r_fp(); // 当前栈帧指针
-
lecture notes have a picture of the layout of stack frames. The return address lives at a fixed
offset (-8)
from the frame pointer of a stackframe, and that the saved frame pointer lives at fixed offset(-16)
from the frame pointer. -
Xv6 allocates one page for each stack in the xv6 kernel at PAGE-aligned address. You can compute the top and bottom address of the stack page by using
PGROUNDDOWN(fp)
andPGROUNDUP(fp)
-
程序设计
void backtrace(void) {//并在 Backtrace 中调用r_fp以读取当前帧指针//此函数使用内联汇编来读取 s0。uint64 fp = r_fp(); // 当前栈帧指针uint64 return_address; // 函数返回地址 printf("backtrace:\n");// 判断是否已经到达栈底while (fp != PGROUNDUP(fp)){// 获取每个栈的返回地址return_address = *(uint64*)(fp - 8);// 更新 fp 获取上个栈的栈帧地址fp = *(uint64*)(fp - 16);printf("%p:\n",return_address);} }
函数调用栈(Stack)
- 栈由高地址往低地址增长
- 在xv6里,有一页大小(4KB)
- 栈指针(stack pointer)保存在
sp
寄存器里

栈帧(Stack Frame)
-
当前栈帧的地址保存在
s0/fp
寄存器里 -
当前栈帧的地址也叫栈帧的指针(frame pointer, fp),指向该栈帧的最高处
-
栈帧指针往下偏移8个字节是函数返回地址
return address
-
往下偏移16个字节是上一个栈帧的栈帧指针
previous frame pointer

- fp 指向当前栈帧的开始地址,sp 指向当前栈帧的结束地址。
- 栈从高地址往低地址生长,所以 fp 虽然是帧开始地址,但是地址比 sp 高
- 栈帧中从高到低第一个 8 字节
fp-8
是 return address,也就是当前调用层应该返回到的地址。 - 栈帧中从高到低第二个 8 字节
fp-16
是 previous address,指向上一层栈帧的 fp 开始地址。 - 剩下的为保存的寄存器、局部变量等。一个栈帧的大小不固定,但是至少 16 字节。
- 在 xv6 中,使用一个页来存储栈,如果 fp 已经到达栈页的上界,则说明已经到达栈底。
查看 call.asm,可以看到,一个函数的函数体最开始首先会扩充一个栈帧给该层调用使用,在函数执行完毕后再回收,例子:
int g(int x) {0: 1141 addi sp,sp,-16 // 扩张调用栈,得到一个 16 字节的栈帧2: e422 sd s0,8(sp) // 将返回地址存到栈帧的第一个 8 字节中4: 0800 addi s0,sp,16return x+3;
}6: 250d addiw a0,a0,38: 6422 ld s0,8(sp) // 从栈帧读出返回地址a: 0141 addi sp,sp,16 // 回收栈帧c: 8082 ret // 返回
注意栈的生长方向是从高地址到低地址,所以扩张是 -16,而回收是 +16。
alarm实验
该实验需要实现 sigalarm和 sigreturn 两个系统调用,为用户进程添加定期通知功能,使得进程在一段时间内使用 CPU 后,会被定期“提醒”,类似于一种用户态的中断处理,用来模拟用户级的异常处理。
作用:
- 对于希望限制其占用CPU时间的计算密集型进程,或者希望进行计算但也希望采取一些定期行动的进程可能很有用。
- 更一般来说,你将实现一种原始的用户级中断/故障处理程序;例如,你可以使用类似的机制来处理应用程序中的页面错误
实验要求与实现:
0. 核心参数设置:
struct proc{...// alarm test0int ticks; // 报警间隔 interval for the alarmuint64 handler; // call functionint ticks_count; // how many ticks right now//test1 test2 struct trapframe *save_trap_frame; // 保存现场int is_handling; // 是否正在中断 防止二次打断...
}
1. 添加新的系统调用:
- user.h 声明系统调用
- usys.pl添加入口
- syscall.h函数编号
- syscall.c更加系统调用,做函数表映射
2. 保存 sigalarm
的报警间隔与 handler
指针保存在 struct proc
中新的字段,并在proc.c中allocproc初始化字段
-
核心代码:
sys_sigalarm: 获取用户参数 argint(0, &ticks); argaddr(1, &handler); // 保存参数 p->ticks = ticks; p->handler = handler; p->ticks_count = 0;sys_sigreturn: p->is_handling = 0; 清空中断标记位 memmove(p->trapframe, p->save_trap_frame, PGSIZE); 中断返回 保存现场的地址
-
完整代码
-
系统调用函数实现:
(1)sys_sigalarm
获取用户数据(间隔时间与处理函数handler)并保存到进程中uint64 sys_sigalarm(void) {int ticks;uint64 handler;// 进入中断// 获取进程struct proc *p = myproc();// 获取用户参数argint(0, &ticks);argaddr(1, &handler);// 保存参数p->ticks = ticks;p->handler = handler;p->ticks_count = 0;return 0; }
(2)
sys_sigreturn
中断返回保存现场,清空中断标志位uint64 sys_sigreturn(void){ //恢复现场// 获取进程struct proc *p = myproc();// 清空异常标志p->is_handling = 0; // 中断返回 保存现场的地址memmove(p->trapframe, p->save_trap_frame, PGSIZE); return 0;}
-
proc/allocproc函数初始化成员变量
// init p->ticks = 0;p->ticks_count = 0;p->handler = 0;
3. 在kernel/trap.c中的实现该时钟中断的代码usertrap
-
核心代码:
保存中断现场 memmove(p->save_trap_frame, p->trapframe, PGSIZE); handler存入epc寄存器,破坏了现场,因此这两行代码不可先后更换 p->trapframe->epc = p->handler;
-
完整代码:
// give up the CPU if this is a timer interrupt.if(which_dev == 2){if(p->ticks > 0){p->ticks_count++;//时间到 并且无其他中断if(p->ticks_count > p->ticks && p->is_handling == 0){p->ticks_count = 0;//保存中断现场 memmove(p->save_trap_frame, p->trapframe, PGSIZE);//执行函数的地址入口p->trapframe->epc = p->handler; // handler存入epc寄存器p->is_handling = 1; // 标记正在中断}}yield();}
相关文章:
【xv6操作系统】系统调用与traps机制解析及实验设计
【xv6操作系统】系统调用与traps机制解析及实验设计 系统调用相关理论系统调用追溯系统调用实验设计Sysinfo🚩系统调用总结(结合trap机制) traptrap机制trap代码流程Backtrace实验alarm实验 系统调用 相关理论 隔离性(isolation)…...
S7-1200 G2移植旧版本S7-1200程序的具体方法示例
S7-1200 G2移植旧版本S7-1200程序的具体方法示例 前期概要: S7-1200 G2必须基于TIA博途V20,之前的程序可通过移植的方式在新硬件上使用。 该移植工具可自动将TIA Portal 项目从 S7-1200 移植到更新的S7-1200 G2。 注意: 该插件支持在同一TIA Portal项目实例内将软件和/或硬…...
海量数据查询加速:Presto、Trino、Apache Arrow
1. 引言 在大数据分析场景下,查询速度往往是影响业务决策效率的关键因素。随着数据量的增长,传统的行存储数据库难以满足低延迟的查询需求,因此,基于列式存储、向量化计算等技术的查询引擎应运而生。本篇文章将深入探讨 Presto、Trino、Apache Arrow 三种主流的查询优化工…...
vscode远程连接服务器并运行项目里的.ipynb文件 如何在 Jupyter Notebook 中切换/使用 conda 虚拟环境?
【最全指南】如何在 Jupyter Notebook 中切换/使用 conda 虚拟环境? 最好用的方法! 使用 nb_conda_kernels 添加所有环境 第二种方法其实也挺不错的。有个缺点是,你新建一个环境,就要重复操作一次。 而这个方法就是一键添加所有…...
二阶优化方法详解
前言 本文隶属于专栏《机器学习数学通关指南》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见《机器学习数学通关指南》 ima 知识库 知识库广场搜索&#…...
C++中通过虚函数实现多态的原理
C中通过虚函数实现多态的原理 我们都知道C是通过虚函数实现多态的,那么其中的原理是什么呢? 在C中,多态性是一种重要的特性,它允许通过基类指针或引用来调用派生类中的函数。多态性主要分为两种:编译时多态ÿ…...
阿里云服务器购买及环境搭建宝塔部署springboot和vue项目
云服务器ECS_云主机_服务器托管_计算-阿里云 一、前言 对于新手或者学生党来说,有时候就想租一个云服务器来玩玩或者练练手,duck不必花那么多钱去租个服务器。这些云服务厂商对学生和新手还是相当友好的。下面将教你如何快速搭建自己的阿里云服务器&…...
【学习笔记】中缀表达式转后缀表达式及计算
C实现中缀表达式转后缀表达式及后缀表达式的计算 在C中,实现中缀表达式转换为后缀表达式(逆波兰表达式)以及后缀表达式的计算是一个非常经典的问题。它不仅涉及到栈(Stack)数据结构的使用,还涉及到对运算符…...
【机器人-基础知识】标定 - 相机标定全解
https://blog.csdn.net/MengYa_Dream/article/details/120233806 1. 相机标定的定义 相机标定是确定相机成像过程中各个参数的过程,它的核心目标是建立从三维世界坐标系到二维图像坐标系的数学映射关系。这一过程包括求解: 内参:描述相机内部光学特性(如焦距、主点位置、像…...
Java 8 + Tomcat 9.0.102 的稳定环境搭建方案,适用于生产环境
一、安装 Java 8 安装 OpenJDK 8 bash sudo apt update sudo apt install openjdk-8-jdk -y 验证安装 bash java -version 应输出类似: openjdk version “1.8.0_412” OpenJDK Runtime Environment (build 1.8.0_412-8u412-ga-1~22.04-b08) OpenJDK 64-Bit Server VM (bui…...
探索 C 语言枚举类型的奇妙世界
目录 一、枚举类型的定义二、枚举类型变量的声明和初始化2.1 先定义枚举类型,再声明变量2.2 定义枚举类型的同时声明变量 三、自定义枚举常量的值四、枚举类型的特点五、注意事项 在C语言中,枚举类型( enum)是一种用户自定义的数…...
buu-ciscn_2019_ne_5-好久不见50
1. 背景分析 目标程序是一个存在漏洞的二进制文件,我们可以通过以下方式利用漏洞获取 shell: 程序中存在 system() 函数,但没有明显的 /bin/sh 字符串。 使用工具(如 ROPgadget)发现程序中有 sh 字符串,可…...
HCIA-ACL实验
前提条件:实现底层互通 转发层面 1、基本ACL ①要求PC3不能访问网段192.168.2.0的网段,PC4和客户端能正常访问服务器 ②AR2配置 acl 2000 rule deny source 192.168.1.1 0 匹配流量 int g 0/0/0 traffic-filter inbound acl 2000 接口调用…...
Java入职篇(2)——开发流程以及专业术语
Java入职篇(2)——开发流程以及专业术语 开发流程 开发术语 测试用例(用例) 测试人员写的测试方案,基本上就是编写的测试过程,以及测试的预取结果 灰度测试 现在小部分范围内使用,然后逐步…...
三相逆变器不控整流场景简要分析
0 三相逆变器拓扑 LCL三相逆变器简要拓扑如下图所示,其他类型如多电平逆变器类似。 1 原理说明 软件在进行直流母线电压Udc的给定取值时,考虑到电压利用率,通常会比电网线电压的峰值稍微高些,比如取线电压峰值的1.0x倍&#x…...
语言识别模型whisper学习笔记
语言识别模型whisper学习笔记 Whisper 是由 OpenAI 于 2022年9月 推出的开源自动语音识别(ASR)系统,旨在实现高精度、多语言的语音转文本及翻译任务。其核心目标是解决传统语音识别模型在噪声环境、口音多样性及多语言场景下的局限性。 一、…...
centos 换阿里云yum
1、备份原有的Yum源配置文件 在更换Yum源之前,先备份CentOS系统中默认的Yum源配置文件,以便在需要时恢复。默认的Yum源配置文件位于 /etc/yum.repos.d/ 目录下,通常包含 CentOS-Base.repo、CentOS-Debuginfo.repo、CentOS-Vault.repo 等文件…...
Jmeter的简单使用
前置工作 确保java8 版本以上jmeter下载路径(选择Binaries):https://jmeter.apache.org/download_jmeter.cgi直接解压,找到bin下面的文件:jmeter.bat(可选)汉化,修改 jmeter.proper…...
CSS元素层叠顺序规则
CSS元素层叠顺序规则 看图说话总结: background/borderz-index(<0)blockfloatinline/inline-blockz-index(0,auto)z-index (>0)...
用Maven创建只有POM文件的项目
使用 mvn 创建一个仅包含 pom.xml 文件的父项目,可以借助 maven-archetype-quickstart 原型,然后移除不必要的文件,或者直接通过命令生成最简的 pom.xml 文件。以下是具体操作步骤: 一、方法一:使用原型创建后清理 1…...
使用Python在Word中生成多种不同类型的图表
目录 工具与环境配置 在 Word 中创建图表的步骤 在Word中创建柱形图 在Word中创建条形图 在Word中创建折线图 在Word中创建饼图 在Word中创建散点图 在Word中创建气泡图 在 Word 文档中插入图表不仅能更直观地呈现数据,还能提升文档的可读性和专业性。常见的…...
Webpack构建流程详解优化前端性能\Dev-Server与Proxy\网络攻击\HMR
简版 核心流程图 根据,Webpack的构建流程分为初始化、编译和输出三个阶段。初始化阶段读取配置、加载插件、实例化Compiler。编译阶段(构建依赖关系)涉及Compiler类的运行,生成Compilation对象,处理模块依赖。输出阶…...
Python 实现的采集诸葛灵签
Python 实现的采集诸葛灵签 项目介绍 这是一个基于 Python 开发的诸葛灵签数据采集和展示项目。通过爬虫技术获取诸葛神签的签文和解签内容,并提供数据存储和查询功能。 项目结构 zhuge/├── zhuge_scraper.py # 爬虫主程序├── zhuge_pages/ # 数据存储目录…...
ESP-IDF ubuntu版本 V5.2
1.MobaXterm 这个软件方面粘贴,文件拷贝 MobaXterm 2.安装之前请确保你安装了Python 和 pip V5.2需要python3.8和pip mkdir esp32 cd esp32 git clone https://gitee.com/EspressifSystems/esp-gitee-tools.git cd esp-gitee-tools ./jihu-mirror.sh set cd .. git clone …...
Opencv之掩码实现图片抠图
掩码实现图片抠图 目录 掩码实现图片抠图1 掩码1.1 概念1.2 创建掩码1.3抠图思路 2 代码测试 1 掩码 1.1 概念 掩码(Mask)是一种用于指定图像处理操作区域的工具。掩码通常是一个与图像尺寸相同的二值图像,其中像素值为0表示不处理ÿ…...
警惕!Ollama大模型工具的安全风险及应对策略
文章目录 **Ollama的安全隐患:不容忽视的风险****未授权访问:门户洞开的风险****数据泄露:敏感信息的外泄****漏洞利用:历史遗留的隐患** **安全加固:守护数据与服务的防线****限制监听范围:内网隔离的保护…...
MySQL -- 表的约束
概念引入:真正的约束表字段的是数据类型,但是数据类型的约束方式比较单一的,所以需要一些额外的一些约束,用于表示数据的合法性,在只有数据类型一种约束的情况下,我们比较难保证数据是百分百合法。通过添加…...
详解数据库范式
范式 1. 第一范式(1NF)2. 第二范式(2NF)3. 第三范式(3NF)4. BC范式(BCNF,Boyce-Codd Normal Form)5. 第四范式(4NF)6. 第五范式(5NF&a…...
Nginx + Keepalived 高可用集群
一、NginxKeepalived 原理 1.1.Nginx 负载均衡机制 Nginx 是一款轻量级且高性能的 Web 服务器和反向代理服务器,在负载均衡方面有着卓越的表现。其具备强大的七层流量管理能力,能够基于 URL、Cookie、HTTP 头信息等对请求进行精准路由。例如࿰…...
循环遍历 Java 集合中元素的方法总结
循环遍历 Java 集合中元素的方法 在 Java 中,有多种方法可以遍历集合中的元素。以下是几种常见的遍历方法及其优缺点: 1. for-each 循环 语法: for (ElementType element : collection) {// 处理 element }适用场景:所有集合类型…...
树莓派上的 TensorFlow Lite:从零开始的摄像头图像识别
**** 1. 引言 随着人工智能(AI)和机器学习(ML)的发展,越来越多的开发者希望在嵌入式设备(如树莓派)上运行 AI 模型,实现目标检测、人脸识别等功能。TensorFlow Lite(TF…...
金融时间序列分析(Yahoo Finance API实战)
这里写目录标题 金融时间序列分析(Yahoo Finance API实战)1. 引言2. 项目背景与意义3. 数据集介绍4. GPU加速在数据处理中的应用5. 交互式GUI设计与加速处理6. 系统整体架构7. 数学公式与指标计算8. 完整代码实现9. 代码自查与BUG排查10. 总结与展望金融时间序列分析(Yahoo …...
Python 正则表达式模块 re
Python 正则表达式模块 re flyfish 一、正则表达式基础 1. 什么是正则表达式? 正则表达式(Regular Expression, RE)是一种用于匹配、查找和替换文本模式的工具,由普通字符(如字母、数字)和特殊字符&…...
Vue生命周期
一、Vue的生命周期及其阶段 Vue生命周期:一个Vue实例从 创建 到 销毁 的整个过程。也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。 生命周期的四个阶段:① 创建 ② 挂…...
vue3数据双向绑定解析
Vue 3 的双向绑定原理主要基于 Proxy 和 Reflect,核心源码在 reactivity 模块中。 1. 核心模块:reactivity reactivity 模块负责响应式数据的实现,主要包括以下几个文件: reactive.ts:处理对象和数组的响应式。ref.t…...
Gemini 2.0 全面解析:技术突破、应用场景与竞争格局
摘要 2025年3月,谷歌正式发布Gemini 2.0大模型,凭借其在多模态处理、代码生成和长上下文理解等领域的突破性进展,迅速成为AI领域的焦点。本文将深入剖析Gemini 2.0的技术架构、应用场景及与Grok3、DeepSeek R1、ChatGPT-4.5等竞品的对比&…...
【Linux系统编程】管道
目录 1、什么是管道2、管道的种类3、数据的读写3.1、管道通信3.2、管道的命令实例: 4、无名管道4.1、pipe() 无名管道的创建示例:简单读写示例:加入进程示例:通过 管道(pipe) 实现 父子进程之间的双向通信 …...
LeeCode题库第643题
643.子数组最大平均数I 项目场景: 给你一个由 n 个元素组成的整数数组 nums 和一个整数 k 。 请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。 任何误差小于 10-5 的答案都将被视为正确答案。 示例 1: 输入ÿ…...
数据炼丹与硬件互动:预测湿度的武学之道
前言 在这茫茫数据江湖中,高手过招,唯有融合机器学习与物联网之精髓,方能于风云变幻间自成一派。本文正是为各位江湖同道献上的秘籍,既有数据炼丹(预处理、模型训练)之奥义,也有硬件互通&#…...
【SpringBoot】MD5加盐算法的详解
目录 一、什么是加盐算法 二、如何实现加盐算法 2.1 加盐算法代码实现 2.2 注册页面中进行密码加盐 2.3 登录页面进行加盐的解密 2.4 注册和登录 一、什么是加盐算法 加盐算法是一种用于增强密码安全性的技术。这种技术通过在密码存储过程中添加一个随机生成的盐值&…...
IP风险度自检,互联网的安全“指南针”
IP地址就像我们的网络“身份证”,而IP风险度则是衡量这个“身份证”安全性的重要指标。它关乎着我们的隐私保护、账号安全以及网络体验,今天就让我们一起深入了解一下IP风险度。 什么是IP风险度 IP风险度是指一个IP地址可能暴露用户真实身份或被网络平台…...
如何手动使用下载并且运行 QwQ-32B-GGUF
首先使用安装 pip install ModelScope 使用 ModelScope 下载对应的模型 modelScope download --model Qwen/QwQ-32B-GGUF qwq-32b-q4_k_m.gguf 第二步开始下载 ollama git clone https://githubfast.com/ggerganov/llama.cpp # githubfast.com 可以加速下载 切换到目录&am…...
【实战ES】实战 Elasticsearch:快速上手与深度实践-附录-3-从ES 7.x到8.x的平滑迁移策略
👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 附录-版本升级指南 3-Elasticsearch 7.x 到 8.x 平滑迁移策略指南1. 升级必要性分析1.1 版本特性对比1.2 兼容性评估矩阵 2. 预升级准备清单2.1 环境检查表2.2 数据备份策略 3. 分阶段…...
IP 地址
文章目录 IP 地址IP 地址的分类IPv4 地址IPv6 地址 公有 IP 与私有 IP静态 IP 与动态 IP子网与子网掩码常见 IP 地址用途IP 地址的工作方式总结 IP 地址 IP(Internet Protocol)地址是计算机网络中的标识符,用于唯一标识网络中的设备。它可以…...
利用余弦相似度在大量文章中找出抄袭的文章
我前面的2篇文章分别讲了如果利用余弦相似度来判断2篇文章的相似度,来确定文章是否存在抄袭,和余弦相似度的原理,即余弦相似度到底是怎么来判断文章的相似性高低的等等。这一篇再说下,对于文章字数多和大量文章时,如果…...
《C语言中“输入魔法师”:scanf函数的奥秘与技巧》
🚀个人主页:fasdfdaslsfadasdadf 📖收入专栏:C语言 🌍文章目入 一、引言二、scanf函数的基本语法三、格式说明符的种类及用法(一)整数输入(二)浮点数输入(三&…...
VSTO(C#)Excel开发6:与窗体交互
初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的,可以在任何平台上使用。 源码指引:github源…...
PCL 点云OBB包围盒(二)
文章目录 一、简介二、实现步骤二、实现代码三、实现效果参考资料一、简介 包围盒是一种求解离散点集最优包围空间的算法,基本思想是用体积稍大且特性简单的几何体(称为包围盒)来近似地代替复杂的几何对象。(来源于百度)常用的求解包围盒的算法主要有AABB和OOB算法,但AAB…...
IDEA 一键完成:打包 + 推送 + 部署docker镜像
1、本方案要解决场景? 想直接通过本地 IDEA 将最新的代码部署到远程服务器上。 2、本方案适用于什么样的项目? 项目是一个 Spring Boot 的 Java 项目。项目用 maven 进行管理。项目的运行基于 docker 容器(即项目将被打成 docker image&am…...
农业建设项目管理系统评测:8款推荐工具优缺点分析
本文主要介绍了以下8款农业建设项目管理系统:1.PingCode; 2. Worktile ;3. 建米农业工程项目管理系统;4. 开创云数字农业管理平台; 5. Trimble Ag Software;6.Conservis; 7. Agworld ࿱…...