1.代码清单

         ;代码清单9-1;文件名:c09_1.asm;文件说明:用户程序 ;创建日期:2011-4-16 22:03;===============================================================================
SECTION header vstart=0                     ;定义用户程序头部段 program_length  dd program_end          ;程序总长度[0x00];用户程序入口点code_entry      dw start                ;偏移地址[0x04]dd section.code.start   ;段地址[0x06]
    realloc_tbl_len dw (header_end-realloc_begin)/4;段重定位表项个数[0x0a]realloc_begin:;段重定位表           code_segment    dd section.code.start   ;[0x0c]data_segment    dd section.data.start   ;[0x14]stack_segment   dd section.stack.start  ;[0x1c]header_end:                ;===============================================================================
SECTION code align=16 vstart=0           ;定义代码段(16字节对齐)
new_int_0x70:push axpush bxpush cxpush dxpush es.w0:                                    mov al,0x0a                        ;阻断NMI。当然,通常是不必要的or al,0x80                          out 0x70,alin al,0x71                         ;读寄存器Atest al,0x80                       ;测试第7位UIP jnz .w0                            ;以上代码对于更新周期结束中断来说 ;是不必要的 xor al,alor al,0x80out 0x70,alin al,0x71                         ;读RTC当前时间(秒)push axmov al,2or al,0x80out 0x70,alin al,0x71                         ;读RTC当前时间(分)push axmov al,4or al,0x80out 0x70,alin al,0x71                         ;读RTC当前时间(时)push axmov al,0x0c                        ;寄存器C的索引。且开放NMI out 0x70,alin al,0x71                         ;读一下RTC的寄存器C,否则只发生一次中断;此处不考虑闹钟和周期性中断的情况 mov ax,0xb800mov es,axpop axcall bcd_to_asciimov bx,12*160 + 36*2               ;从屏幕上的12行36列开始显示mov [es:bx],ahmov [es:bx+2],al                   ;显示两位小时数字mov al,':'mov [es:bx+4],al                   ;显示分隔符':'not byte [es:bx+5]                 ;反转显示属性 pop axcall bcd_to_asciimov [es:bx+6],ahmov [es:bx+8],al                   ;显示两位分钟数字mov al,':'mov [es:bx+10],al                  ;显示分隔符':'not byte [es:bx+11]                ;反转显示属性pop axcall bcd_to_asciimov [es:bx+12],ahmov [es:bx+14],al                  ;显示两位小时数字mov al,0x20                        ;中断结束命令EOI out 0xa0,al                        ;向从片发送 out 0x20,al                        ;向主片发送 pop espop dxpop cxpop bxpop axiret;-------------------------------------------------------------------------------
bcd_to_ascii:                            ;BCD码转ASCII;输入:AL=bcd码;输出:AX=asciimov ah,al                          ;分拆成两个数字 and al,0x0f                        ;仅保留低4位 add al,0x30                        ;转换成ASCII shr ah,4                           ;逻辑右移4位 and ah,0x0f                        add ah,0x30ret;-------------------------------------------------------------------------------
start:mov ax,[stack_segment]mov ss,axmov sp,ss_pointermov ax,[data_segment]mov ds,axmov bx,init_msg                    ;显示初始信息 call put_stringmov bx,inst_msg                    ;显示安装信息 call put_stringmov al,0x70mov bl,4mul bl                             ;计算0x70号中断在IVT中的偏移mov bx,ax                          cli                                ;防止改动期间发生新的0x70号中断push esmov ax,0x0000mov es,axmov word [es:bx],new_int_0x70      ;偏移地址。mov word [es:bx+2],cs              ;段地址pop esmov al,0x0b                        ;RTC寄存器Bor al,0x80                         ;阻断NMI out 0x70,almov al,0x12                        ;设置寄存器B,禁止周期性中断,开放更 out 0x71,al                        ;新结束后中断,BCD码,24小时制 mov al,0x0cout 0x70,alin al,0x71                         ;读RTC寄存器C,复位未决的中断状态in al,0xa1                         ;读8259从片的IMR寄存器 and al,0xfe                        ;清除bit 0(此位连接RTC)out 0xa1,al                        ;写回此寄存器 sti                                ;重新开放中断 mov bx,done_msg                    ;显示安装完成信息 call put_stringmov bx,tips_msg                    ;显示提示信息call put_stringmov cx,0xb800mov ds,cxmov byte [12*160 + 33*2],'@'       ;屏幕第12行,35列
       .idle:hlt                                ;使CPU进入低功耗状态,直到用中断唤醒not byte [12*160 + 33*2+1]         ;反转显示属性 jmp .idle;-------------------------------------------------------------------------------
put_string:                              ;显示串(0结尾)。;输入:DS:BX=串地址mov cl,[bx]or cl,cl                        ;cl=0 ?jz .exit                        ;是的,返回主程序 call put_charinc bx                          ;下一个字符 jmp put_string.exit:ret;-------------------------------------------------------------------------------
put_char:                                ;显示一个字符;输入:cl=字符asciipush axpush bxpush cxpush dxpush dspush es;以下取当前光标位置mov dx,0x3d4mov al,0x0eout dx,almov dx,0x3d5in al,dx                        ;高8位 mov ah,almov dx,0x3d4mov al,0x0fout dx,almov dx,0x3d5in al,dx                        ;低8位 mov bx,ax                       ;BX=代表光标位置的16位数cmp cl,0x0d                     ;回车符?jnz .put_0a                     ;不是。看看是不是换行等字符 mov ax,bx                       ; mov bl,80                       div blmul blmov bx,axjmp .set_cursor.put_0a:cmp cl,0x0a                     ;换行符?jnz .put_other                  ;不是,那就正常显示字符 add bx,80jmp .roll_screen.put_other:                             ;正常显示字符mov ax,0xb800mov es,axshl bx,1mov [es:bx],cl;以下将光标位置推进一个字符shr bx,1add bx,1.roll_screen:cmp bx,2000                     ;光标超出屏幕?滚屏jl .set_cursormov ax,0xb800mov ds,axmov es,axcldmov si,0xa0mov di,0x00mov cx,1920rep movswmov bx,3840                     ;清除屏幕最底一行mov cx,80.cls:mov word[es:bx],0x0720add bx,2loop .clsmov bx,1920.set_cursor:mov dx,0x3d4mov al,0x0eout dx,almov dx,0x3d5mov al,bhout dx,almov dx,0x3d4mov al,0x0fout dx,almov dx,0x3d5mov al,blout dx,alpop espop dspop dxpop cxpop bxpop axret;===============================================================================
SECTION data align=16 vstart=0init_msg       db 'Starting...',0x0d,0x0a,0inst_msg       db 'Installing a new interrupt 70H...',0done_msg       db 'Done.',0x0d,0x0a,0tips_msg       db 'Clock is now working.',0;===============================================================================
SECTION stack align=16 vstart=0resb 256
ss_pointer:;===============================================================================
SECTION program_trail
program_end:

以上就是全部的代码了(加载器采用第八章的)

也不知道我这个插件怎么了,显示出的源码歪歪扭扭,没有对齐好吧,咱们就凑合看吧。

2.用户程序结构图

3.中断处理程序

最开始的部分是头部,严格遵循第八章作者约定的格式,我们就不多说了。

;===============================================================================
SECTION code align=16 vstart=0           ;定义代码段(16字节对齐)
new_int_0x70:push axpush bxpush cxpush dxpush es

这里就开始中断处理程序了。首先是把用到的寄存器入栈,这是必须的。

.w0:                                    mov al,0x0a                        ;阻断NMI。当然,通常是不必要的or al,0x80                          out 0x70,alin al,0x71                         ;读寄存器Atest al,0x80                       ;测试第7位UIP jnz .w0                            ;以上代码对于更新周期结束中断来说 ;是不必要的 xor al,alor al,0x80out 0x70,alin al,0x71                         ;读RTC当前时间(秒)push axmov al,2or al,0x80out 0x70,alin al,0x71                         ;读RTC当前时间(分)push axmov al,4or al,0x80out 0x70,alin al,0x71                         ;读RTC当前时间(时)push ax

这段代码要细说,有很多新知识。

(1)CMOS RAM

在外围设备控制芯片(ICH)内部,集成了实时时钟电路(RTC)和两小块由互补金属氧化物(CMOS)材料组成的静态存储器(CMOS RAM)。实时时钟电路负责计时,而日期和时间的数值则存储在这块存储器中,它们由电脑主板上的一个小纽扣电池提供能量。

日期和时间信息存储在CMOS RAM中,通常CMOS RAM有128个存储单元,而日期和时间信息只占了一小部分容量,其余空间则保存整机的配置信息。

RTC芯片由一个频率为32.768kHz的晶振驱动,经过分频后,用于对CMOS RAM进行每秒一次的时间刷新。

表格9-1 CMOS RAM中的时间信息

偏移地址

内容

偏移地址

内容

0x00

0x07

0x01

闹钟秒

0x08

0x02

0x09

0x03

闹钟分

0x0a

寄存器A

0x04

0x0b

寄存器B

0x05

闹钟时

0x0c

寄存器C

0x06

星期

0x0d

寄存器D

CMOS RAM的访问,需要两个端口:0x70是索引端口,用来指定内存单元;0x71是数据端口,用来读写相应单元里的内容。

举例:

mov al,2
out 0x70,al ;指定内存单元为2
in al,0x71 ;读RTC当前时间(分)

需要说明的是,从很早的时候开始,端口0x70的最高位是控制NMI中断的开关,当它为0时,允许NMI中断;为1时,阻断所有的NMI信号。其他7个bit,实际上用来指定CMOS RAM单元的索引号。

作者为了简化问题,所以在访问RTC时,直接关闭NMI,访问结束后,再打开NMI(不管它之前是不是打开的)。

查阅资料,有的朋友说“访问CMOS RAM可能导致产生NMI,所以需要关闭NMI。”

还有一点要注意:CMOS RAM中保存的日期和时间,默认是8421 BCD编码,也就是用0000~1001分别代表它所对应的十进制数。

.w0:                                    mov al,0x0a                        ;访问寄存器Aor al,0x80                         ;阻断NMIout 0x70,alin al,0x71                         ;读寄存器Atest al,0x80                       ;测试第7位UIP jnz .w0                            ;以上代码对于更新周期结束中断来说是不必要的 

test al,0x80 ,这句是测试寄存器A的bit7

正如书上155页所说:

CMOS RAM中的时间和日期会由RTC周期性地更新,在此期间,用户程序不应当访问它们。

寄存器A的bit7为0时,表示更新周期至少在488us内不会启动。换句话说,此时访问时间信息是安全的。

寄存器A的bit7为1时,表示正处于更新周期或者马上就要启动。

可以看到,上面的代码就是反复测试寄存器A的bit7,如果是0,可以向下执行。

xor al,alor al,0x80out 0x70,alin al,0x71                         ;读RTC当前时间(秒)push ax

这段代码很好理解,就是读出秒,并且把结果压栈(压栈时为了之后显示在屏幕上)

mov al,2or al,0x80out 0x70,alin al,0x71                         ;读RTC当前时间(分)push axmov al,4or al,0x80out 0x70,alin al,0x71                         ;读RTC当前时间(时)push ax

道理同上。

mov al,0x0c                        ;寄存器C的索引。且开放NMI out 0x70,alin al,0x71                         ;读一下RTC的寄存器C,否则只发生一次中断;此处不考虑闹钟和周期性中断的情况

这里要说一下寄存器C,这个寄存器是只读寄存器。可以通过读取这个寄存器,知道中断是否发生,如果发生,还可以知道中断原因。

寄存器C是8位寄存器。

[3:0]:保留;

[7]:中断请求标志,周期性中断/闹钟中断/更新结束中断,任何一种发生都会使这位置1;

[6]:周期性中断标志,置1则表示发生了周期性中断

[5]:闹钟中断标志,置1则表示发生了闹钟中断

[4]:更新结束中断标志,置1则表示发生了更新结束中断

注意,对寄存器的读操作将导致[7:4]清零。在中断发生后,我们应该读取这个寄存器,将其清零,否则同样的中断不再产生。

(2)把BCD码转换为ascii码

前面的代码中,把时分秒都读取出来并且压栈了。下一步的工作就是出栈,在屏幕上显示。前文已经说过,CMOS RAM中保存的日期和时间,默认是8421 BCD编码,所以我们可以利用一个过程,把BCD编码转换成与其对应的ascii码。

bcd_to_ascii:                            ;BCD码转ASCII;输入:AL=bcd码;输出:AX=asciimov ah,al                          ;分拆成两个数字 and al,0x0f                        ;仅保留低4位 add al,0x30                        ;转换成ASCII shr ah,4                           ;逻辑右移4位 and ah,0x0f                        add ah,0x30ret

举个例子来说吧,比如前面我们读取了小时到AL中,比如是12时,那么al=00010010b;前文我们压栈是把AX压进去,也就是说AX的低8位(AL)是有用的。现在我们需要调用这个过程,把00010010b转换成0x3132(因为字符‘1’对应的ASCII码是0x31,字符‘2’对应的ASCII码是0x32)。

mov ah,al ;分拆成两个数字
and al,0x0f ;仅保留低4位(就是个位)
add al,0x30 ;把个位转换成ASCII

shr ah,4 ;逻辑右移4位 ,ah中是十位数字
and ah,0x0f
add ah,0x30 ;把十位转换成ASCII

OK,这样之后,AX的高八位就是十位的ASCII,低八位就是个位的ASCII;

(3)把时间信息显示在屏幕上

mov ax,0xb800mov es,axpop axcall bcd_to_asciimov bx,12*160 + 36*2               ;从屏幕上的12行36列开始显示mov [es:bx],ahmov [es:bx+2],al                   ;显示两位小时数字mov al,':'mov [es:bx+4],al                   ;显示分隔符':'not byte [es:bx+5]                 ;反转显示属性

前两句让es指向了显示缓冲区;

pop ax ;小时出栈
call bcd_to_ascii ;转为ASCII码
mov bx,12*160 + 36*2 ;从屏幕上的12行36列开始显示

mov [es:bx],ah ;显示小时的十位
mov [es:bx+2],al ;显示小时的个位

mov al,':'
mov [es:bx+4],al ;显示分隔符':'
not byte [es:bx+5] ;反转显示属性

其实前两句可以写成

mov [es:bx+4],':’ ;显示分隔符':'

not是按位取反指令,假如之前属性是0x07(黑底白字),那么Not之后就是0xf8(闪烁白底灰色字)。

pop axcall bcd_to_asciimov [es:bx+6],ahmov [es:bx+8],al                   ;显示两位分钟数字mov al,':'mov [es:bx+10],al                  ;显示分隔符':'not byte [es:bx+11]                ;反转显示属性pop axcall bcd_to_asciimov [es:bx+12],ahmov [es:bx+14],al                  ;显示两位小时数字

上面的代码同理。

mov al,0x20                        ;中断结束命令EOI out 0xa0,al                        ;向从片发送 out 0x20,al                        ;向主片发送

书上162页已经说明:在中断处理过程的结尾,我们要显式地向8259芯片写中断结束命令EOI(至于具体原因,可以参考361页,图17-17:8259A的初始化命令字)。如果外部中断是8259主片处理的,那么仅发送给主片即可,端口号是0x20;如果外部中断是由从片处理的,那么命令既要发给主片也要发给从片,端口号是0xa0. 中断结束命令的代码是0x20.

pop espop dxpop cxpop bxpop axiret

寄存器出栈,用iret命令返回。

4.主程序

(1)初始化

start:mov ax,[stack_segment]mov ss,axmov sp,ss_pointermov ax,[data_segment]mov ds,axmov bx,init_msg                    ;显示初始信息 call put_stringmov bx,inst_msg                    ;显示安装信息 call put_string

这就是程序的入口了。首先,设置栈段,栈段被安排在整个程序的末尾,保留了256字节。

SECTION stack align=16 vstart=0resb 256
ss_pointer:;===============================================================================
SECTION program_trail
program_end:

之后,设置好DS,令其指向数据段;然后显示一些信息。

(2)中断初始化和安装

书上158页说:在计算机启动期间,BIOS会初始化中断控制器,将主片的中断号设为从0x08开始,从片的从0x70开始。从上图可以看出来,实时时钟连到了从片的IR0,也就是说实时时钟的中断号是0x70.

mov al,0x70mov bl,4mul bl                             ;计算0x70号中断在IVT中的偏移mov bx,ax                          cli                                ;防止改动期间发生新的0x70号中断

前文已经说过:

中断向量在中断向量表中的位置=中断类型号×4

N*4的字单元存放偏移地址;

N*4+2的字单元存放段基址。

我们已经知道中断类型号是0x70了,下面要计算它在中断向量表中的位置(也就是计算0x70*4):用乘法指令, AX=AL*r8; 前四句执行后,BX中就是0x70号中断向量在向量表中的偏移。

cli这个指令用来清除IF位标志,相当于屏蔽外部中断。因为在修改中断向量表时,如果表项信息只修改了一部分,这时候发生0x70号中断,将会产生不可预料的问题。

push esmov ax,0x0000mov es,axmov word [es:bx],new_int_0x70      ;偏移地址。mov word [es:bx+2],cs              ;段地址pop es

将ES压栈(暂时保存),并使它指向中断向量表所在的段,把偏移地址设置为new_int_0x70 ,把段基地址设置为CS。最后恢复ES。

mov al,0x0b                        ;RTC寄存器Bor al,0x80                         ;阻断NMI out 0x70,almov al,0x12                        ;设置寄存器B,禁止周期性中断,开放更 out 0x71,al                        ;新结束后中断,BCD码,24小时制

上面的代码用来设置寄存器B;寄存器B与本实验相关的位有:

[7]: 0表示更新周期每秒都会发生;1表示中止当前的更新周期,此后也不再产生更新周期;

[6]: 0表示禁止周期性中断,1表示允许周期性中断;

[5]: 0表示闹钟中断禁止,1表示闹钟中断允许;

[4]: 0表示禁止更新结束中断,1表示允许更新结束中断;

[3]:该位空着不用;

[2]:数据模式,0表示BCD,1表示2进制;

[1]: 小时格式,0表示12小时制(bit7为0时表示AM,为1表示PM,举例:在BCD模式下,10010001b表示上午11点),1表示24小时制;

[0]:该位空着不用;

从代码可以看出,我们写入寄存器B的值是0x12,也就是:

[7]:0,允许更新周期发生;

[6]:0,禁止周期性中断;

[5]:0,禁止闹钟中断;

[4]:1,允许更新结束中断;

[3]:0

[2]:0,BCD模式

[1]:1,24小时制

[0]:0

mov al,0x0cout 0x70,alin al,0x71                         ;读RTC寄存器C,复位未决的中断状态

读寄存器C, 使之开始产生中断信号。注意,在向端口0x70写入al的同时,也打开了NMI,因为这是最后一次在主程序中访问RTC。到此,RTC芯片设置完毕。

in al,0xa1                         ;读8259从片的IMR寄存器 and al,0xfe                        ;清除bit 0(此位连接RTC)out 0xa1,al                        ;写回此寄存器 sti                                ;重新开放中断

8259A内部有一个中断屏蔽寄存器,如下图所示:

IMR是一个8位的寄存器,位0-7对应着引脚中断IR0-IR7;如果对应的位为0,则允许中断;为1,则屏蔽中断。

我们通过端口0xa1读取从片的IMR寄存器,用and指令清除bit0(其他位保持原样),然后再写回去。这样,关于中断的初始化就完成了。

最后,sti指令将IF置1,打开中断。从这时候开始,随时发生的中断就可以被处理了。

mov bx,done_msg                    ;显示安装完成信息 call put_stringmov bx,tips_msg                    ;显示提示信息call put_string

显示一些信息,表示中断设置和安装已完成。

mov cx,0xb800mov ds,cxmov byte [12*160 + 33*2],'@'       ;屏幕第12行,33列

在屏幕12行33列显示一个“@”;

.idle:hlt                                ;使CPU进入低功耗状态,直到用中断唤醒not byte [12*160 + 33*2+1]         ;反转显示属性 jmp .idle

hlt是停机指令,使程序停止运行。这时候处理器进入暂停状态,不执行任何操作。当复位线上有复位信号、CPU响应非屏蔽中断、CPU响应可屏蔽中断3种情况之一发生时,CPU就会脱离暂停状态,执行hlt的下一条指令。

代码分析就到这里吧,下次我们看一下运行结果。

8086实时时钟实验(一)——《x86汇编语言:从实模式到保护模式》05相关推荐

  1. 8086实时时钟实验(二)——《x86汇编语言:从实模式到保护模式》读书笔记06

    上次我们说了代码,这次我们说说怎样看到实验结果. 首先编译源文件(我的源文件就在当前路径下,a盘和c盘在上一级目录下): nasm -f bin c08_mbr.asm -o c08_mbr.bin ...

  2. 8086键盘输入实验——《x86汇编语言:从实模式到保护模式》读书笔记07

    1.BIOS中断 我们可以为所有中断类型自定义中断处理过程,包括内部中断.硬件中断和软中断. BIOS中断,又称BIOS功能调用,主要是为了方便地使用最基本的硬件访问功能.通常,为了区分针对同一硬件的 ...

  3. 25 linux ndk 头文件_正点原子Linux第二十五章RTC实时时钟实验

    1)资料下载:点击资料即可下载 2)对正点原子Linux感兴趣的同学可以加群讨论:935446741 3)关注正点原子公众号,获取最新资料更新 第二十五章RTC实时时钟实验 实时时钟是很常用的一个外设 ...

  4. 【正点原子STM32连载】 第二十七章 RTC实时时钟实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

  5. (实验15)单片机,STM32F4学习笔记,代码讲解【RTC实时时钟实验】【正点原子】【原创】

    文章目录 其它文章链接,独家吐血整理 实验现象 主程序 RTC初始化程序 代码讲解 其它文章链接,独家吐血整理 (实验3)单片机,STM32F4学习笔记,代码讲解[按键输入实验][正点原子][原创] ...

  6. STM32学习笔记(十九)RTC实时时钟实验

    STM32F103ZET6之RTC实时时钟实验 文章目录 STM32F103ZET6之RTC实时时钟实验 前言 一.简介 二.相关寄存器及配置过程 三.程序源码 1.rtc.h 2.rtc.c 3.m ...

  7. x86汇编语言从实模式百度云_x86汇编语言:从实模式到保护模式

    x86汇编语言:从实模式到保护模式2013年1月由电子工业出版社出版发行,总共6000行的源代码,全方位地向读者展现汇编语言程序设计之美.尽管汇编语言也是一种计算机语言,但却是与众不同的,与它的同类们 ...

  8. 16位模式/32位模式下PUSH指令探究——《x86汇编语言:从实模式到保护模式》读书笔记16...

    一.Intel 32 位处理器的工作模式 如上图所示,Intel 32 位处理器有3种工作模式. (1)实模式:工作方式相当于一个8086 (2)保护模式:提供支持多任务环境的工作方式,建立保护机制 ...

  9. 16位模式/32位模式下PUSH指令探究——《x86汇编语言:从实模式到保护模式》读书笔记16

    一.Intel 32 位处理器的工作模式 如上图所示,Intel 32 位处理器有3种工作模式. (1)实模式:工作方式相当于一个8086 (2)保护模式:提供支持多任务环境的工作方式,建立保护机制 ...

最新文章

  1. 如何让机器说话更像人?清华和京东的三位大牛给出答案 | CCF C³
  2. 强化学习(二)马尔科夫决策过程(MDP)
  3. 2017-5-4 进程
  4. Python异常:TypeError: a bytes-like object is required, not 'str'
  5. mysql 异常笔记
  6. Ubuntu16.04.1安装Caffe(GPU)
  7. linux下mysql 8.0忘记密码后重置密码
  8. 数据链路层的介质访问控制协议
  9. CentOS 6.7 配置JSP运行环境之resin
  10. 泰然的粒子编辑器~~拿过来玩玩啊
  11. 【转】Java工程师成神之路
  12. ps42k20服务器出现问题_天高客户端访问软件出现“服务器操作系统原因”问题解决方法...
  13. python对excel读写操作
  14. QT on Android的rtsp播放器demo
  15. thrift开源项目研究
  16. elasticsearch 版本区别
  17. 树莓派3B安装64位操作系统(树莓派无需连接显示器键盘鼠标)
  18. 我的世界中国版服务器账号封了,Hypixel中国版服务器停止运营公告
  19. Android中的传感器之---磁场传感器
  20. 原创 基于微信小程序毕业设计题目选题课题 羽毛球篮球足球乒乓球场地球馆预约小程序的设计与实现(3)我的预约列表

热门文章

  1. hdu 2159 FATE 二维背包
  2. 在springboot中使用h2数据库
  3. [leetcode] 62 Unique Paths (Medium)
  4. 洛谷P2158仪仗队(数学,观察找规律,欧拉函数)
  5. 怎样在黑窗口中查找各种端口
  6. 优化应用不可不知道的知识
  7. ubuntu16安装anaconda显示没有文件或那个目录
  8. Python学习笔记:字符串和编码
  9. 二十万字C/C++、嵌入式软开面试题全集宝典九
  10. dmg文件 linux,安装和使用Dmg2Img在Linux上创建macOS安装盘