从裸机启动开始运行一个C++程序(四)

news2024/12/23 14:27:10

先序文章请看
从裸机启动开始运行一个C++程序(三)
从裸机启动开始运行一个C++程序(二)
从裸机启动开始运行一个C++程序(一)

跳转

前面我们介绍过,8086CPU总是在执行CS:IP所对应的内存位置的指令,一般情况下,会按照顺序一条一条执行。除非一种特殊情况——跳转指令。

所谓「跳转」,顾名思义,就是不要再继续向下执行,而是跳到某一个位置开始执行。因此,跳转指令就是要改变CS:IP的指向。

跳转指令主要分为两种,分别是「近跳」和「远跳」。不过笔者认为,这两个名字也起得不是特别恰当,其实他们跟远近并没有直接关系。

近跳

所谓「近跳」,我们可以理解为CS不变,IP做一个偏移,它的操作数是一个偏移量,比如说-3就表示向前跳转3字节、5就表示向后偏移5字节。

然而在汇编语言里,我们也不好手动去计算偏移量,因此这种时候就需要用到强大的汇编器预处理功能——标签。我们来看一个例子:

L1: 
mov ax, 1
jmp L2
mov bx, 2
L2:
mov cx, 8

其中的L1:L2:就是标签,它也是伪指令,并不会生成对应的机器码,而是会影响汇编器的预处理。标签名可以随便起,只要不跟汇编关键字冲突即可,后面的冒号也可以省略。

上面例程中的近跳指令是:

jmp L2

预处理时,汇编器会根据L2标签到当前位置(跳转指令的位置)之前的偏移量来给近跳指令添加操作数。以上面例程来说,实际的操作数正好是mov bx, 2这条指令的长度,也就是3,那么jmp L2就相当于jmp +3

当CPU执行到近跳指令时,则会将IP寄存器与近跳指令的操作数相加,然后去执行对应位置的指令,进而达到跳转的目的。

远跳

所谓「远跳」,其实是给CSIP都给一个绝对值,它的操作数是一个绝对的内存地址,而不是偏移量。例如:

jmp 0x0820:0x0000

这条指令执行完后,CS会赋值为0x0820IP会赋值为0x0000,接着就会执行0x08200位置的指令。

这里需要强调的是,汇编语言指导的是机器指令,它不具备高等语义,因此,汇编器不会去检查0x08200这个地址在不在你当前操作的源文件里,也不会去管那个位置到底会不会加载合法的指令,这一切都应该由程序员自行负责。

当然,使用远跳指令时也可以使用标签,只不过此时的标签会使用「相对于文件头」的偏移量。比如说:

mov ax, 0
mov bx, 1
L1:
mov cx, 2
jmp 0x0000:L1

上面例程中jmp 0x0000:L1就是远跳指令,这时的L1就会解析为这个标签相对于文件头的偏移量,实际上也就是mov ax, 0mov bx, 1的指令长度和,也就是6。那么这条指令其实应该是jmp 0x0000:0x0006

这里再次强调重点:近跳指令不改变CS,操作数是偏移量;远跳指令会改变CS,操作数是绝对数。这一点在8086模式下可能看上去没那么重要,但当后面我们切换到286模式时,这一点会非常重要,所以请读者一定要记住。

多加载几个扇区

到目前为止,我们的程序都挤在软盘的第一个扇区里,指望BIOS自动加载。不过显然这区区512字节的空间很容易捉襟见肘,那么如何把软盘中的其他扇区内容也加载到内存中呢?在8086模式下,BIOS中断可以替我们搞定。

; 加载一个扇区到0x08000的位置
mov ax, 0x0800
mov es, ax
mov bx, 0 ; 软盘中的内容会加载到es:bx的位置
mov ah, 2 ; ah=2, 使用读盘功能
mov al, 2 ; ah表示需要读取连续的几个扇区(读2个就是1KB的大小)
mov ch, 0 ; ch表示第几柱面
mov dh, 0 ; dh表示第几磁头
mov cl, 2 ; cl表示第几扇区
mov dl, 0 ; dl表示驱动器号,软盘会在0x00~0x7F,硬盘会在0x80~0xFF
int 0x13  ; 执行0x13号中断的2号功能(读盘功能)

对于老式机械硬盘、软盘来说,它们都属于「磁盘」的一种。根据其机械结构分为柱面(Cylinder)、磁头(Head)、扇区(Sector),一般表示为CHS,柱面和磁头从0开始,扇区从1开始标号。

BIOS如果设置为软盘启动,就会加载0号驱动器的C0-H0-S1到内存的0x07c00的位置。如果设置为硬盘启动,就会加载0x80号驱动器的C0-H0-S1到内存的0x07c00的位置。

那么现在,我们承担MBR角色的程序,就需要再把其他数据也加载到内存中。不过这时的内存选址就由我们随意了,并不一定要紧接着MBR加载的位置,上面例程中选择了0x08000的位置,你也可以选择其他位置,但要主要,不能占用BIOS预留的位置,也不能占用显存位置。通常8086的内存布局如下:

起始地址结束地址长度作用
0x000000x003ff1KB中断向量表
0x004000x004ff256BBIOS数据区
0x005000x07bff29.75KB-
0x07c000x07dff512BMBR
0x07e000x9fbff607.5KB-
0xa00000xbffff128KB显存
0xc00000xc7fff32KB显卡BIOS
0xc80000xeffff160KB统一编址的I/O
0xf00000xfffff64KBBIOS

从上表可知,0x005000x9fbff这638.75KB的空间都是可用的,但是由于MBR占用了其中的512B,剩下的部分我们可以自由支配。

下面我们就编写一个程序,前512B作为MBR,加载两个扇区(1KB)的数据到0x08000的位置,然后再跳转至该位置,执行指令:

; C0H0S1
; 调用0x10BIOS中断,清屏
mov al, 0x03
mov ah, 0x00
int 0x10 
; 加载一个扇区到0x08000的位置
mov ax, 0x0800
mov es, ax
mov bx, 0 ; 软盘中的内容会加载到es:bx的位置
mov ah, 2 ; ah=2, 使用读盘功能
mov al, 2 ; ah表示需要读取连续的几个扇区(读2个就是1KB的大小)
mov ch, 0 ; ch表示第几柱面
mov dh, 0 ; dh表示第几磁头
mov cl, 2 ; cl表示第几扇区
mov dl, 0 ; dl表示驱动器号,软盘会在0x00~0x7F,硬盘会在0x80~0xFF
int 0x13  ; 执行0x13号中断的2号功能(读盘功能)

jmp 0x0800:0x0000 ; 这里写成0x0000:0x8000OK,只是CSIP的值会不同,但CS:IP是相同的

times 510-($-$$) db 0 ; MBR剩余部分用0填充
dw 0xaa55

; 现在已经是C0H0S2的内容了
begin:
mov ax, 0xb800
mov ds, ax
mov [0x0000], byte 'H'
mov [0x0001], byte 0x0f
mov [0x0002], byte 'e'
mov [0x0003], byte 0x0f
mov [0x0004], byte 'l'
mov [0x0005], byte 0x0f
mov [0x0006], byte 'l'
mov [0x0007], byte 0x0f
mov [0x0008], byte 'o'
mov [0x0009], byte 0x0f
hlt

times 1024-($-begin) db 0 ; 补满2个扇区

可以确认一下,此时的mbr.bin变成了1536B,当然,它现在叫「MBR」已经不太合适了,它应当是包含了MBR和内核程序的一个总包。暂时我们先忽略这个叫法的问题,稍后再来看如何将MBR和内核程序分离。

同样,将其重命名为a.img,然后打开bochs看运行效果:
执行结果

这证明,后面扇区的内容也加载成功了,跳转指令也完成了正确的跳转。

另外,当我们程序有稍微的规模了的时候,大家可以考虑用单步执行命令来做调试。例如启动后,我们先在0x7c00处打断点,然后c执行BIOS的指令,然后按n开始跳过调用流程的单步调试(s是单纯的单步调试,但是会把BIOS中断中的指令也显示出来,按n则不会)。大概效果如下:

调试

而在经历一些加载数据功能后,我们还可以用x命令来查看对应内存位置,例如当执行完0x13中断后,我可以看一下0x08000位置的内存,到底有没有写入数据:

内存数据

也可通过rsreg指令查看寄存器的值,比如在跳转指令前后,查看CSIP的值。跳转前:
寄存器1
寄存器2

然后执行n,完成跳转指令后,再看一下CSIP的值:
寄存器3
寄存器4

大家可以根据需要进行调试观察自己的程序。

改为硬盘启动

BIOS中断的局限性

照理说,按照前面一节的方法,利用BIOS中断加载软盘中的数据到内存中再去执行,在8086下貌似是没什么问题的。但这不是长久之际,8086下只有640KB不到的内存空间供我们支配,自然用当前的这种方式没什么问题,但毕竟8086模式只是过渡,后续我们要切换到32位模式以支持4GB内存,还要切换到64位模式支持更大的内存。

虽然BIOS中断是很方便的工具,相当于基础系统提供了一些库函数供我们使用,但它毕竟依赖BIOS,BIOS中提供的指令都是16位实模式(8086模式)的指令,一旦后续我们切换为i286模式、i386模式后,这些BIOS中断就无法使用了(因为指令集不匹配)。

其实,向显存写入数据的这种需求,也是可以通过BIOS中断来完成的,但笔者并没有介绍这种方法,而是使用直接操作显存的方式,目的也就在此,因为我们不可能一直停留在8086模式。同理,加载外存中的数据这种需求,也应当有它原始方法。

I/O设备的操作

前面我们介绍过I/O,有一些是统一编址的(比如显存),也有一些是独立编址的,CPU会通过专用的指令,控制I/O控制器(或者也可以叫南桥芯片)来管理这些I/O设备。

I/O设备会映射成一个端口号,CPU向对应的端口号发送或读取数据,间接通过I/O控制器来控制外围的I/O设备。软驱也是其中的一员,我们可以控制几个软驱控制器(例如DOR、FDC)来读取和写入软盘中的内容。不过软驱的控制方法比较麻烦(只支持CHS模式,不支持LBA模式。LBA模式在后面章节详细介绍),又因为3.5英寸软盘只有1440KB的限制,迟早不够使,因此,我们姑且就不去详细研究软驱的控制方法了。接下来,我们要将我们的模拟器环境,改为用硬盘启动。

配置硬盘启动

配置硬盘,需要修改bochsrc的内容,我们将软盘启动相关配置注释掉或删除掉,改为以下内容:

# boot: floppy # 设置软盘启动
# floppy_bootsig_check: disabled=0 # 打开自检
# floppya: type=1_44, 1_44="a.img", status=inserted, write_protected=0 # 使用1.44MB的3.5英寸软盘,取镜像为a.img,开机默认已插入软驱,不开启写保护

ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 # 主盘端口映射为1f0,从盘映射为3f0,中断号设置为14(虽然这几个参数都可以定制化,但这个参数是业界标准的,不建议更改)
ata0-master: type=disk, mode=flat, path=a.img, cylinders=1, heads=1, spt=1 # 主盘位置加载一块规格为C1H1S1的硬盘,镜像使用a.img
boot: disk # 设置为硬盘启动

这里需要注意一下,硬盘的规格我们暂时设置的是1柱面1磁头1扇区,也就是只有512字节的硬盘,那么对于a.img来说,超过512B的部分是不会加载进去的。(暂时这样设置一下,后面肯定会改的。)

首先先来测试一下MBR能否正常加载,所有我们把之前MBR中写的那些跳转语句、还有512B后面的部分都先删除,打印几个文字来验一验效果:

; 调用0x10BIOS中断,清屏
mov al, 0x03
mov ah, 0x00
int 0x10 

mov ax, 0xb800
mov ds, ax
mov [0x0000], byte 'H'
mov [0x0001], byte 0x0f
mov [0x0002], byte 'e'
mov [0x0003], byte 0x0f
mov [0x0004], byte 'l'
mov [0x0005], byte 0x0f
mov [0x0006], byte 'l'
mov [0x0007], byte 0x0f
mov [0x0008], byte 'o'
mov [0x0009], byte 0x0f
hlt

times 510-($-$$) db 0 ; MBR剩余部分用0填充
dw 0xaa55

将其编译为mbr.bin,确认一下它的大小是512字节:
mbr.bin

然后把它复制为a.img,再启动一下看看效果:

硬盘启动后

能看到输出,说明我们已经成功切换成硬盘启动了。那么接下来就是如何加载后面扇区的数据的问题了。

通过操作I/O加载硬盘数据

前面我们用了CHS方式来编号硬盘,但除了CHS以外,还有另外一种方式,叫做LBA,也就是Logical Block Address。这种方式下,硬盘会直接按照连续的扇区进行编号,对磁头和柱面不再感知。

LBA28是一种比较原始的方式,28表示用28位编号,也就是0x0000000~0xFFFFFFF的扇区号,注意,0号是预留位,真正的扇区是从1号开始的。

用于控制硬盘的设备会有对应的端口号,在前面我们bochsrc中也有对应的配置,比如当前使用了默认值,也就是0x01f0,从这个端口向后的若干端口都是用来操作硬盘的。因此,我们要按照一定的顺序,向对应的端口中写入数据,来指导硬盘控制器读取硬盘数据。

首先要配置的是需要读取的端口数,这个数据要写入0x01f2端口中:

; 设置读取扇区的数量
mov dx, 0x01f2
mov al, 2 ; 读取连续的几个扇区,每读取一个al就会减1
out dx, al

然后我们来配置起始扇区号。1号扇区就是MBR,已经加载进来了,所以我们从第2号扇区开始加载。虽然只是一个简单的2号,但其实LBA28模式下扇区号是有28位的,因此我们要拆分成4次,分别写入不同的端口中。0x01f3需要传入扇区号的0~7位,0x01f4需要传入扇区号的8~15位,0x01f5需要传入扇区号的16~23位,0x01f5则拆分为3部分,低4位是扇区号的24~27位,第4位表示主从盘,高3位表示扇区编号的模式。

这部分的代码如下,笔者已经加入了详细的注释,请读者仔细阅读:

; 设置起始扇区号,28位需要拆开
mov dx, 0x01f3
mov al, 0x02 ; 从第2个扇区开始读(1起始,0留空),扇区号0~7位
out dx, al
mov dx, 0x01f4 ; 扇区号8~15位
mov al, 0
out dx, al
mov dx, 0x01f5 ; 扇区号16~23位
mov al, 0
out dx, al
mov dx, 0x01f6
mov al, 111_0_0000b ;4位是扇区号24~27位,第4位是主从盘(01从),高3位表示磁盘模式(111表示LBA模式)

接下来要配置操作命令,我们要做「读盘」操作,对应的命令号是0x20,它要写入0x01f7端口:

; 配置命令
mov dx, 0x01f7
mov al, 0x20 ; 0x20命令表示读盘
out dx, al

一切就绪之后,控制器就会开始读盘了,但这需要一定的时间,所以此时程序要等待驱动器工作完成。0x01f7端口如果使用in命令,读取到的是硬盘控制器的状态数据,其中第7位表示是否忙碌,第3位表示是否就绪。那么也就是说,当第7位是0且第3位是1的话,说明驱动器已经完成,否则就要持续等待:

wait_finish:
; 检测状态,是否读取完毕
mov dx, 0x01f7
in al, dx ; 通过该端口读取状态数据
and al, 1000_1000b ; 保留第7位和第3位
cmp al, 0000_1000b ; 要检测第7位为0(表示不在忙碌状态)和第3位是否是1(表示已经读取完毕)
jne wait_finish ; 如果不满足则循环等待

当驱动器就绪后,我们就可以通过0x01f0端口来加载数据到内存了。这个端口是个16位端口,因此每次可以读2字节。这里我们用一个循环语句来完成,循环语句的循环次数要写在cx中,每次循环时cx会自动减1,直到cx为0则跳出循环。

所以,如果我们需要加载2个扇区的数据,那么就是1024字节的内容,而循环次数就是512,所以把这个数配到cx中:

mov cx, 512 ; 一共要读的字节除以2(表示次数,因为每次会读2字节所以要除以2

还是按照一开始的规划,我们把屏幕打印的部分放到第二扇区,然后把它加载到0x08000的内存位置:

mov dx, 0x01f0
mov ax, 0x0800
mov ds, ax
xor bx, bx ; [ds:bx] = 0x08000
read:
in ax, dx ; 16位端口,所以要用16位寄存器
mov [bx], ax
add bx, 2 ; 因为ax是16位,所以一次会写2字节
loop read

最后通过跳转指令跳转过去,查看是否加载成功。下面给出完整代码:

; C0H0S1
; 调用0x10BIOS中断,清屏
mov al, 0x03
mov ah, 0x00
int 0x10 

; LBA28模式,逻辑扇区号28位,从0x00000000xFFFFFFF
; 设置读取扇区的数量
mov dx, 0x01f2
mov al, 2 ; 读取连续的几个扇区,每读取一个al就会减1
out dx, al
; 设置起始扇区号,28位需要拆开
mov dx, 0x01f3
mov al, 0x02 ; 从第2个扇区开始读(1起始,0留空),扇区号0~7位
out dx, al
mov dx, 0x01f4 ; 扇区号8~15位
mov al, 0
out dx, al
mov dx, 0x01f5 ; 扇区号16~23位
mov al, 0
out dx, al
mov dx, 0x01f6
mov al, 111_0_0000b ;4位是扇区号24~27位,第4位是主从盘(01从),高3位表示磁盘模式(111表示LBA; 配置命令
mov dx, 0x01f7
mov al, 0x20 ; 0x20命令表示读盘
out dx, al

wait_finish:
; 检测状态,是否读取完毕
mov dx, 0x01f7
in al, dx ; 通过该端口读取状态数据
and al, 1000_1000b ; 保留第7位和第3位
cmp al, 0000_1000b ; 要检测第7位为0(表示不在忙碌状态)和第3位是否是1(表示已经读取完毕)
jne wait_finish ; 如果不满足则循环等待

; 从端口加载数据到内存
mov cx, 512 ; 一共要读的字节除以2(表示次数,因为每次会读2字节所以要除以2)
mov dx, 0x01f0
mov ax, 0x0800
mov ds, ax
xor bx, bx ; [ds:bx] = 0x08000
read:
in ax, dx ; 16位端口,所以要用16位寄存器
mov [bx], ax
add bx, 2 ; 因为ax是16位,所以一次会写2字节
loop read

jmp 0x0800:0x0000 ; 这里写成0x0000:0x8000OK,只是CSIP的值会不同,但CS:IP是相同的

times 510-($-$$) db 0 ; MBR剩余部分用0填充
dw 0xaa55

; 现在已经是C0H0S2的内容了
begin:
mov ax, 0xb800
mov ds, ax
mov [0x0000], byte 'H'
mov [0x0001], byte 0x0f
mov [0x0002], byte 'e'
mov [0x0003], byte 0x0f
mov [0x0004], byte 'l'
mov [0x0005], byte 0x0f
mov [0x0006], byte 'l'
mov [0x0007], byte 0x0f
mov [0x0008], byte 'o'
mov [0x0009], byte 0x0f
hlt

times 1024-($-begin) db 0 ; 补满2个扇区

注意,由于我们已经把扇区扩展到了3个,因此bochsrc里面 也需要修改一下硬盘的规模:

ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 # 主盘端口映射为1f0,从盘映射为3f0,中断号设置为14(虽然这几个参数都可以定制化,但这个参数是业界标准的,不建议更改)
ata0-master: type=disk, mode=flat, path=a.img, cylinders=1, heads=1, spt=3 # 主盘位置加载一块规格为C1H1S3的硬盘,镜像使用a.img
boot: disk # 设置为硬盘启动

最后通过汇编生成mbr.bin,复制为a.img,再启动bochs就可以看到执行效果:
执行效果

此时也可以通过调试指令来验证0x8000的内存中确实加载了对应的指令:
指令

由此殊途同归,我们没有使用BIOS中断,也同样完成了硬盘加载的工作。

小结

本篇介绍了跳转指令和硬盘加载方法,为后面继续编写内核做了铺垫。
下一篇会介绍如何分写MBR代码,使得MBR和内核代码分开管理。

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

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

相关文章

签章那些事 -- 让你全面了解签章的流程

前言 随着通信、互联网技术的发展,人们接触到的信息纷繁复杂,信息的真真假假让人难以辨认。在严肃性场合,比如电子合同、电子证照等,必须有一种手段确保信息的完整性和真实性,这时签章就派上了用场。 签章的技术原理并…

高频面试八股文用法篇(四) 乐观锁和悲观锁的例子

目录 什么是乐观锁和悲观锁 乐观锁的实现方式主要有两种:CAS机制和版本号机制 1)CAS(Compare And Swap) (2)版本号 乐观锁适用场景 乐观锁和悲观锁优缺点 功能限制 竞争激烈程度 什么是乐观锁和悲观锁 乐观锁&#xff1…

录音软件哪个好用?录音软件免费下载安装

案例:有没有好用的录音软件推荐? 【我想录制电脑上的音乐和音频会议,也想用电脑录制自己的歌声,有没有好用的电脑录音软件推荐?】 在日常生活和工作中,我们经常会遇到需要录音的场景,比如会议…

几行代码,轻松教你用Java 将 Word 文档转换为 HTML

Aspose.Words 是一种高级Word文档处理API,用于执行各种文档管理和操作任务。API支持生成,修改,转换,呈现和打印文档,而无需在跨平台应用程序中直接使用Microsoft Word。此外, Aspose API支持流行文件格式处…

基于windows环境利用VS下通过Linux环境下服务器进行UDP通信交流

目录 前言 Linux udpServer.cc udpServer.hpp makefile windows 细节1 -- 头文件引入 细节2 -- 固定写法 细节3 -- 结束后清理 细节4 -- socket返回值接受 细节5 -- 套接字创建(一样的写法) 细节6 -- 填写sockaddr_in结构体 细节7 -- 接发收数据 细节8 -- 报错信…

自学黑客(网络安全),一般人我还是劝你算了

一、自学网络安全学习的误区和陷阱 1.不要试图先成为一名程序员【以编程为基础的学习】再开始学习 我在之前的回答中,我都一再强调不要以编程为基础再开始学习网络安全,一般来说,学习编程不但学习周期长,而且实际向安全过渡后可…

操作系统复习2.2.4-作业/进程调度算法

算法 FCFS先来先服务、SJF短作业优先、HRRN高响应比优先、时间片轮转、优先级调度、多级反馈队列调度 FCFS先来先服务 公平,按照到达先后顺序进行服务 用于作业时,考虑哪个作业先到达后备队列 用于进程时,考虑哪个进程先到达就绪队列 非抢…

引领科技潮流:国产化操作系统和CPU的市场竞争力

随着国家科技实力的不断提升,国产化已经成为了中国科技发展的一大趋势。在多个领域,中国企业正在加快国产替代进程,取得了一定的成果。 2、国产化现状 2.1、操作系统 中国国产操作系统包括以下几种: 麒麟操作系统(Kylin OS):由…

2023 某行业-CTF

文章目录 miscmisc1misc2misc3misc4 WebWeb1Web2Web3Web5 misc misc1 %26%2365%3B%26%2376%3B%26%2390%3B%26%23107%3B%26%23121%3B%26%2389%3B%26%2377%3B%26%2366%3B%26%2390%3B%26%2351%3B%26%2355%3B%26%23120%3B%26%23102%3B%26%23119%3B%26%2369%3B%26%2371%3B%26%2310…

Python从入门到精通_Day_1_Python的学习路线整理

写在最前: 为什么开这个专栏: 之前我做过一个专栏,专门介绍Python爬虫技术,这一专栏收获了很多朋友们的点赞收藏和关注。但是在爬虫技术专栏中,对于Python语言本身的讲解并不是很细致,由于Python在爬虫、数…

【Unity XCharts - 01】XCharts图表库简介

XCharts 图表库简介 1.概述2.官方资源简介2.1 官网介绍2.2 本体源码资源2.3 Unity .unitypackage 资源包2.4 Demo代码资源 ❤️ 打不开地址、下载慢的话童鞋可以在我的资源中下载 3.6.0 版本相关的资源。❤️ → 开源Unity图表库:XCharts 3.6.0 ← 1.概述 XCharts …

windows物理机如何迁移到VMware集群里简单教程

前言 快速将本地和远程物理机转换为虚拟机,而无需停机。同时转换可实现大规模虚拟化实施。提供对源物理机。 VMware和Microsoft 虚拟机格式以及某些第三方磁盘映像格式的广泛支持。 它可以自动从物理机(运行Windows和Linux)和从其他虚拟机格…

语音转文字怎么转?教你三个转换的方法

录音转文字电脑软件哪个好?分享三款好用的录音转文字工具 一分钟告诉你录音转文字电脑软件哪个好 录音转文字电脑软件哪个好?这几款把语音转成文字的软件推荐给你 如何语音转文字?三款好用语音转文字的软件推荐 语音转文字怎么转&#xf…

智慧社区物业

智慧社区跟物业有什么关系呢? 随着智能化科技的快速发展,智慧社区逐渐成为现代城市的新宠。智慧社区代表着社区信息化和智能化的水平,它的出现彻底改变了传统社区的管理形式。而在智慧社区中,物业管理是其中最为重要的环节之一&a…

【软件技术基础】C#调用NPOI插件对EXCEL进行处理

文章目录 前言一、处理界面二、按钮处理代码0、公共变量1、btnSelectFolder_Click中的代码2、btnOneKey_Click中的代码3、btnImport_Click中的代码4、btnCheck_Click中的代码5、btnProces_Click中的代码6、btnExpert_Click中的代码 三、公共部分函数总结 前言 NPOI插件进行EXC…

如何通过知识星球粉丝变现年入100万?

使用知识星球年入100万的话,那么你的社群收费必须超过125万,因为星球会有20%的手续费。 年入100万并不是一笔小数目,如果要达成这个目标,按照每个人付费100元计算,那么需要1万个付费用户,平均每个月就需要9…

Spark RDD持久化机制

文章目录 一、RDD持久化(一)引入持久化的必要性(二)案例演示持久化操作1、RDD的依赖关系图2、不采用持久化操作3、采用持久化操作 二、存储级别(一)持久化方法的参数(二)Spark RDD存…

最优化理论-KKT定理的推导与实现

目录 一、引言 二、最优化问题的基本概念 三、KKT条件的引入 1. 梯度条件 2. 原始可行性条件 3. 对偶可行性条件 四、KKT定理的表述 五、KKT定理的证明 1. 构造拉格朗日函数 2. 构造拉格朗日对偶函数 3. 推导KKT条件 4. 解释KKT条件 六、KKT定理的应用 七、总结 …

Python数据攻略-Pandas常用数据操作

大家好,我是Mr数据杨。今天我将带领各位走进Python的奇妙世界,就像步入三国演义那样热闹且复杂的战争年代。这里,数据就像那些智勇双全的武将和策士,我们要学习如何访问和修改它们,就如同诸葛亮那样掌控战局。 先来理…