哈工大操作系统实验:动手修改操作系统内核,自定义开机界面
注意:其中一部分英文注释是我的塑料英文,主要是为了和源码一起看起来更和谐一点,很容易懂
实验做完了,才发现实验后网页有老师给的参考答案,哈哈反正我也不看,得记录一下,不记录就亏了。一天多时间都花在汇编上了
0 先上节目效果
1 修改屏幕上的开机Logo,设计一个自己的提示信息
①修改Linux源码0.11中bootsect.s
末尾的msg1
标号处的信息
msg1:.byte 13,10.ascii "DongZhaoChengOS is loading..." .byte 13,10,13,10
②修改Linux源码0.11中bootsect.s
98行附近的汇编代码
mov cx,#35mov bx,#0x0007 ; page 0, attribute 7 (normal)mov bp,#msg1mov ax,#0x1301 ; write string, move cursorint 0x10
注意:
cx
的值应等于msg1
的长度,如上的cx = 35
解析:
调用了BIOS int 0x10
中断,功能号:0x13
,该中断在功能号为0x13
时的调用规则可参考连接
在本例中
0x10是BIOS 中断号
ax=0x1301 也即 ah=0x13 al=0x01
ah=0x13 表示显示字符串的功能,ah用于指定功能号
al=0x01 表示设定的显示模式
bx=0x0007 也即 bh=0x00 bl=0x07
bh=0x00 表示页号
bl=0x07 表示属性
根据源码的上下文,此时es=0x9000
bp=#msg1 表示msg1在段中的偏移地址 es:bp表示msg1的寻址地址
cx表示要显示的字符数量
2 从bootsect.s
载入setup.s
程序时,屏幕输出一行"Now we are in SETUP…"信息,表示进入了setup
部分
①修改Linux源码0.11中setup.s
末尾,仿照bootsect.s
,添加一个msg1
; HIT's OS experment 1.2: To disp a sentence is 'Now we are in SETUP...' coding by dzc
msg1:.byte 13,10 ; 回车、换行的ascii字符值.ascii "Now we are in SETUP...".byte 13,10,13,10.ascii "Memory Size(Get by dzc): "
注意:
上边的第5行是我为了在第3个要求准备的,也可以不加
②在setup.s程序的开头调用BIOS int 0x10
中断以输出我们的字符msg1
; HIT's OS experment 1.2: To disp a sentence is 'Now we are in SETUP...' coding by dzc mov ax,#SETUPSEGmov es,ax ; set segment offset to SETUPSEGmov ah,#0x03 ; ah is used to assign INT 0x10's func code; so we set ah = 0x03 to get cursor posxor bh,bh ; now bh is input para : page numberint 0x10 ; call INT 0x10mov cx,#53 ; cx is characters' lengthmov bx,#0x0007 ; page 0, attribute 7 (normal)mov bp,#msg1 ; es:bp is characters' addr, here es=#SETUPSEG=0x9020 bp= msg1 is offsetmov ax,#0x1301 ; ah = 13 al = 01, 13 means disp something,01 means one of disp mode.int 0x10 ; call INT 0x10
注意:
这里模仿了bootsect.s
中输出字符(汇编都是好几年前学的了,临时拿出来的,haha)
解析:
先调用了BIOS int 0x10
中断,功能号:0x03
,用于获取屏幕中的光标位置,之后的代码和bootsect.s
相似
3 获取一个基本的硬件参数—如扩展内存的大小,并将其输出在屏幕上
①调用BIOS int 0x15
中断,功能号:0x88
获取扩展内存大小
; HIT's OS experment 1.3: To disp the extend memory size ,coding by dzcmov ah,#0x88int 0x15 ; get extend mem size in ax
注意:
这里的int 0x15
中断,功能号:0x88
、会将扩展内存的大小存放在ax
通用寄存器中,具体请点击INT 0x15中断获取扩展内存大小
②利用用堆栈将ax
中的16位二进制转换成10进制的ASCII码输出
; HIT's OS experment 1.3: To disp the extend memory size ,coding by dzcpush di ; 因为后续代码中要暂时使用di作为计数器,但为了不破坏di中的原始数据,所以先将di压入堆栈mov di,#0 ; 清零dimov bx,#10 ; bx作为除数 10 PUSHTOSTACK:mov dx,#0 ; 清零dxdiv bx ; dx:ax(32bit被除数) ÷ bx(16位除数) = ax(ax中存商).....dx(dx中存余数) push dx ; 将余数压入堆栈inc di ; di记录压栈次数 di=di+1cmp ax,#0 jne PUSHTOSTACK ; 这两句比较ax(商)是否为0; 若ax不等于0,则循环,jump到PUSHTOSTACK; 若ax等于0,顺序向下执行; ax is zero,so we can go down code!!!
POPSTACK:mov ah,#0x03 xor bh,bhint 0x10 ; 获取光标位置,注意该中断会更新cx的值,这也是为什么没有使用cx作为计数器的原因pop dx ; 从堆栈中依次弹出值add dl,#0x30 ; dl也即dx的底8位存放了二进制值,再加上30H,得到该值的ASCII码; 例如.1(d) = 0000 0001(b) = 0x01 其ASCII码等于0x31 = 0x01 + 0x30mov al,dl mov ah,#0x0E int 0x10 ; 调用INT 0x10中断,功能号0x0E将该字符显示在光标位置dec di ; di = di - 1,需要弹出堆栈的次数减1cmp di,#0 jne POPSTACK ; 判断di是否为0,若di不为0,jump到POPSTACK继续循环; 若di为0,顺序执行下面的代码; From here, start print msg2 'KB.'etcmov ah,#0x03 xor bh,bh int 0x10 ; 获得光标位置mov cx,#7 mov bx,#0x0007 mov bp,#msg2 mov ax,#0x1301 int 0x10 ; 打印msg2 'KB.'pop di ; 弹出堆栈中di,恢复di的值
注意:
为什么用di
作为计数器?
di
是我随便选了一个通用寄存器,主要是因为cx
在循环过程中会被刷新,本来我用的是cx
,发现一直出错,最后找出原因是cx被刷新了;当然选其他的通用寄存器也问题不大。BIOS
int 0x10
中断,功能号0x0E
的用法功能:
在屏幕上显示字符并且将光标向前移动
参数:
AL 待显示字符、BL前景色
因此,每次调用前要先将要显示的字符的ASCII码存到AL中
4 setup.s 不再向下执行,将上述的信息显示在屏幕上停下即可
这个是最简单的了,直接在上述代码之后整个死循环就行了
INF_LOOP:jmp INF_LOOP ; infinite loop
5 修改完代码后,对Linux 0.11内核进行编译,然后在Boshs中运行该小系统,效果如下
6 最后附上 bootsect.s 和 setup.s的代码
bootsect.s如下
;
; SYS_SIZE is the number of clicks (16 bytes) to be loaded.
; 0x3000 is 0x30000 bytes = 196kB, more than enough for current
; versions of linux
;
SYSSIZE = 0x3000
;
; bootsect.s (C) 1991 Linus Torvalds
;
; bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves
; iself out of the way to address 0x90000, and jumps there.
;
; It then loads 'setup' directly after itself (0x90200), and the system
; at 0x10000, using BIOS interrupts.
;
; NOTE; currently system is at most 8*65536 bytes long. This should be no
; problem, even in the future. I want to keep it simple. This 512 kB
; kernel size should be enough, especially as this doesn't contain the
; buffer cache as in minix
;
; The loader has been made as simple as possible, and continuos
; read errors will result in a unbreakable loop. Reboot by hand. It
; loads pretty fast by getting whole sectors at a time whenever possible..globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.textSETUPLEN = 4 ; nr of setup-sectors
BOOTSEG = 0x07c0 ; original address of boot-sector
INITSEG = 0x9000 ; we move boot here - out of the way
SETUPSEG = 0x9020 ; setup starts here
SYSSEG = 0x1000 ; system loaded at 0x10000 (65536).
ENDSEG = SYSSEG + SYSSIZE ; where to stop loading; ROOT_DEV: 0x000 - same type of floppy as boot.
; 0x301 - first partition on first drive etc
ROOT_DEV = 0x306entry start
start:mov ax,#BOOTSEGmov ds,axmov ax,#INITSEGmov es,axmov cx,#256sub si,sisub di,direpmovwjmpi go,INITSEG
go: mov ax,csmov ds,axmov es,ax
; put stack at 0x9ff00.mov ss,axmov sp,#0xFF00 ; arbitrary value >>512; load the setup-sectors directly after the bootblock.
; Note that 'es' is already set up.load_setup:mov dx,#0x0000 ; drive 0, head 0mov cx,#0x0002 ; sector 2, track 0mov bx,#0x0200 ; address = 512, in INITSEG mov ax,#0x0200+SETUPLEN ; service 2, nr of sectorsint 0x13 ; read itjnc ok_load_setup ; ok - continuemov dx,#0x0000mov ax,#0x0000 ; reset the disketteint 0x13j load_setupok_load_setup:; Get disk drive parameters, specifically nr of sectors/trackmov dl,#0x00mov ax,#0x0800 ; AH=8 is get drive parametersint 0x13mov ch,#0x00seg csmov sectors,cxmov ax,#INITSEGmov es,ax; Print some inane messagemov ah,#0x03 ; read cursor posxor bh,bh ; bh = 0(dzc)int 0x10
; HIT's OS experience 1.1: To disp my custom logo like 'LOS loading...'etc. by dzcmov cx,#35mov bx,#0x0007 ; page 0, attribute 7 (normal)mov bp,#msg1mov ax,#0x1301 ; write string, move cursorint 0x10; ok, we've written the message, now
; we want to load the system (at 0x10000)mov ax,#SYSSEGmov es,ax ; segment of 0x010000call read_itcall kill_motor; After that we check which root-device to use. If the device is
; defined (!= 0), nothing is done and the given device is used.
; Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending
; on the number of sectors that the BIOS reports currently.seg csmov ax,root_devcmp ax,#0jne root_definedseg csmov bx,sectorsmov ax,#0x0208 ; /dev/ps0 - 1.2Mbcmp bx,#15je root_definedmov ax,#0x021c ; /dev/PS0 - 1.44Mbcmp bx,#18je root_defined
undef_root:jmp undef_root
root_defined:seg csmov root_dev,ax; after that (everyting loaded), we jump to
; the setup-routine loaded directly after
; the bootblock:jmpi 0,SETUPSEG; This routine loads the system at address 0x10000, making sure
; no 64kB boundaries are crossed. We try to load it as fast as
; possible, loading whole tracks whenever we can.
;
; in: es - starting address segment (normally 0x1000)
;
sread: .word 1+SETUPLEN ; sectors read of current track
head: .word 0 ; current head
track: .word 0 ; current trackread_it:mov ax,estest ax,#0x0fff
die: jne die ; es must be at 64kB boundaryxor bx,bx ; bx is starting address within segment
rp_read:mov ax,escmp ax,#ENDSEG ; have we loaded all yet?jb ok1_readret
ok1_read:seg csmov ax,sectorssub ax,sreadmov cx,axshl cx,#9add cx,bxjnc ok2_readje ok2_readxor ax,axsub ax,bxshr ax,#9
ok2_read:call read_trackmov cx,axadd ax,sreadseg cscmp ax,sectorsjne ok3_readmov ax,#1sub ax,headjne ok4_readinc track
ok4_read:mov head,axxor ax,ax
ok3_read:mov sread,axshl cx,#9add bx,cxjnc rp_readmov ax,esadd ax,#0x1000mov es,axxor bx,bxjmp rp_readread_track:push axpush bxpush cxpush dxmov dx,trackmov cx,sreadinc cxmov ch,dlmov dx,headmov dh,dlmov dl,#0and dx,#0x0100mov ah,#2int 0x13jc bad_rtpop dxpop cxpop bxpop axret
bad_rt: mov ax,#0mov dx,#0int 0x13pop dxpop cxpop bxpop axjmp read_track/** This procedure turns off the floppy drive motor, so* that we enter the kernel in a known state, and* don't have to worry about it later.*/
kill_motor:push dxmov dx,#0x3f2mov al,#0outbpop dxretsectors:.word 0msg1:.byte 13,10.ascii "DongZhaoChengOS is loading...".byte 13,10,13,10.org 508
root_dev:.word ROOT_DEV
boot_flag:.word 0xAA55.text
endtext:
.data
enddata:
.bss
endbss:
setup.s如下
;
; setup.s (C) 1991 Linus Torvalds
;
; setup.s is responsible for getting the system data from the BIOS,
; and putting them into the appropriate places in system memory.
; both setup.s and system has been loaded by the bootblock.
;
; This code asks the bios for memory/disk/other parameters, and
; puts them in a "safe" place: 0x90000-0x901FF, ie where the
; boot-block used to be. It is then up to the protected mode
; system to read them from there before the area is overwritten
; for buffer-blocks.
;; NOTE; These had better be the same as in bootsect.s;INITSEG = 0x9000 ; we move boot here - out of the way
SYSSEG = 0x1000 ; system loaded at 0x10000 (65536).
SETUPSEG = 0x9020 ; this is the current segment.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.textentry start
start:
;----------------------------------------------------------
; HIT's OS experment 1.2: To disp a sentence is 'Now we are in SETUP...' coding by dzc mov ax,#SETUPSEGmov es,ax ; set segment offset to SETUPSEGmov ah,#0x03 ; ah is used to assign INT 0x10's func code; so we set ah = 0x03 to get cursor posxor bh,bh ; now bh is input para : page numberint 0x10 ; call INT 0x10mov cx,#53 ; cx is characters' lengthmov bx,#0x0007 ; page 0, attribute 7 (normal)mov bp,#msg1 ; es:bp is characters' addr, here es=#SETUPSEG=0x9020 bp= msg1 is offsetmov ax,#0x1301 ; ah = 13 al = 01, 13 means disp something,01 means one of disp mode.int 0x10 ; call INT 0x10
;-----------------------------------------------------------;------------Put this code at top ,it is okey!--------------
; HIT's OS experment 1.3: To disp the extend memory size ,coding by dzcmov ah,#0x88int 0x15 ; get extend mem size in axpush dimov di,#0 ; di is used to count timesmov bx,#10 ; bl is divisor and 8 bitPUSHTOSTACK:mov dx,#0div bx ; ax is quotient dx is divisor push dx inc dicmp ax,#0 jne PUSHTOSTACK; ax is zero,so we can go down code!!!
POPSTACK:mov ah,#0x03 xor bh,bh ; Get curcor pos,Func code 0x03 will change cx; So I use di as counter instead of cxint 0x10pop dx add dl,#0x30 ; result in dl that is ASCII code of nummov al,dlmov ah,#0x0E ; disp a char to screenint 0x10 ; CALL INT 0x10dec di ; di = di - 1cmp di,#0 jne POPSTACK ; From here, start print msg2 'KB.'etcmov ah,#0x03 xor bh,bh int 0x10 mov cx,#7 mov bx,#0x0007 mov bp,#msg2 mov ax,#0x1301 int 0x10 pop di
; HIT's OS experment 1.3: To disp the extend memory size ,coding by dzc
INF_LOOP:jmp INF_LOOP ; infinite loop
; ---------------------------------------------------------------; ok, the read went well so we get current cursor position and save it for
; posterity.mov ax,#INITSEG ; this is done in bootsect already, but...mov ds,ax ; #INITSEG IS 0x9000mov ah,#0x03 ; read cursor pos int 0x10 功能号:读光标xor bh,bhint 0x10 ; save it in known place, con_init fetchesmov [0],dx ; it from 0x90000.; Get memory size (extended mem, kB)mov ah,#0x88int 0x15mov [2],ax ; now ax stores the size of extend memory in HEX; Get video-card data:mov ah,#0x0fint 0x10mov [4],bx ; bh = display pagemov [6],ax ; al = video mode, ah = window width; check for EGA/VGA and some config parametersmov ah,#0x12mov bl,#0x10int 0x10mov [8],axmov [10],bxmov [12],cx; Get hd0 datamov ax,#0x0000mov ds,axlds si,[4*0x41]mov ax,#INITSEGmov es,axmov di,#0x0080mov cx,#0x10repmovsb; Get hd1 datamov ax,#0x0000mov ds,axlds si,[4*0x46]mov ax,#INITSEGmov es,axmov di,#0x0090mov cx,#0x10repmovsb; Check that there IS a hd1 :-)mov ax,#0x01500mov dl,#0x81int 0x13jc no_disk1cmp ah,#3je is_disk1
no_disk1:mov ax,#INITSEGmov es,axmov di,#0x0090mov cx,#0x10mov ax,#0x00repstosb
is_disk1:; now we want to move to protected mode ...cli ; no interrupts allowed ;; first we move the system to it's rightful placemov ax,#0x0000cld ; 'direction'=0, movs moves forward
do_move:mov es,ax ; destination segmentadd ax,#0x1000cmp ax,#0x9000jz end_movemov ds,ax ; source segmentsub di,disub si,simov cx,#0x8000repmovswjmp do_move; then we load the segment descriptorsend_move:mov ax,#SETUPSEG ; right, forgot this at first. didn't work :-)mov ds,axlidt idt_48 ; load idt with 0,0lgdt gdt_48 ; load gdt with whatever appropriate; that was painless, now we enable A20call empty_8042mov al,#0xD1 ; command writeout #0x64,alcall empty_8042mov al,#0xDF ; A20 onout #0x60,alcall empty_8042; well, that went ok, I hope. Now we have to reprogram the interrupts :-(
; we put them right after the intel-reserved hardware interrupts, at
; int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
; messed this up with the original PC, and they haven't been able to
; rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
; which is used for the internal hardware interrupts as well. We just
; have to reprogram the 8259's, and it isn't fun.mov al,#0x11 ; initialization sequenceout #0x20,al ; send it to 8259A-1.word 0x00eb,0x00eb ; jmp $+2, jmp $+2out #0xA0,al ; and to 8259A-2.word 0x00eb,0x00ebmov al,#0x20 ; start of hardware int's (0x20)out #0x21,al.word 0x00eb,0x00ebmov al,#0x28 ; start of hardware int's 2 (0x28)out #0xA1,al.word 0x00eb,0x00ebmov al,#0x04 ; 8259-1 is masterout #0x21,al.word 0x00eb,0x00ebmov al,#0x02 ; 8259-2 is slaveout #0xA1,al.word 0x00eb,0x00ebmov al,#0x01 ; 8086 mode for bothout #0x21,al.word 0x00eb,0x00ebout #0xA1,al.word 0x00eb,0x00ebmov al,#0xFF ; mask off all interrupts for nowout #0x21,al.word 0x00eb,0x00ebout #0xA1,al; well, that certainly wasn't fun :-(. Hopefully it works, and we don't
; need no steenking BIOS anyway (except for the initial loading :-).
; The BIOS-routine wants lots of unnecessary data, and it's less
; "interesting" anyway. This is how REAL programmers do it.
;
; Well, now's the time to actually move into protected mode. To make
; things as simple as possible, we do no register set-up or anything,
; we let the gnu-compiled 32-bit programs do that. We just jump to
; absolute address 0x00000, in 32-bit protected mode.mov ax,#0x0001 ; protected mode (PE) bitlmsw ax ; This is it;jmpi 0,8 ; jmp offset 0 of segment 8 (cs); This routine checks that the keyboard command queue is empty
; No timeout is used - if this hangs there is something wrong with
; the machine, and we probably couldn't proceed anyway.
empty_8042:.word 0x00eb,0x00ebin al,#0x64 ; 8042 status porttest al,#2 ; is input buffer full?jnz empty_8042 ; yes - loopretgdt:.word 0,0,0,0 ; dummy.word 0x07FF ; 8Mb - limit=2047 (2048*4096=8Mb).word 0x0000 ; base address=0.word 0x9A00 ; code read/exec.word 0x00C0 ; granularity=4096, 386.word 0x07FF ; 8Mb - limit=2047 (2048*4096=8Mb).word 0x0000 ; base address=0.word 0x9200 ; data read/write.word 0x00C0 ; granularity=4096, 386idt_48:.word 0 ; idt limit=0.word 0,0 ; idt base=0Lgdt_48:.word 0x800 ; gdt limit=2048, 256 GDT entries.word 512+gdt,0x9 ; gdt base = 0X9xxxxmsg1:.byte 13,10.ascii "Now we are in SETUP...".byte 13,10,13,10.ascii "Memory Size(Get by dzc): "msg2:.ascii "KB.".byte 13,10,13,10.text
endtext:
.data
enddata:
.bss
endbss:
哈工大操作系统实验:动手修改操作系统内核,自定义开机界面相关推荐
- 哈工大操作系统实验——实现proc文件系统
哈工大操作系统实验--实现proc文件系统 参考文章: 操作系统实验08-proc文件系统的实现 在 Linux 0.11 上实现 procfs(proc 文件系统)内的 psinfo 结点.当读取此 ...
- 哈工大操作系统实验一——操作系统的引导
写在前面 哈尔滨工业大学李治军老师的<操作系统>课程实验,相关资源: 哈工大操作系统实验手册 实验资源与参考 不配环境懒人福利:实验楼 在线课程:操作系统,李治军,哈工大(网易云课堂) 参 ...
- 哈工大操作系统实验1-操作系统引导
哈工大操作系统实验1-操作系统引导 实验内容: 1. 改写 bootsect.s 主要完成如下功能: bootsect.s 能在屏幕上打印一段提示信息"XXX is booting...&q ...
- linux系统编程界面实验报告,操作系统实验报告-Linux操作使用编程.doc
操作系统实验报告-Linux操作使用编程 实 验 报 告( 2012/ 2013 学年 第二学期) 课程名称操 作 系 统A实验名称Linux操作.使用.编程实验时间2013年 5 月 6日指导单位计 ...
- 【转载】笔记:计算机_体系结构_操作系统_软件_操作系统内核_GNU_Linux_C_Python_Latex_Java_TCP/IP_MacOS_Windows这些词语的历史,关系
一.计算机的发明 世上本无路,走的人多了,就有了路.世上本无计算机,琢磨的人多了--没有计算机,一切无从谈起. 三个人对计算机的发明功不可没,居功至伟.阿兰·图灵(Alan Mathison Turi ...
- linux操作系统 抢占式,Linux操作系统内核抢占补丁的基本原理(2)
Linux操作系统内核抢占补丁的基本原理(2) 2008-02-23 07:26:45来源:互联网 阅读 () int this_cpu, c; #ifdef CONFIG_PREEMPT ctx_s ...
- linux实验报告哈工大,哈工大操作系统实验---lab8:proc文件的实现
文章目录 实验目的 掌握虚拟文件系统的实现原理 实践文件.目录.文件系统等概念 实验内容 在Linux0.11上实现procfs(proc文件系统)内的psinfo节点,当读取此节点的内容的时候,可得 ...
- 哈工大操作系统实验总结
实验地址, https://www.lanqiao.cn/courses/115/learning/?id=374, 在现做实验, 好处是环境提前都配好了, 不足之处是敲代码有网络延迟, 环境无法保存 ...
- 哈工大操作系统实验4---基于内核栈切换的进程切换
前置知识 关于栈桢 关于栈栈帧详解https://blog.csdn.net/ylyuanlu/article/details/18947951 进程切换流程 首先先了解一下进程切换的流程.开始的时候 ...
最新文章
- C#调用TSC条码打印机打印二维码(转)
- mysql-connector-net-6.7.4.msi,在ActiveReports中使用MySQL数据库
- 7 兼容 因特尔十代_年终抄底十代酷睿 请务必看看它……- ——快科技(驱动之家旗下媒体)-...
- 博弈论的局限性(博弈论的诡计)
- CNN中各类卷积总结:残差、shuffle、空洞卷积、变形卷积核、可分离卷积等
- rocketmq整合mysql事务_分布式事务(4)---RocketMQ实现分布式事务项目
- 代理服务器基本知识普及代理IP使用方法!
- 腾讯QQ表情生意经:建开放平台 与原创者最高六四分成
- Dual display on msm8937
- 用Python模拟高尔顿钉板实验
- 使用Google搜索引擎的10个搜索技巧
- K线形态分析交易系统
- 计算机双硬盘安装需要跳线吗,双硬盘安装的操作流程【详细步骤】
- 计算机无法关闭密码保护,win7的密码保护共享关闭不了怎么办_解决win7的密码保护共享关闭不了的方法...
- 最新Handsome主题V6.0免授权版+Typecho内核
- Linux 命令--SS
- 美通企业日报 | 陶氏杜邦完成对新陶氏的分拆;英特尔1.17亿美元投资14家创新公司...
- 【线性代数 宋浩】P3行列式的性质
- Springboot日志级别
- 吐槽Win7 x64资源管理器