技术栈
待完善
底层
- 操作系统:了解内核、了进程和文件管理等重要的概念和实现方式
- 编译原理:粗略搞懂编译器的执行过程(汇编 √-机器码 ×)
- 计算机网络:彻底搞懂连网原理和 TCP/IP 的具体意义,打通网络黑箱
- JVM:这个底层其实还挺好奇的,目前处于黑箱状态
- Tomcat:热部署怎么使用的,大概了解原理
- 数据库高级:索引概念、视图概念、底层逻辑都不清楚其实。
应用类
-
Vue.js:其实很好奇怎么用 MVVM 模式来进一步优化界面层啊?!借此完善自己的第一个项目
-
Mybatis - Struct2 - SpringMVC - Spring
-
Go 和 一些数据库的中间件
-
多学点 vue 写小程序
-
SpringBoot 框架:如何实现前后端数据传输啊???
现在已知的是三层架构模式:
界面层
JSP(EL+JSTL) + JQuery + Ajax- SpringMVC
业务逻辑层
Web (Servlet+Filter)- Spring
数据访问层
(Druied 链接池+ JDBCUtils)DAO 模式- Mybatis
-
Javaweb 注解配置(及时补充)
随记
5/14/2020 解题的思考
在解题之前先思考题目考察的内容,不要盲目地做题。先想一想、写一下文档理清思路。
如这一道题目要求将指令复制到另一个地方,解题时首先就应该想到在哪个内存地址可以找到要复制的数据,在 8086 中 CS.dreg 指向的就是程序地址,而 di 中存放着 s 标号的地址,此时 CS:DI 就可以找到 S 标号对应的程序地址了,同理便可得目的地址。有了地址就可以考虑如何复制数据,很明显通过 mov 指令和寄存器就轻松完成了复制。
思考的关键就在于如何能快速地利用已经学过的知识来解析问题,用什么方法,需要找到什么数据(或者地址)
5/15/2020 合理计划学习任务
今天学习进度没有赶得很紧,大学课程的实验让我十分的烦恼。最近只想赶快把汇编看完然后去学 OS,自知学知识不能太赶,要一步一个脚印地来,但今天还是跳了很多个实验,寄希望于学完 OS 后再回头把实验写了,只看重理论很不好,打不下基础,我决定分批次完成实验的内容,明天把理论知识学完,然后分批完成汇编的实验和课程的实验(又把学习当任务了!!其实应该把学习时间拉长,每天只学一点但一定要完全领悟通透,只怪我脑子太笨,如今只能争取理论尽快地领悟,实验只能后补了。)唉,如果大学的课程能不那么赶而且还不要考试就好了。
有个妹妹自杀了,她的遗书写得十分平静,她所有牵挂的人和事都已经放下了,信中看不出她的绝望和痛苦,好像死亡对她来说只是一件很平常的事,她说她已经去了自己想要的很美好的地方, 突然间我开始相信我们都拥有灵魂了,相信她只是选择了另一种方式存在,愿超脱了肉体的你能永远徜徉在极乐天堂 R.I,P
时间线
五月
12 - JSON & Ajax & Javaweb_Filter
-
Json 一种轻量化的通用数据传输格式,轻量化是与 XML 对比,JSON 更小、更快、更易解析。
有两种表示方法 JSON 对象 和 JSON 字符串 用 parse() stringify() 互相转化
采用键值对方式存放 可以放 number、String、数组、JSON 对象
语法是花括号 {} 加 key:values 用 逗号,分隔
在 Java 调用时可以选择导 GSON(谷歌) 或者 阿里(推荐阿里 没有太多依赖) 的 jar 包
-
Ajax :Asynchronous JavaScript and XML. (异步的 js 和 xml)是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
同步:在提交表单后必须等待服务器返回才可以继续处理数据(造成页面卡死待响应的现象,体验差)
异步:在提交表单后无须等待,服务器处理完成后会自动返回
Ajax 在 JQuery 中有封装完成的使用方法,JSP 页面中应用较多
-
Filter:JavaWeb 的三大组件之一 用于过滤请求 可以用于用户权限的管理
FilterChain 过滤器链(多个过滤器一起工作)如果通过 XML 配置,则先配置的先调用
FilterChain.doFilter() - 执行过滤操作 如果有下一个过滤器则进入 否则执行后置代码
FilterConfig 类 可以获取在 XML 文件中的配置信息
<init-param> <param-name></param-name> <param-value></param-value> </init-param>
13 - 汇编语言 && 操作系统
重温了汇编的寄存器部分,预计两天内看完
操作系统看了 Linux 0.11 内核的系统启动过程,bochs 真难用
14 - 汇编语言
loop 指令:两步操作 1.(cx)=(cx)-1 2.判断 cx 是否为零。在 debug 中用 g + [偏移地址]可以一次性执行所有 loop
Debug 将它解释为“[idata]” 是-一个内存单元,idata 是内存单元的偏移地址; 而编译器将“[idata]” 解释为 idata 。
在汇编源程序中,数据不能以字母开头,所以要在前面加 0 直接写内存的话应当使用 0:200~0:2ff 这段空间 dos 和其他合法程序通常不会使用这段空间。
程序中的指令就要对这 8 个数据进行累加,这 8 个数据在代码段中,程序在运行的时候 CS 中存放代码段的段地址,所以可以从 CS 中得到它们的段地址。它们的偏移地址是多少呢?因为用 dw 定义的数据处于代码段的最开始,所以偏移地址为 0,这 8 个数据就在代码段的偏移 0、2、4、6、8、A、C、E 处。程序运行时,它们的地址就是 CS:0、CS:2、 CS:4、CS:6、CS:8、CS:A、 CS:C、 CS:E。 … dw 段的数据的值是多少,对于程序来说没有意义。我们定义这些数据的最终目的是,通过它们获取一定容量的内存空间,所以我们在描述 dw 的作用时,可以说用它定义数据,也可以说用它开辟内存空间。
一般在需要暂存数据的时候,我们都应该使用栈
在 8086CPU 中只有 bx si di bp 这 4 个寄存器可以用于内存单元寻址,这 4 个寄存器可以单个出现,或者以 4 种组合出现: bx+si , bx+di , bp+si , bp+di (ds:bx ss:bp) CPU 提供的如[bx+si+idata]的寻址方式为结构化处理提供了方便,用 bx 定位整个结构体,idata 定位结构体中的某一个数据项,si 定位数组项中的每个元素。可以写成 bx.idata[si]。
15 - 汇编语言
jmp 指令经典例题:
flag 寄存器:
CPU 提供了 cmp 指令,也提供了 je 等条件转移指令,如果将它们配合使用,可以实 现根据比较结果进行转移的功能。但这只是“如果”,只是一种合理的建议,和事实上常 用的方法。但究竟是否配合使用它们,完全是你自己的事情。这就好像 call 和 ret 指令的 关系一样。
8086 在内存 0000:0000 到 0000:03FF 的 1024 个单元中存放着中断向量表
18 - 操作系统 - 系统调用
**内核态和用户态:**一种处理器 “硬件设计”, DPL 目标段的特权级、CPL 当前段的特权级 当前程序执行在什么态用 CS 的最低两位来表示:0 是内核态,3 是用户态 数字越小级别越高 计算机对 CPU 是一段一段地使用,故我们可以用段寄存器来判断特权级 而 GDT 表中的表项用来描述一段内存,在操作系统段中所有的 DPL 在 GDT 中都对应内核态 当前用户使用时,根据 PC(内存段)中的 CS 段我们可以知道当前用户的 CPL,故可以判断权限
GDT/LDT 表中的描述符,描述的是一段内存。其中的 DPL 代表着 GDT/LDT 中的描述符描述的内存段的特权级别。比如,当前正在执行的代码,它的特权级别就是当前的 CS 段选择子指向的段描述符中的 DPL 所决定的。即 DPL 是描述一段内存的特权级别。
系统调用的核心:
- 用户程序中包含一段有中断指令(必须是
INT 0x80
)和系统调用号(INT system_call
)的代码 - 操作系统写中断处理,获取调取程序的编号
- 操作系统根据编号执行相应的代码
中断处理-用户态转化成内核态最终调用内核方法
硬件提供了 INT 0x80 中断能让我们主动进入内核,最重要的就是 set_system_gate 设置中断陷阱门
set_system_gate 的参数为 INT 0x80
80 中断和INT system_call
表示要调用是方法,此时的 DPL 为 3;
通过转化后进入到内核态,设置 PC,其中 CS 为 8(100)故可得 CPL 为 0 内核态,而 IP 为 system_call
通过查 system_call_table 得到 _NR_XXX 最终调用 sys_XXX
调用的具体过程: 用户调用 XXX 方法 通过库函数展开成宏,调用中断 中断处理 system_call 查 sys_call_table 得到
19 - 操作系统 - 进程管理(CPU 部分)
CPU 使用的关键是:取值执行 设置完 PC 后便自动开始工作,但这样顺序执行有很多缺点:但程序需要调用 IO 设备或者磁盘时,会等待很久从而造成时间上的浪费,于是我们需要让 CPU 交替执行多个程序
一个 CPU 上交替执行多个程序称为:并发,在交替执行的时候为了完成切换需要保存寄存器等程序信息,从而引入进程(进行中的程序)的概念,CPU 在运行多个程序时跑多个进程,因此 CPU 运行的效率得到大大提升
多进程图像: CPU 用 PCB(Process Control Block 记录进程信息的数据结构) 进程带动内存的使用需要用映射表(MMU)进行内存封存 - 同一地址指向不同物理地址 - 为了共存;
多线程:- TCB 线程控制表 线程有自己的 TCB,thread control block, 只负责这条流程的信息,包括 PC 程序计数器,SP 堆栈,State 状态,和寄存器。有不同的控制流,需要不同的寄存器来表示控制流的执行状态,每个线程有独立的这些信息,但共享一个资源。
多核:线程-用户栈和内核栈-进程切换 每个线程对应一套栈,用户栈和内核栈 用户栈进入内核栈:系统调用(fork())会引起中断并且创建一个进程,有两个重点: 1.一个线程是怎么通过进程切换的 2.创建一个进程需要做哪些准备 线程切换五段论:中断入口,建立用户栈和内核栈的联系 进入内核态执行,执行完毕过程中或者执行完毕后,系统会判断事件有可能引发切换(中间三段) 中断返回 ret_from_sys_call
- 在 A 处调用 fork() 时会产生 INT 0x80 中断和 _NR_fork 同时 CPU 立刻把用户态的相关信息压入内核栈
- call sys_fork 根据 PCB 中的 state 判断进程状态是就绪还是阻塞,非 0 则阻塞,阻塞需要调度切换
reschedule{ jmp _schedule }
;然后需要判断进程时间片再一次reschedule
;最后返回中断。 _schedule{ switch_to(next) }
中具体实现调度和切换 :next=XXX 下一个进程 XXX(用调度算法找到下一个已经就绪的进程),switch_to(切换将 PC 跳到下一个进程 XXX 中执行)switch_to
通过 TSS(task state segment 任务段)进行段跳转切换,首先用TR 寄存器作为选择子在 GDT 表中找到 TSS 描述符,从而找到 TSS 的段地址,把 CPU 中的 EAX、EBX 等寄存器的相关信息保存在原 TSS 段中(相当于存档);同理找到下一个进程的 TSS 段地址赋给 TR,将该 TSS 中的信息拷贝到 CPU 中(相当于读档),完成跳转。此时下一个进程是我们创建的子进程- 中断返回
ret_from_sys_call
:先 popl 用户态中的信息 最后 iret
创建进程的任务就开始了 ThreadCreate:
-
要创建下一个进程中的 TSS 需要用到
sys_fork { call _copy_process }
此时内核栈中的信息全部作为 _copy_process 的参数从而创建出差不多完全一样的子进程 -
copy_process 细节:创建栈
p = (struct task_struct *)get_free_page();//申请内存空间 //创建内核栈 p->tss.esp0 = PAGE_SIZE + (long) p ; p->tss.ss0 = 0x10; //创建用户栈(和父进程共用的栈 ss和esp 是由父进程中用户栈中传入的) p->tss.ss = ss & 0xffff; p->tss.esp = esp;
-
具体过程是:switch_to(next=子进程) 创建子进程同时设置好 TSS 并且和 TCB 相连,上面是创建子进程的具体过程
执行我们想要的代码:
- 我们创建的子进程随着中断返回到 A 处,而此时不会和父进程一样的执行 fork() 而是 call sys_execve 从而执行我们所要执行的操作
- 要找到真正的 PC,我们需要在中断返回前将当前得到的 IP 压入到 用户栈中的 EIP 从而真正的赋值给 IP 寄存器,得到 PC 从而执行
- 获取当前 IP 要根据
ls -> entry (call do_entry )
获得 do_entry { eip[0] = ex. a_entry };
a_entry 代表的就是编译器在编译链接产生 ls.exe 时编译器提供的入口: unsigned a_entry,而 ls 文件实际存在于磁盘之中
20 - 操作系统 - 调度算法 - 进程合作
调度算法 - IO 约束型和 CPU 约束型
**IO 约束型:**通常优先级会比较高,提前调用 IO 可以让设备先处理,CPU 可以并发执行,等待设备处理完后 CPU 读数据就行 此时是 read 中断,对应的通常是前台进程(用户进程) **CPU 约束型:**用的 TimeOut 中断,对应的通常是后台进程
一个好的调度算法(Schedule()
)应该折中、会自适应、能自动分辨前后台进程、以 RR(轮转调度算法)为核心、有优先级。具体实现方式是:
- 首先找到就绪队列中优先级最大的一个进程(用 COUNT 时间片表示,越大优先级越高)
- 然后当该进程的时间片为 0 时,重新设置所有进程的 COUNT(右移一位+初始时间片长度),让处于拥塞状态的进程的优先级比当前进程优先级高,等待就绪后可以被 CPU 调用
- 动态调整 COUNT 同时也意味着此时调度会从 CPU 约束转成 IO 约束,而 IO 约束对应的是前台进程的特征,于是分辨出前后台进程。原理是拥塞状态下的进程大部分都是前台进程(需要调用 IO)在调整后优先级高,阻塞越久优先级越高,但COUNT 不会无限增长破坏 RR 算法
- 这和 COUNT 的重置算法有关:$c(n)=c(n-1)/2+P$ 其中 $ c(0)= P$ 于最终收敛于 2P 不会无限大符合时间片的规则
- 后台进程一直循环可以近似看出是 SJF 调度,符合 CPU 约束型
进程合作 - 多个进程共同完成一个任务 - 信号量
COUNT 信号已经无法简单处理了多个进程等待和资源生产复杂情况了,需要一个信号量来实现“走走停停”,信号量里有 value(资源的个数)和 PCB 阻塞队列(等待在该信号量上的进程),信号量中 P 操作是消费资源,V 操作是生产资源。
信号量解生产者-消费者问题: 生产者第一步 P(empty)需要判断资源是否满了,满了则不能再生产了,empty= 0 表示缓冲区满了,消费者的第一步 P(full)则是判断当前已经生产的个数,为零则无法消费,full 表示已经生产的个数;第二步两者都是上锁 P(mutex),然后分别执行读入写出,最后解锁 V(mutex);生产者第三步是 V(full) 代表生产了一些资源,而消费者是 V(empty) 消耗了一些资源
信号量需要临界区来保护,因为信号量是所以进程共享的。 临界区保护原则:
- 基本原则:互斥进入,进程间的互斥关系保证了是临界区
- 好的保护原则:有空让进和有限等待
信号量的具体实现过程:面包算法、关中断方法、原子指令上锁法 在内核中为用于一个数组存放信号量,再建立队列用于存放 PCB,因为是全局数据所以要写在内核中,用的时候必须使用系统调用队列中对应的信号量
死锁处理:PC 机通常使用死锁忽略
21 - 操作系统 - 内存管理
内存使用和分段:
要从逻辑地址转化成物理地址用运行时重定位最合适,利用的基址放在 PCB 中,执行指令时第一步就是从 PCB(特殊数据结构,其中包含了GDT和LDT)中取出基地址
由于程序是分段的 code segment | data segment … 方便管理而且在载入时节约资源,所以在存放基地址时还需要按段号分段存放到 DS 中,形成进程段表。 操作系统的段表就是GDT,子进程的段表是LDT(LDT 可以看成是 GDT 分配的)
地址翻译的具体实现过程是:首先找到本段的程序段号,然后在 LDT 表中的找到对应的基地址,完成重定位过程,段号存放在 ldtr 中
内存分页:
分区是为了能找到空闲的内存区并管理内存,这样在进程载入的时候才找得到空闲内存区,分段分区都是针对的虚拟内存。但分区会产生很多碎片,因此我们采用分页。
一个程序有很多段,一个段又对应很多页 用页表实现分页,页表起始地址放在 cr3 通过 MMU 得到页号,页号找到页框号,最后将页框号和逻辑地址相连,页框规定了页的大小,页表存放在 PCB 中,每个进程都有自己的页表
段页内存同时存在:
程序用段-虚拟内存-虚拟内存分页-映射到物理内存 段页同时存在时的重定位(地址翻译): 段依旧按段表找出基地址加上段偏移得到虚拟地址-再从虚拟地址映射到页表找到页号,利用页号和偏移地址得到物理地址,两层地址翻译:段号到虚拟地址、页号到物理地址
备忘录
深入
底层 - 汇编:(王爽《汇编语言第三版》)
3.7 CPU 提供的栈机制
入栈和出栈都是以字为单位,通过 SS 段寄存器:SP 寄存器 来指向栈顶元素
栈是从高地址往低地址增长的,这同时解决了“CPU 如何知道此段内存空间是栈“的问题,当 SP 寄存器 负增长的底线是 0000h,故定义起始地址后栈大小也随之确定。但 CPU 是不会保证我们对栈的操作不会越界的,POP 和 PUSH 的过程中有可能会产生溢出
当栈为空时 SS:SP 指向栈空间最高地址单元的下一个单元
我们当然希望 CPU 可以帮我们解决这个问题,比如说在 CPU 中有记录栈顶上限和栈 底的寄存器,我们可以通过填写这些寄存器来指定栈空间的范围,然后,CPU 在执行 push 指令的时候靠检测栈顶上限寄存器、在执行 pop 指令的时候靠检测栈底寄存器保证不 会超界。 不过,对于 8086CPU,这只是我们的一个设想(我们当然可以这样设想,如果 CPU 是 我们设计的话,这也就不仅仅是一个设想)。实际的情况是,8086CPU 中并没有这样的寄 存器。 8086CPU 不保证我们对栈的操作不会超界。这也就是说,8086CPU 只知道栈顶在何 处(由 SS:SP 指示),而不知道我们安排的栈空间有多大。这点就好像 CPU 只知道当前要执 行的指令在何处(由 CS:IP 指示),而不知道要执行的指令有多少。从这两点上我们可以看 出 8086CPU 的工作机理,它只考虑当前的情况:当前的栈顶在何处、当前要执行的指令 是哪一条。 我们在编程的时候要自己操心栈顶超界的问题,要根据可能用到的最大栈空间,来安 排栈的大小,防止入栈的数据太多而导致的超界;执行出栈操作的时候也要注意,以防栈 空的时候继续出栈而导致的超界。
应用层 - JavaWeb_Filter & Ajax & JSON
-
Json 一种轻量化的通用数据传输格式,轻量化是与 XML 对比,JSON 更小、更快、更易解析。
有两种表示方法 JSON 对象 和 JSON 字符串 用 parse() stringify() 互相转化
采用键值对方式存放 可以放 number、String、数组、JSON 对象
语法是花括号 {} 加 key:values 用 逗号,分隔
在 Java 调用时可以选择导 GSON(谷歌) 或者 阿里(推荐阿里 没有太多依赖) 的 jar 包
-
Ajax :Asynchronous JavaScript and XML. (异步的 js 和 xml)是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
同步:在提交表单后必须等待服务器返回才可以继续处理数据(造成页面卡死待响应的现象,体验差)
异步:在提交表单后无须等待,服务器处理完成后会自动返回
Ajax 在 JQuery 中有封装完成的使用方法,JSP 页面中应用较多
-
Filter:JavaWeb 的三大组件之一 用于过滤请求 可以用于用户权限的管理
FilterChain 过滤器链(多个过滤器一起工作)如果通过 XML 配置,则先配置的先调用
FilterChain.doFilter() - 执行过滤操作 如果有下一个过滤器则进入 否则执行后置代码
FilterConfig 类 可以获取在 XML 文件中的配置信息
<init-param> <param-name></param-name> <param-value></param-value> </init-param>