OS实战笔记(9)-- 构建二级引导器

news2024/11/28 21:57:56

Grub内核映像格式

        Grub工作的时候,需要一个内核映像文件,其中包括了二级引导器模块、内核模块、图片和字库等。这些不同的文件都被放到了一个映像文件中,为了Grub能够正常加载,需要一个预先定义好的格式,以便Grub能解析。

         上图中,Grub头为4KB大小,Grub会用这一小段代码来识别映像文件。根据映像文件头描述符和文件描述符的信息,这一小段代码还可以解析映像文件中的其它文件。

        映像文件头描述符和文件头描述符的结构如下:


//映像文件头描述符
typedef struct s_mlosrddsc
{
    u64_t mdc_mgic; //映像文件标识
    u64_t mdc_sfsum;//未使用
    u64_t mdc_sfsoff;//未使用
    u64_t mdc_sfeoff;//未使用
    u64_t mdc_sfrlsz;//未使用
    u64_t mdc_ldrbk_s;//映像文件中二级引导器的开始偏移
    u64_t mdc_ldrbk_e;//映像文件中二级引导器的结束偏移
    u64_t mdc_ldrbk_rsz;//映像文件中二级引导器的实际大小
    u64_t mdc_ldrbk_sum;//映像文件中二级引导器的校验和
    u64_t mdc_fhdbk_s;//映像文件中文件头描述的开始偏移
    u64_t mdc_fhdbk_e;//映像文件中文件头描述的结束偏移
    u64_t mdc_fhdbk_rsz;//映像文件中文件头描述的实际大小
    u64_t mdc_fhdbk_sum;//映像文件中文件头描述的校验和
    u64_t mdc_filbk_s;//映像文件中文件数据的开始偏移
    u64_t mdc_filbk_e;//映像文件中文件数据的结束偏移
    u64_t mdc_filbk_rsz;//映像文件中文件数据的实际大小
    u64_t mdc_filbk_sum;//映像文件中文件数据的校验和
    u64_t mdc_ldrcodenr;//映像文件中二级引导器的文件头描述符的索引号
    u64_t mdc_fhdnr;//映像文件中文件头描述符有多少个
    u64_t mdc_filnr;//映像文件中文件头有多少个
    u64_t mdc_endgic;//映像文件结束标识
    u64_t mdc_rv;//映像文件版本
}mlosrddsc_t;

#define FHDSC_NMAX 192 //文件名长度
//文件头描述符
typedef struct s_fhdsc
{
    u64_t fhd_type;//文件类型
    u64_t fhd_subtype;//文件子类型
    u64_t fhd_stuts;//文件状态
    u64_t fhd_id;//文件id
    u64_t fhd_intsfsoff;//文件在映像文件位置开始偏移
    u64_t fhd_intsfend;//文件在映像文件的结束偏移
    u64_t fhd_frealsz;//文件实际大小
    u64_t fhd_fsum;//文件校验和
    char   fhd_name[FHDSC_NMAX];//文件名
}fhdsc_t;

        知道了映像文件格式,我们还需要有工具能够打包出映像文件才行。这个工具可以到下面这个链接中去拉取:

极客时间-操作系统实战45讲: 极客时间-操作系统实战45讲课程已经上线,欢迎订阅 - Gitee.comhttps://gitee.com/lmos/cosmos/tree/master/tools/lmoskrlimg        将代码拉下来后在Linux下编译出这个工具即可。工具的使用方法如下:

lmoskrlimg -m k -lhf GRUB头文件 -o 映像文件 -f 输入的文件列表
-m 表示模式 只能是k内核模式
-lhf 表示后面跟上GRUB头文件
-o 表示输出的映像文件名 
-f 表示输入文件列表
例如:lmoskrlimg -m k -lhf grubhead.bin -o kernel.img -f file1.bin file2.bin file3.bin file4.bin 

二级引导器的工作有哪些

        我们要做的二级引导器,主要作为操作系统的先驱,它需要收集机器信息,确定这个计算机能不能运行我们的操作系统,对 CPU、内存、显卡进行一些初级的配置,放置好内核相关的文件。对于要搜集的信息,在后面的笔记中会记录。

设计机器信息数据结构

        二级引导器所搜集的信息,需要按照一定的数据结构来存储。我们规定在内存1MB的地方来存放这个数据结构。

        下面是这个数据结构的一些关键信息(完整的结构,请查看Cosmos/initldr/include/ldrtype.h,代码仓库极客时间-操作系统实战45讲: 极客时间-操作系统实战45讲课程已经上线,欢迎订阅 - Gitee.comhttps://gitee.com/lmos/cosmos/tree/master/lesson10~11/Cosmos)。


typedef struct s_MACHBSTART
{
    u64_t   mb_krlinitstack;//内核栈地址
    u64_t   mb_krlitstacksz;//内核栈大小
    u64_t   mb_imgpadr;//操作系统映像
    u64_t   mb_imgsz;//操作系统映像大小
    u64_t   mb_bfontpadr;//操作系统字体地址
    u64_t   mb_bfontsz;//操作系统字体大小
    u64_t   mb_fvrmphyadr;//机器显存地址
    u64_t   mb_fvrmsz;//机器显存大小
    u64_t   mb_cpumode;//机器CPU工作模式
    u64_t   mb_memsz;//机器内存大小
    u64_t   mb_e820padr;//机器e820数组地址
    u64_t   mb_e820nr;//机器e820数组元素个数
    u64_t   mb_e820sz;//机器e820数组大小
    //……
    u64_t   mb_pml4padr;//机器页表数据地址
    u64_t   mb_subpageslen;//机器页表个数
    u64_t   mb_kpmapphymemsz;//操作系统映射空间大小
    //……
    graph_t mb_ghparm;//图形信息
}__attribute__((packed)) machbstart_t;

二级引导器规划

        我们的二级引导器模块划分如下表所示:

文件功能描述
imginithead.asm GRUB头汇编
inithead.cGRUB头的C语言实现部分,用于将二级引导器放到指定内存中
realintsve.asm实现调用BIOS中断功能
ldrkrl32.asm二级引导器核心入口汇编
ldrkrlentry.c二级引导器核心入口
bstartpram.c收集机器信息,创建页面数据
chkcpmm.c检查CPU工作模式和内存视图
fs.c解析映像文件
graph.c切换显卡图形模式
vgastr.c字符串输出显示

        这些文件,完整的代码,在下面这个链接中可以找到(lesson10~11/Cosmos/initldr/ldrkrl):

极客时间-操作系统实战45讲: 极客时间-操作系统实战45讲课程已经上线,欢迎订阅 - Gitee.comhttps://gitee.com/lmos/cosmos/tree/master/lesson10~11      这个工程编译后,会生成三个bin文件:  initldrmh.bin,initldrsve.bin和initldrkrl.bin。我们使用前面所说的lmoskrlimg打包工具制作出映像文件即可。打包命令如下:


lmoskrlimg -m k -lhf initldrimh.bin -o Cosmos.eki -f initldrkrl.bin initldrsve.bin

实现GRUB头

        GRUB头由两个文件组成:

        1. imginithead.asm汇编,它被GRUB所识别,也设置C语言运行环境(用于后续调用C函数)。

        2. inithead.c,它主要查找二级引导器的核心文件 -- initldrkrl.bin,然后将其放到内存指定地址上去。

imginithead.asm

        我们先来实现imginithead.asm,它主要是初始化CPU寄存器,加载GDT,将CPU切换到保护模式。

        我们先做出GRUB1和GRUB2的两个头结构:


MBT_HDR_FLAGS  EQU 0x00010003
MBT_HDR_MAGIC  EQU 0x1BADB002
MBT2_MAGIC  EQU 0xe85250d6
global _start
extern inithead_entry
[section .text]
[bits 32]
_start:
  jmp _entry
align 4
mbt_hdr:
  dd MBT_HDR_MAGIC
  dd MBT_HDR_FLAGS
  dd -(MBT_HDR_MAGIC+MBT_HDR_FLAGS)
  dd mbt_hdr
  dd _start
  dd 0
  dd 0
  dd _entry
ALIGN 8
mbhdr:
  DD  0xE85250D6
  DD  0
  DD  mhdrend - mbhdr
  DD  -(0xE85250D6 + 0 + (mhdrend - mbhdr))
  DW  2, 0
  DD  24
  DD  mbhdr
  DD  _start
  DD  0
  DD  0
  DW  3, 0
  DD  12
  DD  _entry 
  DD  0  
  DW  0, 0
  DD  8
mhdrend:

        如果想要深入理解multiboot头格式,可以去网上搜索一下Grub multiboot/multiboot2规范,这里给一个multiboot2规范的下载地址仅供参考:

https://download.csdn.net/download/vivo01/85626166https://download.csdn.net/download/vivo01/85626166        接下来,关闭中断,加载GDT:

_entry:
  cli           ;关中断
  in al, 0x70 
  or al, 0x80  
  out 0x70,al  ;关掉不可屏蔽中断   
  lgdt [GDT_PTR] ;加载GDT地址到GDTR寄存器
  jmp dword 0x8 :_32bits_mode ;
  ;………………
;GDT全局段描述符表
GDT_START:
knull_dsc: dq 0
kcode_dsc: dq 0x00cf9e000000ffff
kdata_dsc: dq 0x00cf92000000ffff
k16cd_dsc: dq 0x00009e000000ffff ;16位代码段描述符
k16da_dsc: dq 0x000092000000ffff ;16位数据段描述符
GDT_END:
GDT_PTR:
GDTLEN  dw GDT_END-GDT_START-1  ;GDT界限
GDTBASE  dd GDT_START

        上面的in,out指令是读和写0x70端口的指令,关于x86端口的描述,之前的笔记中有给过参考链接,也可以自行去google。

        lgdt指令是修改全局描述符表GDT寄存器的指令,此处表示加载GDT_PTR地址处开始的6个字节,在GDT_PTR处,前两个字节表示GDT的大小,后四个字节表示GDT的起始地址。

        如果需要了解GDT,请回看之前的笔记:

OS实战笔记(3) -- X86 CPU三种工作模式(实模式,保护模式,长模式)_x86保护模式_亦枫Leonlew的博客-CSDN博客X86 CPU三种工作模式(实模式,保护模式,长模式)https://blog.csdn.net/vivo01/article/details/125752362        jmp dword 0x8 : _32bits_mode,这里的0x8表示选择了GDT里(TI(bit2) = 0)索引为1的段描述符所确定的基地址(参考下图),加上_32bits_mode所指示的偏移得到最终地址。

        下标为1的段 描述符值为0x00cf9e000000ffff,可以参考下图进行理解。

        可以看到这个段的基地址是0x0,jmp指令实际可以理解为跳转到_32bits_mode处。


_32bits_mode:
  mov ax, 0x10
  mov ds, ax
  mov ss, ax
  mov es, ax
  mov fs, ax
  mov gs, ax
  xor eax,eax
  xor ebx,ebx
  xor ecx,ecx
  xor edx,edx
  xor edi,edi
  xor esi,esi
  xor ebp,ebp
  xor esp,esp
  mov esp,0x7c00 ;设置栈顶为0x7c00
  call inithead_entry ;调用inithead_entry函数在inithead.c中实现
  jmp 0x200000  ;跳转到0x200000地址

         _32bits_mode处代码最核心的功能是设置esp,有了栈后就可以调用C语言的函数了。后面call inithead_entry函数主要功能就是加载对应的映像文件中的 initldrsve.bin 文件和 initldrkrl.bin 文件写入到特定的内存地址空间中去。


#define MDC_ENDGIC 0xaaffaaffaaffaaff
#define MDC_RVGIC 0xffaaffaaffaaffaa
#define REALDRV_PHYADR 0x1000
#define IMGFILE_PHYADR 0x4000000
#define IMGKRNL_PHYADR 0x2000000
#define LDRFILEADR IMGFILE_PHYADR
#define MLOSDSC_OFF (0x1000)
#define MRDDSC_ADR (mlosrddsc_t*)(LDRFILEADR+0x1000)

void inithead_entry()
{
    write_realintsvefile();
    write_ldrkrlfile();
    return;
}
//写initldrsve.bin文件到特定的内存中
void write_realintsvefile()
{
    fhdsc_t *fhdscstart = find_file("initldrsve.bin");
    if (fhdscstart == NULL)
    {
        error("not file initldrsve.bin");
    }
    m2mcopy((void *)((u32_t)(fhdscstart->fhd_intsfsoff) + LDRFILEADR),
            (void *)REALDRV_PHYADR, (sint_t)fhdscstart->fhd_frealsz);
    return;
}
//写initldrkrl.bin文件到特定的内存中
void write_ldrkrlfile()
{
    fhdsc_t *fhdscstart = find_file("initldrkrl.bin");
    if (fhdscstart == NULL)
    {
        error("not file initldrkrl.bin");
    }
    m2mcopy((void *)((u32_t)(fhdscstart->fhd_intsfsoff) + LDRFILEADR),
            (void *)ILDRKRL_PHYADR, (sint_t)fhdscstart->fhd_frealsz);
    return;
}
//在映像文件中查找对应的文件
fhdsc_t *find_file(char_t *fname)
{
    mlosrddsc_t *mrddadrs = MRDDSC_ADR;
    if (mrddadrs->mdc_endgic != MDC_ENDGIC ||
        mrddadrs->mdc_rv != MDC_RVGIC ||
        mrddadrs->mdc_fhdnr < 2 ||
        mrddadrs->mdc_filnr < 2)
    {
        error("no mrddsc");
    }
    s64_t rethn = -1;
    fhdsc_t *fhdscstart = (fhdsc_t *)((u32_t)(mrddadrs->mdc_fhdbk_s) + LDRFILEADR);
    for (u64_t i = 0; i < mrddadrs->mdc_fhdnr; i++)
    {
        if (strcmpl(fname, fhdscstart[i].fhd_name) == 0)
        {
            rethn = (s64_t)i;
            goto ok_l;
        }
    }
    rethn = -1;
ok_l:
    if (rethn < 0)
    {
        error("not find file");
    }
    return &fhdscstart[rethn];
}

         在调用完inithead_entry后,最后的jmp 0x200000是跳转到二级引导器的主模块(initldrkrl.bin)。

进入二级引导器主模块

        前面加载了initldrkrl.bin到内存0x200000并跳转到了二级引导器的主模块,由于模块发生了变化,我们首先会重新去设置一下GDT,IDT,栈指针,相关寄存器。这些工作在ldrkrl32.asm文件中去实现。


_entry:
  cli
  lgdt [GDT_PTR];加载GDT地址到GDTR寄存器
  lidt [IDT_PTR];加载IDT地址到IDTR寄存器
  jmp dword 0x8 :_32bits_mode;长跳转刷新CS影子寄存器
_32bits_mode:
  mov ax, 0x10  ; 数据段选择子(目的)
  mov ds, ax
  mov ss, ax
  mov es, ax
  mov fs, ax
  mov gs, ax
  xor eax,eax
  xor ebx,ebx
  xor ecx,ecx
  xor edx,edx
  xor edi,edi
  xor esi,esi
  xor ebp,ebp
  xor esp,esp
  mov esp,0x90000 ;使得栈底指向了0x90000
  call ldrkrl_entry ;调用ldrkrl_entry函数
  xor ebx,ebx
  jmp 0x2000000 ;跳转到0x2000000的内存地址
  jmp $
GDT_START:
knull_dsc: dq 0
kcode_dsc: dq 0x00cf9a000000ffff ;a-e
kdata_dsc: dq 0x00cf92000000ffff
k16cd_dsc: dq 0x00009a000000ffff ;16位代码段描述符
k16da_dsc: dq 0x000092000000ffff ;16位数据段描述符
GDT_END:
GDT_PTR:
GDTLEN  dw GDT_END-GDT_START-1  ;GDT界限
GDTBASE  dd GDT_START

IDT_PTR:
IDTLEN  dw 0x3ff
IDTBAS  dd 0  ;这是BIOS中断表的地址和长度

        大部分内容基本和前面一致,这里需要注意最后jmp的地址是0x2000000( IMGKRNL_PHYADR, 内核映像地址),不是initldrkrl.bin加载到内存中的地址。

        ldrkrl_entry函数是二级引导器的C语言主函数,暂时还未实现。

调用BIOS中断

        前面说过,二级引导器主要功能中最重要的一点是收集机器信息,比如内存布局,显卡图形模式等。这些信息要通过BIOS提供的中断服务才能实现。

        BIOS中断服务工作在16位实模式,而目前我们所用的二级引导器工作在32位保护模式下。因此在二级引导器中无法直接调用BIOS中断服务,我们需要做一些额外的事情:

        1. 保存 C 语言环境下的 CPU 上下文 ,即保护模式下的所有通用寄存器、段寄存器、程序指针寄存器,栈寄存器,把它们都保存在内存中。

        2. 切换回实模式,调用 BIOS 中断,把 BIOS 中断返回的相关结果,保存在内存中。

        3. 切换回保护模式,重新加载第 1 步中保存的寄存器。这样 C 语言代码才能重新恢复执行。

        上面的这些过程,我们写在ldrkrl32.asm 文件中:


realadr_call_entry:
  pushad     ;保存通用寄存器
  push    ds
  push    es
  push    fs ;保存4个段寄存器
  push    gs
  call save_eip_jmp ;调用save_eip_jmp 
  pop  gs
  pop  fs
  pop  es      ;恢复4个段寄存器
  pop  ds
  popad       ;恢复通用寄存器
  ret
save_eip_jmp:
  pop esi  ;弹出call save_eip_jmp时保存的eip到esi寄存器中, 
  mov [PM32_EIP_OFF],esi ;把eip保存到特定的内存空间中
  mov [PM32_ESP_OFF],esp ;把esp保存到特定的内存空间中
  jmp dword far [cpmty_mode];长跳转这里表示把cpmty_mode处的第一个4字节装入eip,把其后的2字节装入cs
cpmty_mode:
  dd 0x1000
  dw 0x18
  jmp $

        唯一需要注意的是最后的jmp dword far [cpmty_mode],这个指令是一个长跳转,表示把[cpmty_mode]处的数据装入 CS:EIP,也就是把 0x18:0x1000 装入到 CS:EIP 中。根据之前的描述,我们知道所使用的段的index是0x3(0x18的bits[15:3],根据段选择子的定义, 低3位是RPL和TI, 其他位才是索引)。 之前在汇编中定义的二级引导器的GDT表中,下标为3的段描述符对应的是16位代码段。0x1000 代表段内的偏移地址(由于段描述符指定的基地址是0x0,因此实际物理地址就是0x1000)。我们跳转到这里,必须要有代码CPU才能继续干活。这个地址开始的代码是16位的指令,这段汇编我们放到一个单独的realintsve.asm文件中:


[bits 16]
_start:
_16_mode:
  mov  bp,0x20 ;0x20是指向GDT中的16位数据段描述符 
  mov  ds, bp
  mov  es, bp
  mov  ss, bp
  mov  ebp, cr0
  and  ebp, 0xfffffffe
  mov  cr0, ebp ;CR0.P=0 关闭保护模式
  jmp  0:real_entry ;刷新CS影子寄存器,真正进入实模式
real_entry:
  mov bp, cs
  mov ds, bp
  mov es, bp
  mov ss, bp ;重新设置实模式下的段寄存器 都是CS中值,即为0 
  mov sp, 08000h ;设置栈
  mov bp,func_table
  add bp,ax
  call [bp] ;调用函数表中的汇编函数,ax是C函数中传递进来的
  cli
  call disable_nmi
  mov  ebp, cr0
  or  ebp, 1
  mov  cr0, ebp ;CR0.P=1 开启保护模式
  jmp dword 0x8 :_32bits_mode
[BITS 32]
_32bits_mode:
  mov bp, 0x10
  mov ds, bp
  mov ss, bp;重新设置保护模式下的段寄存器0x10是32位数据段描述符的索引
  mov esi,[PM32_EIP_OFF];加载先前保存的EIP
  mov esp,[PM32_ESP_OFF];加载先前保存的ESP
  jmp esi ;eip=esi 回到了realadr_call_entry函数中

func_table:  ;函数表
  dw _getmmap ;获取内存布局视图的函数
  dw _read ;读取硬盘的函数
    dw _getvbemode ;获取显卡VBE模式 
    dw _getvbeonemodeinfo ;获取显卡VBE模式的数据
    dw _setvbemode ;设置显卡VBE模式

        这个文件会单独编译成initldrsve.bin,在前面write_realintsvefile()函数作用就是将这个文件的内容放到对应的内存地址0x1000上。

二级引导器主函数

        二级引导器主函数放在ldrkrlentry.c中,目前仅调用一个空函数init_bstartparm()用来收集机器信息,在后面实现。


void ldrkrl_entry()
{
    init_bstartparm();
    return;
}

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

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

相关文章

代码实现 ResNet 详解

零、ResNet的介绍 ResNet代码&#xff08;含详细的使用说明&#xff09;&#xff1a; https://github.com/GarsonWw/resnet-garson.git 当谈到深度学习中的卷积神经网络时&#xff0c;ResNet&#xff08;Residual Network&#xff09;是一个备受赞誉且引人注目的架构。ResNet…

最全的mysql编码集问题排查

用navicate导入一个json文件数据的时候&#xff0c;发现中文有一些是乱码的&#xff0c;查了很多资料&#xff0c;发现mysql、navicate编码集都没问题&#xff0c;包括导入流程&#xff0c;那是什么原因呢&#xff1f;想着难道是电脑的编码集影响了&#xff1f;于是调整以后&am…

【C语言】结构体——我就是秩序的创建者!(结构体数组、结构体指针、嵌套、匿名、字面量、伸缩型数组、链式结构)

一、结构体基础1.1 声明和定义1.1 初始化和赋值1.3 访问结构体成员 二、结构体数组2.1 定义和初始化2.2 访问 三、结构体的嵌套五、指向结构体的指针六、向函数传递结构体6.1 只传递结构体成员6.2 传递结构体指针6.3 传递结构体 七、结构体的其他特性——不容小觑7.1 结构体的大…

硬盘数据丢失怎么办?一招轻松恢复硬盘数据!

硬盘应该是最为常用的数据存储设备了&#xff0c;它为电脑等设备提供巨大的存储空间。我们在平时的工作和学习中也经常会使用硬盘来存储数据&#xff0c;很多用户会将多年搜集到的资料存到电脑硬盘里。 硬盘上的文件&#xff0c;不论是工作文档还是照片、视频&#xff0c;对用…

(四)ArcGIS空间数据的转换与处理——数据结构转换

ArcGIS空间数据的转换与处理——数据转换 空间数据的来源很多&#xff0c;如地图、工程图、规划图、航空与遥感影像等&#xff0c;因此空间数据也有多种格式。根据应用需要&#xff0c;需对数据进行格式转换&#xff0c;不同数据结构间的转换主要包括矢量数据到栅格数据的转换…

Guava的骚操作,大大提升了我摸鱼的时间

以面向对象思想处理字符串对基本类型进行支持总结 概述 1、Guava是google公司开发的一款Java类库扩展工具包&#xff0c;包括新的集合类型&#xff08;如 multimap 和 multiset&#xff09;、不可变集合、图形库&#xff0c;以及用于缓存、并发、I/O等实用程序。使用这些API一…

内存溢出导致的Full GC异常

背景 线上服务GC耗时过长&#xff0c;普遍10s&#xff0c;此外GC后&#xff0c;内存回收不多 问题一 通过查询gc日志可以发现&#xff0c;CMS进行垃圾回收的时候报concurrent mode failure错误&#xff0c;该错误是因为CMS进行垃圾回收的时候&#xff0c;新生代进行GC产生的对象…

JupyterLab(Jupyter Notebook)安装与使用

文章目录 前言安装JupyterLab切换中文语言JupyterLab desktop 使用演示其它补充后记 前言 目前在看《程序员数学&#xff1a;用Python学透线性代数和微积分》这个书&#xff0c;它里面的代码是在Jupyter中编写的&#xff0c;所以也安装下用用。 JupyterLab是一个可以同时编写…

[PyTorch][chapter 37][经典卷积神经网络-2 ]

1&#xff1a; VGG 2: GoogleNet 一 VGG 1.1 简介 VGGNet 是牛津大学计算机视觉组&#xff08;Visual Geometry Group&#xff09;和谷歌 DeepMind 一起研究出来的深度卷积神经网络&#xff0c;因而冠名为 VGG。VGG是一种被广泛使用的卷积神经网络结构&#xff0c;其在在20…

生成式模型的质量评估标准

Sample Quality Matrix 如何评价生成式模型的效果&#xff1f;ISFIDsFIDPrecision & RecallPrecisonRecall计算precision和recall 如何评价生成式模型的效果&#xff1f; Quality: 真实性&#xff08;逼真&#xff0c;狗咬有四条腿&#xff09; Diversity: 多样性&#x…

全网为数不多清晰可行的在VUE中使用sortable.js实现动态拖拽排序的教程!

目录 0 写在前面的 1 依赖安装 2 手写简单标签演示 3 要点 4 效果 0 写在前面的 首先批评以下文章 (10条消息) sortable.js 实现拖拽_sortablejs_花铛的博客-CSDN博客 (10条消息) sortablejs拖拽排序功能&#xff08;vue&#xff09;_C_fashionCat的博客-CSDN博客 他们…

Spring6入门 + Log4j2

1、环境要求 JDK&#xff1a;Java17&#xff08;Spring6要求JDK最低版本是Java17&#xff09; Maven&#xff1a;3.6 Spring&#xff1a;6.0.2 2、构建模块 &#xff08;1&#xff09;构建父模块spring6 在idea中&#xff0c;依次单击 File -> New -> Project ->…

什么是内部网络分段渗透测试?

网络攻击的规模、范围和复杂性与日俱增。随着黑客及其攻击方法变得越来越复杂&#xff0c;您的企业必须做出相应的响应&#xff0c;否则您的安全边界就会不堪重负。 如今&#xff0c;内部网络分段是将攻击成功风险降至最低、改善数据流和隔离关键支付数据的主要方法之一。 但是…

【U-Boot 之七】fastboot原理分析及uboot fastboot功能实践

本文首先介绍了fastboot的基本原理&#xff0c;然后分析了uboot中fastboot的实现&#xff0c;最后&#xff0c;从实践的角度测试了fastboot协议及各种fastboot命令的使用方式等 。本文的仅按照我本人的实际测试过程进行了简单的描述。若有不当之处&#xff0c;欢迎各位大神不吝…

专家警告AI可能会导致人类灭绝?

人工智能可能导致人类灭绝&#xff0c;包括 OpenAI 和 Google Deepmind 负责人在内的专家警告说 数十人支持在人工智能安全中心 的网页上发表的声明。 它写道&#xff1a;“减轻人工智能灭绝的风险应该与其他社会规模的风险&#xff08;如流行病和核战争&#xff09;一起成为全…

Linux系统安装RabbitMQ

rabbitmq安装 说明&#xff1a;本次使用centos7.9 安装虚拟机. 1. 安装依赖环境 在线安装依赖环境&#xff1a; yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c kernel-devel m4 ncurses-devel tk tc xz2. 安装Erlang 根据课…

[原创]集权设施保护之LDAP协议

LDAP是一种目录访问协议&#xff0c;它规定了以树状结构的方式来存储和访问数据。然而协议是抽象的&#xff0c;要产生具体的功效&#xff0c;必须在应用中实现&#xff0c;比如AD域服务就实现了LDAP协议。 LDAP最明显的优势就是读取速度快&#xff0c;拥有极高的搜索效率。 可…

Drools规则引擎

Drools规则引擎 Drools规则引擎1、Drools简介2、Drools入门案例2.1、业务场景2.2、maven坐标2.3、编写xml配置文件&#xff08;多方法&#xff09;2.4、创建drl规则文件2.5、单元测试 3、Drools基础语法3.1、规则文件的构成3.2、规则体语法结构3.2.1、条件部分3.2.1.1、约束连接…

day4,day5 -java集合框架

List、Set、Map等常用集合类的特点和用法。 常用集合类&#xff08;List、Set、Map 等&#xff09;是 Java 中提供的数据结构&#xff0c;用于存储和操作一组数据。以下是它们的特点和用法&#xff1a; List&#xff08;列表&#xff09;: 特点&#xff1a;有序集合&#xff0…

多元办公场景下,企业如何保障工作效率与数据安全流通?

为适应数字化转型需求&#xff0c;提升办公效率&#xff0c;很多企业对工作模式进行革新&#xff0c;并将更多协同办公工具引入工作流程。然而&#xff0c;这也扩大了企业内网对外的安全暴露面&#xff0c;企业亟需进一步加强底层基础设施的网络安全建设&#xff0c;严防勒索病…