1 制作真正的IPL

IPL(Initial Program Loader),启动程序装载器,但是之前并没有实质性的装载任何程序,这次作者要开始装载程序了。

虽然现在开发的操作系统啥功能也没有,作者说轻轻松松做。

起始我觉得吧,有了启动程序加载器之后,相当于给了我们一个给CPU传送指令的入口,我们想让CPU干啥,就给它传指令和数据就可以啦。操作系统嘛,就是一个启动程序装载器调用的一个很大的、很复杂的函数而已,突然感觉自己有些升华了( ^_^ )。

CPU就是一个干活很快的家伙,但是智商基本为0,现在我们已经牵住了CPU的牛鼻子了。

这里还是磁盘的第一个扇区,也就是512字节,然后作者又修改了以下程序,加了点东西。

看到读盘这一块,我突然有点明白了,为啥要从扇区2,开始读盘,第一块扇区的index其实是0,所以1去哪儿了呢?

参考上一篇:30天自制操作系统(二)汇编语言学习与Makefile入门

JC指令

JC即"jump if carry"的缩写,意思是,如果进位标志(carry flag)是1的话,就跳转。

AH=0x02,INT 0x13是读盘的意思,这个可以查找BIOS的调用可知。

Flags标志寄存器

从软盘的哪儿读数据

软盘的磁头有两个Head:磁头0,读正面;磁头1,读反面

一共80个柱面Cylinder:柱面0,柱面1,...,柱面79

一共18个扇区Sector:扇区1,扇区2,...,扇区18(╥﹏╥...),mlgb,为啥不是从0开始,所以上面的解释,不好意思我错了,直接读了启动扇区的下一个扇区。

含IPL的启动区就是C0-H0-S1,那么下一个扇区就是C0-H0-S2,这个扇区就是我们想装载的扇区了。

用一个寄存器来表示地址的话,例如BX,只能表示0~0xffff的值,也就是0~65535,最大64KB。

为了解决这个问题,首先考虑到的是段寄存器,后来就是想EBX这样的扩展寄存器,可以处理4GB的内存。

使用段寄存器的方式是ES:BX。

作者把第二个扇区读到了0x0820,因为0x7c00到~0x7dff用于启动区,0x8000~0x81ff这512字节也是留给启动区的,要将启动区的内容读到这里。

但是为啥又说0x7e00以后直至0x9fbff为止的区域都没有特别的用途。

说说为什么会有0x8000这一说:是有些操作系统会把操作系统的代码放到0x8000,这是因为BIOS读完启动扇区以后,会跳转到0x7C00启动,占用0x7C00-0x7DFF这一段(512字节),而一般bootloader还需要一个栈空间或者读磁盘的交换空间,一般是放到0x7E00-0x7FFF这512字节里,所以有些操作系统的镜像起点是0x8000。但是BIOS里读取启动扇区都是加载到0x7C00上的。

我一直疑惑一个问题,每个磁道的扇区一样,那为什么每个扇区的字节数还一样多捏?欲知详情,请戳此处。

不管要怎么处理内存看,都必须同时指定段寄存器,如果省略的话,会把"DS:"作为默认的段寄存器。

MOV CX, [1234]的意思,其实是MOV CX, [DS:1234]。

因为有这样的规则,所以DS必须预先指定为0,否则地址的值就要加上这个数的16倍,就会读到其他地方。

2 试错

就是读失败了之后就多读几次

; hello-os
; TAB=4 ORG 0x7c00 ; 指明程序的装载地址 ; 以下这部分记录的是FAT12格式的软盘 JMP entry
DB 0x90
DB "HELLOIPL" ; 启动扇区的名称可以是任意的字符串 (8字节)
DW 512 ; 每个扇区(sector)的大小(必须是512字节)
DB 1 ; 簇(cluster)的大小 (必须为512字节)
DW 1 ; FAT的起始位置 (一般从第一个扇区开始)
DB 2 ; FAT的个数 (必须为2)
DW 224 ; 根目录的大小(一般设成224项)
DW 2880 ; 该磁盘的大小(必须是2880扇区)
DB 0xf0 ; 磁盘的种类 (必须是0xf0)
DW 9 ; FAT的长度 (必须是9扇区)
DW 18 ; 1个磁道(track)有几个扇区(必须是18)
DW 2 ; 磁头数 (必须是2)
DD 0 ; 不使用分区, 必须是0
DD 2880 ; 重写一次磁盘大小
DB 0,0,0x29 ; 意义不明, 固定
DD 0xffffffff ; (可能是)卷标号码
DB "HELLO-OS " ; 磁盘的名称(11字节)
DB "FAT12 " ; 磁盘格式名称 (8字节)
RESB 18 ; 先空出18字节 ; 程序主体 entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX ; 新加了一部分读盘 MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2 MOV SI,0 ; 记录失败次数的计数器
retry:
MOV AH,0x02 ; AH=0x02 : 读入磁盘
MOV AL,1 ; 1个扇区
MOV BX,0
MOV DL,0x00 ; A驱动器
INT 0x13 ; 调用磁盘BIOS
JNC fin ; 没错的话跳转到fin
ADD SI,1 ; 否则给SI加1
CMP SI,5 ; 比较SI与5
JAE error ; SI >= 5 跳转到error
MOV AH,0x00 ; AH和INT指令配合使用
MOV DL,0x00 ; A驱动器
INT 0x13 ; 重置驱动器
JMP retry fin:
HLT ; 让CPU停止等待指令
JMP fin ; 无限循环 error:
MOV SI,msg putloop:
MOV AL,[SI]
ADD SI,1 ; 给SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用显卡BIOS
JMP putloop msg:
DB 0x0a, 0x0a ; 换行2次
DB "load error"
DB 0x0a ; 换行
DB 0
RESB 0x7dfe-$ ; 填写0x00, 直到0x7dfe
DB 0x55, 0xaa

下面这个就是试错的部分

JNC指令

JNC是"Jump if not carry"的缩写,也就是进位标志还是0的话就跳转。

JAE也是条件跳转,是"Jump if above or equal"的缩写,意思是大于或等于就跳转。

AH=0x00, DL=0x00, INT 0x13是BIOS的"系统复位",是用来复位软盘的状态,再读一次。

3 读到18扇区

突然想到软盘就18个扇区,这不就是都读完么?(我错了,一个柱面就有10个扇区

; hello-os
; TAB=4 ORG 0x7c00 ; 指明程序的装载地址 ; 以下这部分记录的是FAT12格式的软盘 JMP entry
DB 0x90
DB "HELLOIPL" ; 启动扇区的名称可以是任意的字符串 (8字节)
DW 512 ; 每个扇区(sector)的大小(必须是512字节)
DB 1 ; 簇(cluster)的大小 (必须为512字节)
DW 1 ; FAT的起始位置 (一般从第一个扇区开始)
DB 2 ; FAT的个数 (必须为2)
DW 224 ; 根目录的大小(一般设成224项)
DW 2880 ; 该磁盘的大小(必须是2880扇区)
DB 0xf0 ; 磁盘的种类 (必须是0xf0)
DW 9 ; FAT的长度 (必须是9扇区)
DW 18 ; 1个磁道(track)有几个扇区(必须是18)
DW 2 ; 磁头数 (必须是2)
DD 0 ; 不使用分区, 必须是0
DD 2880 ; 重写一次磁盘大小
DB 0,0,0x29 ; 意义不明, 固定
DD 0xffffffff ; (可能是)卷标号码
DB "HELLO-OS " ; 磁盘的名称(11字节)
DB "FAT12 " ; 磁盘格式名称 (8字节)
RESB 18 ; 先空出18字节 ; 程序主体 entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX ; 新加了一部分读盘 MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2 readloop:
MOV SI,0 ; 记录失败次数的计数器
retry:
MOV AH,0x02 ; AH=0x02 : 读入磁盘
MOV AL,1 ; 1个扇区
MOV BX,0
MOV DL,0x00 ; A驱动器
INT 0x13 ; 调用磁盘BIOS
JNC next ; 没出错时跳转到next
ADD SI,1 ; 往SI加1
CMP SI,5 ; 比较SI和5
JAE error ; SI >= 5时, 跳转到error
MOV AH,0x00
MOV DL,0x00 ; A驱动器
INT 0x13 ; 重置驱动器
JMP retry
next:
MOV AX,ES ; 把内存地址往后移0x200=512字节
ADD AX,0x0020
MOV ES,AX ; 因为没有ADD ES, 0x20指令, 所以这里稍微绕个弯
ADD CL,1 ; 往CL里加1
CMP CL,18 ; CL和18比较
JBE readloop ; CL <= 18 则跳转到readloop fin:
HLT ; 让CPU停止等待指令
JMP fin ; 无限循环 error:
MOV SI,msg putloop:
MOV AL,[SI]
ADD SI,1 ; 给SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用显卡BIOS
JMP putloop msg:
DB 0x0a, 0x0a ; 换行2次
DB "load error"
DB 0x0a ; 换行
DB 0
RESB 0x7dfe-$ ; 填写0x00, 直到0x7dfe
DB 0x55, 0xaa

起始感觉做的事情也很简单,每读一个扇区,就把软盘加载到内存中的位置往后挪,直到18个扇区全部读完了。

JBE指令

JBE是"jump if below or equal"的缩写,就是小于等于则跳转

给ES加上0x20就行,0x20是十六进制下,512除以16的结果,或者ADD AX, 512/16也行,或则往BX里加上512也可以。

后来读者又解释了为啥要循环来读的原因:

虽然在调用读盘函数的INT 0x13处,将AL设置为17就行,这样,程序一下子能将扇区2~18共17个扇区的数据完整读出来,但是BIOS读盘有补充说明:指定处理的扇区数,范围在0x01~0xff【指定0x02以上的数值时,要特别注意能够连续处理多个扇区的条件,如果是FD(Floppy Disk),似乎不能跨多个磁道,也不能超过64KB的界限。】

现在已经将磁盘上的C0-H0-S2到C0-H0-S18的512x17=8704字节的内容,装载到了内存的0x8200~0xa3ff处。

4 读入10个柱面

; hello-os
; TAB=4 CYLS EQU 10 ; 读取的柱面数 ORG 0x7c00 ; 指明程序的装载地址 ; 以下这部分记录的是FAT12格式的软盘 JMP entry
DB 0x90
DB "HELLOIPL" ; 启动扇区的名称可以是任意的字符串 (8字节)
DW 512 ; 每个扇区(sector)的大小(必须是512字节)
DB 1 ; 簇(cluster)的大小 (必须为512字节)
DW 1 ; FAT的起始位置 (一般从第一个扇区开始)
DB 2 ; FAT的个数 (必须为2)
DW 224 ; 根目录的大小(一般设成224项)
DW 2880 ; 该磁盘的大小(必须是2880扇区)
DB 0xf0 ; 磁盘的种类 (必须是0xf0)
DW 9 ; FAT的长度 (必须是9扇区)
DW 18 ; 1个磁道(track)有几个扇区(必须是18)
DW 2 ; 磁头数 (必须是2)
DD 0 ; 不使用分区, 必须是0
DD 2880 ; 重写一次磁盘大小
DB 0,0,0x29 ; 意义不明, 固定
DD 0xffffffff ; (可能是)卷标号码
DB "HELLO-OS " ; 磁盘的名称(11字节)
DB "FAT12 " ; 磁盘格式名称 (8字节)
RESB 18 ; 先空出18字节 ; 程序主体 entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX ; 新加了一部分读盘 MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2 readloop:
MOV SI,0 ; 记录失败次数的计数器
retry:
MOV AH,0x02 ; AH=0x02 : 读入磁盘
MOV AL,1 ; 1个扇区
MOV BX,0
MOV DL,0x00 ; A驱动器
INT 0x13 ; 调用磁盘BIOS
JNC next ; 没出错时跳转到next
ADD SI,1 ; 往SI加1
CMP SI,5 ; 比较SI和5
JAE error ; SI >= 5时, 跳转到error
MOV AH,0x00
MOV DL,0x00 ; A驱动器
INT 0x13 ; 重置驱动器
JMP retry
next:
MOV AX,ES ; 把内存地址往后移0x200
ADD AX,0x0020
MOV ES,AX ; 因为没有ADD ES, 0x20指令, 所以这里稍微绕个弯
ADD CL,1 ; 往CL里加1
CMP CL,18 ; CL和18比较
JBE readloop ; CL <= 18 则跳转到readloop MOV CL,1
ADD DH,1
CMP DH,2
JB readloop ; 如果DH<2, 则跳转到readloop
MOV DH,0
ADD CH,1
CMP CH,CYLS
JB readloop ; 如果CH<CYLS, 则跳转到readloop fin:
HLT ; 让CPU停止等待指令
JMP fin ; 无限循环 error:
MOV SI,msg putloop:
MOV AL,[SI]
ADD SI,1 ; 给SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用显卡BIOS
JMP putloop msg:
DB 0x0a, 0x0a ; 换行2次
DB "load error"
DB 0x0a ; 换行
DB 0
RESB 0x7dfe-$ ; 填写0x00, 直到0x7dfe
DB 0x55, 0xaa

注意前面定义了一个变量,读完这10个柱面之后,最初的10x2x18x512=180KB的内容就都读到内存中了。

5 着手开发操作系统

这个就是最简单的操作系统了

fin:
HLT
JMP fin

我有点疑惑,之前的文件中不都有这个么?

这里作者将将启动区编译成了.img镜像文件,然后将上面两行代码编译成了.sys文件,然后再用工具将.sys文件写入到了.img文件。

最终我们要从启动区执行这个.sys文件,至于.sys还将可以写啥,你懂的?!

总结:向一个空的软盘保存文件时:

(1)文件名会写到0x002600的地方

(2)文件的内容会写到0x004200的地方

6 从启动区执行操作系统

现在作者想要执行的是软盘0x004200的代码,但是又说磁盘上的内容装载到了0x8000号地址,老子无语了不是读到了0x8200么,怎么又要跑到了0x8000,启动程序不是在0x7c00的地方装着的么,刚开始没想通。

说说为什么会有0x8000这一说:是有些操作系统会把操作系统的代码放到0x8000,这是因为BIOS读完启动扇区以后,会跳转到0x7C00启动,占用0x7C00-0x7DFF这一段(512字节),而一般bootloader还需要一个栈空间或者读磁盘的交换空间,一般是放到0x7E00-0x7FFF这512字节里,所以有些操作系统的镜像起点是0x8000。但是BIOS里读取启动扇区都是加载到0x7C00上的。

后来明白了,第二个扇区是从0x8200开始的,所以,磁盘的首地址在0x8000没毛病,真是扎心了,老铁

下面的就好说了,那么软盘0x4200的就是内存的0x8000+0x4200=0xc200的位置了。

这样的话在我们要执行的那个最简单的操作系统加上一个ORG 0xc200,然后再启动装载程序读完软盘之后,加一个JMP 0xc200不就好了?

7 确认操作系统的执行

为了让它表现,作者弄了一个开机画面,不好意思,都是黑屏的。

这个是读盘结束,然后跳转到"操作系统的一段代码"

        CMP        CH,CYLS
JB readloop ; CH < CYLS 则跳转到readloop ; 已完成读盘,准备运行haribote.sys! MOV [0x0ff0],CH ; 请注意IPL已经读取了多远
JMP 0xc200 error:
MOV SI,msg

不知道为什么要存到0xff0处这个位置?

接下来是"操作系统"

; haribote-os
; TAB=4 ORG 0xc200 ; 这个程序要被装载到内存的什么地方呢 MOV AL,0x13 ; VGA显卡, 320x200x8位彩色
MOV AH,0x00
INT 0x10
fin:
HLT
JMP fin

AL中存放的是VGA的模式。

上面这个读盘的只能读10个柱面,同时还想把磁盘装载内容结束的地址告诉.sys文件,所以在JMP 0xc200之前加了一条将CYLS的值写到内存地址0x0ff0中。

这就清楚了撒,然后作者让"make run"就行了,系统启动

8 32位模式前期准备

之前都是用汇编语言来开发的,现在要用C语言来开发了。

编译器只能生成32位机器语言,所以就不再用十六位的了:

32位寻址大,使用32位寄存器很方便,32位可以使用CPU的保护模式。

32位不好的地方是不能直接调用BIOS的功能了,所以最好在一开始就把要调用BIOS才能做的事给做完。

需要BIOS做哪些事情呢?

画面模式的设定,键盘的状态(也就是NumLock是ON还是OFF)

于是就有了下面的这段代码:

; haribote-os
; TAB=4 ; BOOT_INFO関係
CYLS EQU 0x0ff0 ; 设定启动区, 这个是我们将读取的柱面数存在这个地方了
LEDS EQU 0x0ff1
VMODE EQU 0x0ff2 ; 关于颜色的数目信息, 颜色的位数
SCRNX EQU 0x0ff4 ; 分辨率的X
SCRNY EQU 0x0ff6 ; 分辨率的Y
VRAM EQU 0x0ff8 ; 图像缓冲区的开始地址 ORG 0xc200 ; 这个程序要被装载到内存的什么地方 MOV AL,0x13 ; VGA显卡, 320X200X8位彩色
MOV AH,0x00
INT 0x10
MOV BYTE [VMODE],8 ; 记录画面模式
MOV WORD [SCRNX],320
MOV WORD [SCRNY],200
MOV DWORD [VRAM],0x000a0000 ; 用BIOS取得键盘上各种LED指示灯的状态 MOV AH,0x02
INT 0x16 ; keyboard BIOS
MOV [LEDS],AL fin:
HLT
JMP fin

这里设置了开机的画面,指定了图像缓冲区开始地址,显卡内存地址,也就是说显卡要显示什么都可以在这里设置。

VRAM分布在内存分布图的好几个地方,因为不同画面模式的像素也不一样,所以可以把预先要用的VRAM地址保存在BIOS_INFO中备用。

这里VRAM的范围是0xa0000~0xaffff的64KB

关于显卡内存的安排也是和BIOS有关。

此外,还把画面的像素数、颜色数、以及从BIOS取得的键盘信息都保存下来了,保存位置是在0x0ff0附近,从内存分布图来看,这一块并没有被使用。

9 开始导入C语言

之前都是用的汇编,现在就用要用C了,这里有个很显然的问题就是:

汇编和C怎么连起来?

这里作者给出了一个100行左右的汇编代码,用于与C语言写的对接,不过这里不打算讲这段汇编。

给出了一个C语言文件,内容很简单:

void HariMain(void)
{ fin:
/* 这里想写上HLT, 单C语言中不能用HLT */
goto fin; }

也就是也会汇编会调用这一段代码的,当然是不可能直接调用的,得通过编译器把这段C语言写的程序编译成机器语言。

那么这个过程是怎么样的呢?

也就是链接过程。其实我觉得这里的关键还是寻址的问题,你想一下C语言的函数经过编译之后肯定会存在于某个地方,我们需要jump到HariMain这个函数名,也就是汇编的标号,来执行函数中的这段代码。

然后是怎么通过源文件来制作启动的镜像文件。

然后镜像文件导入内存(这个在前面有讲到过),然后就是从内存的 那个地址开始执行(0xc200)之前也有讲到过。

还有一个是Makefile,这个只是个编译的工具,到时候真的要用再去看一下。

10 实现HLT

这个地方也特别有意思,开发操作系统少部分是汇编完成的,然后主要部分是C语言,当运行C语言中的代码的时候,C语言中的代码有些事情又做不了,然后又得靠汇编来解决,总之就汇编就是屌啊。

问题是C语言写的程序如何调用汇编,然后参数传递的问题又是怎么样的呢?这个也许要挖掘,当然这里没有讲到参数传递的问题,只是,我想到了这点。

; naskfunc
; TAB=4 [FORMAT "WCOFF"] ; 制作目标文件的模式
[BITS 32] ; 制作32位模式用的机器语言 ; 制作目标文件的信息 [FILE "naskfunc.nas"] ; 源文件名信息 GLOBAL _io_hlt ; 程序中包含的函数名 ; 以下是实际的函数 [SECTION .text] ; 目标文件中写了这些之后再写程序 _io_hlt: ; void io_hlt(void);
HLT
RET

然后再C语言函数HariMain中调用这一汇编写的函数,实现了C语言中的HLT的功能

/* 告诉编译器, 有一个函数在别的文件中 */
void io_hlt(void); /* 是函数声明却不用{}, 而用; 这表示函数是在别的文件中, 自己找一下吧 */ void HariMain(void)
{ fin:
io_hlt(); /* 执行naskfunc.nas里的_io_hlt */
goto fin; }

11 总结

30天自制操作系统(三)进入32位模式并导入C语言的更多相关文章

  1. 挑战30天写操作系统-day3-进入32位模式并导入C语言

    目录 1.制作真正的IPL IPL:启动区,启动程序装载器完整代码: ; haribote-ipl ; TAB=4 CYLS EQU 10 ; 声明CYLS=10 ORG 0x7c00 ; 指明程序装 ...

  2. 《30天自制操作系统》读书笔记(3) 引入C语言

    这一次的学习相当曲折, 主要是因为粗心, Makefile里面的错误导致了文件生成出现各种奇奇怪怪的问题, 弄得心力交瘁, 因此制作过程还是尽量按着作者的路子来吧. 作者提供的源码的注释在中文系统下是 ...

  3. 《30天自制操作系统》笔记(06)——CPU的32位模式

    <30天自制操作系统>笔记(06)——CPU的32位模式 进度回顾 上一篇中实现了启用鼠标.键盘的功能.屏幕上会显示出用户按键.点击鼠标的情况.这是通过设置硬件的中断函数实现的,可以说硬件 ...

  4. 《30天自制操作系统》笔记(02)——导入C语言

    <30天自制操作系统>笔记(02)——导入C语言 进度回顾 在上一篇,记录了计算机开机时加载IPL程序(initial program loader,一个nas汇编程序)的情况,包括IPL ...

  5. 30天自制操作系统第八天学习笔记(u盘软盘双启动版本)

    暑假学习小日本的那本书:30天自制操作系统 qq交流群:122358078    ,更多学习中的问题.资料,群里分享 environment:开发环境:ubuntu 第八天的学习思考: 关于鼠标是怎么 ...

  6. 30天自制操作系统-day1

    30天自制操作系统(linux环境)--第一天 我是在CentOS的环境上面实现的,使用ubuntu的环境也是类似的 第一步:因为要对二进制文件进行编辑,所以安装二进制编辑器hexedit(当然其他的 ...

  7. 30天自制操作系统day2汇编语言

    <30天自制操作系统>一书中第1天和第二天中有关汇编语言的程序的理解 ; hello-os ; TAB=4 ORG 0x7c00 ; 指明程序的装载地址 ; 以下的记述用于标准FAT12格 ...

  8. 30天自制操作系统第九天学习笔记(u盘软盘双启动版本)

    暑假学习小日本的那本书:30天自制操作系统 qq交流群:122358078    ,更多学习中的问题.资料,群里分享 environment:开发环境:ubuntu 第九天的课程已学完,确实有点不想写 ...

  9. 《30天自制操作系统》笔记(01)——hello bitzhuwei’s OS!【转】

    转自:http://www.cnblogs.com/bitzhuwei/p/OS-in-30-days-01-hello-bitzhuwei-OS.html 阅读目录(Content) 最初的OS代码 ...

随机推荐

  1. post sharp 与log4net 结合使用,含执行源码 转拷

    环境: VS 2012 PostSharp-4.1.28 (下载地址)https://visualstudiogallery.msdn.microsoft.com/a058d5d3-e654-43f8 ...

  2. js常用方法 备用

    /* function obj$(id)                      根据id得到对象 function val$(id)                      根据id得到对象的值 ...

  3. MySQL数据库”mysql SQL Error:1146,SQLState:42S02 “解决方法

    项目在开发的时候在Mac平台下开发的,开发完了之后在LINUX环境上部署好之后,运行时MySQL数据库报错,提示为某个表不存在之类的错误信息,后来修改了MySQL的配置文件将大小写敏感去掉,问题解决. ...

  4. JavaScript-性能优化,函数节流(throttle)与函数去抖(debounce)

    我在写一个类似百度搜索框的自动提示功能时候,使用了AJAX+keydown事件.调试时候我发现,当在搜索框中输入文字的时候,控制台在不停发送AJAX.这在本地服务器测试还好,如果我把它拿到运行环境,很 ...

  5. PHP json_encode 转换成空对象和空数组

    对于以下对象 $foo = array( "bar1" => array(), "bar2" => array() ); 我想转换成 { " ...

  6. 转载:html+js实现只允许输入两位小数的输入框

    JS代码: <script language="JavaScript" type="text/javascript"> function clear ...

  7. EOJ 3.30 B. 蛇形矩阵【找规律/待补】

    [链接]:https://acm.ecnu.edu.cn/contest/59/problem/B/ B. 蛇形矩阵 Time limit per test: 2.0 seconds Memory l ...

  8. Codeforces 147B Smile House(DP预处理 + 倍增)

    题目链接  Smile House 题意  给定一个$n$个点的有向图,求一个点数最少的环,使得边权之和$>0$,这里的环可以重复经过点和边.   满足  $n <= 300$ 首先答案肯 ...

  9. dhlin-vim-wiki

    记录vim中常用的几个操作 入门指南 $ vimtutor vim中是区分大小写 vim中移动光标 h 向左移动 j 向下移动 k 向上移动 l 向右移动 其实使用方向键也是能移动的,但是熟悉后再一些 ...

  10. jzyzoj 栈——P1148:括号匹配加强版

    括号匹配加强版 描述 Description 对于一个由(,),[,]括号组成的字符串,求出其中最长的括号匹配字串. 具体来说,满足如下条件的字符串成为括号匹配的字符串: (1) (),[] 是括号匹 ...