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

操作系统入门系列-MIT6.828(操作系统工程)学习笔记(七)---- 系统调用函数与GDB(Lab: system calls)

系列文章目录

操作系统入门系列-MIT6.828(操作系统工程)学习笔记(一)---- 操作系统介绍与接口示例
操作系统入门系列-MIT6.828(操作系统工程)学习笔记(二)---- 课程实验环境搭建(wsl2+ubuntu+quem+xv6)
操作系统入门系列-MIT6.828(操作系统工程)学习笔记(三)---- xv6初探与实验一(Lab: Xv6 and Unix utilities)
操作系统入门系列-MIT6.828(操作系统工程)学习笔记(四)---- C语言与计算机架构(Programming xv6 in C)
操作系统入门系列-MIT6.828(操作系统工程)学习笔记(五)---- 操作系统的组织结构(OS design)
操作系统入门系列-MIT6.828(操作系统工程)学习笔记(六)---- 初窥操作系统启动流程(xv6启动)
操作系统入门系列-MIT6.828(操作系统工程)学习笔记(七)---- 系统调用函数(Lab: system calls)


文章目录

  • 系列文章目录
  • 前言
  • 一、使用GDB
    • 1.打开gdb
    • 2.查看回溯输出,哪个函数调用了syscall()?
    • 3. p->trapframe->a7的值是多少?这个值代表什么?
    • 4.CPU之前的状态是什么?
    • 5.写下CPU崩溃处的汇编代码?num局部变量被赋值给哪个寄存器?
    • 6.kernel因为什么崩溃?
    • 7.kernel崩溃的时候运行的程序名字是啥?pid是多少?
  • 二、实现系统调用trace
    • 1.题目解析
    • 2.代码
    • 3.结果
  • 三、实现系统调用sysinfo
    • 1.题目描述
    • 2.代码实现
    • 3.结果
  • 总结


前言

本节对应的是MIT 6.828课程第三节的实验:Lab: system calls

本文主要探究xv6操作系统的系统调用函数的实现过程,以及为xv6增加两个系统调用函数trace和sysinfo,通过该实验可以深入理解操作系统的系统调用函数。


首先,需要更换git库的分支。课程的github代码库中有两个分支,一个是util分支(支持实验1:utilities);一个是syscall分支(支持实验2:systen calls),标注*号的,就是当前的分支。

在这里插入图片描述
使用命令,切换到syscall分支完成实验2.

git fetch
git checkout syscall
make clean

切换完分支后,使用命令:

make grade

结果如下:(没完成实验前会报错)

在这里插入图片描述

一、使用GDB

在许多情况下,打印语句足以调试您的内核,但有时候能够单步执行一些汇编代码或检查堆栈上的变量是有帮助的。gdb就是可以进行单步调试的工具,想要学习GDB的使用方法,可以看课程的PPT资料:Using the GNU Debugger,以及一些博客:【Linux】GDB用法详解(5小时快速教程)

第一部分实验是使用GDB,结合实验的引导,根据GDB的打印,在文件“answers-syscall.txt”(make grade会自动检测该文件)中回答一系列问题:

1.打开gdb

首先在一个linux命令台的实验目录中输入命令:

make qemu-gdb

在这里插入图片描述
再打开另一个窗口的实验目录,输入命令

gdb-multiarch

在这里插入图片描述

2.查看回溯输出,哪个函数调用了syscall()?

# 在syscall处打断点
b syscall
# 运行支断点处
c
# 打开C语言源码界面
layout src
# 查看栈回溯
backtrace

在这里插入图片描述
栈中只有usertrap()函数有待返回,所以是usertrap()函数调用了syscall()

3. p->trapframe->a7的值是多少?这个值代表什么?

# 继续执行一步,但是不进入函数
n
# 继续执行一步,但是不进入函数
n
# 以16进制的方式打印指针p中的内容
p /x *p

在这里插入图片描述
这里打印出来的是p的内容,我们需要p->trapframe->a7的值,所以应该打印p->trapframe的内容

p /x *(p->trapframe)

在这里插入图片描述
发现p->trapframe->a7为0x7。查看syscall()的源代码:p->trapframe->a7的值被赋给了num,num作为系统调用函数的索引被程序使用,所以该值代表的是系统调用的编号。
(因为笔者做完了实验,所以源码有内容被添加,想独立完成的读者跳过该部分完成实验)

void syscall(void)
{int num;struct proc *p = myproc();num = p->trapframe->a7;// num = * (int *) 0;if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {// Use num to lookup the system call function for num, call it,// and store its return value in p->trapframe->a0p->trapframe->a0 = syscalls[num]();// printf("%s %d %d %x %x\n", syscall_name[num-1], p->trace_mask, ((p->trace_mask >> (num-1)) & 0x1), p->trace_mask, (p->trace_mask >> (num-1)));if(((p->trace_mask >> num) & 0x1)){printf("%d: syscall %s -> %d\n", p->pid, syscall_name[num-1], p->trapframe->a0);}} else {printf("%d %s: unknown sys call %d\n",p->pid, p->name, num);p->trapframe->a0 = -1;}
}

4.CPU之前的状态是什么?

p /x $sstatus

在这里插入图片描述
$$sstatus代表起寄存器,对于该寄存器的描述查看文档:RISC-V privileged instructions 的63页

原文摘录:The SPP bit indicates the privilege level at which a hart was executing before entering supervisor mode. When a trap is taken, SPP is set to 0 if the trap originated from user mode, or 1 otherwise.
对应翻译:SPP位表示hart在进入主管模式之前正在执行的特权级别。当接收到trap时,如果该trap来自用户模式,则SPP设置为0,否则设置为1。
在这里插入图片描述
根据寄存器的各位含义说明,SPP是第9位,结合0x22,则SPP为0,说明trap来自用户模式,则CPU之前的状态是用户模式。结合代码运行的背景是用户程序的一次系统调用,因此可以印证是用户模式。

5.写下CPU崩溃处的汇编代码?num局部变量被赋值给哪个寄存器?

修改syscall()的源码,将语句

num = p->trapframe->a7;

替换为:

num = * (int *) 0;

关闭gdb后,使用make qemu重新启动xv6系统,发现系统崩溃并且报错了:
在这里插入图片描述
关闭系统,再打开gdb,我们单步跟踪执行到修改处,看看再哪个汇编代码处崩溃退出的。

# 在syscall处打断点
b syscall
# 运行支断点处
c
# 打开汇编源码界面
layout asm

在这里插入图片描述
然后使用n单步跟踪,直到gdb按下n后不动,说明系统崩溃退出了。
在这里插入图片描述
在这里插入图片描述
说明失效的代码是:,num的值关联s2寄存器(由于笔者的代码做后面的实验修改,所为汇编代码也不一致)

0x800020d8 <syscall+24> lw      s2,0(zero) # 0x0 

这条指令从地址0(zero)加载一个字(word)到寄存器s2中。由于zero寄存器的值总是0,这条指令实际上是在尝试从地址0加载数据。在RISC-V中,地址0通常被映射到一个只读的零页,因此这条指令实际上是在将零值加载到s2寄存器中。

笔者之前的代码是:
在这里插入图片描述

6.kernel因为什么崩溃?

查看文档:RISC-V privileged instructions 寻找寄存器sepc和scause的介绍:
发现:

sepc:
原文:When a trap is taken into S-mode, sepc is written with the virtual address of the instruction that was interrupted or that encountered the exception. Otherwise, sepc is never written by the implementation, though it may be explicitly written by software.
翻译:当trap进入s模式时,sepc写入被中断或遇到异常的指令的虚拟地址。否则,sepc永远不会由实现编写,尽管它可能由软件显式编写。
通过系统的崩溃返回报错,可以发现sepc的值是0x00000000800020d8,与崩溃的汇编代码的所在地址一致。(回看上文)

scause:
原文:The scause register is an SXLEN-bit read-write register formatted as shown in Figure 4.11. When a trap is taken into S-mode, scause is written with a code indicating the event that caused the trap.
翻译:原因寄存器是一个slenbit读写寄存器,格式如图4.11所示。当一个trap进入s模式时,会用一个代码来表示引起该trap的事件。
在这里插入图片描述
在这里插入图片描述
结合上文的报错,scause=0x000000000000000d,对照表格,原因是Load page fault,因为地址0没有映射到内核空间(下一节page table会讲)

7.kernel崩溃的时候运行的程序名字是啥?pid是多少?

打印进程名字与pid

p p->name
p p->pid

在这里插入图片描述
崩溃程序是initcode,pid是1。initcode的作用是在系统启动中作为用户空间的第一个程序启动,因为只有存在第一个用户程序,内核才能进一步实现多进程。详情看:操作系统入门系列-MIT6.828(操作系统工程)学习笔记(六)---- 初窥操作系统启动流程(xv6启动)

至此,答案是:
1.
usertrap()
2.
0x7 it represnts the system call number of the exec() that is a sysytem call fuction
3.
user mode
4.
8000204e: 00002683 lw a3,0(zero) # 0 <_entry-0x80000000>
a3 corresponding to num
5.
Load page fault
6.
initcode 1

二、实现系统调用trace

1.题目解析

实验原文:
在这里插入图片描述

翻译:

在这项任务中,您将添加一个系统调用跟踪功能,这在后续实验室调试时可能会对您有所帮助。您将创建一个新的跟踪系统调用,用于控制跟踪。它应该接受一个参数,即一个整数“掩码”,其位指定要跟踪哪些系统调用。例如,要跟踪fork系统调用,程序调用trace(1 << SYS_fork),其中SYS_fork是来自kernel/syscall.h的系统调用编号。您需要修改xv6内核,以便在每个系统调用即将返回时,如果该系统调用的编号在掩码中设置,则打印一行信息。该行应包含进程ID、系统调用的名称和返回值;您不需要打印系统调用的参数。跟踪系统调用应为调用它的进程及其随后fork出的任何子进程启用跟踪,但不应影响其他进程。

课程提供了一个用户级的跟踪程序,它能够启用跟踪功能来运行另一个程序(参见user/trace.c)。当你完成任务后,你应该能看到类似下面的输出:
在这里插入图片描述
用户程序的源码是:

#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"int
main(int argc, char *argv[])
{int i;char *nargv[MAXARG];if(argc < 3 || (argv[1][0] < '0' || argv[1][0] > '9')){fprintf(2, "Usage: %s mask command\n", argv[0]);exit(1);}if (trace(atoi(argv[1])) < 0) {fprintf(2, "%s: trace failed\n", argv[0]);exit(1);}for(i = 2; i < argc && i < MAXARG; i++){nargv[i-2] = argv[i];}exec(nargv[0], nargv);exit(0);
}

可以看到这个程序中调用了系统调用trace,通过调用方式可以推测出系统调用的原型:

int trace(int);

然后根据实验手册给的提示,将实现系统调用函数的准备工作做好:
在这里插入图片描述
1.在Makefile中添加$U/_trace到UPROGS

2.运行make qemu,您会发现编译器无法编译user/trace.c,因为系统调用的用户空间桩(stub)还不存在:在user/user.h中添加系统调用的原型,在user/usys.pl中添加桩,在kernel/syscall.h中添加系统调用编号。Makefile会调用perl脚本user/usys.pl,它会生成user/usys.S,即实际的系统调用桩,这些桩使用RISC-V的ecall指令来切换到内核。一旦您解决了编译问题,运行trace 32 grep hello README;它会失败,因为您还没有在内核中实现系统调用。

3.在kernel/sysproc.c中添加sys_trace()函数,通过在proc结构体中添加一个新变量来实现新系统调用(参见kernel/proc.h)。从用户空间检索系统调用参数的函数位于kernel/syscall.c中,您可以在kernel/sysproc.c中看到它们的使用示例。

4.修改fork()(参见kernel/proc.c),将父进程的跟踪掩码复制到子进程。

5.修改kernel/syscall.c中的syscall()函数以打印跟踪输出。您需要添加一个系统调用名称的数组以便索引。

6.如果直接在qemu中运行测试用例时通过了,但在使用make grade运行测试时出现超时,请尝试在Athena上测试您的实现。这个实验室的一些测试用例可能对您的本地机器来说计算量太大(特别是如果您使用WSL)。

之后我们思考,trace是要追踪每一个系统调用函数:被调用程序的pid;系统调用的返回值。那我们可以想到,只要在系统调用的时候,检测一下掩码,根据这个掩码使用if语句进行判断,如果是ture的话就打印这些信息(proc结构中都有);那么trace的功能就是更新这个掩码就好。为了方便,我们在proc结构体中加入掩码这个变量,大致功能就实现了。

2.代码

trace的实现:

uint64
sys_trace(void)
{int n;struct proc *p = myproc();  argint(0, &n);if(n < 0){return -1;}p->trace_mask = n;return 0;
}

syscall的修改:

void syscall(void)
{int num;struct proc *p = myproc();num = p->trapframe->a7;// num = * (int *) 0;if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {// Use num to lookup the system call function for num, call it,// and store its return value in p->trapframe->a0p->trapframe->a0 = syscalls[num]();// printf("%s %d %d %x %x\n", syscall_name[num-1], p->trace_mask, ((p->trace_mask >> (num-1)) & 0x1), p->trace_mask, (p->trace_mask >> (num-1)));if(((p->trace_mask >> num) & 0x1)){printf("%d: syscall %s -> %d\n", p->pid, syscall_name[num-1], p->trapframe->a0);}} else {printf("%d %s: unknown sys call %d\n",p->pid, p->name, num);p->trapframe->a0 = -1;}
}

fork函数的修改;

// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
fork(void)
{int i, pid;struct proc *np;struct proc *p = myproc();// Allocate process.if((np = allocproc()) == 0){return -1;}// Copy user memory from parent to child.if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){freeproc(np);release(&np->lock);return -1;}np->sz = p->sz;// copy saved user registers.*(np->trapframe) = *(p->trapframe);// Cause fork to return 0 in the child.np->trapframe->a0 = 0;//复制mask码np->trace_mask = p->trace_mask; // xxxxxxxxxxxx// increment reference counts on open file descriptors.for(i = 0; i < NOFILE; i++)if(p->ofile[i])np->ofile[i] = filedup(p->ofile[i]);np->cwd = idup(p->cwd);safestrcpy(np->name, p->name, sizeof(p->name));pid = np->pid;release(&np->lock);acquire(&wait_lock);np->parent = p;release(&wait_lock);acquire(&np->lock);np->state = RUNNABLE;release(&np->lock);return pid;
}

3.结果

在这里插入图片描述

三、实现系统调用sysinfo

1.题目描述

在这里插入图片描述
在这项任务中,您将添加一个名为sysinfo的系统调用,用于收集有关运行中的系统的信息。该系统调用接受一个参数:指向sysinfo结构体的指针(参见kernel/sysinfo.h)。内核应填充此结构体的字段:freemem字段应设置为可用内存的字节数,而nproc字段应设置为状态不是UNUSED的进程数。我们提供了一个测试程序sysinfotest;如果它打印出"sysinfotest: OK",则表示您通过了这项任务。

sysinfotest.c文件如下:该文件已经给出了我们如何得到剩余内存的方法:使用增加/缩减内存函数,一直增加内存直到没有剩余内存可以增加,在增加过程中记住增加了多少内存,这个内存就是剩余内存,最后一定记住缩减内存到原来的量。获取非UNUSED的进程数量,可以通过便利proc[NPROC] 数组的状态成员,简单进行一个统计即可。

#include "kernel/types.h"
#include "kernel/riscv.h"
#include "kernel/sysinfo.h"
#include "user/user.h"void
sinfo(struct sysinfo *info) {if (sysinfo(info) < 0) {printf("FAIL: sysinfo failed");exit(1);}
}//
// use sbrk() to count how many free physical memory pages there are.
//
int
countfree()
{uint64 sz0 = (uint64)sbrk(0);struct sysinfo info;int n = 0;while(1){if((uint64)sbrk(PGSIZE) == 0xffffffffffffffff){break;}n += PGSIZE;}sinfo(&info);if (info.freemem != 0) {printf("FAIL: there is no free mem, but sysinfo.freemem=%d\n",info.freemem);exit(1);}sbrk(-((uint64)sbrk(0) - sz0));return n;
}void
testmem() {struct sysinfo info;uint64 n = countfree();sinfo(&info);if (info.freemem!= n) {printf("FAIL: free mem %d (bytes) instead of %d\n", info.freemem, n);exit(1);}if((uint64)sbrk(PGSIZE) == 0xffffffffffffffff){printf("sbrk failed");exit(1);}sinfo(&info);// printf("%d  %d\n", info.freemem, info.nproc);  if (info.freemem != n-PGSIZE) {printf("FAIL: free mem %d (bytes) instead of %d\n", n-PGSIZE, info.freemem);exit(1);}if((uint64)sbrk(-PGSIZE) == 0xffffffffffffffff){printf("sbrk failed");exit(1);}sinfo(&info);if (info.freemem != n) {printf("FAIL: free mem %d (bytes) instead of %d\n", n, info.freemem);exit(1);}
}void
testcall() {struct sysinfo info;if (sysinfo(&info) < 0) {printf("FAIL: sysinfo failed\n");exit(1);}if (sysinfo((struct sysinfo *) 0xeaeb0b5b00002f5e) !=  0xffffffffffffffff) {printf("FAIL: sysinfo succeeded with bad argument\n");exit(1);}
}void testproc() {struct sysinfo info;uint64 nproc;int status;int pid;sinfo(&info);nproc = info.nproc;// printf("%d %d\n",countfree(), nproc);pid = fork();if(pid < 0){printf("sysinfotest: fork failed\n");exit(1);}if(pid == 0){sinfo(&info);if(info.nproc != nproc+1) {printf("sysinfotest: FAIL nproc is %d instead of %d\n", info.nproc, nproc+1);exit(1);}exit(0);}wait(&status);sinfo(&info);if(info.nproc != nproc) {printf("sysinfotest: FAIL nproc is %d instead of %d\n", info.nproc, nproc);exit(1);}
}void testbad() {int pid = fork();int xstatus;if(pid < 0){printf("sysinfotest: fork failed\n");exit(1);}if(pid == 0){sinfo(0x0);exit(0);}wait(&xstatus);if(xstatus == -1)  // kernel killed child?exit(0);else {printf("sysinfotest: testbad succeeded %d\n", xstatus);exit(xstatus);}
}int
main(int argc, char *argv[])
{printf("sysinfotest: start\n");testcall();testmem();testproc();printf("sysinfotest: OK\n");exit(0);
}

我们可以得到sysinfo函数的原型是:

int sysinfo(struct sysinfo *);

然后再编写函数实现之前完成准备工作:
在这里插入图片描述
1.在Makefile中添加$U/_sysinfotest到UPROGS

2.运行make qemuuser/sysinfotest.c将无法编译。按照之前任务的步骤添加系统调用sysinfo。为了在user/user.h中声明sysinfo()的原型,您需要预先声明struct sysinfo的存在:

struct sysinfo;
int sysinfo(struct sysinfo *);

3.一旦您解决了编译问题,运行sysinfotest;它会失败,因为您还没有在内核中实现该系统调用。sysinfo需要将一个struct sysinfo复制回用户空间;请参考sys_fstat()(在kernel/sysfile.c中)和filestat()(在kernel/file.c中)的示例,了解如何使用copyout()来完成这一操作。

4.为了收集可用内存的数量,您需要在kernel/kalloc.c中添加一个函数。

5.为了收集进程的数量,您需要在kernel/proc.c中添加一个函数。

2.代码实现

sysinfo函数:

uint64 sys_sysinfo(void)
{struct proc *p = myproc();struct sysinfo kernel_info;uint64 addr;argaddr(0, &addr);if(addr > PHYSTOP || addr == 0){// printf("%x %d\n", addr, addr);return -1;}kernel_info.freemem = get_freemem();kernel_info.nproc = get_nproc();if(copyout(p->pagetable, addr, (char *)&kernel_info, sizeof(kernel_info)) < 0){return -1;}return 0;
}

get_freemem()函数:

uint64 get_freemem(void)
{ int n = 0;while(1){if((uint64)growproc(PGSIZE) == 0xffffffffffffffff){break;}n += PGSIZE;}growproc(-n);return n;
}

get_nproc()函数:

uint64 get_nproc(void)
{struct proc *p;int num = NPROC;for(p = proc; p < &proc[NPROC]; p++) {if(p ->state == 0){num--;}}return num;
}

3.结果

在这里插入图片描述


总结

系统调用函数的实现有多种方法,有更简单更符合操作系统设计思想的方法欢迎大家评论区交流!

相关文章:

操作系统入门系列-MIT6.828(操作系统工程)学习笔记(七)---- 系统调用函数与GDB(Lab: system calls)

系列文章目录 操作系统入门系列-MIT6.828&#xff08;操作系统工程&#xff09;学习笔记&#xff08;一&#xff09;---- 操作系统介绍与接口示例 操作系统入门系列-MIT6.828&#xff08;操作系统工程&#xff09;学习笔记&#xff08;二&#xff09;---- 课程实验环境搭建&am…...

ORA-12560: TNS:协议适配器错误

项目场景&#xff1a; 由于最近一直没有连接oracle&#xff0c;然后之前windows也是正常可以启动oracle&#xff0c;正常连接。无论是SQL Developer还是SQL PLUS命令&#xff0c;都能正常连接和操作。 问题描述 这两天刚好用SQL Developer工具连接&#xff0c;然后报错&#…...

不容小觑的“白纸黑字”:银行重空凭证的风险与防控

一、定义与重要性 定义&#xff1a; 银行重空凭证&#xff0c;也称为重要空白凭证&#xff0c;是银行专业术语&#xff0c;指银行印制的无面额、经银行或单位填写金额并签章后&#xff0c;即具有支取款项效力的空白凭证。 重要性&#xff1a; 它是银行资金支付的重要工具&a…...

30v-180V降3.3V100mA恒压WT5107

30v-180V降3.3V100mA恒压WT5107 WT5107是一款恒压单片机供电芯片&#xff0c;它可以30V-180V直流电转换成稳定的3.3V直流电&#xff08;最大输出电流300mA&#xff09;&#xff0c;为各种单片机供电。WT5107的应用也非常广泛。它可以用于智能家居、LED照明、电子玩具等领域。比…...

Spring Boot 和 Spring Cloud 的区别及选型

Spring Boot 和 Spring Cloud 是现代 Java 开发中非常流行的两个框架&#xff0c;它们分别解决了不同层次的问题。本文将详细介绍 Spring Boot 和 Spring Cloud 的区别&#xff0c;以及在不同场景下如何选择合适的技术。 Spring Boot 什么是 Spring Boot Spring Boot 是一个…...

【神经网络】图像的数字视角

文章目录 图像的数字视角引言直观感受内在剖析图像常用函数图像三维层次 经验总结 图像的数字视角 引言 在机器视觉和目标识别领域&#xff0c;需要处理的对象都是图像&#xff0c;但这些领域的模型都是针对数值进行训练的&#xff0c;那么图像和数值之间是什么关系呢?答案是…...

ChatGPT的问题与回复的内容导出(Chorme)

我给出两种方式&#xff0c;第一种方式无使用要求&#xff0c;第二种方式必须安装Chorme 个人更推荐第二种方式 第一种方式&#xff1a;使用chatgpt自带的数据导出 缺点&#xff1a;会将当前未归档的所有聊天记录导出&#xff0c;发送到你的电子邮箱中 第二种方式&#xff1a…...

游戏开发中的坑之十四 photoshop的javascript脚本批量修改分辨率

原因&#xff1a;美术提交大量2048x2048的贴图&#xff0c;导致工程臃肿。 方案&#xff1a;使用photoshop的javascript脚本批量把指定的文件夹以及所有子文件夹的贴图进行压缩。 脚本中指定针对2048x2048的贴图进行处理。 // Photoshop JavaScript to resize TGA images with…...

leetcode打卡#day45 携带研究材料(第七期模拟笔试)、518. 零钱兑换 II、377. 组合总和 Ⅳ、爬楼梯(第八期模拟笔试)

携带研究材料&#xff08;第七期模拟笔试&#xff09; #include<iostream> #include<algorithm> #include<vector>using namespace std;int main() {int N, V;cin >> N >> V;vector<int> weights(N1);vector<int> values(V1);int w…...

Vite+Vue3安装且自动按需引入Element Plus组件库

一&#xff0c;安装Element Plus npm install element-plus //node环境16二&#xff0c;安装插件 npm install unplugin-auto-import unplugin-vue-components -D三&#xff0c;配置vite.config.ts文件 //按需引入element-plus组件 import AutoImport from unplugin-auto-i…...

敬酒词大全绝对实用 万能敬酒词

举杯共饮&#xff0c;友情初识&#xff1b;再续一杯&#xff0c;情深似海&#xff0c;朋友相伴人生路更宽。酒逢知己千杯少&#xff0c;一饮而尽显真意&#xff0c;浅尝则留情&#xff0c;深情则尽欢。友情到深处&#xff0c;千杯不倒&#xff0c;若情浅则饮少&#xff0c;醉卧…...

【Java】已解决com.mysql.cj.jdbc.exceptions.CommunicationsException异常

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决com.mysql.cj.jdbc.exceptions.CommunicationsException异常 一、分析问题背景 com.mysql.cj.jdbc.exceptions.CommunicationsException是Java程序在使用MySQL Connector/J与…...

Leetcode 76. 最小覆盖子串

76. 最小覆盖子串 - 力扣&#xff08;LeetCode&#xff09; class Solution {/**也是滑动窗口&#xff0c;思路简单&#xff0c;但实现起来容易出错。一个tmap记录目标串t的各个字符出现的次数&#xff1b;一个smap记录原串的某个滑动窗口里字符出现次数。两个指针left&#x…...

JAVAWEB--Mybatis03

Mybatis映射 什么是映射器 MyBatis的映射器就是用来解决这一问题&#xff0c;映射器其实是一个Interface接口,我们通过编写简单的映射器接口&#xff0c;就可以将我们之前在Dao中做的重复的&#xff0c;看起来比较低级的代码给替换掉。也就是说我们以后不用向之前那样写代码&…...

论文学习_Fuzz4All: Universal Fuzzing with Large Language Models

论文名称发表时间发表期刊期刊等级研究单位Fuzz4All: Universal Fuzzing with Large Language Models2024年arXiv-伊利诺伊大学 0.摘要 研究背景模糊测试再发现各种软件系统中的错误和漏洞方面取得了巨大的成功。以编程或形式语言作为输入的被测系统&#xff08;SUT&#xff…...

元数据相关资料整理 metadata

目录 定义和特点 关注点 流程 使用场景 元数据影响分析 元数据冷热度分析 元数据关联度分析 血缘分析 数据地图 元数据接口 相关产品的架构图 定义和特点 元数据&#xff08;Metadata&#xff09;是指关于数据的数据&#xff0c;或者说是描述数据的数据。它提供了一…...

【Android面试八股文】谈一谈你对http和https的关系理解

文章目录 HTTPHTTPSSSL/TLS协议HTTPS加密、解密流程HTTP 和 HTTPS 的关系具体的差异实际应用总结扩展阅读HTTP(HyperText Transfer Protocol)和HTTPS(HyperText Transfer Protocol Secure)是用于在网络上进行通信的两种协议。 它们在很多方面是相似的,但关键的区别在于安全…...

Vue3 中 setup 函数与 script setup 用法总结

在 Vue3 中&#xff0c;setup 函数和 script setup 是两种新引入的编写组件的方式&#xff0c;它们都是 Composition API 的一部分。 setup 函数: setup 函数是一个新的组件选项&#xff0c;它作为在组件内使用 Composition API 的入口。在 setup 函数中&#xff0c;我们可以定…...

Springboot 开发之任务调度框架(一)Quartz 简介

一、引言 常见的定时任务框架有 Quartz、elastic-job、xxl-job等等&#xff0c;本文主要介绍 Spirng Boot 集成 Quartz 定时任务框架。 二、Quartz 简介 Quartz 是一个功能强大且灵活的开源作业调度库&#xff0c;广泛用于 Java 应用中。它允许开发者创建复杂的调度任务&…...

企业中面试算法岗时会问什么pytorch问题?看这篇就够了!

如果要面试深度学习相关的岗位&#xff0c;JD上一般会明确要求要熟悉pytorch或tensorflow框架&#xff0c;那么会一般问什么相关问题呢&#xff1f; 文章目录 一. 基础知识与概念1.1 PyTorch与TensorFlow的主要区别是什么&#xff1f; 1.2 解释一下PyTorch中的Tensor是什么&…...

【学习】程序员资源网址

1 书栈网 简介&#xff1a;书栈网是程序员互联网IT开源编程书籍、资源免费阅读的网站&#xff0c;在书栈网你可以找到很多书籍、笔记资源。在这里&#xff0c;你可以根据热门收藏和阅读查看大家都在看什么&#xff0c;也可以根据技术栈分类找到对应模块的编程资源&#xff0c;…...

【3D模型库】机械三维模型库整理

1 开拔网 简介&#xff1a;开拔网是中国较早的机械设计交流平台&#xff0c;广受行业内的各个大学&#xff0c;公司以及行业人士的欢迎。网站有非常丰富的3D模型&#xff0c;CAD图纸&#xff0c;以及各类热门软件的下载。同时我们也为行业搭建一个平台&#xff0c;提供各类设计…...

基于Python-CNN深度学习的物品识别

基于Python-CNN深度学习的物品识别 近年来&#xff0c;深度学习尤其是卷积神经网络&#xff08;CNN&#xff09;的快速发展&#xff0c;极大地推动了计算机视觉技术的进步。在物品识别领域&#xff0c;CNN凭借其强大的特征提取和学习能力&#xff0c;成为了主流的技术手段之一…...

Qt | 简单的使用 QStyle 类(风格也称为样式)

01、前言 者在 pro 文件中已添加了正确的 QT+=widgets 语句 02、基础样式 1、QStyle 类继承自 QObject,该类是一个抽像类。 2、QStyle 类描述了 GUI 的界面外观,Qt 的内置部件使用该类执行几乎所有的绘制,以确保 使这些部件看起来与本地部件完全相同。 3、Qt 内置了一系…...

Idea连接GitLab的过程以及创建在gitlab中创建用户和群组

上期讲述了如何部署GitLab以及修复bug&#xff0c;这期我们讲述&#xff0c;如何连接idea。 首先安装gitlab插件 下载安装idea上并重启 配置ssh免密登录 使用管理员打开命令行输入&#xff1a;ssh-keygen -t rsa -C xxxaaa.com 到用户目录下.ssh查看id_rsa.pub文件 打开复制…...

关于glibc-all-in-one下载libc2.35以上报错问题

./download libc版本 下载2.35时报错&#xff1a;原因是缺少解压工具zstd sudo apt-get install zstd 下载后重新输命令就可以了 附加xclibc命令 xclibc -x ./pwn ./libc-版本 ldd pwn文件 xclibc -c libc版本...

C语言之#define #if 预处理器指令

在 C 语言中&#xff0c;预处理器指令用于条件编译代码。你可以使用 #define 和 #if 指令来根据某些条件包含或排除代码块。以下是一个完整的例子&#xff0c;演示了如何使用 #define 和 #if 指令来控制代码的编译&#xff1a; #include <stdio.h>// 定义宏 MERGE_TYPE …...

modbus流量计数据解析(4个字节与float的换算)

通过modbus协议从流量计中读取数据后&#xff0c;需要将获得的字节数据合成float类型。以天信流量计为例&#xff1a; 如何将字节数据合并成float类型呢&#xff1f;这里总结了三种方法。 以温度值41 A0 00 00为例 目录 1、使用char*逐字节解析2、使用memcpy转换2、使用联合体…...

关于element-plus中el-select自定义标签及样式的问题

关于element-plus中el-select自定义标签及样式的问题 我这天天的都遇到各种坑&#xff0c;关于自定义&#xff0c;我直接复制粘贴代码都实现不了&#xff0c;研究了一下午&#xff0c;骂骂咧咧了一下午&#xff0c;服气了。官网代码实现不了&#xff0c;就只能 “ 曲线救国 ”…...

硕思logo设计师下载-2024官方最新版-logo制作软件安装包下载

硕思​​Logo设计​​师是一款操作灵活简单、功能强大的logo制作​​软件​​。可以通过简单的点击就可以为网站、博客、论坛和邮件创建专业的logo、条幅、按钮、标题、图标和签名等。 硕思logo设计师提供了很多精心设计的模板和丰富的资源&#xff0c;为更好的创建logo艺术作品…...

做百度移动端网站排名软件/优化网站首页

题目链接&#xff1a;https://ac.nowcoder.com/acm/contest/992/J 时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C 32768K&#xff0c;其他语言65536K 64bit IO Format: %lld 题目描述 红红和蓝蓝是随机降生在苹果树上的苹果仙灵&#xff0c…...

网站关键字优化工具/ 百度一下

2019独角兽企业重金招聘Python工程师标准>>> sql里distinct后&#xff0c;没办法order by可以用min()解决。 SELECT 字段名 FROM dd_deviceinfo_copy GROUP BY 字段名 ORDER BY MIN(id) 转载于:https://my.oschina.net/u/1765449/blog/1511666...

wordpress的图片代码是什么/网站自然排名优化

堆中建立对象 为了执行这种限制&#xff0c;你必须找到一种方法禁止以调用new以外的其它手段建立对象。这很容易做到。非堆对象在定义它的地方被自动构造&#xff0c;在生存时间结束时自动被释放&#xff0c;所以只要禁止使用隐式的构造函数和析构函数&#xff0c;就可以实现这…...

建设一个货代网站想要多少钱/今日特大军事新闻

思路 分开数组&#xff0c;加入如下分成两组&#xff0c;创建一个辅助空间&#xff0c;用来合并。用两个指针指向两个数组的下标&#xff0c;比较哪个小&#xff0c;那个小就复制哪个&#xff0c;然后其中一个下标越界&#xff0c;另一个数组剩余都复制下来。 public static …...

岳阳设计网站推荐/企业建站

1. 序列号和确认号的简介及作用 TCP协议工作在OSI的传输层&#xff0c;是一种可靠的面向连接的数据流协议&#xff0c;TCP之所以可靠&#xff0c;是因为它保证了传送数据包的顺序。顺序是用一个序列号来保证的。响应包内也包括一个序列号&#xff0c;表示接收方准备好这个序列…...

wordpress资源站源码/友情链接样式

‍‍最近腾讯文档上线了&#xff0c;什么是腾讯文档呢&#xff1f;腾讯文档是一款可多人协作的在线文档&#xff0c;可同时编辑Word和Excel文档&#xff0c;云端实时保存。电脑腾讯文档功能受到很多用户喜爱&#xff0c;但是大部分用户不知道腾讯文档的具体使用方法和功能&…...