x86中断机制
打印一个字符,中断似乎和函数类似?
中断的概念和意义
概念:正在执行任务时,出现某个请求暂停当前任务,转而处理这个请求(类似函数调用)处理结束后继续任务的执行
意义:中断是提高系统整体性能的必要方式
中断与外设
中断是一种处理器与外设进行通信的机制
用于“通知”处理器外部有“重要事件”发生
一般情况下,中断需要被处理器响应
操作系统的本质是中断驱动的死循环!从摁下开机键一直到任务完成,一直死循环等待中断,OS kernel就用于处理这些中断。比如摁下‘F’键,操作系统有关于键盘按键的处理方式,操作系统会用相应处理方式处理键盘中断
中断的分类:
外部中断:包括不可屏蔽中断(内存读写错误,总线校验错误,电源掉电),可屏蔽中断(外部设备键盘网卡硬盘等等来数据了)。
内部中断:包括软中断(程序自身执行的时候发出的中断请求,比如汇编int 10h,看起来像是函数调用)和异常(程序执行的时候有指令错误,比如除法操作中除数是0)
中断处理中断服务程序(Interrupt Service Routine):对于处理器而言, 处理中断的方式就是执行一段事先写好的代码, 这段代码叫做中断服 ...
CCF-CSP认证历年真题解
差分法对于一个 将区间[l, r]整体增加一个值 v 操作,我们可以对差分数组 c 的影响看成两部分:
对 b[l] += v:由于差分是前缀和的逆向过程,这个操作对于将来的查询而言,带来的影响是对于所有的下标大于等于 l 的位置都增加了值 v;对 b[r + 1] -= v:由于我们期望只对 [l, r]产生影响,因此需要对下标大于 r的位置进行减值操作,从而抵消“影响”。
12345678910111213141516171819202122232425262728293031#include<iostream>using namespace std;const int N = 100010;int n,m;int a[N],b[N];void insert(int l,int r,int c){ b[l] += c; b[r+1] -= c;}int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf ...
物理内存大小
获取物理内存大小BIOS 提供的内存相关中断(int 0x15)基础功能(eax = 0xE801)
分别检测低15MB和高 16MB-4GB的内存空间
最大支持 4GB 内存检测
高级功能(eax = 0xE820)
遍历主机上所有的内存范围
获取各个内存范围的详细信息
因此设置中断参数eax=0xE801,调用中断后查看相关寄存器的返回值就可以得到内存容量
cf –> 成功 0,出错 1
ax,cx –> 以 1KB 为单位, 表示 15MB 以下的内存容量
bx,dx –> 以 64 KB 为单位, 表示 16MB 以上的内存容量
汇编小贴士:标志寄存器
123456789101112131415xor eax, eaxmov eax, 0xE801 ; 获取物理内存的大小int 0x15jc geterr ; CF位为1说明出错了;eax 以kB为单位;ebx yshl eax, 10 ; 15M以下内存容量shl ebx, 6 ; 16M以上内存容量shl ebx, 10add dword [MEM_SIZE], eaxadd ...
x86系统的内存分页
x86 系列处理器上的页式内存管理
硬件层直接支持内存分页机制,我们需要做的工作仅仅变为配置页表
默认情况下不使用分页机制(段式内存管理)
分页机制启动后,使用二级页表对内存进行管理
x86 系列处理器的分页方式 (32位)
x86分页机制示意图
页目录大小=2^10项=1024项,共计2^10*4字节=2^12字节=4K
子页表大小=2^10项=1024项,共计2^10*4字节=2^12字节=4K
页大小=2^12字节=4K
—些重要结论(针对 32 位 x86 处理器)
页目录占用 1 内存页4K(1内存页可访问1024 个子页表)
单个子页表占用1 内存页(内存页又可以可访问1024 个页面)
页面起始地址按4K 字节对齐 (每个内存页大小4K,页面起始地址总是4096 整数倍)
32位系统分页后可访问的虚拟内存空间为: 4K *( 1024 * 1024 ) = 4G
x86系列处理器上的页属性由于物理页面的起始地址按照4K(4096)字节对齐,而我们页表存储的是每个物理页的起始地址,因此页表里每一项的低12位理论上应该全为0(恰被4K整除)
既然低12位在寻址上派不上 ...
位运算
318. 最大单词长度乘积给你一个字符串数组 words ,找出并返回 length(words[i]) * length(words[j]) 的最大值,并且这两个单词不含有公共字母。如果不存在这样的两个单词,返回 0 。
示例 1:
123输入:words = ["abcw","baz","foo","bar","xtfn","abcdef"]输出:16 解释:这两个单词为 "abcw", "xtfn"。
示例 2:
123输入:words = ["a","ab","abc","d","cd","bcd","abcd"]输出:4 解释:这两个单词为 "ab", "cd"。
示例 3:
123输入:words = ["a",&q ...
逐位相加
while (i >= 0 || j >= 0 || carry != 0)含义:字符串 a 和 b 只要有一个没遍历完,那么就继续遍历;如果字符串 a 和 b 都遍历完了,但是最后留下的进位 carry != 0,那么需要把进位也保留到结果中。取 digit 的时候,如果字符串 a 和 b 中有一个已经遍历完了(即 i <= 0或者 j <= 0),则认为 a 和 b 的对应位置是 0 。
2. 两数相加给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
123输入:l1 = [2,4,3], l2 = [5,6,4]输出:[7,0,8]解释:342 + 465 = 807.
示例 2:
12输入:l1 = [0], l2 = [0]输出:[0]
示例 3:
12输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]输出:[8,9,9,9 ...
页式存储管理
页式存储管理段式内存管理这里的 ,段, 具体指什么?一段连续的内存空间
为什么会有段式内存管理?
程序的各个部分相对独立 (如: 数据段, 代码段)
早期 x86 处理器无法通过一个寄存器访问所有内存单元
解决早期程序运行时的重定位问题
段式内存管理的应用
在 x86 系列的处理器中, 硬件对段式内存管理进行了直接支持
另外, 段式内存管理也可使用纯软件实现
核心: 段首地址 + 段内偏移地址 = 内存单元地址
段式内存管理在 C 语言中的体现(纯软件实现)
数组的本质: 一片连续的内存 (段)
数组名 (array) : 数组的起始内存地址 (段地址)
数组元素的访问: array[i] 等价于*(array + i)
第 i 个元素的地址: array(段地址) + i(偏移地址)
问 题应用程序规模越来越大, 导致多数时候无法全部加载进入内存, 如何解决?
可行的解决方案 : 按段加载 ( 局部性原理)
只将当前程序运行需要的段加载进内存
当某个段不再需要使用, 立即从内存中移除
按段加载可能带来的问题
段的大小不确定, 可能大于实际的物理 ...
双指针
844. 比较含退格的字符串给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
示例 1:
12输入:s = "ab#c", t = "ad#c"输出:true
解释:s 和 t 都会变成 “ac”。示例 2:
12输入:s = "ab##", t = "c#d#"输出:true
解释:s 和 t 都会变成 “”。
12345678910111213141516171819string getString(string s){ string result = s; int l = 0,r = 0; for(;r < s.length();r++){ if(s[r] == '#'){ if(l > 0)l--; continue; } ...
保护模式的特权级
保护模式内存访问检测使用选择子访问段描述符表时 , 索引值的合法性检测当索引值越界时 , 引发异常判断规则 : 索引值 * 8 + 7 <= 段描述表界限值
内存段类型合法性检测
具备可执行属性的段( 代码段 ) 只能加载到CS寄存器
具备可写属性的段( 数据段 ) 才能加载到SS寄存器
具备可读属性的段才能加载到DS,ES,FS,GS寄存器
代码段和数据段的保护处理器每访问地址都要确认该祖不超过界限值判断规则 :代码段 : IP + 指令长度 <= 代码段界限数据段 : 访问起始地址 + 访问数据长度 <= 数据段界限
保护模式的特权级
x86 架构中的保护模式提供了 4 个特权级( 0,1,2,3 )
特权级从高到底分別是 0,1,2,3 ( 数字越大特权级越低 )
为了安全,让应用程序运行在低特权级,不能访问高特权级的数据代码段
特权级的表现形式
CPL(CurrentPrivilege Level)当前可执行代码段的特权级,由 CS寄存器最低2位定义
DPL(Descriptor Privilege Level)内存段的特权级,在段 ...
局部段描述表
LDT部段描述符表(Local Descriptor Table)
本质段描述符表 , 用于定义段描述符
与 GDT 类似 , 可以看作 “段描述符的数组”
通过定义选择子访问局部段描述符表中的元素
局部段描述符选择子与全局段描述符差距在于第二位是不同的
局部段描述符表特殊寄存器存储ldt_ptr
注意事项:
局部段描述符表(它也是一段内存)需要在全局段描述符表中注册( 增加描述项 )
通过对应的选择子加载局部段描述符( lldt load local descriptor table)
局部段描述符表从第0项(GDT默认第一项占位不使用)开始使用( different from GDT )
LDT的意义:
代码层面的意义:分级管理功能相同意义不同的段,减少GDT描述项的负担( 如 : 多个代码段 )
系统层面的意义:实现多任务的基础要素( 每个任务对应一系列不同的段 )
LDT的定义与使用
定义独立功能相关的段( 代码段,数据段,栈段 )
将上述目标段描述符组成局部段描述符表( LDT )
为各个段描述符定义选择子( SA_TIL )
在 ...