《操作系统》by李治军 | 实验5 - 基于内核栈切换的进程切换

news2024/12/24 20:15:59

目录

一、实验目的

二、实验内容

三、实验准备

(一)TSS 切换

(二)基于堆栈的进程切换流程

四、实验过程

(一)实现 switch_to()

1、修改 schedule() 中的 switch_to()

2、实现 switch_to() 的汇编代码

(二)修改 PCB

(三)修改 fork() 系统调用

1、如何修改 fork 系统调用

2、修改 copy_process

3、first_return_kernel

(四)编译运行新的 Linux 0.11

(五)分析实验 4 的日志,体会修改前后系统运行的差别

1、运行 process.c

2、分析 log 文件,统计数据

3、TSS 和堆栈切换的差异


一、实验目的

1、深入理解进程和进程切换的概念。

2、综合应用进程、CPU 管理、PCB、LDT、内核栈、内核态等知识解决实际问题。

3、开始建立系统认识。

二、实验内容

       现在的 Linux 0.11 采用 TSS 和一条指令就能完成任务切换,虽然看似简单,但指令的执行时间却很长,在实现任务切换时大概需要 200 多个时钟周期。而通过 堆栈 实现任务切换会更快,而且采用堆栈的切换还可以使用指令流水并行优化技术,同时又使得 CPU 的设计变得简单。所以,无论是 Linux 还是 Windows 系统,进程/线程的切换都没有使用 Intel 提供的这种 TSS 切换手段,而都是通过堆栈实现的。

       本次实验的内容就是修改 Linux 0.11 中的进程/线程切换手段,将 0.11 原本采用的 TSS 切换部分去掉,替换为——基于堆栈的切换程序。具体一点,就是将 Linux 0.11 中的 switch_to 实现去掉,并写实现一段基于堆栈切换的代码。

具体内容包括:

(一)编写汇编程序 switch_to ,实现:

  • 完成主体框架

  • 在主体框架下依次完成 PCB 切换、内核栈切换、LDT 切换等

(二)修改 PCB

  • task_struct 结构,增加相应的内容域,同时处理由于修改了 task_struct 所造成的影响

(三)修改 fork() 系统调用

  • 由于是基于内核栈的切换,所以进程需要创建出能完成内核栈切换的样子

(四)修改后的 Linux 0.11 仍然可以正常启动、使用

(五)分析实验 4(进程运行轨迹的跟踪与统计)的日志,体会修改前后系统运行的差别

三、实验准备

(一)TSS 切换

Linux 学习笔记(九):基于 TSS 的进程切换_Amentos的博客-CSDN博客

(二)基于堆栈的进程切换流程

1、 PCB 切换

       进程切换的第一步是进程控制块 PCB 的切换,PCB 中保存着该进程上一次停止时内核栈栈顶的指针(esp)。先保存好当前进程 PCB 中的内核栈栈顶指针,然后再切换到目标进程的 PCB,之后就可以根据目标进程 PCB 中相关内容切换到目标进程的内核栈。

2、内核栈切换

       PCB 中包含指向内核栈栈顶的指针,现在 PCB 已经切换,就可以根据 PCB 中内容切换内核栈。

3、LDT 切换

       进程切换还包含 LDT(内存映射表)的切换,这是和线程切换的不同之处。

4、用户栈切换

       内核栈中的 SS、SP 指向用户栈,在内核中运行一段代码后,通过 iret 指令退出内核时就能实现用户栈的切换。

四、实验过程

(一)实现 switch_to()

1、修改 schedule() 中的 switch_to()

       目前 Linux 0.11 中工作的 schedule() 首先找到目标进程的数组位置 next,这个 next 就是 GDT 中的 n,也就是说 next 是用来找到要切换的目标进程对应 TSS 段的段描述符的。一旦获得这个 next 值,然后直接调用 switch_to(next) 就能完成基于 TSS 的进程切换了。

       但现在,我们不用 TSS 进行切换,而是采用切换内核栈的方式来完成进程的切换。所以在新的 switch_to 中将用到当前进程的 PCB、目标进程的 PCB、当前进程的内核栈、目标进程的内核栈、目标进程的 LDT 等信息。所以要修改 switch_to 的参数。

  • 进程的内核栈可以通过 PCB 找到(具体怎么找见下文),所以我们只需要传递进程的 PCB 和 LDT 就行

       由于指向当前进程 PCB 的 current 指针是一个全局变量,就无需向函数传递了,所以只需要告诉新的 switch_to() 一个指向目标进程 PCB 的指针 pnext ,同时还要将 next 也传递进去。

       虽然 TSS(next) 已经不需要了,但是 LDT(next) 还是需要的。也就是说,现在每个进程不必拥有自己的 TSS 了,因为已经不采用 TSS 切换了,但是每个进程还是需要有自己的 LDT,地址分离地址还是必须要有的,因为进程的切换必然要涉及到 LDT 的切换。

【1】修改 switch_to 函数的参数

综上所述,将目前的 schedule() 函数(在 kernal/sched.c 中)稍做修改,将下面的代码:

if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
    c = (*p)->counter, next = i;

//......

switch_to(next);

修改为:

if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
    c = (*p)->counter, next = i, pnext = *p;

//.......

switch_to(pnext,_LDT(next));

修改后如图所示:

【2】添加 pnext 的变量声明

其中 pnext 指向目标进程的 PCB,_LDT(next) 指向目标进程的 LDT 。

_LDT(n) 在 sched.h 中已经有定义:

但是 pnext 是我们自己定义的指向 PCB 的指针,所以需要在 sched.h 中手动添加声明:

struct tast_struct *pnext = &(init_task.task);
  • Linux 0.11 中进程的 PCB 就是一个名为 task_struct 的结构体(在 sched.h 中定义)

添加到如下位置即可:

  • 这里 pnext 和 current 的初始赋值是一样的

【3】添加 switch_to 函数声明

       虽然 switch_to 还没有开始正式编写,但是我们现在已经确定了 switch_to() 的两个参数,所以可以先添加对应的函数声明,不会影响接下来 switch_to 的编写

在 sched.c 中声明:

extern long switch_to(struct task_struct *p, unsigned long address);

添加到如下位置即可:

       这样一来,在 schedule() 中经过一番操作后,pnext 就指向了要切换的目标进程的 PCB(具体过程不用管,我们只需要知道结果即可)。当调用 switch_to(pnext, _LDT(next)); 时,会依次将 参数 2  _LDT(next)、 参数1  pnext、返回地址  }  压栈。当执行 switch_to 的返回指令 ret 时,就回弹出 schedule() 函数的  并执行这个 schedule() 的返回指令  }  。

2、实现 switch_to() 的汇编代码

       实现 switch_to() 是本次实验中最重要也最难的一部分,由于要对内核栈进行精细的操作,所以需要用汇编代码来完成编写,switch_to() 要依次完成如下功能:

(1)由于是 C 语言调用汇编,所以首先需要在汇编中处理栈帧,即处理 ebp 寄存器。

(2)接下来要取出表示目标进程 PCB 的参数,并和 current(当前进程 PCB)做一个比较:

         >> 如果 “等于” current,则什么也不用做。

         >> 如果 “不等于” current,则开始进程切换:依次完成 PCB 的切换、TSS 中的内核栈指针的重写、内核栈的切换、LDT 的切换以及 PC 指针(即 CS:EIP)的切换。

【1】删除原来的 switch_to()

       Linux 0.11 原本的 switch_to() 展开就是一段 ljmp 指令,现在要改写成基于堆栈(内核栈)切换的函数,就要删除掉该语句,直接注释掉即可(在 sched.h 中),如下图:

【2】编写基于堆栈的 switch_to 代码

       新的 switch_to() 就不在 sched.h 中编写了,我们将它写成一个系统调用函数,所以要在 kernel/system_call.s 中编写。

switch_to 完整代码:

.align 2
switch_to:
    //因为该汇编函数要在c语言中调用,所以要先在汇编中处理栈帧
	pushl %ebp
	movl %esp,%ebp
	pushl %ecx
	pushl %ebx
	pushl %eax
 
    //将ebp+8指向的数据(目标进程的PCB)传递给ebx,然后进行判断:
    //如果目标进程的pcb <<等于>> 当前进程的pcb => 不需要进行切换,直接退出函数调用
    //如果目标进程的pcb <<不等于>> 当前进程的pcb => 需要进行切换,直接跳到下面去执行
	movl 8(%ebp),%ebx
	cmpl %ebx,current
	je 1f
 
    /** 执行到此处,就要进行真正的基于堆栈的进程切换了 **/
	
    // 切换PCB
	movl %ebx,%eax
	xchgl %eax,current
	
	// 重写TSS中内核栈的指针
	movl tss,%ecx
	addl $4096,%ebx
	movl %ebx,ESP0(%ecx)
 
	// 切换内核栈
	movl %esp,KERNEL_STACK(%eax)
	movl 8(%ebp),%ebx
	movl KERNEL_STACK(%ebx),%esp
 
	// 切换LDT
	movl 12(%ebp),%ecx
	lldt %cx
    // 切换 LDT 之后
	movl $0x17,%ecx
	mov %cx,%fs
	
    // 这一段先不用管
	cmpl %eax,last_task_used_math
	jne 1f
	clts
	
	// 现在进入新进程的内核栈工作了,所以接下来做的四次弹栈以及ret处理使用的都是新进程内核栈中的东西
1:	popl %eax
	popl %ebx
	popl %ecx
	popl %ebp
	ret

以上代码添加到 system_call.s 中最后位置即可(即 parallel_interrupt 之后),如下图所示:

【3】switch_to 代码分析

《操作系统》by李治军 | 实验5.pre - switch_to 汇编代码详解_Amentos的博客-CSDN博客

建议先看看这篇文章再继续。

【4】添加对应变量定义和宏定义

① tss

       虽然此时不使用 TSS 进行进程切换了,但是 Intel 的中断处理机制还是要保持的。switch_to 代码中使用了 tss 来作为所有进程的 TSS(这里使用 0 号进程的 TSS),因此需要定义一个全局指针变量 tss 来指向那一段 0 号进程的 TSS 内存(在 sched.c 中定义):

struct tss_struct *tss = &(init_task.task.tss);

添加到如下位置即可:

② ESP0

      ESP0 定义为 4 添加在 system_call.s 中(至于为什么是 4 看看 TSS 的结构体定义就明白了):

(二)修改 PCB

       Linux 0.11 中的 PCB 即 task_struct 结构,因为我们添加的堆栈切换使用到了 PCB 中的内容(即内核栈指针域)来进行内核栈的切换,但是在 Linux 0.11 原本的 PCB 中是没有这个域的。所以对应要增加相应的内容域,同时要处理由于修改了 task_struct 所造成的影响。

【1】添加内核栈指针域 kernelstack

       Linux 0.11 中 PCB 的定义里并没有用于保存内核栈指针的域(kernelstack),但是在 switch_to 中的内核栈切换模块使用到了这个域,所以需要我们自己在 PCB 结构定义中找位置加上这个定义。

       Linux 0.11 的 PCB 就是 task_struct 结构(在 include/linux/sched.h 中定义),需要在其中添加一个 kernelstack 作为内核栈指针域。当然将 kernelstack 域加在 task_struct 中的哪个位置都可以,但是在某些汇编文件中(主要是在 kernel/system_call.s 中)有关于操作这个结构的汇编硬编码,所以一旦增加了 kernelstack,这些硬编码也需要跟着修改。

       由于第一个位置,即 long state 出现的汇编硬编码很多,所以 kernelstack 千万不要放置在 task_struct 中的第一个位置(不然修改起来非常麻烦)。当放在除第一个位置外的其他位置时,只需要修改 kernel/system_call.s 中对应硬编码就可以了。

long kernelstack;

添加到如下位置即可:

【2】修改硬编码

       添加完成后在 system_call.s 中修改汇编硬编码,并添加定义 KERNEL_STACK = 12(为什么是 12 ?因为 kernelstack 添加在第四个位置),修改代码如下:

/* ...... */
state   = 0     # these are offsets into the task-struct.
counter = 4
priority = 8
kernelstack = 12
signal  = 16
sigaction = 20      # MUST be 16 (=len of sigaction)
blocked = (37*16)

ESP0 = 4
KERNEL_STACK = 12

修改情况如下:

【3】添加内核栈指针的初始化

       由于我们修改了 PCB 结构体的定义,所以在产生 0 号进程的 PCB 初始化时也要跟着一起变化。需要在 sched.h 中做如下修改,即在 PCB 的第四项(内核栈指针)中增加关于内核栈指针的初始化。

#define INIT_TASK \
/* state etc */ { 0,15,15,PAGE_SIZE+(long)&init_task,\
/* signals */   0,{{},},0, \
......
}

添加情况如下图所示:

(三)修改 fork() 系统调用

  • 修改为基于内核栈的切换,所以进程需要创建出能完成内核栈切换的样子

1、如何修改 fork 系统调用

      ① 修改 fork() 系统调用和书中论述的原理一致,就是要把进程的用户栈、用户程序和其内核栈通过压在内核栈中的 SS:ESP、CS:IP 关联在一起。

      ② 另外,由于 fork() 这个叉子的含义就是要让父进程和子进程共用同一个代码、数据和堆栈,现在虽然修改成了使用内核栈完成任务切换,但 fork() 的这个基本含义不会发生变化。也就是说基于堆栈的进程切换还是要实现父子进程共用同一个代码、数据和堆栈。

      将上面两段描述联立在一起,修改 fork() 的核心工作就是要形成如下图所示的子进程内核栈结构。

 

      根据上图不难想象,对 fork() 的修改就是对子进程的内核栈的初始化。所以我们要做的就是将新建子进程的内核栈和其父进程的用户栈、用户程序地址关联在一起(因为原来的 TSS 没有做这样的关联,所以要我们自己做),实现父子进程共享同一块内存空间、堆栈和数据代码块。

2、修改 copy_process

fork() 系统调用的代码在 system_call.s 汇编文件中,我们先看看原本的代码:

.align 2
sys_fork:
	call find_empty_process
	testl %eax,%eax
	js 1f
	push %gs
	pushl %esi
	pushl %edi
	pushl %ebp
	pushl %eax
	call copy_process//跳转到copy_process()函数
	addl $20,%esp
1:	ret

       可以看出 fork() 函数的核心就是调用 copy_process() ,所以接下来去到 copy_process()(定义在 kernel/fork.c 中),添加代码和注释分析如下:

int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
		long ebx,long ecx,long edx,
		long fs,long es,long ds,
		long eip,long cs,long eflags,long esp,long ss)
{
	struct task_struct *p;
	int i;
	struct file *f;

    //用来申请一页内存空间作为子进程的PCB
	p = (struct task_struct *) get_free_page();

	...

	//容易看出下面的部分就是基于tss进程切换机制时的代码,所以要将此片段注释掉
    /*
	p->tss.back_link = 0;
	p->tss.esp0 = PAGE_SIZE + (long) p;
	p->tss.ss0 = 0x10;

	...

	*/
	
	/** 从这里开始添加基于堆栈切换的代码(对fork的修改其实就是对子进程内核栈的初始化 **/

	long *krnstack;

    //p指针加上页面大小就是子进程的内核栈位置,所以krnstack指向子进程的内核栈
    krnstack = (long)(PAGE_SIZE +(long)p);

	//现在开始初始化子进程内核栈(krnstack)中的内容:

	//下面的五句话可以完成上图所示的关联效果(父子进程共用同一内存、堆栈和数据代码块)
	//因为下面的ss,esp,eflags,cs,eip这些参数就是来自调用copy_process()函数的进程的内核栈中
	//也就是父进程的内核栈,所以下面5条指令就是将父进程内核栈中的前五个内容拷贝到子进程的内核栈中

	*(--krnstack) = ss & 0xffff;
	*(--krnstack) = esp;
	*(--krnstack) = eflags;
	*(--krnstack) = cs & 0xffff;
	*(--krnstack) = eip;

    *(--krnstack) = ds & 0xffff;
    *(--krnstack) = es & 0xffff;
    *(--krnstack) = fs & 0xffff;
    *(--krnstack) = gs & 0xffff;
    *(--krnstack) = esi;
    *(--krnstack) = edi;
    *(--krnstack) = edx;

    //处理switch_to返回的位置,详细解释见下文
	*(--krnstack) = (long) first_return_kernel;

    //接下来的工作需要和switch_to接在一起考虑
    //故事从哪里开始呢?回顾一下前面给出的switch_to,应该从“切换内核栈”完成的那个地方开始
    //当时已经切换到子进程的内核栈开始工作,所以switch_to最后的四次弹栈以及ret使用的都是子进程内核栈中的东西

    /*  switch_to:
        ...
    1:	popl %eax
	    popl %ebx
	    popl %ecx
	    popl %ebp
	    ret
    */

    //为了顺利完成上面4条弹栈工作,子进程的内核栈中应该有这些内容
    //所以需要对子进程的krnstack进行相应初始化:(下面的ebp,ecx,ebx一样都来自父进程的内核栈)
	*(--krnstack) = ebp;
	*(--krnstack) = ecx;
	*(--krnstack) = ebx;

	*(--krnstack) = 0;
    //这里为啥是0而不继续继承父进程的eax呢?
    //这里的eax就是进程的返回值,也是子进程和父进程的不同之处
    //父进程返回子进程的pid,子进程返回0

    //最后别忘了,由于我们初始化了子进程的内核栈,栈顶指针也变了
    //所以要更新存放在PCB中对应的内核栈指针:
	p->kernelstack = krnstack;
	...

【1】删除 TSS 进程切换片段

【2】添加子进程内核栈初始化代码

添加情况如图所示:

3、first_return_kernel

       注意 switch_to 最后还有一条 ret 指令,这条指令就是要从内核栈中弹出一个 32 位数作为 EIP 并跳转去执行。所以需要一个函数地址(仍然是一段汇编程序,所以这个函数地址就是这段汇编程序开始处的标号)并将其初始化到栈中。我们使用一个名为 first_return_kernel 的汇编标号,然后通过  *(--krnstack) = (long) first_return_kernel;  将这个地址初始化到子进程的内核栈中。之后执行 ret 就会跳转到 first_return_kernel 处去执行了。

ps:根据栈先进后出的特点,注意  *(--krnstack) = (long) first_return_kernel;  添加的位置。

       现在我们想一想 first_return_kernel 要完成什么工作?PCB、内核栈、LDT 都已经切换完成,所以就剩下 “内核级线程切换五段论” 中的最后一段切换了 —— 用户栈和用户代码的切换,依靠的核心指令就是 iret

       所以这个 first_return_kernel 就是通过 iret 完成用户栈和用户代码的切换。当然在 iret 切换之前应该恢复一下执行现场,主要就是 eax、ebx、ecx、edx、esi、gs、fs、es、ds 这些寄存器的恢复。

【1】添加 first_return_kernel 函数

       将 first_return_kernel 具体的函数实现放在 system_call.s 里面(添加到 switch_to 后即可):

.align 2
first_return_kernel:
    popl %edx
    popl %edi
    popl %esi
    pop %gs
    pop %fs
    pop %es
    pop %ds
    iret

添加情况如下图:

【2】设置 first_return_kernel 全局可见

       还需要将 first_return_kernel 在 fork.c 中设置为全局可见(switch_to 也可以设置一下),这样才能在 fork.c 中使用 first_return_kernel。

.globl switch_to
.globl first_return_kernel

 添加情况如下图:

【3】添加函数声明

       最后要记得,因为在 fork.c 中使用了 system_call.s 中的 first_return_kernel 和 switch_to 函数,所以要在 fork.c 里添加对应的外部函数声明:

extern long switch_to(struct task_struct *p, unsigned long address);
extern long first_return_from_kernel(void);

添加到如下位置即可:

(四)编译运行新的 Linux 0.11

由于我们修改了 Linux 0.11 的内核,所以要重新编译系统。

// linux-0.11 目录下运行
make all

进入 Linux 0.11

// oslab 目录下
./run

运行结果:

正常运行!

(五)分析实验 4 的日志,体会修改前后系统运行的差别

       在新修改的 Linux 0.11 系统中运行实验 4 的样本程序 process.c,统计同样的时间数据,得到新的 process.log ,接着运行统计脚本 stat_log.py 分析新的 log 文件,和 Linux 0.11 原来的 TSS 情况进行对比,体会不同切换手段带来的差异。

1、运行 process.c

2、分析 log 文件,统计数据

3、TSS 和堆栈切换的差异 

可以看出基于堆栈的进程切换比基于 TSS 的进程切换的平均周转时间和平均等待时间更低。

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

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

相关文章

企业网站架构部署与优化之LAMP

LAMP LAMP概述1、各组件的主要作用2、各组件安装顺序 编译安装Apache http服务编译安装MySQL服务编译安装PHP解析环境安装论坛 LAMP概述 LAMP架构是目前成熟的企业网站应用模式之一&#xff0c;指的是协同工作的一整套系统和相关软件&#xff0c;能够提供静态和动态Web站点服务…

REST API和GraphQL API的比较

REST API REST&#xff08;表述性状态传输&#xff09;API 是一种应用程序接口 (API) 的架构风格&#xff0c;它使用 HTTP 请求来访问和使用数据。该数据可用于GET、PUT、POST和DELETE数据类型&#xff0c;指的是对资源的读取、更新、创建和删除操作。 RESTful API 使用 HTTP 方…

智能马达保护器在有色冶金行业中的应用

关注acrelzxz 摘要&#xff1a;简要介绍了热继电器、电子式电动机保护器和智能电动机保护器的优缺点&#xff0c;并阐述了智能电动机保护器如何正确的选型&#xff0c;重点比较了智能电动机保护器保护模式、端子控制模式、全通信模式、半通信模式等几种常见工作模式&#xff0…

Java基础(9)——从匿名内部类 到 函数式编程

引出 从匿名内部类到函数式编程的进化 什么是匿名内部类 没有类名----匿名内部类 List的匿名内部类 FunctionalInterface 注解 Collections.sort(list, new Comparator<Emp>() {Overridepublic int compare(Emp o1, Emp o2) {return (int) (o1.getAge()-o2.getAge())…

问题解决:npm修改路径后获取vue信息出错,npm info vue 出错。

问题&#xff1a; 在cmd中修改了npm缓存文件及全局模块文件路径后&#xff0c;通过获取vue信息出错。 C:\Users\SueMagic>npm info vue npm ERR! code EPERM npm ERR! syscall mkdir npm ERR! path b:\nodejs\node_cache\_cacache npm ERR! errno -4048 npm ERR! Error: E…

基于django的物流管理系统

摘要 随着全球经济的蓬勃发展&#xff0c;WTO的成立也给全球的商业活动带来了新的挑战&#xff0c;因此&#xff0c;企业需要充分发挥自身的优势&#xff0c;运用最新的科学技术&#xff0c;在互联网、信息科学的指导下&#xff0c;完善现有的管理体系&#xff0c;实现全面的创…

chatgpt赋能python:Python为什么出现多个语句?

Python为什么出现多个语句&#xff1f; 在Python中&#xff0c;同一行代码可以包含多个语句&#xff0c;这是Python与其他编程语言不同之处之一。那么&#xff0c;为什么Python出现了多个语句呢&#xff1f; 1. 简洁性 Python是一种高级编程语言&#xff0c;可以通过更少的代…

NoSql数据库及使用Python连接MongoDB

NoSQL 数据库 NoSQL 数据库是非关系数据库&#xff0c;不使用结构化查询语言 (SQL) 进行数据操作。相反&#xff0c;他们使用其他数据模型进行访问和数据存储。SQL 数据库通常用于处理结构化数据&#xff0c;但它们可能不是处理非结构化或半结构化数据的最佳选择。 NoSQL 数据…

环境感知算法——1.简介与GPU驱动、CUDA和cudnn配置

1. 环境感知算法概述 在自动驾驶领域&#xff0c;环境感知算法主要负责处理周围环境中障碍物和道路的信息&#xff0c;为车辆的决策与执行提供车辆与环境的数据。包括检测移动和静止的障碍物、确定车辆在环境中所处的位置&#xff0c;涉及的传感器有摄像头、激光雷达和毫米波雷…

软件工程的问题

我长期以来一直是这个应用程序的用户&#xff0c;它一直快速、响应迅速且易于使用。 然而&#xff0c;在上周的一次更新之后&#xff0c;启动时间从不到一秒钟大幅增加到超过 15 秒。唯一明显的变化是添加了一个新的启动界面和更新了用户界面。 我经常发现自己在质疑为什么工程…

qt+mysl的制作软件的整体思路

环境配置 conda env list 创建环境conda create -n qt python=3.8 激活环境conda activate qt

软考A计划-电子商务设计师-计算机系统基础知识

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

年内落地BEV,大算力芯片准备好了吗?

讲述&#xff5c;余轶南 编辑 | Amy 编者注&#xff1a; 本文是HiEV出品的系列直播「硬核拆解BEV」第二期&#xff0c;地平线副总裁兼软件平台产品线总裁余轶南博士分享的内容梳理。 第三期商汤绝影量产行车智能驾驶研发负责人蒋沁宏分享的《BEV三大关键&#xff1a;数据、迁移…

TSNE 参数超级详细设置 from sklearn.manifold import TSNE

文章目录 一、TSNE是什么&#xff1f;二、使用步骤1.引入库 from sklearn.manifold import TSNE2.参数详细说明 一、TSNE是什么&#xff1f; TSNE&#xff08;t-Distributed Stochastic Neighbor Embedding&#xff09;是一种非线性降维技术&#xff0c;主要用于高维数据的可视…

什么是lamp架构

LAMP架构介绍 LAMP动态网站架构 LAMP是指一组通常一起使用来运行动态网站或者服务器的自由软件名称首字母缩写。 1、LAMP分别代表什么&#xff1f; L代表服务器操作系统使用LinuxA代表网站服务使用的是Apache软件基金会中的httpd软件M代表网站后台使用的数据库是MySQL数据库…

代码审计笔记之未授权审计(缺失功能级别访问控制)

主题 1、未授权漏洞以及危害介绍 2、未授权漏洞的审计思路与方法简介 大多数网络应用程序在用户使用功能之前&#xff0c;应用程序需要验证该用户是否有功能级的访问权限。如果请求未经应用程序的验证。攻击者讲通过伪造请求参数的手段&#xff0c;获取应用的业务响应。 危害…

更轻更快更耐用,重量79g,支持4KHz回报率的雷柏VT9上手

喜欢用键鼠玩游戏的朋友&#xff0c;都会选择专业游戏鼠标和键盘&#xff0c;特别是游戏鼠标&#xff0c;往往会有着更高的精度&#xff0c;以及更丰富的自定义空间&#xff0c;在游戏中可以实现更加精准的点击。最近我看到雷柏出了一款支持4K回报率的VT9鼠标套装&#xff0c;这…

查看Oracle中指定用户下包含的包、函数、存储过程及其对应内容语句——查看当前数据库的连接内容

一、需求分析 在项目运维的时候&#xff0c;公司出于数据安全的考虑&#xff0c;对数据库的权限进行了控制&#xff0c;限制运维人员只能够通过堡垒机查看Oracle数据库内容&#xff0c;并且堡垒机只是设定了一个只读的账号查看数据库&#xff1b;这就导致了运维时在查看数据库的…

【量化交易笔记】7.基于随机森林预测股票价格

前言 机器学习在量化交易主要有两方面的应用&#xff0c;第一就是用时间序列的日频数据来预测未来的股价&#xff0c;第二 用截面数据来预测收益&#xff0c;现在量化基因的因子都基于这个模型。 接下来&#xff0c;我分别来说明&#xff0c;机器学习分成预测结果分成分类和回…

MyBatis——MyBatis配置信息

1.MyBatis的日志配置 log4j1和log4j2的使用 在项目中添加依赖 log4j2 <dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.12.1</version> </dependency>log4j1 <…