操作系统入门系列-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.结果

在这里插入图片描述


总结

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

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

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

相关文章

Python 围棋游戏【含Python源码 MX_008期】

简介&#xff1a; 围棋&#xff0c;源自中国&#xff0c;是一种两人对弈的策略棋类游戏。它被认为是世界上最复杂的棋类游戏之一&#xff0c;因为它的规则简单&#xff0c;但变化复杂多样。围棋的游戏目标是在棋盘上占领更多的地盘&#xff0c;并用自己的棋子围住对手的棋子&am…

docker-compose harbor 2.11

harbor 前言 “Harbor” 是一个用于管理容器镜像的开源仓库项目。由 VMware 开发和维护,Harbor 提供一个企业级的 Docker 镜像仓库,具有丰富的功能,包括: 镜像管理:提供存储和分发 Docker 镜像的能力。安全性:支持镜像签名和漏洞扫描,确保镜像的安全性。身份认证:集成…

41 mysql subquery 的实现

前言 sub query 是一个我们经常会使用到的一个 用法 我们这里 看一看各个场景下面的 sub query 的相关处理 查看 本文, 需要 先看一下 join 的相关处理 测试数据表如下, 两张测试表, tz_test, tz_test03, 表结构 一致 CREATE TABLE tz_test (id int(11) unsigned NOT NUL…

QT调用vs2019生成的c++动态库

QT调用vs2019生成的c动态库 dll库的创建方法&#xff1a; VS2019创建c动态链接库dll与调用方法-CSDN博客 加减法示范&#xff1a; 头文件 // 下列 ifdef 块是创建使从 DLL 导出更简单的 // 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 DLL3_EXPORTS // 符号编…

SurfaceView->SurfaceView基本概念

绘制过程 View和SurfaceView绘制过程 PhoneWindow&#xff1a;Window的具体实现&#xff0c;在Activity中调用setContentView()方法时&#xff0c;一个PhoneWindow实例会对应一个ViewRootImpl实例&#xff0c;绘制&#xff0c;事件分发传递给ViewRootImpl进行ViewRootImpl&…

少样本学习元学习

基本概念 首先是机器学习&#xff1a; 然后&#xff0c;什么是元学习&#xff08;what is meta learning?) 之前&#xff0c;Component都是让人自己设置的。在Meta Learning中&#xff0c;我们期望它能够自己学出来。 不同的meta learning方法就是想办法去学learning algori…

【面向就业的Linux基础】从入门到熟练,探索Linux的秘密(二)

主要内容介绍可tmux和vim的一些常用操作&#xff0c;可以当作笔记需要的时候进来查就行。 文章目录 前言 一、tmux和vim 二、Linux系统基本命令 1.tmux教程 2. vim教程 3.练习 总结 前言 主要内容介绍可tmux和vim的一些常用操作&#xff0c;可以当作笔记需要的时候进来查就行…

javaWeb项目-ssm+vue中国风音乐推介网站功能介绍

本项目源码&#xff1a;java-ssmvue中国风音乐推介网站源码说明文档资料资源-CSDN文库 项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、…

Python网络爬虫5-实战网页爬取

1.需求背景 在上一篇Python网络爬虫4-实战爬取pdf中&#xff0c;以松下品牌说明书为例说明了网页爬取PDF的分析流程。在实际的应用中&#xff0c;具体代码需要根据不同的网址情况和需求进行更改。 明确要求&#xff1a; 此次&#xff0c;想要爬取苏泊尔品牌下的说明书pdf&…

【第5章】Vue之API风格

文章目录 前言一、选项式 API&#xff08;Options API&#xff09;1. 代码2. 效果 二、组合式 API&#xff08;Composition API&#xff09;1. 代码2. 效果 三、两者之间的区别总结 前言 Vue.js 提供了两种主要的 API 风格来组织组件的代码&#xff1a;选项式 API&#xff08;…

ios描述文件.mobileprovision 如何查看包含的设备 udid|IPA查看是否包含设备 UDID|轻松签查看证书是否包含自己设备 UDID

前言 之前蒲公英支持上传证书查看证书有效期和包含设备 【干货】IOS苹果P12证书有效性检测 及查看证书是否包含自己的设备 【干货】IOS苹果P12证书有效性检测 及查看证书是否包含自己的设备 - 路灯IT技术博客 - 后厂村路灯 如今蒲公英下架了该功能&#xff0c;已经没有证书检…

新等保2.0防护体系方案

等保2.0防护体系 吉祥学安全知识星球&#x1f517;除了包含技术干货&#xff1a;Java代码审计、web安全、应急响应等&#xff0c;还包含了安全中常见的售前护网案例、售前方案、ppt等&#xff0c;同时也有面向学生的网络安全面试、护网面试等。 最近在写一些咨询相关的材料&…

计算机网络(6) TCP协议

TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是互联网协议套件中一种核心协议。它提供面向连接的、可靠的字节流传输服务&#xff0c;确保数据从一端正确无误地传输到另一端。TCP的主要特点包括&#xff1a; 可靠性&#xff1a;TCP使用…

返回值返回引用返回指针之间的区别

一、返回值 当函数返回一个值时&#xff0c;实际返回的是一个变量的拷贝。 优点&#xff1a; 简单易用&#xff1b;安全&#xff0c;不会导致悬挂指针或悬挂引用&#xff1b; 缺点&#xff1a; 当返回值是一个较大的对象时会产生拷贝开销&#xff0c;影响程序性能&#xf…

PHP聚合通多平台支付平台源码

源码介绍 php聚合通多平台支付平台源码&#xff0c;源码搭建了一下&#xff0c;这个源码不复杂&#xff0c;修改一下数据库账号密码然后导入数据库就可以&#xff0c;和网站恢复备份一样简单&#xff01; 源码截图 源码下载 PHP聚合通多平台支付平台源码

【小白专用 已验证24.6.7】C# MySQL数据库访问操作封装类

一、底层库介绍 本文主要介绍数据库访问操作类&#xff0c;包含&#xff1a;SQL插入脚本、SQL查询脚本、数据库表是否存在判断、带参脚本执行、包含事务回滚脚本执行、存储过程脚本等等。 特殊说明 在使用之前&#xff0c;先安装 MySql.Data 插件 二、底层库源码 2.1 程序源…

C++ 24 之 拷贝构造函数

c24拷贝构造函数.cpp #define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std;class Person3 { private:int p_age; public:// 构造函数分类&#xff1a;// 按参数分类&#xff1a;1、有参 2、无参// 按类型分类:普通、拷贝&#xff08;复制&…

第2章 Rust初体验5/8:match表达式和模式匹配:更富表达力:猜骰子冷热游戏

讲动人的故事,写懂人的代码 2.5 故事3: 比较答案与点数之和 贾克强:“同学们,我们开始用三种语言来实现故事3吧!” 2.5.1 Rust版故事3 这个故事实在是轻松容易地实现了。赵可菲照着书,一下子就写好了。 @@ -1,4 +1,5 @@use rand::Rng; +use std::cmp::Ordering;use std…

【动态规划】| 路径问题之最小路径和 力扣64

&#x1f397;️ 主页&#xff1a;小夜时雨 &#x1f397;️专栏&#xff1a;动态规划 &#x1f397;️如何活着&#xff0c;是我找寻的方向 目录 1. 题目解析2. 代码 1. 题目解析 题目链接: https://leetcode.cn/problems/minimum-path-sum/description/ 这道题目和之前一道…

使用CSS、JavaScript、jQuery三种方式实现手风琴效果

手风琴效果有不少&#xff0c;王者荣耀官网&#xff08;源网址 https://pvp.qq.com/raiders/ &#xff09;有一处周免英雄&#xff0c;使用的就是手风琴效果&#xff0c;如图所示。 我试着用css、js、jQuery三种方式实现了这种效果&#xff0c;最终效果差不多&#xff0c;美中不…