-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 205 KB
/
Copy pathcontent.json
File metadata and controls
1 lines (1 loc) · 205 KB
1
{"meta":{"title":"弦上的箭","subtitle":"","description":"我是即将到来的日子","author":"弦上的箭","url":"http://example.com","root":"/"},"pages":[],"posts":[{"title":"编译原理实践—python构建编译器","slug":"编译原理实践—python构建编译器","date":"2024-01-03T15:54:24.000Z","updated":"2024-01-03T15:56:42.556Z","comments":true,"path":"2024/01/03/编译原理实践—python构建编译器/","link":"","permalink":"http://example.com/2024/01/03/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E5%AE%9E%E8%B7%B5%E2%80%94python%E6%9E%84%E5%BB%BA%E7%BC%96%E8%AF%91%E5%99%A8/","excerpt":"","text":"简介:  本项目用于为指定语句编写编译器和解释器,除了编译原理基础概念,本项目其它算法都为原创 一、项目要求为五种语句编写编译器和解释器 1. 循环绘图语句 2. 比例设置语句 3. 坐标平移语句 4. 角度旋转语句 5. 注释语句 6. 程序演示 二、词法分析1. DFA设计如图:其中0是初态,其余都是终态其中每个状态的含义为:ERROR=0 ID=1 CONST_ID=2CONST_FLOAT_ID=3 MUL=4 POWER=5MINUS=6 NOTE=7 DIV=8PLUS=9 L_BRACKET=10 R_BRACKET=11COMMA=12 SEMICOLON=13 T=14ORIGIN=15 SCALE=16 ROT=17IS=18 FOR=19 FROM=20TO=21 STEP=22 DRAW=23FUNC=24 EOF=25 2. 词法分析器2.1 问题与解决词法分析器思想并不难,核心思想就是不断扫描字符,然后进行状态转移。但是存在一个问题,如何判断单词的开始和结束,该什么时候去记录单词,一开始的思路是按照空格划分,但是如3+4这样的字符串,没有空格,按照空格划分无法记录单词,而我们要能够区分3、+、4。于是转变思路,转移到终态就记录,能够应对上述例子,但是如12,按照这样的思想,会将1和2区分开来,不满足最长匹配原则。经过思考,得出结论:当前状态接收当前字符无法转移的时候进行记录,记录时如果当前状态不在终态,证明当前单词不合法,就要进行出错处理另一个问题是如何区分两个ID类型的单词,如for和int按照DFA都会转移到状态ID并记录,它们是同一终态,但是是不同含义。经过思考得出结论:建立符号表,如果是ID类型查询符号表,状态更新为查表得到的状态,如果查表失败就代表是不合法ID 2.2 输入词法分析器输入为一个字符串,其为程序文件的全部文本 2.3 工作细节词法分析器工作流程如下:将文本记为一整个字符串,对字符串从头开始扫描: 读取当前扫描字符ch,当前单词tempStr+=ch 若ch为空格或者回车(且是第一次遇到),record当前记号,重置所有标志,然后继续下一次扫描 如果出错标志为1或者注释标志为1,继续下一次扫描 若ch不为空格和回车,查询DFA进行状态转移,如果转移失败record该记号,tempStr-=ch;如果转移成功,更新状态(如果当前状态是注释,注释标志置1)。接着继续下一次扫描 record函数:接收当前单词tempStr,和当前状态state, 如果state是终态,查询符号表,如果命中,改变状态为查表所得状态,记录当前单词;如果未命中,令state=-1,记录当前单词,然后reportError 如果state不是终态,记录当前单词,然后reportError reportError函数接收当前单词tempStr、当前状态state、当前单词位于的行line、当前单词在行中的位置idx打印报错信息:(line,idx,state对应的报错类型,tempStr),报错标志置1如:state=-1,报错类型就是无法识别的ID 2.4 输出输出为记号流,包含若干记号,每个记号是一个四元组(记号类型,单词,值,函数)如:pi的记号是(CONST_FLOAT_ID,’pi’,3.1415926,None) 三、语法分析1. 上下文无关法Program → { Statement SEMICO }Statement → ORIGIN OriginStatment| SCALE ScaleStatment| ROT RotStatment| FOR ForStatmentOriginStatment →IS L_BRACKET Expression COMMA Expression R_BRACKETScaleStatment →IS L_BRACKET Expression COMMA Expression R_BRACKETRotStatment →IS ExpressionExpression→ Term { ( PLUS | MINUS ) Term }Term→ Factor { ( MUL | DIV ) Factor }Factor→ ( PLUS | MINUS ) Factor | ComponentComponent→ Atom [ POWER Component ]Atom →(CONST_ID|CONST_FLOAT_ID|T)(PLUS|MINUS |MUL|DIV|POWER) Expression | FUNC L_BRACKET Expression R_BRACKET | L_BRACKET Expression R_BRACKET 该文法是EBNF文法,无二义性,无左递归左因子 2. 语法分析器2.1 问题与解决问题构建分析树非常简单,只需要将产生式右部全体作为产生式左部的孩子即可,但是构建语法树并不简单,如3+2*1该如何读入+时,将其作为3的父亲,读入2时又作为+的孩子,读入* 时又该如何改变树 查阅资料得到算法: 如果当前读入的字符是'(',添加一个新的节点作为当前节点的左子节点,并下降到左子节点处。 如果当前读入的字符在列表['+', '-', '/', '*']中,将当前节点的根值设置为当前读入的字符。添加一个新的节点作为当前节点的右子节点,并下降到右子节点处。 如果当前读入的字符是一个数字,将当前节点的根值设置为该数字,并返回到它的父节点。 如果当前读入的字符是’)’,返回当前节点的父节点。 缺陷:依赖括号处理优先级,如3+2*1就无法处理,读入2的时候当前节点在+号,再读入 * 时,该运算符就不知道该往哪放了 改进思想优先级的处理:在语法树中越靠近下的越优先计算,如3+2*1,读入 * 时,当前节点在+号,* 优先级高于+号,应该先计算 * 号,所以 * 号把+号的右孩子2夺过来,作为其左孩子,然后+号的右孩子改为 * 号由上述过程可知,在一个语法树中,根节点永远是运算符,其左右孩子是一个表达式,在读到下一个运算符时,上一个运算符的右孩子被夹在了这两个运算符之间,如果这个运算符优先级比上一个更高,那么就要抢夺上一个运算符的右孩子,优先计算这一个运算符。更进一步,如4+3^2*1,在读到 * 时,此时3*2+1,是+号的右孩子,2是 ^ 号的右孩子,^该抢夺哪一个右孩子?在语法树中,运算符永远比它的父亲优先级高,比他的孩子优先级低,所以应当向上和向右下搜索,直到找到一个节点,当前运算符优先级介于当前节点优先级和该节点右孩子优先级之间(为什么是右下,因为我们判断的是右孩子该跟谁结合的问题,而不可能去抢夺左孩子,左孩子和当前要判断的运算符是不邻接的)特别的,操作数的优先级永远是最低的,因为它们永远是根节点 结合性的处理:结合性的问题体现在两个操作符优先级相等的情况,左结合就是应当把两个操作符之间的表达式作为第一个操作符的右孩子,第二个操作符无法抢夺,如3+2-1就是先计算3+2在计算-1,右结合同理。此项目没有右结合的运算符,所以都按左结合处理 括号的处理:语法树要求括号被隐含在树中,所以处理括号很重要。3+(2-1)中,( 的优先级比+号高,先计算(2-1)再计算3+,但是 ( 的优先级又比 - 号低,先计算2-1,再计算括号,在语法树中的体现是括号前的那个符号永远在括号上层,而括号内的表达式永远被封闭在括号下面括号的优先级比外面高,比里面低,所以需要特殊的算法,在遇到左括号时,意味着接下来要计算括号内的内容,此时所有构建要封闭在括号之下,所以给左括号设置最低的优先级,而遇到右括号时,意味着括号计算完毕,所有操作符都不能在进入括号之下,于是给左括号设置最高的优先级 函数的处理:此项目的表达式不仅有加减乘除,还有函数,函数也可以看作是一种操作符,其孩子是它的参数,所以函数按照操作符一样处理即可另外还有语句如scale is (a,b),类似于scale这种语句也可以看作是操作符,根节点是scale,左孩子是a,右孩子是b,一样操作即可。但是由于本项目没有语句的嵌套,所以没有必要给scale这种语句构建语法树,只需要作为另外的信息标注即可 最终算法构建表达式的语法树算法如下:现有表达式,从头开始对其扫描,当前字符为ch,当前节点为cNode,若: ch为左括号:cNode值设为左括号,优先级设置为最低,为cNode添加右孩子,cNode下降到该新节点 ch为右括号:cNode上升到其对应左括号节点,并将该节点优先级设置为最高 ch为操作数:cNode值设为此数,优先级设置为最低,cNode上升到父节点 ch为操作符:从cNode开始不断向上/向右下搜索,直到搜索到某个节点tNode,ch优先级介于tNode优先级和tNode右孩子优先级之间,此时创建新节点nNode,其值为ch,优先级为ch优先级,其左孩子为tNode右孩子,并为其添加右孩子,令tNode右孩子为nNode, cNode更新为nNode右孩子 最后删除该括号节点,其父亲继承其孩子 2.2 输入输入为修饰后的记号流,修饰主要是删除记号流中记号类型(状态)不是终态的记号(代表出错记号)或者记号类型是注释的记号 2.3 工作细节本项目采用的是递归下降分析器构建的核心思想是, 对于每个产生式Ei->…,引入函数Funi,其内容由产生式右部构成 函数内容中,遇见或,就引入if elif,遇见终结符就加入到if的条件中,谋求记号和终结符的匹配,遇到非终结符就加入到if的内容中,进入非终结符对应的函数。如果或可以为空,那么else中就为空,否则else中报错(所有候选项都不匹配 例:Factor → ( PLUS | MINUS ) Factor | Component构建为如下函数: 1234567def factor(self): if self.token_list[self.idx][0]==Token.PLUS.value or self.token_list[self.idx][0]==Token.MINUS.value: self.idx+=1 self.factor() elif 1: self.component() else: 工作原理就是不断进入终结符的函数(展开终结符)谋求对非终结符的匹配,分析完毕后,检查出错标志,如果没出错,那么记录,如果出错,则不记录,并进行出错处理 出错处理:函数的else语句中报错,则将出错标志置1,并return,在上层函数判断,如果出错标志为1,继续return,直到退出开始函数。存在问题,报错该扫描到哪里为止,如果从出错位置开始的下一个位置继续进行语法分析,大概率仍然会报错,如scale (1,1),按照上述思想会报六次错误。如果将一整行抛弃掉那么又有可能会抛弃掉正确的语句经过思考,得出结论:扫描到直到下一个记号可以合法的进行语法分析,如scale (1,1); rot is 0,报错会抛弃(1,1);,直到遇到rot才进行下一次语法分析 2.4 输出输出是语法列表流,语法列表包括了语法的类型,语法的表达式的语法树如rot is 0;的语法列表是[ROT,astTree] 四、语义分析1. 问题与解决如何计算一颗语法树?先构建一个字典,其键为操作符,值为操作符对应的函数然后构造函数calTree(tree)calTree中: 如果tree根节点为操作符,返回calTree(tree.left,tree.right) 如果tree根节点为操作数,返回该操作数(特别的,如果操作数为t,查询当前t设置的值,返回该值) 2. 翻译器2.1 输入语法列表流 2.2 工作细节扫描语法列表流,若语法类型为 ORIGIN:设置origin_x=calTree(tree1)origin_y=calTree(tree2) SCALE:设置scale_x=calTree(tree1)scale_y=calTree(tree2) ROT:设置 rot=calTree(tree1) FOR:设置for_from=calTree(tree1)for_to=calTree(tree2)for_step=calTree(tree3)生成list=[for_from:for_step:for_to]对于list中的每个元素i,令t=i,然后令x=calTree(tree4) * scale_xy=calTree(tree5) * scale_yxx=x * cos(rot)+y * sin(rot)-origin_xyy=y * cos(rot)-x * sin(rot)-origin_yfor_x.appen(xx)for_y.appen(yy))对于xx,yy中的每个元素xi,yi,绘制点(xi,yi) 出错处理:在操作符对应的函数中进行出错处理,如div函数,如果除数为0,返回报错语句,上层检测到返回值不是一个数字,则继续返回,直到退出函数,并终止程序。 2.3 输出输出为绘制的图 五、结果展示源程序: 12345origin is (0, 0);scale is (1, 1);for t from e to 10 step 0.1 draw(t, ln(t));scale is (1, 1);for t from 0 to 2 step 0.1 draw(t, t**2); 结果: 结语:  ","categories":[{"name":"项目","slug":"项目","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/"}],"tags":[{"name":"编译原理","slug":"编译原理","permalink":"http://example.com/tags/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/"},{"name":"编译器","slug":"编译器","permalink":"http://example.com/tags/%E7%BC%96%E8%AF%91%E5%99%A8/"}]},{"title":"计算机网络笔记","slug":"计算机网络笔记","date":"2023-12-14T13:58:14.000Z","updated":"2024-01-15T11:45:21.546Z","comments":true,"path":"2023/12/14/计算机网络笔记/","link":"","permalink":"http://example.com/2023/12/14/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E7%AC%94%E8%AE%B0/","excerpt":"","text":"简介:   一、网络1. 分类1.1 物理分类 网状:每台设备之间都有一条专用链路。优点:健壮,即使一条链路不可用,整个系统也不会不可用;缺点:线缆多 星型:每台设备与中央控制器(集线器)有一条链路,集线器负责转播信号。优点:健壮,即使一条链路不可用,整个系统也不会不可用;缺点:依赖集线器 总线:由一条较长的线缆作为主干,每台设备引出一条线连接主干。优点:安装简单,路径高效;缺点:总线电缆上的错误会使得所有传输中止 环状:由一条环型线缆,环中有若干中继器,每个中继器引出一条线连接设备。优点:信号循环,故障隔离;缺点:环中的故障会使整个网络瘫痪 混合型:混合上述结构 1.2 范围分类 局域网LAN:通常是专用的,如办公室 城域网MAN:通常覆盖一个城市,为高速连接而设计 广域网WAN:提供远距离数据传输,可跨越国家甚至更大 互联网:多个上述网络一起连接组成的网络 2. 模型2.1 基本概念 实体:第n层的活动元素称为n层实体。同一层的实体叫对等实体 协议:为对等实体数据交换建立的规则,包括语法(数据的格式)、语义(数据的含义)、时序(操作的顺序) 接口:上层使用下层服务的入口 服务:下层为相邻上层提供的功能调用 2.2 OSI参考模型OSI是由官方创立的法定标准由物理层、数据链路层、网络层、传输层、会话层、表示层以及应用层组成 物理层:负责位从一个节点到另一个节点的传递 数据链路层:负责帧从一节点到另一节点传递。负责一个网络内的寻址 网络层:负责分组从源地址传递到目的地址。负责需要跨越网络的寻址 传输层:负责报文从一个进程到另一个进程传递。负责数据的进程(计算机程序)到进程传递 会话层:负责对话控制和同步 表示层:负责翻译、加密和压缩数据 应用层:负责向用户提供服务 2.3 TCP/IP模型TCP是已经被广泛接受的事实标准,以下主要介绍其规定的协议 物理层和数据链路层:不定义任何协议,支持所有标准协议 网络层: 网际协议(IP):提供尽力传递服务,没有差错检错和跟踪。ip传输的数据称为数据报,每个数据报都独立传输 地址解析协议:将逻辑地址与物理地址相联系 逆地址解析协议:允许主机在仅知道物理地址的情况下寻找因特网地址 因特网控制报文协议:向发送方通知数据报所发生的问题 因特网组报文协议:用于将一个报文同时发送给一组接收者 传输层: 用户数据报协议:进程到进程的协议,将端口号、校验、差错控制及信息长度添加在数据的头部 传输控制协议:可靠的流传输协议,面向连接,在传输前先建立连接。 该协议划分数据流为片段,每个片段包含序号,用于接收后重新排序 流传输控制协议:对新应用提供支持,如音频 应用层: 相当于OSI模型中的会话层、表示层和应用层 2.4 寻址以TCP/IP模型为例,地址有四部分:物理地址、逻辑地址(IP)、端口地址和专用地址 物理地址:局域网或广域网定义的节点地址,在数据链路层被添加在数据头部。不同网络可能有不同格式的物理地址 逻辑地址:逻辑地址适用于因特网,它唯一定义了连接到因特网的一台主机,通常为32位地址 端口地址:进程地址 专用地址:一些面向用户的应用,如电子邮件 寻址过程见书P32,文字表述过于麻烦 二、数据通信1. 基础概念模拟信号:连续的值数字信号:离散的值 码元(信号元素):码元是一个固定时长的信号元素,其可以用带代表不同离散数值的基本波形。当一个信号的离散状态有K个时,此时码元称为K进制码元。一个K进制码元可以携带log2(K)个bit的信息量。如010101,有两种不同的码元,0,1。它是2进制码元,每个码元携带1bit信息 2. 通信模式2.1 方向模式 单工:通信是单向的。两台设备只有一台能够发送,另一台只能接受 半双工:通信是双向的。每台设备均能发送和接受,但不能同时进行,一台设备发送时,另一台只能接收 全双工:通信是双向的。每台设备能同时发送和接受 2.2 数据模式 并行传输:一次可以发送n位数据。 串行传输:一次只能发送1为数据。速度慢,费用低,适用远距离 2.3 时序模式 同步传输:先送出一个或多个同步字符 异步传输:添加开始位和结束位 2.4 传输模式 基带传输:将数字信号1和0用不同电压表示,再送到数字信道上传输 宽带传输:将基带信号经过载波调制,频率范围搬移到更高频段,再送到模拟信道上去传输 3. 通信过程为解决多台设备的通信问题,提出了交换网络的方法,交换网络由一系列节点构成,这些节点成为交换机,交换机可以在多个端系统(设备)间建立临时链接交换网络有三种:电路交换网、分组交换网、 报文交换网,分组交换网又分为虚电路网和数据报网。文字表述过于复杂,见书P139 4. 模拟信号与数字信号转换4.1 数字信号转数字信号通过编码方式 不归零编码(NRZ):正电平定义为1,零电平定义为0。存在问题:由于信号使用此方式会出现一整段高电平信号或低电平信号,接收方不知道这一段里又多少个1多少个0 归零编码(RZ):正电平定义为1,零电平定义为0,但是在每个码元后半段都会置零。和1存在同样问题,且低电平大量出现,信道利用率低 反向不归零编码(NRZI):信号电平和上一个电平相比,翻转表示0, 不翻转表示1。和1存在同样问题 曼彻斯特编码:一个码元内前半段高电平后半段低电平表示0,反之是1。该编码特点是一个码元中间出现电平跳变可以用于同步。该编码最小带宽是NRZ两倍 差分曼彻斯特编码:该码元前半段和上一个码元的后半段电平相同为1,反之是0。特点和曼彻斯特编码一样,但是抗干扰性更强。该编码最小带宽是NRZ两倍 mBnL编码:用n为L进制数表示m位B进制数,L=2时用B表示,L=3时用T表示,L=4时用Q表示 MLT-3编码:如果下一位是0,则信号保持,如果下一位是1且当前电平不是0,则下一个电平是0,如果下一位是1且当前电平是0,则下一个电平是最后一个非零电平的相反值 4.2 数字信号转模拟信号调幅、调频、调相 4.3 模拟信号转数字信号 抽样:对模拟信号周期性扫描,得到一系列离散的信号。为了使得无失真代表,要求采样频率>=2*信号最高频率 量化:把抽样得到的电平按照一定的分级标准转化为整数 编码:将量化的结果转化为二进制编码 5.性能5.1 性能指标 速率(比特率、数据传输率、数据率):主机在信道上传送数据位数的速率,单位为b/s,B/s,bps… 码元速率(信号速率、波特率):主机在信道上传送码元(信号元素)的速率,速率=码元速率*一个码元携带的信息量,单位为波特每秒 传播速率:信号在链路上传播的速度,单位是m/s 信号带宽:信号最高频率和最低频率之差,单位为Hz 带宽:指单位时间内从网络某一点能通过的最高数据率,单位是b/s 吞吐量:单位时间内通过某个网络/信道/接口的数据量,单位为b/s 发送时延:第一个bit被推送到信道和最后一个bit被推送到信道的时间差,发送时延=数据长度/发送速率(理想为信号带宽) 传播时延:信号在信道上传播的时间,传播时延=信道长度/传播速率 排队时延:信号等待输入可用的时间 处理时延:检错、找出口 等的时间 时延带宽积:留存在信道的数据量,时延带宽积=传播时延*带宽,单位为bit 信道利用率:有数据通过的时间/(有+无)数据通过的时间 网络利用率:信道利用率加权平均 5.2 性能干扰奈奎斯特定理(内部干扰)在无噪声、带宽受限条件下,为避免码间干扰,极限码元传输速率为2W,极限数据传输速率为2Wlog2(V),其中W是信号带宽,V是信号离散电平数 香农定理(外部干扰)噪声会影响信号,信号若强,则噪声影响就弱,因此信噪比就很重要,信噪比=信号的平均功率/噪声的平均功率,记为S/N(db),其数值为10log10(S/N)。为避免信号干扰影响传输,极限数据传输速率=Wlog2(1+S/N),W是信号带宽 三、物理层1. 有向介质有向介质是指在设备之间提供通路的介质,包括双绞线、同轴电缆和光缆 1.1 双绞线双绞线由两根并排绞合的相互绝缘的铜导线组成双绞线分为非屏蔽双绞线与屏蔽双绞线,后者有一层金属薄片将导体包围起来,防止噪声或串扰 特质:便宜、通信距离为几公里到数十公里应用:电话线路、局域网 1.2 同轴电缆同轴电缆由导体铜制芯线、绝缘层、网状编制屏蔽层和塑料外壳构成 特质:抗干扰性比双绞线强、传输距离更远,被广泛用于传输高速率数据,但是价格贵应用:以太局域网、模拟电话网络 1.3 光缆光缆由纤芯和包层构成,传递光脉冲来进行通信,有光脉冲表示1,无为0光缆的传播模式有多模和单模两种,前者代表光束沿多个不同的路径通过纤芯 特质:带宽高、信号衰减小、无电磁干扰、抗腐蚀、重量轻、不易被窃听,但是安装维护费用高、传播单向性、成本高应用:混合网络、快速以太网 2. 无向介质无线通信不适用物理导体传输电磁波,信号通过空气传播,能被任何人接收 2.1 无线电波频率在1-3GHz之间,使用全向天线发送信号 应用:多播通信,如收音机、电视、寻呼系统 2.2 微波频率在1-300GHz,使用单向天线(有抛物面碟形天线和喇叭天线)发送信号 应用:单播通信,如移动电话、卫星网络和无线局域网 2.3 红外波频率在300GHz-400THz,频率很高不会穿透墙体,可防止系统之间相互干扰 应用:封闭区域通信,如视线传播、遥控器 四、数据链路层数据链路层将数据封装成帧并传输,主要任务包括成帧、寻址、流量控制、差错控制和介质访问控制。此处不讨论流量控制和介质访问控制 1. 帧1.1 成帧数据链路层要将位组合成帧,并且帧与帧之间要可识别。成帧方法有固定大小成帧(长度本身就用做分隔符)和可变长度成帧,其中可变长度成帧又可分为面向字符协议和面向位协议 1.1.1 面向字符协议在帧的开始和结束增加一字节的标记。由于数据中可能出现和标记一样的字符,为区别标记和数据中作为文本的标记字符,采用字节填充,在数据段出现和标记一样的文本时,在其前面添加换义字符,同样的,如果要将换义字符作为文本传输,在换义字符前再添加换义字符即可。接收方会在接收时移走这些被额外添加的换义字符 1.1.2 面向位协议使用8位标记01111110来作为分隔符。由于数据中可能也会出现01111110,所以采用位填充,在数据中每遇到1个0和5个连续的1便在后面添加一个0,接收方在接收时会移走这些0 2. PPP帧PPP帧是点到点协议PPP所使用的帧,用于广域网,用于广播,所以帧中不需要寻址 2.1 帧格式面向字节协议帧包括: 标记:使用01111110的一个字节标记开始和结束 地址:常量11111111。预留,目前无作用 控制:常量11000000。预留,目前无作用 协议:1-2字节,指明数据字段承载什么 有效载荷字段:承载数据或其他信息。如果标记出现在此段,需要用转移字节01111101在前面填充 帧校验序列:CRC循环校验的余数 2.2 传输过程 闲置:链路没有被使用的阶段 建立:当一个节点开始通信,就进入此阶段。双方进行协商,使用链路控制协议LCP,协商成功则进入鉴别阶段,否则回到闲置阶段 鉴别:使用PAP口令鉴别协议或CHAP查询握手鉴别协议,或者不使用鉴别。鉴别成功或无需鉴别则进入联网阶段,否则进入终止阶段 联网:使用字段规定的NCP协议(包括不同的网络层协议,如互联网络协议控制协议IPCP),进行网络层的协商。因为PPP在网络层支持多协议,所以在网络层的数据进行交换之前,节点双方必须达成一个网络协定 打开:在此阶段,进行数据传输,直到一个终端想终止连接就进入终止阶段 终止:终止连接,两端交换一些分组用于关闭链路 3. MAC帧MAC帧是用于多点到多点的帧,被用于局域网,所以它的帧要包括寻址信息 3.1 帧格式|PC|D|地址1|地址2|地址3|序列控制|地址4|帧主体|FCS||-|-|-|-|-|-|-|-|-|-||2|2|6|6|6|2|6|0-2312|4| D:在一个控制帧中,用来定义帧ID,其余用于定义传输间隔时间序列控制:定义帧的序列号,用于流量控制FCS:包含CRC-32的差错检验序列 其中:FC的内容如下: 版本协议 类型 子类型 ToDs FromDs 更多标记 重传 pwrmgt 更多数据 WEP Rsvd 2 2 4 1 1 1 1 1 1 1 1 类型:管理(00)、控制(01)、数据(10)toDS和fromDs:规定了四个地址的含义,见书P286 2. 差错控制2.1 数据差错数据差错需要通过检错与纠错实现,即判断数据是否发生了错误,并且改正错误 2.1.1 差错类型 单个位差错:数据单元中仅有一位发生变化,1变成了0或0变成了1 突发性差错:数据单元中有两位或更多位发生了变化 2.1.2 方法 奇偶校验:在数据末尾添加一位奇偶校验位,使得数据中1的个数位奇数(奇校验)或偶数(偶校验),接收方查看数据中的1是否为奇数/偶数 多维奇偶校验: 循环冗余校验:约定多项式,发送方计算余数并在数据后添加该余数,接收方把接收到的数据连同余数一起重新计算,若余数为0则代表传输无误 汉明编码: 2.2 数据丢失数据丢失需要用到可靠传输协议解决,可靠传输即保证接收方的接受数据的完整性、正确性和有序性可靠传输用于 最简单的协议:发送方数据链路层不断发送数据,接收方数据链路层从物理层接收到帧,从帧取出数据传输到网络层 停止等待协议:发送方发送一个帧后必须停下来直到接收到接收方的确认(ACK帧)才能发送下一个帧 停止等待自动重复请求协议(SW-ARQ):发送方发送一个帧后停下来等待接收方的ACK帧,如果超过一定时间未接受到ACK帧,则发送方重传当前帧。并且,为了避免重复接收的错误,要给每个帧(包括ACK帧)使用一个bit编号。信道利用率=数据发送时延/(数据发送时延+传播时延+ACK帧发送时延),信道利用率很低。 回退N帧自动重发请求协议(GBN-ARQ):使用n个bit给数据编码。发送方与接收方使用滑动窗口(一种抽象的假想概念),落在滑动窗口内的帧可以被发送/接收(相当于流水线处理,滑动窗口中的第一个帧被发送后不需要等待确认就可以发送滑动窗口中的第二个帧),当接收方接收到滑动窗口的第一个帧时,滑动窗口向后滑动一格,并发送确认帧。当发送方接收到滑动窗口中的帧的确认时,就滑动到该帧之后(使用累积确认方法,接收到编号为M的确认帧,代表0-M都被正确接收)。如果超过一定时间窗口都没有滑动,则重传窗口内的所有数据。发送方的滑动窗口尺寸Wt要满足:1<Wt<=2^n-1,如果Wt=1,该协议退化为停止等待协议,如果Wt>2 ^n-1,则接收方无法辨别新旧分组。接收方的滑动窗口尺寸Wr要满足:Wr=1。5.选择重传协议(SR-ARQ):相比与GBN-ARQ,接收方的滑动窗口尺寸满足:1<Wr<=Wt,当Wr等于1时,退化为GBN-ARQ,当Wr>Wt时无意义。且该协议抛弃累计确认方法,需要对每一个分组逐一确认。 其中1,2用于无噪声通道,即不会丢失帧、复制帧或损坏帧的通道,是理想的情况 3. 多路访问当有多个站点存在时,发送数据可能会发生碰撞,造成冲突,未解决冲突需要用到多路访问的协议 3.1 随机访问协议所有站点通过竞争,随机在信道上发送数据,每个站点都是平等的。然而当多个站点同时发送时,就会产生冲突,随机访问协议就用于避免冲突以及冲突产生后尽快恢复通信 纯ALOHA协议最早的随机访问方法,每个站点只要有帧要发送即发送,解决冲突依赖于接收方的确认,当超时还未收到确认,则重发帧,如果重发次数超过最大次数,则必须放弃一段时间再尝试。 性能评估:脆弱时间(可能发生的冲突时间的长短)=2*Tfr,其中Tfr指每个帧的发送时间吞吐量=G * e^-2G^,其中G为一个帧传输时间内系统产生帧的平均数量 时隙ALOHA协议把时间分隔成Tfr秒的时隙,强制站点只有在时隙开始时才能发送,一旦站点错过这个时刻,就要等待下一个时隙开始。 性能评估:脆弱时间=Tfr吞吐量=G * e^-G^ 载波侦听多路访问协议CSMA站点在发送帧前先监听介质是否空闲,如果空闲再发送帧,如果繁忙则等待下一个随机监听时间。这种方法仍然会导致冲突,由于传播延迟的存在,一个站点发送的一帧未到达下一个站点,导致下一个站点仍然监听到空闲的介质而发送了一个帧,导致了碰撞 性能评估:脆弱时间=传播时间 带冲突检测的载波侦听多路访问协议CSMA/CD再CSMA基础上引入冲突检测,同时要监测帧是否传送成功,如果不成功就要退避一段时间再重发帧,如果成功则继续发送(还可以在发生碰撞时加强冲突,发送一个短小的干扰信号以便其它站点更容易检测冲突)。为了实现该协议,需要限定最小帧长,发送方必须在发送帧的最后一位前检测冲突,因为一旦整个帧被发送了,站点就无法保留帧的副本并掌握线路的冲突检测,帧传输时间Tfr至少是最大传播时间Tp的两倍。 性能评估:吞吐量大于ALOHA方法,当G=1是,吞吐量最大约为50% 带冲突避免的载波侦听多路访问协议CSMA/CACMSA/CD用于有线网络中,基本理念是没有冲突时,站点接收到它自身的信号,有冲突时,站点接收到自身的信号和第二个站点传输的信号,为区分这两种情况,不同情况接收到的信号应该明显不同,为此需要增加第一个站点产生的信号的能量。然而无线网络中,大量能量丢失,这使得难以检测冲突,所以采用避免冲突的策略,使用三种方法 帧间间隔IFS:当发现通道是空闲的时,站点还需要的等待一段时间(IFS)再次发生,由于通道看上去是空闲的,其实可能有远端站点已经开始传输,只是信号还未到达这个站点 竞争窗口:当发现通道是空闲的后,不仅要等待IFS,还要再等待一段不同的随机时间,避免同时发送。通过计时器实现,等待IFS后启动定时器,如果在等待的时间里监听到通道繁忙,计时器停止(而非重启),直到下一次等待IFS后继续计时。当计时结束,再等待IFS后可以发送数据 确认:确认帧 3.2 受控访问协议预约:站点发送数据前需要预约轮询:由主站选择哪个站能发送数据令牌传递:站点被组织在一个逻辑环中,如果当前站点不需要传输数据,则权限被传递给它的后驱 3.3 通道化协议预先分配好信道,不同站点在时间上、频率上或编码来公用带宽。这类方法非常不灵活,对于突发数据信道利用率很低,通常在无线网络的物理层使用 频分多路访问FDMA公用通道的可用带宽被分割成频带并由防护频带隔离。每一个频带都预留给特定的站点使用,该频带永远属于一个站点 时分多路访问TDMA带宽被认为是时间上共享的一个通道。每个站点被分配一个时隙,只有在该时隙内才能发送数据。由于系统的传播延迟会难以同步,所以插入保护时间用于同步化 码分多路访问CDMA一个通道同时承载所有的传输。该协议给每一个站点指定一个被称为芯片的数字序列(该数字序列用-1表示0,用1表示1)(要求:两两站点的芯片要不同且正交,正交即规格化内积为0),如果该站点要发送比特1则发送该芯片的值,要发送比特0则发送该芯片的反码,不发送数据则视为0。信道上发送的数据为所有站点发送的数据的叠加,接收方收到该数据后,针对每一个站点,求该站点芯片和该数据的规格化内积,如果是1代表站点发送了比特1,如果是-1代表站点发送了比特0,如果是0代表站点未发送数据。 例:A站点芯片序列(-1 +1 -1 -1)B站点芯片序列(+1 +1 -1 +1)其规格化内积为:(-1 * 1+1 * 1+ -1 * -1+ -1 * +1)/4=0,符合要求现有A站点要发送1(即发送(-1 +1 -1 -1)),B站点要发送0(即发送(-1 -1 +1 -1)),则通路发送叠加数据(-2 0 0 -2),接收方求与A站点芯片序列规格化内积为1,代表A发送了比特1,求与B站点规格化内积为-1,代表发送了比特0 五、网络层数据链路层用于单链路通信,而多链路通信就需要用到网络层,它不仅负责主机间的传递,还负责路由器交换机的路由选择网络层将数据分组并用数据报的形式通信,这种通信是无连接的 1. IP地址1.1 网络号与主机号IP地址由两个字段组成,分别为网络号和主机号,前者用于区分主机所连接的网络,后者用于区分主机 1.2 掩码掩码和IP地址长度一样,掩码中对于IP地址网络号的部分都为1,其余都为0 斜杠标记法/无类域间路由选择CIDR:掩码除了用二进制表示,还可以用\\n表示,n代表了网络号的位数 1.3 块块是一组IP地址的集合 1.2 IPv4IPv4用32位二进制表示或用4个十进制表示(点分十进制)如某一个IP地址为10000000 00000001 00000001 00000001,也可以表示为128.1.1.1 1.2.1 分类寻址结构在该结构中,地址空间被划分为5类 A类地址:首位为0,第一个字节为网络号,按网络号分块可分为128块,每块含2^24个地址,用于大型组织机构 B类地址:首位为10,前两个字节为网络号,用于中型组织机构 C类地址:首位为110,前三个字节为网络号,用于小型组织机构 D类地址:首位为1110,没有分网络号和主机号,用于多播,这里浪费了大量地址 E类地址:首位为1111,用作保留,也引起了浪费 可见在分类寻址结构中,大部分可用地址被浪费了 1.2.2 无类寻址结构为克服地址耗尽的问题(可用地址被大量浪费),所以使用无类寻址替代分类寻址,该结构中当一个实体需要连接因特网时,会给它分配一个合适的块,其满足: 块中的地址必须时一个接着一个连续的 块中地址的个数必须是2的整数次幂(不含0) 块的起始地址必须能被块的个数整除 块地址可以用x.y.z.t/n来定义,其中:x.y.z.t是块中的某个地址,/n为掩码(二级结论:块内地址的最后32-n位,即主机号对应的位必是从全为0到全为1) 1.2.3 专用网络(私有网络)地址专用网络地址用于解决地址短缺的问题,它被用于用户内部通信。一个用户有多个主机,它们在内部使用不同的专用地址通信,而在外部统一使用一个外部地址用于通信。然后当有来自外部的通信时,目的地址是内部地址统一使用的外部地址,无法区分该去往哪个内部主机,所以为了实现内部和外部的多对多通信,还需要使用网络地址转换NAT技术,使用转换表(专用地址、专用端口、外部地址、外部端口),外部地址+专用端口唯一定义了任何一台主机被用于专用网络的地址有:10.0.0.0-10.255.255.255172.16.0.0-172.31.255.255192.168.0.0-192.168.255.255 1.2.4 特殊网络地址IP地址全为0表示本地地址主机号全为1表示广播地址IP地址为255.255.255.255表示有限广播地址,用于主机试图在本网内通信而又不知道主机号 1.3 IPv6IPv6用于解决地址耗尽、不支持实时音频/视频传输、数据不能加密和鉴别的问题 1.3.1 表示方法 二进制表示:使用128位二进制表示 十六进制冒号法:用八组每组四个十六进制数表示,每组用冒号连接 缩短形式:省略多余的0,如000F可写为F,移去多组的0,如74:0:0:0:12:0可以表示为74::12:0,注意移去多组0只能使用一次,不然无法恢复成原来的地址 1.3.2 类型前缀如同分类寻址结构,IPv6使用一些类型前缀来区分地址类型,如前三位是010是单播地址 2. IP协议(网际协议)IP协议定义了数据报的格式,数据报由头部+数据组成 2.1 IPv4IPv4头部和数据由20-65536个字节组成,其中头部由20-60个字节组成 版本:4位,目前版本为4,如果计算机使用的版本和数据报版本就会丢弃数据报 头部长度:4位,该部分的值×4=头部的字节数,最小值为5,最大值为15 服务:8位,以前该字段为服务类型,现在为差分服务 服务类型: 前三位为优先位,定义了优先值,拥塞时丢弃最低优先级数据报,但IPv4中未使用优先字段。后四位为TOS,只能有一位是1 差分服务: 前六位为码点,后两位不用。码点最后三位都为0时,码点的含义是优先级(同服务类型),最后一位为0时,码点代表了因特网定义的服务类型,最后两位为11时 ,代表了本地组织机构的服务类型,最后两位为01时,代表了临时/实验的服务类型 总长度:16位,该字段值=头部+数据的总长度。尽管大多数情况接收到帧时删去头部和尾部就得到了数据报,并不需要知道总长度,但是有些情况封装在帧中的不止是数据报,如以太网协议限定帧中数据长度为46-1500字节,当数据包长度小于46字节时,必须填充字节。所以分离数据报要检查总长度 标识:16位,表示数据报,为使得唯一的定义一个数据报,IPv4协议使用一个计数器,当发送数据报时,标识赋值为计数器的值,然后计数器值加一。当数据被分段时,标识字段的值被复制到所有分段的标识号中 标记:3位,第一位保留暂不使用,第二位为不分段位,其值为1代表该数据不能被分段,如果因此导致数据报不能通过任何可用网络传递,该数据包被丢弃。第三位为多分段位,其值为1代表在该分段后还有更多的分段,其值为0代表该分段时最后的分段 分段偏移:13位,表示在原始的数据包中数据部分的偏移量,以8个字节为度量单位。如某分段总长度100,该字段值为0,则代表在原数据中的编号为0-99,若该字段值为2,则代表编号为16-115 生存时间:8位,定义了数据报通过路由器的最大跳数,约为任何两个主机之间的路由器数量的两倍。处理数据包的每一个路由器将此数-1,如果-1后值为0,则将其丢弃。这个字段是必需的,由于一些路由表可能故障,导致一个数据包在一些路由之间反复传输而无法到达目的端。另一个用途是限制分组的行程,如若要限制在局域网内,则设置此字段为1,这样就不会经过路由器转发。 协议:8位,定义了来自高层的协议。值和协议的对应关系如下:1:ICMP,2:IGMP,6:TCP,17:UDP,89:OSPF 校验和:16位,校验和只对头部进行,不对数据进行。将头部划分为每部分16位,将每部分相加,取结果的反码存入该字段。 源地址: 32位,定义了源端的IPv4地址,这个字段保持不变 目的地址:32位,定义了目的端的IPv4地址,这个字段保持不变 选项:头部包含固定部分(20字节)和可变部分(选项)(0-40字节),选项部分包含 无操作选项:1字节,用作选项间填充符 选项结束选项:1字节,用作选项字段结束的标志 记录路由选项:处理数据报的因特网路由器,可列出最多9个路由器地址,用作调试和管理 严格源路由选项:预先严格规定数据报传送的路由,数据报必须经过规定的所有路由器,如果数据报通过了一个未规定的路由器,就会被丢弃并发出差错报文 松散源路由选项:必须经过规定的所有路由器,但是仍然可以访问别的路由器 时间戳选项:用来记录路由器处理数据报的时间 (补充:分段:由于每个数据链路层协议规定了最大传输单元MTU(数据字段最大长度),所以如果数据报大于该最大长度,需要进行分段。对数据报分段的设备需要改变标志、分段偏移、总长度、校验和四个字段的值,并将源数据报头部的某些部分复制到所有分段中,选项可以被复制也可以不被复制) 2.2 IPv6IPv6协议相比于IPv4的优势有: 更大的地址空间 更好的头部格式:选项和头部分开 新的选项 允许扩充 支持资源分配 加密和鉴别(安全性) 结语:  ","categories":[{"name":"笔记","slug":"笔记","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/"},{"name":"课程","slug":"笔记/课程","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/%E8%AF%BE%E7%A8%8B/"}],"tags":[{"name":"计网","slug":"计网","permalink":"http://example.com/tags/%E8%AE%A1%E7%BD%91/"}]},{"title":"数据库笔记","slug":"数据库笔记","date":"2023-11-24T07:26:00.000Z","updated":"2024-01-15T11:44:59.824Z","comments":true,"path":"2023/11/24/数据库笔记/","link":"","permalink":"http://example.com/2023/11/24/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AC%94%E8%AE%B0/","excerpt":"","text":"一、概述1. 数据数据:完整性(不包含任何冗余信息)、可共享 2. 数据库数据库:相关数据的集合 2.1 模式 模式(逻辑模式):数据库中全体数据的逻辑结构和特征的描述 外模式(视图层):数据库用户使用的局部数据的逻辑结构和特征描述 内模式(物理层):数据物理结构和存储方式的描述 3. 数据库管理系统数据库管理系统:为用户提供访问数据库的方法功能: 数据定义:利用DDL方便的对数据对象定义 数据操作:利用DML实现对数据库的基本操作,如增删改查 数据库运行管理:利用DBMS统一管理,确保数据的安全性、完整性、并发问题以及故障修复 数据库建立和维护:初始数据的录入、转换功能、恢复功能、分析功能等 4. 数据库系统数据库系统:由数据、数据库、数据库管理系统、硬件、软件和用户组成(硬件:主存和辅存 软件:操作系统、数据库管理系统(mysql等)、其他软件 用户:终端用户(使用者)、应用程序员、数据库管理员) 二、关系模型1. 概念关系:数据结构 关系表:存储关系的表,逻辑结构,是一个二维表 元组:关系表中的一行 属性:关系表中的列(标题) 属性值:属性对应列中的值 域:属性值的取值范围 超码:部分属性的集合,根据这些属性,可以区分任意两个元组 候选码:最小的超码(包含的属性个数最少) 主码:从候选码中选出的一个码外码:当一个表的某一个属性,引用到另外一个表的主码 关系模式(Relation Schema)是对关系的描述,它可以形式化地表示为:R(U,D,dom,F)。其中R为关系名,U为组成该关系的属性名集合,D为属性组U中属性所来自的域,dom为属性向域的映象集合,F为属性间数据的依赖关系集合。通常简记为:R(U)或R(A1,A2,…,An)其中R为关系名,U为属性名集合,A1,A2,…,An为各属性名。 关系完整性: 实体完整性:每个关系应该有一个主码(可唯一标识表中的一条记录),每个元组的主码值惟一确定该元组主码的任何属性都不能取空值 参照完整性:实现这种引用规则(一对一,多对多),要求外码的取值只是被参照表主码的值或者取空值 用户定义完整性:对某一具体应用的数据必须满足的语义要求。如列值非空,列值唯一,列值必须大于10 2. 关系代数基本操作2.1 选择σ expression(table)expression 是命题演算,可以包含与或非、大于小于等于…作用是筛选出table中满足expression的元组 2.2 投影π attribute(table)attribute是一些属性的集合作用是筛选出table中属性属于attribute的列组成一个新的表(并删除重复元组) 2.3 并r∪s将r中元组和s中元组组成一个新的表(并删除重复元组),要求r和s列数相同,且域兼容 2.4 集合差r-s将r中元组中存在s中的部分删除,要求r和s列数相同,且域兼容 2.5 笛卡尔积r×s将r中的每一个元组和s中的每一个元组进行拼接,将所得的所有元组组成新的表 2.6 重命名ρ name(n1,n2,…)(E)将表E的名称重命名为name,并将其属性重命名为n1,n2…(n1,n2,…)可以不写,即不重命名属性 2.7 交r∩s将r中元组和s中元组中共有的元组组成一个新的表,要求r和s列数相同,且域兼容 2.8 自然连接r⋈s比较r和s中共有的属性,对于s中的每个元组,如果r中某一个元组和s中的这个元组在这些共有属性上的值相同,那么将s中的这个元组的其他值,拼接在r中这个元组后,得到一个新的元组。将所有这些新元组组合起来得到一个新表 2.9 外连接r⋈s将自然连接中没有成功匹配的元组加入到新表,并给新表中没有的值用NULL代替左外连接:只保留r中没有匹配的元组右外连接:只保留s中没有匹配的元组 2.10 除法r÷s其中:r=(A1…Am,B1…Bn)s=(B1…Bn)查找r中所有元组,对于s中每一个元组(bi1…bin),r中都存在(ai1…aim,bi1…bin)这样一个元组,那么将(ai1…aim)加入到新表中 三、SQL1. 基于表操作1.1 创建表123456789create table table_name( data_name1 data_type1 not null, //非空约束 ... data_namen data_typen default value //默认值 primary key (data_name), //主键约束 foreign key (data_name), //外键约束 references table_name(data_name), constraint con_name check(data_name>0) //约束 ); 1.2 撤销表1drop table table_name 1.3 修改表123alter table table_name add data_name data_type; //添加字段alter table table_name drop data_name; //删除字段alter table table_name alter data_name data_type //修改字段(类型) 1.4 表运算123r union s; //交r intersect s; //并r except s; //差 1.5 视图操作视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。但是,数据库中只存放了视图的定义,而并没有存放视图中的数据,这些数据存放在原来的表中。使用视图查询数据时,数据库系统会从原来的表中取出对应的数据。因此,视图中的数据是依赖于原来的表中的数据的。一旦表中的数据发生改变,显示在视图中的数据也会发生改变。同样对视图的更新,会影响到原来表的数据。视图可以用于简化查询,不用每次输入复杂的查询语句。视图是从不同的角度看待数据 1234create view view_name(A1,...An) as(expression)with check option;//expression是可以得到一个表的一些语句,如select,并交//带有with check option 在插入数据时会检查expression中的限制语句(where),如果不符合则拒绝插入 1.6 索引索引:用来查找记录的属性的集合索引文件:索引+指针的形式有序索引:有严格的顺序哈希索引:乱序主索引副索引:找满足一定条件的值 稠密索引:包含了所有的属性值,如id有1234,索引包含了1234稀疏索引:包含了部分的属性值,如id有1234,索引只包含14B-树索引 创建索引 1create index index_name on table_name(table_attribute) 删除索引 1drop index index_name on table_name 经常更新、数据少、分布平均、大量重复的表不适合添加索引 经常用于查询、需要排列、唯一性的属性适合做索引 2. 基于数据库操作1.1 查询操作12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273//基本语句select A1,A2,...,Anfrom r1,r2,...,rmwhere p;//对应关系表达式://π A1,A2...,An(σ p(r1×r2×...×rn))//distinct 和 all 关键字select distinct A //没有重复项select all A //所有都保留// * 关键字select * //查询所有属性// as 关键字select A as B //将A重命名Bfrom r as s//like 关键字select Afrom rwhere m like '%Main\\%%' escape '\\';// %:匹配任意字符串// _:匹配任意字符// \\:转义//上述语句的含义为:查找所有m中包含main%的数据//表达式select A*100 //查询后所有值*100//order by 排序select A1,A2,...,Anfrom r1,r2,...,rmwhere p;order by ri DESC;//按照ri降序排列,ASC为升序,默认降序 //集函数select avg(A) //求平均select min(A) //求最小select max(A) //求最大select sum(A) //求和select count(A) //计数,有多少值//集函数只能用于select 不能嵌套//group by 分组select A1,count(distinct A2)from rwhere pgroup by A1;//按照A1分组后再进行计数,可得到每个分组的A2值数量,count可改为其它集函数//havingselect A1,count(distinct A2)from rwhere pgroup by A1 having count(A2)>10//再筛选出计数大于10的分组 //select 嵌套select Afrom rwhere A not in (select A from s);//集合比较where A>some(select A from s) //大于某个where A>any(select A from s) //同somewhere A>all(select A from s) //大于所有//exists和uniquewhere exists(A) //如果A存在,返回truewhere unique(A) //如果A没有重复元组,返回true select语句解析顺序: from where group by /having select order by where的逻辑运算:逻辑值:true false unknown除了 unknown or true=true,unknown and false=false,其余unknown参与的逻辑运算都是unknownr and s; //与r or s; //或not //非 1.2 插入操作12345678insert into table_namevalues(v1,v2,...,vn);//或者insert into table_name(A1,A2,...,An)values(v1,v2,...vn);//或者insert into table_name expression//expression是可以得到一个表的一些语句,如select 1.3 更新操作12345678910update table_nameset A1=A1*100,A2=A2+1where condition;//或者update table_nameset A1= case when A1<100 then A1+1 else A1-1 end; 1.4 删除操作12delete from table_namewhere condition 3. 授权操作授权类型(基于数据库): 查询 插入 修改 删除授权类型(基于表): 索引:允许添加删除索引 资源:允许创建新的表 更改:修改已存在的表结构 删除:删除表 赋予权限grant <privilegde> on <tablel_name or view_mame> to <user_list>收回权限revoke <privilegde> on <tablel_name or view_mame> from <user_list or all> 创建角色create role <name>继承grant <role_name> to <role_name or user_name> 4. 函数操作1234567create function fun_name(parameter_list) returns integer //返回数据类型 begin declare var integer //声明变量 //此处一般写一些sql语句 return var end 5. 过程操作12345create procedurepro_name(parameter_list)begin //执行代码end 理解:过程和函数的区别,函数用于返回特定的数据,而过程用于执行特定的任务,如插入删除 6. 触发器操作12345678910create trigger tri_name <before or after> <insert or delete or update on table_name for each row begin trigger_action //新的数据为new,旧的数据为old, //可以通过new.,old.来访问 end 四、事物和并发控制1. 事物数据库事务(Database Transaction)是指对数据库的一系列操作组成的逻辑工作单元。 1.1 事物特性并非任意的数据库操作序列都是数据库事务。数据库管理系统(DBMS)在写入或更新资料的过程中,为保证交易(Transaction)正确可靠,必须具备四个特性,这四个特性通常称为 ACID 特性。 原子性(Atomicity)事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。 一致性(Consistency)一致性确保事务将数据库从一种一致的状态转换为另一种一致的状态。换句话说,事务在执行前后,数据库必须满足一些预定义的一致性规则,如约束、触发器、级联等。如果事务执行后数据库不满足这些规则,整个事务将被回滚。(不一致性一定是存在的,但数据库需要保证不一致状态只能在事物执行当中存在) 隔离性(Isolation)多个事务并发执行时,一个事务的执行不应影响其他事务的执行。 持久性(Durability)已被提交的事务对数据库的修改应该永久保存在数据库中。 (一致性级别: 强一致性:要求对系统数据的修改可以立刻读取,用户体验性最好。 弱一致性:不保证什么时候能达到一致性,但是尽可能保证在某个时间级别后,数据达到一致性 最终一致性:弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。) 1.2 状态 活动:开始执行就处于的状态 部分提交:所有指令执行完的状态 失败:指令不能正常执行时的状态 终止:回滚恢复到事物的初始状态 事务提交:事物执行完之后的状态 终结:包含终止状态和事务提交状态 状态跳转关系图: 12345 部分提交 → 事务提交↑ |活动 |↓ ↓ 失败 → 终止 2. 并发控制目标是解决并发冲突,使并发执行过程等价于某个串行执行过程 2.1 锁排他锁(X锁):拿到排他锁,能够对事物进行读写操作,其它事物不能进行任何操作。X锁和任何锁互斥共享锁(S锁):拿到共享锁,可以对事物进行读操作,其他事物只能读不能写。S锁和S锁可以共存 申请锁,如果该锁和其它事物保持的锁相容,才能获得锁,否则就等待,直到互斥的锁被释放 存在问题:死锁:互相等待对方释放锁饿死:一直等待 解决方法:锁协议:两阶锁协议: 增长阶段:允许事物申请锁,不允许释放。在对任何数据进行读写前,要申请锁 消减阶段:允许事物释放锁,不允许申请。在事物执行完后才允许释放锁,且不允许再申请锁 两阶段协议会出现死锁 2.2 时间戳对于每一个事物都赋予一个唯一的时间戳。时间戳越大执行顺序越靠后。W-t(Q)记录了成功执行write(Q)的事物的最大的时间戳,R-t(Q)同理。例:当事物T要write时,比较W-t(Q),如果比它小,则T试图写的值已经过时,拒绝写,并将T回滚 3. 日志3.1 WALWAL预写日志:修改并不直接写入到数据库文件中,而是写入到另外一个称为 WAL 的文件中;如果事务失败,WAL 中的记录会被忽略,撤销修改;如果事务成功,它将在随后的某个时间被写回到数据库文件中,提交修改。WAL包括了redo和undo信息 3.2 undo和redoRedo Log(重做日志):为了系统崩溃之后恢复数据用的,让数据库照着日志,把没做好的事情重做一遍。保证了事物的持久性Undo Log(回滚日志):为了回滚用的。保证了事物的原子性和一致性 更新数据时,把我要改的东西记录在日志里,我再根据日志统一写到磁盘中,万一我在写入磁盘的过程中系统崩溃了,等系统恢复,我先查看日志的完整性。 如果日志是完整的,里面有Commit Record,我就照着日志重新做一遍,最后也能成功。如果日志是不完整的,里面没有Commit Record,我就回滚整个事务,什么都不做。 这个日志就叫做 Redo Log,也就是“重做日志”,中途崩溃的数据库,根据这个日志把事务重做一遍。 Undo Log五、数据库设计1. E-R图设计数据库1.1 概念设计将需求分析得到的用户需求抽象为信息结构(即概念模型)的过程就是概念结构设计。以下介绍使用E-R图设计概念模型 1.1.1 E-R图概念E-R图包括:实体、属性、联系 实体实体是某个特定的对象,其属性区别于其它实体。如学生、教室,也可以是事件如教学 弱实体:依赖于另一个实体的实体,没有另一个实体,就没有该实体 属性属性是依附于实体的特征。如姓名、电话 属性分类: 单一属性和组合属性:组合属性,由其它属性组合而来,如姓+名组合出姓名 单值属性和多值属性:多值属性,一个实体可以有多个该属性的值,如一个人可以有多个电话 派生属性:可以由其它属性计算而来 联系联系可以用于表示以下内容: 隶属:如员工隶属于部门 事件:由多个实体参与的事件。如教师、学生、课程构成事件上课 联系分类:按参与的多寡分 一对一联系:A实体集中一个实体最多与B实体集中一个实体产生联系;B实体集中一个实体最多与A实体集中一个实体产生联系 一对多联系:A实体集中一个实体可以与B实体集中多个实体产生联系;B实体集中一个实体最多与A实体集中一个实体产生联系 多对多联系:A实体集中一个实体可以与B实体集中多个实体产生联系;B实体集中一个实体可以与A实体集中多个实体产生联系 按实体参与的级别分6. 完全参与:实体集中的每个实体都参与联系7. 部分参与:实体集中仅有部分实体参与联系 1.1.2 E-R图绘制实体:用矩形表示弱实体:用同心矩形表示 属性:用椭圆表示多值属性:用同心椭圆表示派生属性:用虚线椭圆表示 联系:用菱形表示单一联系:连线上标注1多联系:连线上标注 * 1.1.3 E-R图原则 属性没法产生联系,要想产生联系,需要将属性变为实体 某实体的属性不能是别的实体的属性,解决方法:1. 引入弱实体 2.将实体改为联系 联系也可以参与联系 1.2 逻辑设计逻辑设计将E-R图转换为多个关系模式,每一个实体、联系就是一个模式,它们的属性、联系构成了模式的内容 1.2.1 实体实体:代表一个关系模式弱实体:其主码和其依赖的实体的主码构成它的主码,其另外的属性为其自身的属性 1.2.2 属性属性:构成模式的属性集合组合属性:被其组合项替代作为模式的属性多值属性:独立构成一个关系模式,其所属的实体的主码作为其外码,其另外的属性为其自身代表的属性派生属性:不在关系中体现 1.2.3 联系多对多联系:该联系单独成关系表,把构成联系的实体的主码作为该联系的外码,其另外的属性为其自身的属性 一对多联系和多对一联系:将一的那一方的主码作为多的那一方的外码 一对一联系:可以采用一对多、多对一和多对多模式 2. 规范化设计数据库数据库存在问题: 数据冗余:一个实体或联系不能只用一行来表达 修改异常:实体发生一次改变,数据库要修改多行 删除异常:删除某些行时,被迫把其它信息删除了。如表(老师,课程),有数据(A,俄语),A辞职了,删除该数据,数据库中俄语这门课程的信息也没有了 插入异常:想要插入一个信息,但是被迫的插入了一些没有的信息。如表(老师,课程),想添加俄语课程,但是还没有老师教学,老师对应的值就是空值 2.1 函数依赖函数依赖:在关系模式r上,对于任意元组t1、t2,如果在a属性上有t1(a)=t2(a)可得在b属性上t1(b)=t2(b),那么函数依赖a决定b在r上成立 阿姆斯特朗公理: 包含规则:若y属于x,那么函数依赖x决定y 传递规则:若x决定y,y决定z,那么x决定z 增广规则:若x决定y,那么xz决定yz 合并规则(推论):若x决定y,x决定z,那么x决定yz 分解规则(推论):若x决定yz,x决定y,那么x决定z 2.2 函数依赖集的闭包由已知的函数依赖集可以推出的所有函数依赖 2.3 属性集的闭包由该属性集中的属性根据某个函数依赖集能决定的所有属性初始令属性集=原属性集,遍历属性集的每个属性,查询函数依赖集,利用阿姆斯特朗公理,若能决定某个属性,加入属性集。不断循环,直到属性集没有新属性加入 判断函数依赖集是否可以推出x决定y成立,只需要判断,x在该函数依赖集上的属性集的闭包是否包含y,如果包含则成立,否则不成立 2.4 函数依赖集的覆盖函数依赖集F可以推出G,那么F覆盖G(F的闭包包含G) 2.5 函数依赖集等价F和G相互覆盖,则F和G等价 2.6函数依赖集冗余函数依赖集的某些依赖可以被其它依赖集覆盖,或依赖的左右部分有多余例:一般冗余:{A->B,B->C,A->C},A->C多余右手冗余:{A->B,B->C,A->CD}改为{A->B,B->C,A->D}左手冗余:{A->B,B->C,AC->D}改为{A->B,B->C,A->D} 2.7 函数依赖集的最小等价覆盖函数依赖集的最小等价覆盖,没有多余的依赖、多余的左右部分 求解步骤: 除去右手冗余:将某个依赖分解为多个依赖,并除去重复依赖 除去一般冗余:对于A->B,若将其从函数依赖集删除,新的函数依赖集和原来的函数依赖集等价,那么可以做该删除 除去左手冗余:对于α->B,若用α-A->B替换,新的函数依赖集和原来的函数依赖集等价,那么可以做该替换 2.8 函数依赖集的正则覆盖函数依赖集F的正则覆盖是这样一个函数依赖集G,F和G等价,且G中没有相同的左部 构造方法:计算函数依赖集的最小等价覆盖,然后将其中左部相同的依赖合并 2.9 候选码候选码是这样一个属性集合,它是最小的能够决定r上的所有属性的集合 主属性:候选码中的属性 构造方法: 将只出现在函数依赖中左边的属性,和未出现在函数依赖中的属性合并到一起加入候选码集合G 遍历剩余属性Y,取出单个属性Yi,计算GYi的闭包,如果等于全属性集合,则将记录Yi,并从Y中去除,如果不等于继续。 遍历剩余属性Y,取出两个属性Yii,计算GYii的闭包,如果等于全属性集合,记录Yii,并从Y中去除,如果不等于继续。 … 取出Y中所有属性,计算(Y+G)的闭包,如果等于全属性集合,则记录GY,如果不等于结束。 将所有记录的属性加入到G 2.10 多值依赖多值依赖:设R(U)是一个属性集U上的一个关系模式, X、 Y和Z是U的子集,并且Z=U-X-Y。关系模式R(U)中多值依赖 X→→Y成立,当且仅当对R(U)的任一关系r,给定的一对(x,z)值,有一组Y的值,这组值仅仅决定于x值而与z值无关 判断方法:对于任意关系中,如果存在两个元组(就是行),记为A,B,如果他们的某一属性X的值相等,那么我们交换它们另外的属性Y的值后,得到的新的两个元组,在表中是可以在原来的表中找到与它们相匹配的元组的。 2.10.1 平凡多值依赖若多值依赖X->->Y,X交Y=R,则该多值依赖是平凡的 2.11 范式2.11.1 一范式属性都为原子性的关系集合都在一范式中原子性: 没有组合属性 每个属性作为整体处理,而不是从中提取子串 一个元组中属性值只能取 一个值 2.11.2 二范式没有非主属性部分依赖于候选码(非主属性不依赖于候选码的部分,如AB->C,C不会依赖于A,也不会依赖于B) 2.11.3 三范式没有非主属性对码的传递依赖(如非主属性C,D,若存在C->D则就是存在传递依赖) 2.11.4 BC范式函数依赖集中,所有左部都是超码(候选码连接其它属性) 2.11.1 四范式不存在非平凡且非函数依赖的多值依赖 2.12 模式分解将R<U,F>分解为多个Ri<Ui,Fi>其中每个Ui都是U的子集,它们相交后等于U,且不存在Ui属于Uj。Fi继承了F中不出现Ui以外的属性的函数依赖。 2.12.1 无损连接分解后的表子表自然连接后和原表一样判定条件:分解后子表的公共属性能够决定至少一个子表的所有属性 分解必须是无损的 2.12.2 保持函数依赖分解后的每个Fi相交后等于F 尽可能保持函数依赖 六、NoSQL哈希存储:键-值分布式存储技术优点:数据结构不固定,适用于数据量爆发式增长 1. 特点分布式数据库, 最大的特点就是分布式,即满足BASE,ASE方法通过牺牲一致性和孤立性来提高可用性和系统性能。BASE为Basically Available, Soft-state, Eventually consistent,其中BASE分别代表: 基本可用(Basically Available):系统能够基本运行、一直提供服务。 软状态(Soft-state):系统不要求一直保持强一致状态。 最终一致性(Eventual consistency):系统需要在某一时刻后达到一致性要求。 2. 评估CAPC:一致性,对于客户端的每次读操作,要么读到的是最新的数据,要么读取失败。换句话说,一致性是站在分布式系统的角度,对访问本系统的客户端的一种承诺:要么我给您返回一个错误,要么我给你返回绝对一致的最新数据,不难看出,其强调的是数据正确。 A:可用性,任何客户端的请求都能得到响应数据,不会出现响应错误。换句话说,可用性是站在分布式系统的角度,对访问本系统的客户的另一种承诺:我一定会给您返回数据,不会给你返回错误,但不保证数据最新,强调的是不出错。 P:分区容忍性,由于分布式系统通过网络进行通信,网络是不可靠的。当任意数量的消息丢失或延迟到达时,系统仍会继续提供服务,不会挂掉。换句话说,分区容忍性是站在分布式系统的角度,对访问本系统的客户端的再一种承诺:我会一直运行,不管我的内部出现何种数据同步问题,强调的是不挂掉。 七、嵌入式SQL数据库和高级程序语言通信 SQL通信区SQLCA:SQL语句执行后,系统反馈给应用程序信息 ,描述系统当前工作状态,描述运行环境。这些信息将送到SQL通讯区中,应用程序从SQL通信区中取出这些状态信息,据此决定接下来执行的语句 主变量:嵌入式SQL语句中可以使用主语言的程序变量来输入或输出数据。在SQL语句中使用的主语言程序变量简称为主变量(Host Variable) 指示变量:是一个整型变量,用来“指示”所指主变量的值或条件 游标的定义:游标是系统为用户开设的一个数据缓冲区,存放SQL语句的执行结果每个游标区都有一个名字,也可以理解为该数据区的指针用户可以用SQL语句逐一从游标中(指针所指示的位置)获取记录,并赋给主变量,交由主语言进一步处理 八、考点第一章:数据库四位图灵奖得主:查尔斯·巴赫曼、埃德加·科德、詹姆斯·格雷、迈克尔·斯通布雷克(背下来)数据库管理系统概念数据库物理层、逻辑层、视图层 第二章:关系模式(所有都是重点) 第三章:SQL语句:表操作语句(要知道主码外码完整约束)增删查改创建视图授权(必考)嵌入式sql了解主变量、指示变量、游标的概念要知道函数和过程建议掌握(创建方法)触发器掌握,语句级触发器和行级触发器 第四章:E-R图(所有都是重点):完全参与、共同参与规范化(所有都是重点):第四范式了解,BC范式分解规则了解(最好知道规则) 第五章索引哈希(最多是选择题):**什么时候不要用索引、什么时候用索引(重点) 第六章:查询优化:查询基本概念、优化基本概念,等价规则,简化表达式(原则能投影先投影、能选择先选择,尽量不要连接) 第七章:事物并发控制(重点):ACID,事物状态、并发、可串行化调度,一致性的级别。并发改造,锁,基于时间戳的协议 第八章:恢复:什么时候需要undo,什么时候需要redo,什么是WAL 第九章:nosql:什么时候使用sql,什么时候使用nosql,cap理论(什么时候cp,ca,ap),nosql和rdbms区别 第一道大题:关系表达式,sql语句第二道大题:数据库设计(E-R图)第三道大题:规范化(属性依赖集、等价等)第四道大题:改写并发","categories":[{"name":"笔记","slug":"笔记","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/"},{"name":"课程","slug":"笔记/课程","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/%E8%AF%BE%E7%A8%8B/"}],"tags":[{"name":"数据库","slug":"数据库","permalink":"http://example.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"}]},{"title":"编译原理笔记","slug":"编译原理笔记","date":"2023-11-24T07:26:00.000Z","updated":"2024-01-15T11:45:03.017Z","comments":true,"path":"2023/11/24/编译原理笔记/","link":"","permalink":"http://example.com/2023/11/24/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E7%AC%94%E8%AE%B0/","excerpt":"","text":"简介:本篇博客记录了我学习编译原理的过程,由于编译原理包含了大量概念和算法,所以在最开始阐述编译原理的大框架,帮助形成学习体系,更好理解。   零. 编译原理框架1. 词法分析词法分析是用于分析一个单词合法与否,比如变量aaaa是合法的,但1aaaa是不合法的,词法分析的目的就是让机器能够理解一套词法规则来判断单词的合法性。为此学习以下方面: 识别单词的词法规则:正规式 正规式的数据结构:DFA/NFA 词法分析的工作器:词法分析器 要使得机器能够完成词法分析,就要先书写我们需要的词法的正规式,然后构造能让机器理解的正规式的数据结构,最后构造词法分析器,其利用了该数据结构完成词法分析。 2. 语法分析语法分析和词法分析是非常相似的,语法分析是用于分析语法合法与否,比如int a=1;是合法的,int a=是不合法的。语法分析的目的就是让机器能够理解一套语法规则来判断语句的合法性。为此学习以下方面: 识别语句的语法规则:上下文无关法 上下文无关法的数据结构:预测分析表/移进规约分析表 词法分析的工作器:递归预测分析器、非递归预测分析器、移进规约预测分析器 要使得机器能够完成语法分析,就要先书写我们需要的语法的上下文无关法,然后构造让机器能理解的上下文无关法的数据结构,最后构造语法分析器,其中不同的数据结构代表机器的不同的理解方式,其构造出的语法分析器也就不同。 一. 编译器概述1. 计算机语言面向机器语言:机器指令、汇编面向人类语言:通用程序设计语言、数据查询语言… 2. 语言之间的翻译2.1 翻译模式正向工作: 汇编语言->机器指令:汇编/交叉汇编 高级语言->汇编语言/机器指令:编译/交叉编译 高级语言->高级语言:转换/预编译/预处理(如#include) 逆向工作: 反汇编/反编译 2.2 翻译形态 2.2.1 编译器:先翻译后执行特点:工作效率高(时间快、空间省)、交互性与动态特性差、可移植性差功能:完成翻译 2.2.2 解释器:便翻译边执行特点:工作效率低(时间慢、空间费)、交互性与动态特性好、可移植性好功能:完成翻译 3. 编译器的构成与工作原理3.1 通用程序设计语言的主要成分基本抽象:过程(函数)、抽象数据类型、类定义方式:声明+操作组成部分:头+体(如函数头+函数体)( 其中:体包括: 声明性语句 编译器生成相应环境(存储空间) 操作性语句 编译器生成可执行代码序列) 3.2 编译器的工作阶段 词法分析:识别记号(关键字(保留字)、标识符、字面量、特殊符号) 语法分析:识别语言结构,并以树的形式表示 语义分析:语义计算和处理。如考察结构正确的句子是否语义合法、修改树结构、查填符号表 中间代码生成(可选):生成一种既接近目标语言,又与具体机器无关的表示,便于代码优化 中间代码优化(可选):优化实际上是等价变换,变换前后功能完全相同,但空间时间上都更好 目标代码生成:不同形式的目标代码,如:汇编、可重定位的二进制代码、内存形式(Load and Go) 符号表管理:合理组织符号,便于各阶段查找、填写等 出错处理:词法错、语法错、静态语义错(x/0)、动态语义错(x/y,y在运行中可能为0) 3. 编译器的构造方法架构:分析(前端)->中间代码->综合(后端)构造方法: 直接使用汇编语言/通用程序设计语言 利用编译器编写工具 基于编译器基础架构的编译器构造系统(开放式编译器,如GCC/LLCM/SUIF/COINS) 二. 词法分析1. 基本概念 单词:被识别出的元素自身的值,也叫词值。类别:关键字:kw,如if while标识符:id,如变量名字面量:literal,如60特殊符号:ks,如= + 模式:产生和识别单词(元素)的规则 记号:按照某个模式识别出的元素记号=记号类别+记号属性 词法分析器:特征:唯一接触源程序的部分任务: (根据模式)识别记号,并交给语法分析器 过滤源程序无用成分,如注释、空格、回车 处理与具体平台有关的输入(如回车) 调用符号管理器或出错处理器 工作方式: 单独扫描一遍 作为语法分析器的子程序(需要时调用词法分析器) 并行(词法分析器生成记号流同时语法分析器处理) 2. 正规式2.1 语言与字符串在词法分析的角度,语言L是有限字母表上有限长度字符串的集合(语言是由记号组成的集合)字符串运算:集合运算: 2.2 正规式与正规集其中:正规式运算优先级从高到低为:闭包运算、连接运算、或运算 2.3 等价正规式若正规式P,Q表示同一正规集,则P和Q等价,记作P=Q证明方法: 证明不同正规式表示的集合相同 根据正规式代数性质运算 2.3 简化正规式若r是表示L(r)的正规式,则 正闭包:r⁺=rr*=r*r,r*=r|ε 其中:*和⁺运算优先级相同 可缺省:r?=r|ε 其中:*和?运算优先级相同 串:"r"=r,ε="" 可用于字符转义,如:“|”表示字符|而非或运算 字符组:r=[r'] 其中:r是由若干字符进行或运算构成的正规式, r’有两种书写形式: 枚举:a|b|c|d=[abcd] 分段:a|b|c|d=[a-d] 非字符组:[^r]是表示∑-[r]的正规式 辅助定义:名字=正规式 名字仅供正规式内部使用(类似于C语言中define) 2.4 正规式说明记号记号=正规式如:relation = <|>|<=|>=|= 3. 不确定的 有限自动机(NFA)模式的识别/正规集的描述:正规式记号的识别/正规集的识别:有限自动机最大的特点:不确定性(即某一状态对于一个字符可能有多个状态转移) 3.1 定义NFA是一个五元组:M=(S,∑,move,s0,F)其中: S:有限个状态的集合 ∑:有限个输入字符的集合 move:状态转移函数。 move(si,ch)=sj表示当前状态si遇到输入字符ch转移到状态sj s0:唯一的初态(开始状态) F:终态集(接收状态集),它是S的子集,包含所有终态 3.2 表示方法 状态转换图:节点:代表NFA中每个状态边:代表NFA中的状态转移函数。连接两个状态,边上标记输入的字符 状态转移矩阵:行标:∑中的字符列标:S中的状态矩阵值:对应状态接受对应字符后转移到的状态集合 3.3 识别方法对于一个输入序列: 从NFA初态开始 对于输入的每个字符,寻找其下一个状态转移,直到到达一个终态,或没有下一个状态转移(根据最长匹配原则,要求所有字符都被输入) 如果此时处于终态,则NFA接受该序列 否则,回溯+试探:沿原路返回,对于每个状态寻找另一种可能的状态转移,若能到达终态,则NFA接受该序列,否则(一直返回到初态都不能到达终态)不接受 3.4 等价有限自动机若有限自动机M和M’识别同一正规集,则M和M’等价,记作M=M’ 3.5 构造NFA使用Thompson算法,先将正规式分解,然后使用以下规则构造NFA: 4. 确定的有限自动机(DFA)由于DFA识别记号时需要尝试所有路径才能确定一个序列不被接受,且识别时要进行大量回溯,算法较为复杂,时间复杂度高,所以提出构建一种有限自动机,其在任一状态下遇到同一字符最多有一个状态转移 4.1 定义DFA是NFA的一个特例,其中 没有状态具有ε状态转移(即状态中没有标记ε的边) 对于每个状态和每个字符,最多有一个下一状态 4.2 构造DFA(确定化NFA)ε-闭包ε-闭包(T):状态集T上的ε-闭包(T)代表从状态集T出发,经过ε(即不经过任何字符)可到达的状态全体,其构造方法为: (初始化)T中所有状态属于ε-闭包(T) (更新)将smove(ε-闭包(T),ε)中的状态添加进ε-闭包(T) 循环2,直到不能转移到任一其它状态 其中:smove(S,ch)代表对于S中的每个元素接受字符ch后能够到达的状态(move(item_in_S,ch))的合集 子集法构造DFA输入:NFA,M=(S,∑,move,s0,F)输出:DFA,DM=(DS,D∑,Dmove,Ds0,DF) (初始化)初态Ds0为ε-闭包(s0);令DS∪=Ds0 对于DS中每个元素ds,∑中每个字符ch,计算得smove(ds,ch)=new_ds;令DS∪=ε-闭包(new_ds),move+=(move(ds,ch)=ε-闭包(new_ds)) 循环2,直到DS中所有元素都被计算过 其中:D∑=∑,终态DF为DS中包含终态F中元素的元素集合 例: 初态:求0的ε的闭包:经过任意条ε所能到达的状态,集合为{0,1,3,4,5,6,7,9},将其填入表格第一行第一列 a b {0,1,3,4,5,6,7,9} {0,1,3,4,5,6,7,9}中:1接受a到达2,4接受a到达4,其余没有接受a到达某个状态,所以集合为{2,4},其的ε-闭包为{2,4,6,7,9},将其填入第一行第二列 {0,1,3,4,5,6,7,9}中:同2,接受b,最后得到集合{5,6,7,8,9},将其填入第一行第第三列 a b {0,1,3,4,5,6,7,9} {2,4,6,7,9} {5,6,7,8,9} 观察{2,4,6,7,9}和{5,6,7,8,9}未在第一列出现,将他们填入第一列 a b {0,1,3,4,5,6,7,9} {2,4,6,7,9} {5,6,7,8,9} {2,4,6,7,9} {5,6,7,8,9} 同2,3对{2,4,6,7,9}和{5,6,7,8,9}计算,然后同4添加元素,再重复计算,最终得到表格,并对新的状态集重新编号: 根据状态表绘制状态图: 4.2 最小化DFA 可区分:对于状态s和t,若其中一个状态接受字符串w而另一个状态不接受,或者存在字符串w使得s和t接受后到达不同的状态,则s和t是可区分的 (不可区分:对于状态s和t,接受任意字符串到达的状态都是一样的,则s和t不可区分) 死状态:接受任意字符都转向其自身,且不是终态 不可达状态:从初态不可到达的状态 最小化算法对于DFA:D=(S,∑,move,s0,F) 初始化分∏={S-F,F} 开始新一轮划分,对于两个状态s,t,若它们接受任意字符后到达的状态同属于∏中的某一个状态集合,则将它们划分至一个状态,否则它们就是不同的状态。 遍历每个状态(不包括∏中不可再划分的状态集合中的元素),应用上述规则,产生新的∏ 循环2,直到∏中的每个状态集合都不可以再划分 得到最终的∏,包含s0的状态集合为新的初始状态,包含F的状态集合构成新的终态集,然后对于其中的每个状态集合,用状态集合中的某一个元素代替该集合,并继承所有对该集合的状态转移函数 删除死状态和不可达状态 例: 初始划分∏={ABCD,E} ∏[2]不可再划分,所以只遍历ABCD,ABCD接受a都到达B,B属于∏[1],ABC接受b分别到达CDC,CDC都属于∏[1]所以ABC不可区分,划分为同一组,而D接受b到达E,E属于∏[2],所以D划分为另一组,于是∏更新为{ABC,D,E} 同理对ABC遍历,∏更新为{AC,B,D,E} 对AC遍历,AC不可再划分,此时所有组都已经不可被划分,所以最终∏={AC,B,D,E} 选代表,用A代替组AC,更新状态转移表,删除死状态和不可达状态(此例没有)得到DFA如图 5. 词法分析器(基于DFA)5.1 表驱动型词法分析器使用时,驱动器调用DFA查表 5.2 直接编码型词法分析器将驱动器和DFA操作合并,直接模拟DFA输入 5.3 对比 表驱动 直接编码 分析器速度 慢 快 程序与模式关系 无关 有关 适合的编写方式 工具生成 手工编程 分析器的规模 较大 较小 三. 语法分析词法分析:字母是元素,组成字符串,记号的集合,线性结构语法分析:记号是元素,组成句子,句子的集合,树结构语法的含义: 语法规则:上下文无关法 语法分析:下推自动机,自上而下和自下而上分析 1. 基本概念语法分析器作用: 根据词法分析器提供的记号流,构造语法树 检查输入中的语法(可能包括词法)错误,并调用出错处理器进行处理。 源程序中出现的错误: 词法错误:如非法字符、拼写错误(20id,intege) 语法错误:语法结构出错,如缺少分号、begin/end不匹配 静态语义错误:如类型不一致、参数不匹配 动态语义错误(逻辑错误):如死循环、除数为0 错误的恢复策略 紧急方式恢复:抛弃若干输入,直到接受合法输入 短语级恢复:采用串替换方式(抛弃+插入)方式对剩余输入局部纠正 出错产生式:用出错产生式捕捉错误,用预制的短语级恢复 全局纠正:寻找相近输入序列 2. 上下文无关法(CFG)2.1 定义CFG是一个四元组:G=(N,T,P,S)其中: N是非终结符的有限集合 T是终结符的有限集合,且N∩T=ф P是产生式的有限集合(产生式A->a,其中A∈N(左部),a∈(N∪T)*(右部),若a=ε,则称A->ε为空产生式,也记为A->) S是非终结符,称为文法的开始符号 2.2 表示方法由产生式集表示CFG约定: 第一个产生式左部是文法开始符号S 大写英文字母表示非终结符,小写英文字母表示终结符分析树 当若干个产生式左部相同时,可以将它们合并为一个产生式,所有右部用或运算连接 例:简单的算术表达式可以表示为N={E} T={+,*,(,),-,id} S=Ep:E->E + EE->E * EE->(E)E->-EE->id或者表示为E->E + E|E * E|(E)|-E|id 2.3 上下文无关语言(由CFG定义的语言)推导推导是产生语言的方法 直接推导:对于产生式A->γ,令αAβ替换为 αγβ的过程叫做直接推导,记作αAβ⇒αγβ 零步或多步推导:α1⇒α2⇒…⇒αn,记作α1⇒∗αn,其中α1=α2的情况为零步推导 至少一步推导:对于零步或多步推导,若α1不等于αn则为至少一步推导,记作α1⇒^+^αn 若每次推导均替换句型中最左边非终结符,则称为最左推导,由最左推导产生的句型为左句型,另外最右推导也被称为规范推导 语言由上可知:由CFG G产生的语言L(G)被定义为:L(G)={ω|S⇒^+^ ω and ω∈T^∗^ } L(G)称为上下文无关语言,ω称为句子(句型:若 S⇒^∗^α,α∈(N∩T)^∗^则α为G的一个句型) 例:N={E} T={+,*,(,),-,id} S=E可有以下推导:E⇒−E⇒−(E)⇒−(E+E)⇒−(id+E)⇒−(id+id)其中-(id+id)为句子,其余除了E都为句型 2.4 分析树和语法树对于CFG的句型,其分析树(具体语法树)被定义为: 根由开始符号标记 对于推导出CFG的每一步推导 αAβ⇒αγβ,将γ中的每一个终结符、非终结符(或ε)从左到右添加为A的孩子 其语法树(抽象语法树)被定义为:3. 根与内部节点由操作符标记4. 叶子节点由操作数标记5. 用于改变优先级的括号被隐含在语法树结构中 例:左边为分析树,右边为语法树 2.4 二义性(了解什么是二义性,和消除二义性的方法,不要求具体实现)二义性:若文法 G 对 同 一句子产生不止一棵分析树,则称G是二义的造成文法二义性的原因:文法对文法符号优先级缺少规定消除二义性: 改写二义文法为非二义文法 规定二义文法中符号的优先级和结合性(3. 修改语言的语法) 2.5 左递归左因子左递归若文法G中的非终结符A,对某个文法符号序列$\\alpha$存在推导$A\\Rightarrow ^+A\\alpha$,则G是左递归的 消除左递归对于A⇒Aα1∣Aα2∣…∣Aαm∣β1∣β2∣…∣βn,其中αi非空,βj均不以A开始,用下述产生式替代:A⇒β1A′∣β2A′∣…∣βnA′A′⇒α1A′∣α2A′∣…∣αmA′∣ε 左因子若文法G中的非终结符A,存在推导A⇒αβ1∣αβ2,则文法有公共左因子 消除左因子对于A⇒αβ1∣αβ2∣…∣αβn∣γ,用下述产生式替代:A⇒αA′∣γA′⇒β1∣β2∣…∣βn 2.6 正规式的上下文无关法正规式所描述的语言均可用CFG表示,但反之不一定 构造法首先构造正规式的NFA,然后构造CFG: 若0为初态,则A0为开始符号; 对于move(i,a)=j,引入产生式Ai→aAj; 对于move(i,ε)=j,引入产生式 Ai→Aj; 若i是终态,则引入产生式Ai →ε。 经验法拆分正规式,如r=(a|b) * abb包含(a|b) *和abb两部分,则引入A->HT,H->ε| aH | bH,T->abb 2.7 其它文法文法G=(N,T,P,S)的每个产生式α→β中,均有α∈(N∪T)* ,且至少含有一个非终结符,β∈(N∪T)* ,则称G为0型文法。对0型文法施加以下第i条限制,即得到i型文法。 G的任何产生式α→β(S→ε除外)满足|α|≤|β| G的任何产生式形如A→β,其中A∈N,β∈(N∪T)* ; G的任何产生式形如A→a或者A→aB(或者A→Ba),其中A和B∈N,a∈T。 3. 递归下降分析器递归下降分析器使用了自上而下语法分析的思想,对于任意一个输入序列(记号流),从S开始进行最左推导,直到得到一个合法的句子或发现一个非法结构。自上而下语法分析是从开始符号一步步推导到句子的过程。递归下降分析器使用文法的状态转换图实现 3.1 文法状态转换图3.1.1 定义对于产生式A→X1X2…Xn,生成一个初态和终态,然后构造从初态到终态的路径,边标记依次为X1,X2,…,Xn。 3.1.2 化简对于产生式A→X1X2…Xn的状态转换图,化简如下: 标记为A的边可等价为标记ε的边转向初态 合并ε边连接的两个状态 合并不可区分的状态 3.2 EBNF文法EBNF文法有别于上下文无关法的一种文法,它使用以下特殊符号 { }:重复0或若干次 [ ]:可缺省 | :括弧之内的或关系 ( ):改变运算的优先级和结合性 3.3 分析器构造过程 构造文法的状态转换图并且化简 将转换图转化为EBNF表示 从EBNF构造子程序:每个非终结符都是一个函数,该非终结符的产生式右部中,遇到非终结符则进入该非终结符对应函数,遇到终结符则与输入记号进行匹配({}:while,[]:if,|:case) 4. 非递归预测分析器非递归预测分析使用自上而下的思想非递归预测分析使用了预测分析表来辅助推导句子。下面将阐述如何构造预测分析表以及非递归预测分析器的工作步骤 4.1 预测分析表理解:预测分析表是一张可以根据当前非终结符和我要经过推导得到的终结符告诉我我需要将该非终结符替换成什么内容,如果预测分析表对应处是空白,则意味着该非终结符没法推导得到该终结符 4.1.1 First集合文法符号序列α的First集合为:First(α)={a|α⇒^^a…,a∈T},特别的,若α⇒^^ε,则ε∈First(α) 理解:对于每个α能推导到的序列,该序列以终结符开头,则将该终结符加入First(α)集合 计算方法: 若X是终结符,则First(X)={X} 若X是非终结符,且有X⇒^*^ε,则将ε加入First(X) 若X是非终结符且有X⇒Y1Y2…Yk,则应用以下规则:将First(Y1)中所有元素(除ε)加入到First(X),若First(Y1)包含ε,则将First(Y2)元素加入。以此类推若First(Yj)包含ε则将First(Yj+1)元素加入(除ε) 4.1.2 Follow集合非终结符A的Follow集合为:Follow(A)={a|S⇒^*^…Aa…,a∈T},若A是某句型的最后一个符号,则#∈Follow(A) 计算方法: 若S是开始符号,加入#到Follow(S) 若有产生式A->αBβ,则将First(β)的所有元素(除ε)加入到Follow(B) 若有产生式A->αB或A->αBβ(且First(β)包含ε),则将Follow(A)的所有元素加入到Follow(B) 例: 4.1.3 构建分析表 对文法的每个产生式A->α,执行2和3 对First(α)中的每个终结符a,令M[A,a]=α 若ε∈First(α),则对Follow(A)中的每个终结符b(包括#),令M[A,b]=α 4.2 工作过程 初始栈中为#S,匹配符号为原句子第一个符号 若栈顶为终结符:如果栈顶符号=匹配符号,pop栈,然后匹配符号移至下一个符号;若栈顶为非终结符,根据栈顶符号和匹配符号查询预测分析表,将栈顶符号替换为预测分析表对应内容; 若不满足2中所有情况,则报错,该语句不符合语法 循环2、3,直到栈中只剩#,并且原句子所有符号都已经被匹配过,则该语句符合语法,否则出错 4.3 LL(1)分析器LL(1)分析器是非递归预测分析器的特例,当且仅当它的预测分析表没有多重定义的条目(一个格子里出现多个选项),构成该预测分析表的文法被称为LL(1)文法。(LL(K)的含义是:L:从左到右扫描,L:最左推导,K:向前看K个符号知道下一个动作) 判定方法文法G是LL(1)的,当且仅当G的任意两个产生式 A->α|β 满足: 对任何终结符a,α和β不能同时推导出以a开始的串 α和β最多有一个可以推导出ε 若β可以推导出ε,则α不能推导出以Follow(A)中终结符开始的任何串 二义性、有左递归或者左因子的不是LL(1)文法 5. 移进规约预测分析器非递归预测分析其使用了自下而上语法分析的思想,对于任意一个输入序列(记号流)ω,从ω开始进行反复用产生式的左部替换产生式的右部、谋求对ω的匹配,最终得到文法的开始符号,或者发现一个错误。自下而上语法分析是从句子一步步推导得到开始符号的过程移进规约预测分析器通过移进规约分析表指导其工作,通过不断移进(将剩余输入的下一字符入栈)和规约(将栈顶句柄(符合某一产生式右部)替换为产生式左部)完成分析。要想构建移进规约预测分析器,就要先想清楚如何识别一个文法的活前缀 5.1 基础概念5.1.1 短语设αβγ是文法G的一个句型,若存在S⇒αAγ,A⇒+β,则:称β是句型αβγ相对于A的*短语特别的,若有A->β,则称β是句型αβγ相对于A->β的直接短语一个句型的最左直接短语称为句柄 利用语法分析树快速查找短语: 短语:以非终结符为根的子树中所有从左到右的叶子 直接短语:短语中以非终结符为根的子树树高为2(其和其为根的子树中所有叶子都为父子关系) 句柄:最左边的直接短语 5.1.2 最左规约文法G的句子α的一个最左归约为一个序列 αn,αn-1,…,α0,其中 αn = α α0 = S(S是G 的开始符号) 对任何i(0<i<=n),αi-1是将αi中的句柄替换为 相应产生式左部非终结符得到的。 最左规约又称规范规约,最左规约的逆过程是一个最右推导(规范推导) 例: 利用语法分析树快速得到最左规约:最左规约在语法分析树上的表现是不断剪句柄的过程,不断在语法分析树上剪去当前语法分析树的句柄,直到只剩下开始符号例: 5.1.2 活前缀5.1.3 项目项目:在产生式的基础上,在其右部某一位置添加一个 .可移进项目:项目A->α.β,且β不为空可规约项目:项目A->α.β,且β为空 理解:项目A->α.β代表的含义是,我现在已经有了α,我期待一个β出现在α后面,这样我可以把αβ规约为A例:对于产生式E->E-T|T,其能生成的全部项目有:E->.E-T ; E->E.-T ; E->E-.T ; E->E-T.E->.T ; E->T. 5.1.4 拓广文法拓广文法G‘=G∪{S’->S} 5.1.5 closure项目集项目集I的closure(I)是这样一个项目集: I中所有项目属于closure(I) 若对于非终结符B,A->α.Bβ属于closure(I),则若有形如B->γ的产生式,那么B->.γ项目属于closure(I) 例: 5.1.6 goto项目集对于项目集I,终结符或非终结符X,goto(I,X)是这样一个项目集: 项目集I中若存在形如A->α.Xβ的项目,则A->αX.β属于goto(I,X) 例: 5.2 识别活前缀的DFA 计算DFA的初态,初态I0=closure({S’->.S}) 对于状态集Ii,对其中形如A->α.Xβ的产生式,引入move(li,X)=lj,其中lj的内容=closure(goto(Ii,X))(若lj内容已经和DFA的某个状态的内容一样,则li只用指向已经存在的那个状态,否则新建一个状态) 对于所有未被计算过的状态集执行2,直到所有状态集都被计算过 内容中包含S’->.S的状态为初态,包含S’->S.的状态为终态 例:其中I0是初态,I1是终态 5.3 SLR(1)分析表移进规约分析表包含两部分,一部分是action用于指导移进规约(横标题为终结符的部分),另一部分是goto用于指导状态转移(横标题为非终结符的部分)(LR(K)的含义是:L:从左到右扫描,R:逆序的最右推导,K:向前看K个符号知道下一个动作) 5.3.1 定义如果项目没有冲突或者冲突可以解决,则该分析表是SLR(1)分析表,构造其的文法是SLR(1)文法 项目冲突: 移进/归约冲突:A→β1.β2和B→β.:既可移进又可归约 归约/归约冲突:A→α.和B→β.:均可指导下一步分析 SLR(1)方法解决冲突: 移进/归约冲突:若FIRST(β2)∩FOLLOW(B)=Φ,冲突可解决 归约/归约冲突:若FOLLOW(A)∩FOLLOW(B)=Φ,冲突可解决 另外补充,如果有冲突就不是LR(0)文法 5.3.2 构造分析表根据识别活前缀的DFA构造SLR(1)分析表: 对于DFA每个状态转移move[i,x]=j : 若x是终结符,令action[i,x]=sj 若x是非终结符,令goto[i,x]=j 每个状态i的可归约项A→α.: 若该项目为S’→S.,令action[i, #]=acc 否则,对于Follow(A)中的每个元素b,令action[i,b]=rk。 其中k代表第k个产生式,该产生式可以生成项目A→α. 例: 5.4 工作过程对于输入序列ω,分析器分析器的步骤为: 初始栈中为(#0),剩余输入为(ω#) 设栈中最右符号为T,剩余输入最左符号为a,查询分析表得到action[T,a]=Z,若: Z为sj,将a和j入栈,a从剩余输入移走。(完成移进) Z为rj,找到第j个产生式,不断弹出栈中内容,直到弹出的所有终结符和非终结符构成了产生式的右部,此时栈中最右符号是一个状态Ta,将产生式左部F入栈,然后将goto[Ta,F]的内容入栈(完成规约) Z为acc,分析成功,接收该输入序列 Z为空白,报错 循环2,直到分析成功或报错 四、语义分析语法是语言的结构语义是附着于语言结构上的实际含义语义分析作用,检查结构正确的句子所表示的意思是否合法,执行规定的语义动作 1. 语义规则1.1 属性属性就是附着在非终结符或终结符的一些值,如A.value可以代表附着在A上的值,A.type可以代表附着在A上的类型,这些值是由自己定义的 1.2 语义规则对于产生式A->X1X2…Xn,其语义规则可以表示为有关属性的函数f:b=f(c1,c2,…,ck),称b依赖于c1,c2,…,ck 若b是A的属性,c是x中或A的属性,则b是A的综合属性 若b是x中xi的属性,c是x中或A的属性,则b是xi的继承属性 若b为空(即语义规则为f(c1,c2,…,ck)),则其为A的虚拟属性 语义规则的两种形式: 语法制导定义:抽象的属性和运算。如公式 翻译方案:具体的实际和运算。如程序 例:表达式的语义规则 1.3 语法制导翻译语法制导翻译:以语法分析为基础,伴随语法分析的各个步骤,执行相应语义动作 用文法符号属性代表语言结构的意思 用语义规则规定语言结构的关系(计算属性) 执行语义规则 简而言之就是在语法分析的某些过程中,根据语义规则做计算属性、返回属性等操作 1.3.1递归下降分析器语法制导翻译在产生式右部任意位置语义操作,文法符号的属性通过返回值、变量、参数等体现 1.3.1 移进规约分析器语法制导翻译方式一:在规约时,执行相应语义操作方式二:增加语义栈,存放文法符号属性值,根据属性进行相应语义操作 2. 中间代码的语义规则2.1 中间代码中间代码的表现形式有后缀式、三地址码、注释语法树 2.1.1 后缀式后缀式:操作符在前,操作数紧随其后如3+5 * 2/7的后缀式为:352 * 7/+ 计算后缀式:从左往右扫描后缀式, 如果是操作数,入栈 如果是操作符,查算符表得到该操作符是几元运算符,然后从栈中取出对应数量操作数,计算结果,将结果入栈 2.1.2 三地址码类型有: 二元运算:result=arg1 op arg2 一元运算:result=op arg1 直接拷贝:result=arg1 其中:op代表操作符,arg代表操作数,用操作符计算操作数,然后将等式右边结果存放在result中 例:x=a+b * c的三地址码序列为T1=b * cT2=a+T1x=T2 三地址码有两种表现形式 三元式:(i)(op,arg1,arg2)序号i唯一标识了三元式,且代表了结果存放的位置 四元式:(i)(op,arg1,arg2,result)序号不再代表结果存放位置,而由result代表 例:x=a+b * c的三元式序列为(1)( * ,b,c)(2)(+,a,(1))(3)(=,x,(2))四元式序列为:(1)( * ,b,c,T1)(2)(+,a,T1,T2)(3)(=,x,T2,T3) 2.1.3 注释语法树注释语法树就是在语法树的基础上,在节点处标记一些信息 树的优化表示DAG:在构建的时候,查找要构造的节点是否存在,若已存在,则无需构造新的节点,直接返回指向已有节点的指针即可 2.3.4 注释语法树转后缀式和三地址码转后缀式:对树进行深度优先后序遍历,遍历的序列就是后缀式 转三地址码:对树进行深度优先后续遍历,每遇到一个非叶子节点,就生成一个三地址码,操作符是其本身,操作数是其孩子的信息。 2.2 生成中间代码的语义规则2.2.1 四元式语义规则 2.2.2 注释语法树语义规则 3. 声明语句的语义规则声明语句的翻译主要是为变量分配存储空间,并将变量信息正确的填入符号表中 3.1 符号表符号表就是用于区分记号类型都为ID的变量、标识符、保留字、特殊符号,并记录相关信息,如作用域 存储方式 直接存储:变量名就是变量文本本身 间接存储:变量名通过其它文本存储,如存储1代表aaa,存储2代表abd。解决了直接存储由于变量名不定长而难以存储的问题。 类型 线性表 哈希表 3.1 变量声明的翻译 3.2 过程声明的翻译3.2.1 过程简介过程和函数类似,有过程头(包含参数)、过程体,但是没有返回值 过程的参数调用有三种类型: 值调用:形参 引用调用:实参 复写恢复类型:过程内对参数修改不会影响实参,返回时将形参内容恢复给实参 换名调用:宏定义左值 右值(等式右边) 3.2.2 符号表树由于作用域的存在,若过程允许嵌套,那么就不单单是往符号表里填过程声明的信息,而是要构建符号表树来表示每个过程定义的变量的作用域,每个过程就对应一个符号表 作用域规则 静态作用域规则:静态读取程序就能知道名字的作用域 最近嵌套原则:若程序块A有变量x的声明,则x的作用域包括A,若A中没有x的声明,但是引用了x,x的由嵌套A的程序块中离A最近且有x的声明的程序块定义 符号表树定义 3.2.3 过程声明的翻译 函数mktable(previous):建立一个新的符号表节点,并返回指向该节点的指针。参数previous是逆向链,指向该节点的前驱,即外层的符号表节点。 过程enter(table, name, type, offset):在table指向的符号表节点中,为变量name建立新的条目,包括名字的类型和存储位置等。 过程addwidth(table, width):计算table指向的符号表节点中所有条目的累加宽度,并记录在table的头部信息中。 过程enterproc(table, name, newtable):在table指向的符号表节点中,为过程name建立一个新的条目。参数newtable是正向链,指向过程name自身的符号表节点。 4. 可执行语句的语义规则4.1 简单算术表达式的语义规则涉及到强制类型转换的语义规则还要改变,核心思想是低精度类型转为高精度类型进行在进行运算 4.2 数组元素引用的语义规则a[0..2, 2..4]代表三行三列,其中列标从2开始若以行存放,则顺序为:a[0,2] a[0,3] a[0,4] a[1,2] a[1,3] a[1,4] a[1,2] a[1,3] a[1,4]若以列存放,则顺序为:a[0,2] a[1,2] a[2,2] a[0,3] a[1,3] a[2,3] a[0,4] a[1,4] a[2,4]下面按以行存放设计翻译方案: 属性.array:数组名在符号表中的入口(数组首地址a)。 属性.dim:数组维数计数器,记录当前分析到的维数。 属性.place:下标列表EL:存放vj=vj-1*dj+ij (j=2,3,…, n)的地址(临时变量),简单变量id:仍然表示简单变量的地址,数组元素id[EL]:存放不变部分,一般可以是一个临时变量。 属性.offset:保存数组元素地址中的可变部分,简单变量的offset为空,可记为null。 函数limit(array, k):计算并返回数组array中第k维成员个数dk。 4.3 布尔表达式的语义规则布尔表达式:由not,and,or以及关系运算构成的表达式,结果为true或者false 4.3.1 直接计算布尔表达式的语义规则将布尔表达式翻译成三地址码 not、and、or构成的运算的三地址码A or B and not C的三地址码:T1 := not CT2 := B and T1T3 := A or T2 关系运算构成的三地址码a<b的三地址码:(0) if a<b goto (3)(1) t1 := 0(2) goto (4)(3) t1 := 1(4) … 语义规则 4.3.2 短路计算布尔表达式的语义规则短路计算:一旦能确定布尔表达式,就返回结果,如A or B,如果A已经为ture,表达式结果就为true,不必考虑B的值。短路计算还可以这样的逻辑,如if (p!=NULL&&p->a==0),如果p为空就不会执行p->a,避免了错误 核心思想真出口:A的属性A.ture,指向A为真时要前往的地址假出口:A的属性A.false,指向A为假时要前往的地址 对于布尔表达式E-> E1 or E2E1.ture=E2.true=E.trueE2.false=E.falseE1.fasle=E2.code的地址即如果E1或者E2为真,那么表达式E就为真,转向E为真要转向的地址如果E1为假,那么还要继续执行E2的代码,如果E2还为假,那么表达式E为假,转向E为假要转向的地址可见.ture属性是一个继承属性 拉链回填技术为设计短路计算的翻译方案,要设计一种翻译方案,使得仅对分析树进行一次遍历即可生成所需的中间代码序列,确定所有真出口假出口。为此设计拉链回填技术:当三地址码中的转向不确定时,将所有转向同一地址的三地址码拉成一个链;一旦所转向的地址被确定,则为此链上的所有三地址码中回填入此地址。 翻译方案新增属性与函数: 属性. tc:真出口链,链接所有转向同一真出口的三地址码; 属性.fc:假出口链,链接所有转向同一假出口的三地址码。 函数mkchain(i):为序号是i的三地址码构造一个新链,且返回指向该链的指针; 函数merge(P1,P2):合并链P1和P2,且P2成为合并后的链头,并返回链头指针;过程backpatch(P,i):向P链中所有三地址码均回填 出口地址为值 i 4.4 控制语句的翻译 结语:缺少活前缀  ","categories":[{"name":"笔记","slug":"笔记","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/"},{"name":"课程","slug":"笔记/课程","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/%E8%AF%BE%E7%A8%8B/"}],"tags":[{"name":"编译原理","slug":"编译原理","permalink":"http://example.com/tags/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/"}]},{"title":"计算机组成原理II笔记","slug":"计算机组成原理II笔记","date":"2023-11-24T07:26:00.000Z","updated":"2024-01-12T09:21:37.138Z","comments":true,"path":"2023/11/24/计算机组成原理II笔记/","link":"","permalink":"http://example.com/2023/11/24/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86II%E7%AC%94%E8%AE%B0/","excerpt":"","text":"简介:本课程缺少8259、8253、8255等芯片的讲述,建议b站搜索车向泉微机原理(最后几p)自行观看   一、高速缓冲存储器1. 原理系统全由硬件实现,比主存访问速度快很多 1.1 理论依据时间局部性:某位置若被访问,则近期内很可能又被访问空间局部性:某位置若被访问,则临近的位置也可能被访问 1.2 构成小容量高速存储器、相联存储器构成的存储表、替换管理模块 1.3 工作过程cpu访问主存时,先访问相联存储表,若发现要访问的信息就在cache中(命中),通过存储表将地址转换为cache地址,从cache中读出信息。若未命中,则cpu从主存中读取信息,同时将该信息替换cache中的某一块(为提高效率,通常将cache和主存分成容量相等的块) 2. 映射方式1.1 全相联映射主存主存分块主存地址为主存块号+块内地址主存的任何一块可装入cache的任何一块中 相联存储器相联存储器有和cache块数相同的单元(相联存储器的单元地址和cache块号一一对应)相联存储器中每个单元存储主存的某个块号 cachecpu访问时,查询存储器,如果要访问的块号就在存储器的某个单元中(命中),cache地址=单元地址+块内地址,用该地址访问cache 优点灵活性(主存任意块都可装入cache任意块,全部装满后才需要替换) 缺点要求相联存储器容量最大地址变换机构最复杂(比较检索式需要全部检索相联存储器才能获得cache块号) 1.2 直接映射:主存将主存按照每区为cache的容量分区,并在每个区内分块。主存中块号相同的块只能装入cache中相同块号的块。(如:主存第一区0号块和第二区0号块只能装入cache中第0块)主存地址为主存区号+块号+块内地址 相联存储器相联存储器有和cache块数相同的单元相联存储器每个单元存储主存区号 cachecpu访问时,查询存储器,比较地址为要访问的块号的单元,若该单元存储的区号和要访问的主存区号相同,则命中cache地址=要访问的块号+块内地址 优点地址变换简单(只需要以主存地址的块号访问存储器) 缺点效率低(不允许不同区的相同块号同时出现,可能会出现频繁替换的情况,所以更适合用于大容量cache) 1.3 组相联映射:组相联映射是组间直接映射而组内块全相联映射,结合了直接映射和全相联映射 主存主存先按cache容量分区,区内分组,组内分块主存地址为主存区号+组号+块号+块内地址 相联存储器相联存储器和cache一样分组分块相联存储器存储主存区号+块号 cachecache和主存一样分组分块cpu访问时,在存储器中对应组查询,若能在该组中找到某个单元存储的信息和要访问区号+块号相同,则命中,cache地址=组号+单元地址+块内地址 3. 替换算法 随机替换RAND:随机函数发生器产生需要替换的块号,命中率低 先进先出算法FIFO:将最先装入cache的块替换,命中率低 近期最少使用算法LRU:将近期最少使用的块替换。对每块设置一个计数器,某块命中时,将该块计数器清零,其它块计数器加一,替换时将计数值最大的块替换。命中率更高 最不经常使用算法LFU:将一段时间里访问次数最少的块替换。对每块设置一个计数器,某块命中时,该块计数器加一。需要替换时将计数值最小的块替换,并将所有块计数器清零 最优替换算法OTP:将最长时间不会被访问的块替换。该算法要求已知访问的顺序,替换时在访问序列中查找最长时间会被访问的块,将其替换。该算法不可实现,可用于衡量其他算法 4. 主存cache一致性cache内容是主存的复制,所以cache要和主存内容保持一致,采用方法有: 全写法:当cpu写cache命中时,数据写入cache时的同时写入主存。未命中时,直接对主存修改,而后将主存内容调入cache。速度慢 写回法:cpu写cache命中时,只将数据写入cache,只有当cache中被修改过的块替换出去时才写回主存。未命中时,将相应主存块直接调入cache,然后对cache修改。速度快,但存在替换前不一致问题 5. 性能 命中率H:H=命中次数/访问总次数 理论命中率:H=1-S^-0.5^S为cache容量 平均访问周期T:T=H * Tc+(1-H) * (Tc+Tx)Tc为cache访问周期Tm为主存访问周期Tx=n * Tm,n代表cache以n个字(节)为一块 加速比Sp:Sp=Tm/T 存储器平均价格C:C=(C1 * S1+C2 * S2)/(S1+S2)S1,S2为主存与cache容量C1,C2为主存与cache单位价格 总失效率:总失效率=一级cache失效率 * 二级cache失效率失效率即为未命中率 二、流水线1. 基本概念流水线技术是将重复的处理过程分解为若干子过程,且每个子过程可以与其它子过程同时执行 每个子过程需要的时间应尽可能相等 每个子过程称为流水线的级/段 级的数量为流水线深度 第一个任务从进入到流出的时间叫做通过时间,在此之后每个时钟周期(拍)就流出一个结果 流水线适合于大量重复过程 时空图:用于描述流水线的图,横坐标代表时间,纵坐标为事件/指令/任务 2. 分类2.1 按使用级别(系统层次)分 系统级流水线/宏流水线:由多个处理机组成流水线,每个处理机完成任务的一部分 处理机级流水线:由多个部件组成流水线,流水线位于处理机内部 部件级流水线:由多个微程序控制单元组成,流水线位于部件内部 2.2 按功能数 单功能流水线:只含一种功能 多功能流水线 2.3 按功能连接 静态流水线:流水线同一时间内只能按同一种功能的连接方式工作。如流水线有A、B两功能,同一时间要么按AB这样的方式工作,要么按BA方式工作 动态流水线:同一时间可以实现多种功能的连接。效率更高、实现麻烦(需要硬件能够识别当前在完成哪一种连接方式) 2.4 按反馈功能 线性流水线:没有反馈回路 非线性流水线:有反馈回路,可以根据反馈来决定下一步进行哪个功能 2.5 按出入顺序 顺序流水线:任务输入顺序和流出顺序相同 乱序流水线 3. 流水线存在问题—相关相关:相邻任务因存在某种制约、依赖、影响,使任务之间有了某种关联,其以影响了流水线性能,甚至影响流水线正常工作 3.1 结构相关结构相关指资源冲突,无法满足同时重叠执行两个任务,如取指令和取数据,都要访问主存。 解决方法 增加资源副本:如设计数据存储器和指令存储器 延迟/暂停流水线:插入流水线气泡(气泡只占用资源但没有实际操作 改变资源:改变资源使它们能并发使用 3.2 数据相关数据相关指某任务需要用到另一个任务的结果,因而不能重叠执行 分类: 先写后读相关:要求写完之后读,如果在写还在执行的时候读就会出现错误 先读后写相关:要求读完之后写,如果在读之前写了就会出现错误 先写后写相关:要求第一个写比第二个写先执行,如果第二个写先执行就会出现错误 解决方法 直通技术(定向技术):在流水线之间设置直接连接通路,把相关的任务的前一个任务的结果直接送给后一个任务,后一个任务就无需从资源里读数据,没有冲突 增加专用硬件:使用互锁硬件,互锁硬件检测是否有数据相关,如果有就使流水线停顿,直到相关消失 利用编译器(流水线调度):编译器对指令重新排序或插入空指令 3.3 控制相关控制相关/全局性相关是指存在分支转移等指令,会打乱流水线指令顺序 解决方法 冻结流水线:当检测到分支指令,就在转移前保存或删除紧随分支指令之后的指令,确定出新的PC值后,再填充流水线 预取分支目标:当条件分支指令被识别时,分支目标被预取,保存到分支指令被执行,如果分支跳转,已取到的指令可立即执行 多流:在条件分支的两路上同时取指令,并将指令保存到分支指令被执行的时候,分支指令执行时,真的执行通路即刻可以获得 循环缓冲器:保存最近获取的n条顺序的指令,分支发生时,先检查分支目标是否在缓冲其中 分支预测: 静态分支预测: 预测分支不会发生:在知道分支结果之前,流水线流动分支指令和紧随其后指令,如果分支预测失败,流水线正常执行,如果预测成功,用空操作代替已经取得的指令,并到目标地址重新取指令。如果程序中大多数条件是用于出错检测,那么这种机制合理,因为出错的概率是小的 预测分支总是发生:一旦分支转移成功,从目标地址取指令。如果程序包含较多循环,那么这种机制有利。 由编译器预测:编译器预测条件转移,认为会转移时将指令某位置1,硬件遇到这样的指令就按照指示采取行动 动态分支预测:见书p201 文字表述过于麻烦 延迟分支:将跳转指令前的某些指令放到延迟槽,判断条件跳转后,将延迟槽执行完后在执行跳转限制条件:被移动指令和移动过程中经过的指令不相关且被移动指令不破坏条件 4. 性能4.1 CPI每条指令所需要的平均时钟周期带相关的CPI:=理想CPI+相关造成的停顿周期数/指令数=理想CPI+相关造成的停顿周期数 * 相关指令频度 4.2 吞吐率TP单位时间内流水线完成的任务数根据时空图计算,可以将时空图分为第一个任务完成前和完成后,完成后每个时间周期就完成一个任务 4.3 最大吞吐率TPmax流水线在达到稳定状态时的吞吐率 若各段时间相等:假设每段时钟周期为t,则TPmax=1/t 各段时间不等:TPmax=1/max{ti} 提高最大吞吐率的方法: 细分瓶颈(耗时最长的段):将瓶颈再细分为若干个小段 重复:增加一个和瓶颈一样的段,使它们并行工作 4.4 加速比S代表比原来性能提升了多少S=不采用流水线完成任务时间/采用流水线完成任务时间S=流水线深度/CPI 4.5 效率E流水线有工作时空的占比E=任务覆盖的面积/任务开始到结束时空图的总面积 E与S关系 E=S/m,m为流水线段数 5. 提高流水线性能5.1 提高指令级并行指令级并行是指当指令不相关时,它们能在流水线中重叠执行 5.1.1 乱序执行乱序执行是指跳过存在相关的指令去执行不相关的指令,使顺序被打乱但结果不变。乱序执行会导致中断不准确 5.1.2 寄存器重命名对寄存器重命名,有效减少先写后读,先写后写相关 5.1.3 推测执行在不知道是否需要执行前就执行代码的技术,核心思想是允许指令乱序执行,但是要求指令按序提交,并在提交前阻止不可恢复的动作。指令提交之前不会实际更新寄存器,发现预测错误时,处理器撤销推测的动作。该方法能够保证精确的中断和异常 5.2 多指令流出技术5.2.1 超标量处理机处理器内有多条流水线,这些流水线能并行处理,流水线数量称为度。处理n条指令的时间等于一条流水线处理n/度条指令时间。相对性能最高 5.2.2 超流水线处理机普通流水线一个时钟周期只能流出一条指令,而超流水线处理机把一个时钟周期划分为更小的段(划分段数称为度),使一个时钟周期能流出更多条指令(每一段都流出一条指令)。相对性能最低,由于指令启动延时大,条件转移损失大 5.2.3 超标量超流水线处理机结合二者,相对性能中等 5.2.4 超长指令字处理机采用并行编译技术(软件实现),将能够并行执行的不相关的多条指令组合在一起,构成一条位数超长的指令 三、输入输出系统1. 总线总线:连接在两个以上数字系统元器件的信息通路 1.1 分类1.1.1 片内总线芯片内部各功能元件的连接线典型片内总线:如AMBA、Wishbone 1.1.2 元件级总线电路板内各元器件的连接 1.1.3 内总线(系统总线)电路板的连接。直接影响计算机性能 典型内总线: PC/XT总线:最早的以8088为CPU的系统总线 ISA总线:16位,包含24个地址线、16个数据线 EISA总线:32位,适用于32位CPU PCI总线:并行传输方式总线设备工作独立于CPU高性能即插即用支持多主控设备 PCIE总线:与PCI相比:串行传输方式,每个通道独享带宽支持双向传输和数据分通道传输点到点互联、交换技术,降低复杂性、开发设计成本,提高了性价比 1.1.4 外总线(通信总线)计算机与外设或计算机系统之间的连接。 典型外总线: RS-232C:串行外总线,传送距离远,结构简单 SCSI总线:并行外总线面向服务器、磁盘阵列等高端存储器传输速率高,提高CPU效率支持多任务不支持热插拔 SAS总线:兼容性好点对点架构全双工设计支持热插拔 ATA总线:并行总线通常用于PC硬盘、光盘等外设接口 SATA总线:串行总线,对ATA的改进 通用串行总线USB:定义四个信号:Vusb(+5V),GND,D+(信号正端),D-(信号负端),这四个信号用四芯电缆进行传送支持即插即用、热插拔扩展性好,可靠性高,标准统一可通过总线供电 IEEE-1394:串行总线即插即用、热插拔支持同步异步传输 1.2 结构单总线结构双总线结构多总线结构 1.3 传送方式 串行传送 并行传送 同步传送:同步总线包括一个公用的时钟,和一个固定的协议,该协议用于通信。同步方式不需要应答,实现方便,但每个设备以相同的频率工作,总线速度由慢设备决定。总线不宜过长 异步传送:异步总线使用握手协议通信。允许总线周期可变 1.4 性能:位宽: 总线能同时传送的二进制数据的位数,即数据总线的位数工作频率:以Mhz为单位带宽(数据传输速率):单位时间内总线上传输的数据量,总线带宽=总线工作频率×每个时钟周期传送的数据次数×总线位宽/8。单位为MB/s 注意:计算总线传输n位数据需要的时间别忘了额外加上传输地址的时间 1.5 总线仲裁决定哪个设备可以使用总线 1.5.1 集中式仲裁:使用集中的仲裁器串行总线仲裁(链式查询)并行总线冲裁(独立请求)计数器查询仲裁(轮询)见书P268,文字表述过于麻烦 1.5.2 分布式仲裁不需要集中的冲裁器,每个主设备有自己的仲裁号和仲裁器。当请求总线时,设备把它们的仲裁号发送到仲裁总线上,每个仲裁器将从仲裁总线上得到其它仲裁号,和自己的仲裁号比较,如果仲裁总线上的号大,则它的请求不予相应。可见仲裁号越大优先级越高 2. 存储器设备2.1 磁表面存储器如磁盘和磁带 2.1.1 原理用磁状态保存二进制信息,记录时将二进制信息变化为电流进而实现磁记录层上磁场的变化。方式有: 归零制RZ:数据0代表负电流脉冲,1代表正电流脉冲,两个数据间电流归零 不归零制NRZ:在数据发生变化时电流改变 见1不归零制NRZ1:遇到数据1时电流改变 调频制FM:写入每个数据都要将电流翻转,且遇到数据1时,电流中心再翻转 改进调频制MFM:写入1时,电流中心翻转,遇到连续两个或以上0,每位电流翻转一次 调相制PM:用电流方向表示0/1,如电流中心从0到1(正方向)代表1 2.1.2 性能 自同步能力R:从磁道中读出的脉冲序列中提取时钟周期的难易程度R=最小磁化翻转间隔/最大磁化翻转间隔 编码效率(记录密度)n:n=位密度/最大磁化翻转密度=1/记录1位二进制编码需磁化方向最大改变次数 2.2硬磁盘存储器2.2.1 结构 记录面:磁盘中可记录信息的磁介质表面 磁道:记录面上若干个同心闭合圆环 扇区:磁道分为的若干段。第一个扇区为主引导扇区,存储主引导记录和磁盘分区表 2.2.2 数据格式 标志区:每一扇区的开始,包含磁道号+磁头号+扇区号+CRC校验码 数据区:存放数据和校验码 间隙:用于缓冲 2.2.3 性能 道密度:沿磁盘半径方向,单位长度内磁道的数目 位密度:磁道圆周上单位长度存储的二进制位数 存储容量:磁盘能存储的二进制信息总量,有以下两种: 非格式化容量=位密度×内圈磁道周长×每个记录面上的磁道数×记录面数 格式化容量=每个扇区的字节数×每道的扇区数×每个记录面上的磁道数×记录面数 寻道时间:磁头从某一位置移动到指定位置所需时间。是个常数,通常为几ms 等待时间:指待读取的扇区转到磁头下方所需时间,一般选用磁盘旋转一周所用时间的一半,通常为几ms 平均存取时间:其等于寻道时间+等待时间 转速:硬盘内驱动电机主轴的旋转速度 数据传输速率:单位时间内读写的字节数。数据传输速率=每个扇区的字节数×每道扇区数×磁盘转速 2.2.4 磁盘阵列RAID技术一般在SCSI磁盘驱动器上实现。RAID提供了专用服务器中接入多个磁盘时,以磁盘阵列的方式组成一个超大容量、响应速度快、可靠性高的存储子系统。其有六种标准 冗余无校验的磁盘阵列RAID0:至少需要两个硬盘,读写速度最快,但安全性不高 镜像磁盘阵列RAID1:增加镜像磁盘机使得安全性高、可靠性强、快速恢复数据 ,但磁盘冗余,利用率50% 并行海明纠错阵列RAID2:采用海明冗余纠错码,可靠性高、可确定出错硬盘,但磁盘冗余,开销大 奇偶校验并行为交错阵列RAID3 … 3. 输入输出技术输入输出技术实现了外设的输入输出,其要满足数据传输速率尽可能高且不丢失数据开销尽可能小,充分利用硬件资源 输入输出地址编制方式: 统一编址:外设地址和内存地址统一安排在内存地址空间,即把内存地址分配给外设。用于内存和用于外设的指令一样。优点:可以用访问寄存器指令直接访问IO。缺点:IO占用寄存器空间、指令长度比单独IO指令长,执行时间长 独立编址:外设和内存地址相互独立,各自有自己的寻址空间。用于内存和用于外设的指令不一样 3.1 程序控制方式多用于慢速的外设 3.1.1无条件传送方式在需要的时刻让CPU直接与外设进行输入输出操作。实现简单,只需要提供接口和相应的输入输出指令 3.1.2 查询传送方式利用程序不断询问外部设备的状态,根据它们所处的状态来实现数据的输入和输出。适用于不总是准备好的外设,但降低了CPU效率,无法对突发事件及时响应 3.2 中断方式提高了CPU效率,能对突发事件及时响应 3.2.1 中断概念中断源:引起中断的事件内部中断源:处理机内部的中断事件。如浮点数运算上溢等外部中断源:外设的中断事件。如外设请求输入输出中断优先级控制解决的问题:不同优先级中断源同时提出请求,先响应高优先级、CPU正在进行中断服务时,更高优先级的中断提出请求 3.2.2 中断向量表向量地址=中断向量码(中断类型码)×4(每一个中断占四个字节)偏移地址IP=向量地址加一内容+向量地址的地址内容段地址CS=向量地址内容加三+向量地址加二的地址内容 3.2.3 中断过程(以外不可屏蔽中断INTR为例) 中断请求:外部设备产生一个有效的中断请求信号,保持到CPU发现,响应后撤销请求 中断承认:满足以下四个条件,中断请求才会被承认 一条指令执行结束 CPU处于开启中断状态 没有复位、保持、非屏蔽中断请求等优先级高于该请求的请求发生 开中断指令、中断返回指令等特殊指令执行完还需要执行另外的一条指令后才能响应请求 断点保护:中断事件处理结束后必须回到被中断的程序,因此必须进行断点保护。一部分为CPU硬件自动保存断点的部分信息,另一部分为程序设计者完成(保护中断要用到的所有寄存器)。通常用压入堆栈的方法实现 中断源识别:两种方式: 软件查询:利用输入接口将多个中断源的状态读入,逐个查询 中断向量法:给每个中断源分配一个特定的中断服务程序的入口地址(中断向量) 中断处理:一般根据预先确定的程序实现 断点恢复:恢复程序设计者保护的中断要用到的所有寄存器,通常由弹出堆栈指令完成 中断返回:中断返回是一条IRET指令,命令CPU恢复CPU硬件自动保存断点的部分信息 3.3 DMA方式无论是查询方式还是中断方式,CPU都需要花费时间执行指令,为提高传输速率引入直接存储器存取DMA方法DMA原理就是从CPU中获取总线控制权有三种方式 3.3.1 CPU停止工作方式DMA发送请求,使CPU放弃总线控制权 3.3.2 周期挪用方式 固定周期挪用:如CPU取指令后需要对指令进行分析,这段(固定)时间不占用系统总线,可分给DMA使用 周期延长:延长CPU机器周期,供给DMA使用。如遇到慢设备时,读写周期就会增加,多余的等待设备时间可以用于DMA 3.3.3 CPU与DMA交替分时工作将时间分片,一片给CPU使用,一片给DMA使用。由于DMA只是偶尔使用,不是每时每刻进行,所以此方式不如另外两种方式(DMA需要工作时才占用总线) 3.4 通道方式通道是一个特殊功能的处理器,它有自己的指令和程序专门负责输入输出的传输控制,这样CPU只负责数据处理,通道负责传输控制,二者分时使用内存,实现并行有三种方式 3.4.1 选择通道通道可连接多台设备,但设备不能同时工作。数据传送以数据块为单位。会造成长时间等待的问题 3.4.2 数组多路通道通道可连接多个设备,允许同时工作,但只允许一台设备传输。当某设备进行数据传送时,通道只为该设备服务。当设备需要等待完成某些辅助操作时,通道暂时挂起该设备,去执行其他设备 3.4.3 字节多路通道连接大量低速外设。允许同时工作,且允许同时传输。以字节为单位,一个设备传送完一个字节,即可为另一个设备传送一个字节 四、多机系统1. 多机系统思想多机系统利用并行处理提高计算机性能,它包括两个含义: 同时性(两个或以上事件同一时间发生) 并发性(两个或以上事件在同一时间间隔内发生) 并行处理方法 时间重叠:时间并行技术,使多个子任务同时使用系统中不同功能的部件。如流水线 资源重复:空间并行技术,大量重复设置硬件资源,使多个子任务同时使用系统中相同功能的部件。如多核处理器 时间重复加资源重复:结合时间并行和空间并行。 资源共享:软件方式,通过操作系统调度,使多个任务轮流使用同一设备。如共享存储器 2. 并行计算机分类2.1 单指令流单数据流SISD一个时刻只能做一件事情模式有: 2.1.1 指令级并行指令按照流水线进行 2.1.2 芯片多线程CPU可以在多个线程之间切换,是虚拟的多处理器 2.1.3 单片多处理器同一个芯片设置两个及以上处理器内核,允许同时进行 2.2 单指令流多数据流SIMD相同的指令以并行的方式应用于不同的数据流,实现数据流的并行模式有: 2.2.1 向量计算机采用共享内存结构,所有加法运算由一个高度流水的加法器实现 2.2.2 阵列计算机由许多在不同数据集上执行同样指令、完成同样功能的完全相同的处理器组成 2.3 多指令流单数据流MISD多条指令同时在一个数据上进行操作,目前还没有这类的机器 2.4 多指令流多数据流MIMD实现线程级并行,同时有多个CPU执行不同操作,每个处理器获取自己的指令并对自己的数据进行操作处理模式有: 2.4.1 集中共享存储器结构(多处理器)所有处理器共享公共内存,有三种模式: 一致性存储器访问计算机UAM:通过共享总线通信,CPU访问共享内存时间一致 非一致性存储器访问计算机NUAM:共享内存被分组并分布到每个处理器。这使得远程内存访问时间比本地内存长 基于cache的存储器访问计算机:cache可以在不同处理器间移动而非固定 2.4.2 分布式存储器结构(多计算机)实际上由多个独立计算机组成,不共享内存,每个CPU有自己的私有内存,其它CPU不能访问 3. 多机互联网络多机互联网络用于连接系统中的计算机,互联网络对多机系统性能而言至关重要 3.1 互联函数互联函数用于表示互连网络的输入端号和输出端号的关系 3.1.1 表示方式 对应表示法:[0 1 … N-1][f(0) f(1) … f(N-1)]代表输入N对应输出f(N-1) 函数表示法:f(Xn-1Xn-2…X0)=XaXb…Xi其中Xn-1Xn-2…X0是输入X序号的二进制表示,根据XaXb…Xi可得到输出Y序号的二进制表示代表输入X和输入Y对应 循环表示法:(XaXbXc…Xi)代表输入Xa对应输出Xb,输入Xb对应输出Xc,…,前一个X和后一个X对应 3.1.2 基本互联函数 恒等置换:r(Xn-1Xn-2…Xi…X0)=Xn-1Xn-2…Xi…X0 交换置换:h(Xn-1Xn-2…Xi…X0)=Xn-1Xn-2…Xi…¬X0 方体置换:Ci(Xn-1Xn-2…Xi…X0)=Xn-1Xn-2…¬Xi…X0 均匀洗牌置换:σ(Xn-1Xn-2…Xi…X0)=Xn-2Xn-3…Xi…X0Xn-1 逆均匀洗牌置换:σ-1(Xn-1Xn-2…Xi…X0)=X0Xn-1Xn-2…Xi…X1 加减2i置换:PM2+i(X)=(X+2i)modMPM2-i(X)=(X-2i)modM其中M时输入/输出设备数 蝶形置换:β(Xn-1Xn-2…Xi…X0)=X0Xn-2…Xi…X1Xn-1 3.2 静态互联网络 线性阵列:每个节点和它左右节点连接 二维网络:每个节点和它上下左右节点连接 树形:每个节点和它父亲以及两个孩子连接 n-超立方:每个立方体顶点由n个节点组成 全互联网络:每个节点和其他节点都连接 3.3 动态互联网络 总线:结构简单,但存在总线仲裁、中断处理等问题 交叉开关网络:利用开关阵列,控制不同开关打开关闭,实现任意输入输出端的连接。2×2开关模块的四种控制状态: 直送:0入0出,1入1出 上播:0入输出0和1,1入输出悬空 下播:1入输出0和1,0入输出悬空 交叉:0入1出,1入0出 具备四种功能的乘坐四功能模块,具备1和4功能的称为二功能模块 多级互联网络:对交叉开关网络的改进,用小规模的交叉开关模块互相串并联构成多级交叉开关网络,减少设备量 多级均匀洗牌网络:又叫omega网络,寻址过程为,0:消息从开关上输出线输出,1消息从开关下输出线输出 4. 多机系统实例4.1 对称多处理机SMP有一致性存储访问模型和非一致性存储访问模型两种 4.1.1 特点 由两个及以上相同的处理机构成 多个处理机通过总线或其它互联方式连接 如果是一致性存储访问模型,那么多个处理器共享同一主存储器 所有的处理机通过通道共享IO设备 每个处理机完成相同的功能,在一个集中的操作系统统一管理下工作 4.1.2 优点 对称性 单地址空间,易编程 高速缓存且一致,数据局部性 低通信延迟 4.1.3 问题 欠可靠,总线、存储器或OS失效会使整个系统崩溃 竞争加剧 不可扩放,限制了处理器数量 4.2 大规模并行机MPP由成百上千个处理器组成的大规模计算机系统,每个节点是独立的计算单元,规模可变,高速互联网络,高带宽低延迟,只有微内核,成本高,用于科学计算 4.3 工作站集群COW分布式存储,每个节点都是一个完整的计算机,有自己的磁盘和操作系统。普通总线通信投资风险小、系统结构灵活,可扩展性强,成本低,用于中小型并行任务 4.4 网格GRID利用互联网把广泛分布的资源连成一个逻辑整体,即一台虚拟超级计算机,实现资源共享 5. 性能加速比S:并行系统的加速比是指并行算法比串行算法的执行加快了多少倍 Amdahl定律:S=1/(f+(1-f)/p)其中:f是串行分量的比例(应用中的不可并行化部分和问题规模之比)p是处理机数","categories":[{"name":"笔记","slug":"笔记","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/"},{"name":"课程","slug":"笔记/课程","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/%E8%AF%BE%E7%A8%8B/"}],"tags":[{"name":"计算机组成原理","slug":"计算机组成原理","permalink":"http://example.com/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/"}]},{"title":"目标检测算法Yolo(二)—YoloV1V2原理及源码使用","slug":"目标检测算法Yolo-二-—YoloV1V2原理及源码使用","date":"2023-10-30T03:46:34.000Z","updated":"2023-11-28T09:06:00.461Z","comments":true,"path":"2023/10/30/目标检测算法Yolo-二-—YoloV1V2原理及源码使用/","link":"","permalink":"http://example.com/2023/10/30/%E7%9B%AE%E6%A0%87%E6%A3%80%E6%B5%8B%E7%AE%97%E6%B3%95Yolo-%E4%BA%8C-%E2%80%94YoloV1V2%E5%8E%9F%E7%90%86%E5%8F%8A%E6%BA%90%E7%A0%81%E4%BD%BF%E7%94%A8/","excerpt":"","text":"简介:  本类博客用于记录目标检测方法yolo的学习,本篇博客介绍了yolov1,yolov2的原理,和yolo源码的下载和使用 参考资料: 【百万播放】零基础、快速学YOLO目标检测算法!完整学习路线一条龙,无脑通关!【YOLOv5|YOLO算法|目标检测算法】_哔哩哔哩_bilibili   Yolo属于one-stage的目标检测,优点是速度快,实时检测,缺点是牺牲效果 一、Yolo原理1.评估指标1.1 MAP  查准率:预测对的正样本占所有预测为正样本的比值  召回率:预测对的正样本占所有正样本的比值  AP:将该类别预测样本按置信度排序,分别计算查准率和召回率,绘制查准率关于召回率的图像,计算面积  MAP:将每个类的AP平均  MAP@0.5:9.5:使用不同的IOU并平均 1.2 IOU  真实值和预测值并集比上交集 ,越接近1效果越好 1.3 前传耗时  从输入一张图像到输出结果所耗时间 1.4 FPS  每秒能处理的图像数量 2. YoloV1 填充图片并缩放至448*448 获取每张图片的labels(包括每个目标的x,y,w,h,p) 设置网络结构 其中最后的输出结果为7 * 7 * 30的向量,其含义为将网络分成7*7个格子,每个格子包含两个候选框的信息(x,y,w,h,p)和20个类别的预测概率,共计30个元素 计算损失函数 3. YoloV2改进 增加了448*448的图片的训练 删除了全连接层 根据聚类选择先验框 x,y,w,h改为相对于先验框的偏移量 特征融合,利于检测小目标 二. Yolo源码1.源码下载和配置环境  进入ultralytics/yolov5 at v5.0 (github.com)下载源码,然后使用pip install -r yourpath/requirements.txt下载所需库(可以把requirements中关于toch,tochvision的部分删了自己下载,按照requirements中安装的torch是cpu版本的) 2.源码使用  更改参数为你对应的 2.1 detect.py123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051def parse_opt(): parser = argparse.ArgumentParser() #指定训练模型 defult选择训练模型 parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s.pt', help='model path(s)') #指定预测对象来源 default可选为: #file.jpg #file.mp4 #path/ #path/*/jpg #检测所有jpg文件 #YouTube video地址 parser.add_argument('--source', type=str, default=ROOT / 'data/images', help='file/dir/URL/glob, 0 for webcam') parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='(optional) dataset.yaml path') #图片尺寸 parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w') #置信度阈值 parser.add_argument('--conf-thres', type=float, default=0.25, help='confidence threshold') #交集阈值,大于该值就判定为同一目标,合并框 parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold') parser.add_argument('--max-det', type=int, default=1000, help='maximum detections per image') #设备 parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') #是否展示结果 parser.add_argument('--view-img', action='store_true', help='show results') #保存一些结果 parser.add_argument('--save-txt', action='store_true', help='save results to *.txt') parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels') parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes') parser.add_argument('--nosave', action='store_true', help='do not save images/videos') #指定检测某些类别 parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --classes 0, or --classes 0 2 3') #增强 parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS') parser.add_argument('--augment', action='store_true', help='augmented inference') parser.add_argument('--visualize', action='store_true', help='visualize features') #屏蔽不需要的优化器 parser.add_argument('--update', action='store_true', help='update all models') #保存地址 parser.add_argument('--project', default=ROOT / 'runs/detect', help='save results to project/name') parser.add_argument('--name', default='exp', help='save results to project/name') #是否创建新文件夹保存结果 parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)') parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels') parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences') parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference') parser.add_argument('--dnn', action='store_true', help='use OpenCV DNN for ONNX inference') opt = parser.parse_args() opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand print_args(vars(opt)) return opt 2.2 train.py123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657def parse_opt(known=False): parser = argparse.ArgumentParser() #指定初始化参数,可不填 parser.add_argument('--weights', type=str, default=ROOT / 'yolov5s.pt', help='initial weights path') #指定网络的结构 parser.add_argument('--cfg', type=str, default='', help='model.yaml path') #指定数据集 parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='dataset.yaml path') #超参数 parser.add_argument('--hyp', type=str, default=ROOT / 'data/hyps/hyp.scratch-low.yaml', help='hyperparameters path') #训练轮数 parser.add_argument('--epochs', type=int, default=300) #打包数据集的大小 parser.add_argument('--batch-size', type=int, default=16, help='total batch size for all GPUs, -1 for autobatch') #图片大小 parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='train, val image size (pixels)') #矩阵填充 parser.add_argument('--rect', action='store_true', help='rectangular training') #是否在已训练的模型上继续训练 parser.add_argument('--resume', nargs='?', const=True, default=False, help='resume most recent training') #是否只保存最后的模型 parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--noval', action='store_true', help='only validate final epoch') #锚点 锚框 parser.add_argument('--noautoanchor', action='store_true', help='disable AutoAnchor') parser.add_argument('--noplots', action='store_true', help='save no plot files') parser.add_argument('--evolve', type=int, nargs='?', const=300, help='evolve hyperparameters for x generations') parser.add_argument('--bucket', type=str, default='', help='gsutil bucket') parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"') parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') #图片尺寸变换 parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%') parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class') parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW'], default='SGD', help='optimizer') parser.add_argument('--sync-bn', action='store_true', help='use SyncBatchNorm, only available in DDP mode') #线程数 parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') parser.add_argument('--project', default=ROOT / 'runs/train', help='save to project/name') parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') parser.add_argument('--quad', action='store_true', help='quad dataloader') parser.add_argument('--cos-lr', action='store_true', help='cosine LR scheduler') parser.add_argument('--label-smoothing', type=float, default=0.0, help='Label smoothing epsilon') parser.add_argument('--patience', type=int, default=100, help='EarlyStopping patience (epochs without improvement)') parser.add_argument('--freeze', nargs='+', type=int, default=[0], help='Freeze layers: backbone=10, first3=0 1 2') parser.add_argument('--save-period', type=int, default=-1, help='Save checkpoint every x epochs (disabled if < 1)') parser.add_argument('--seed', type=int, default=0, help='Global training seed') parser.add_argument('--local_rank', type=int, default=-1, help='Automatic DDP Multi-GPU argument, do not modify') # Weights & Biases arguments parser.add_argument('--entity', default=None, help='W&B: Entity') parser.add_argument('--upload_dataset', nargs='?', const=True, default=False, help='W&B: Upload data, "val" option') parser.add_argument('--bbox_interval', type=int, default=-1, help='W&B: Set bounding-box image logging interval') parser.add_argument('--artifact_alias', type=str, default='latest', help='W&B: Version of dataset artifact to use') return parser.parse_known_args()[0] if known else parser.parse_args() 结语:无  ","categories":[{"name":"笔记","slug":"笔记","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/"},{"name":"人工智能","slug":"笔记/人工智能","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"},{"name":"yolo","slug":"笔记/人工智能/yolo","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/yolo/"}],"tags":[{"name":"目标检测","slug":"目标检测","permalink":"http://example.com/tags/%E7%9B%AE%E6%A0%87%E6%A3%80%E6%B5%8B/"}]},{"title":"目标检测算法Yolo(一)—环境配置Pytorch","slug":"目标检测算法Yolo-一-—环境配置Pytorch","date":"2023-10-30T03:25:33.000Z","updated":"2023-11-28T09:06:00.461Z","comments":true,"path":"2023/10/30/目标检测算法Yolo-一-—环境配置Pytorch/","link":"","permalink":"http://example.com/2023/10/30/%E7%9B%AE%E6%A0%87%E6%A3%80%E6%B5%8B%E7%AE%97%E6%B3%95Yolo-%E4%B8%80-%E2%80%94%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AEPytorch/","excerpt":"","text":"简介:  本类博客用于记录目标检测方法yolo的学习,本篇博客介绍了yolo的环境配置—pytorch 参考资料: PyTorch深度学习快速入门教程(绝对通俗易懂!)【小土堆】_哔哩哔哩_bilibili   Pytorch是torch的python版本,是由Facebook开源的神经网络框架,专门针对 GPU 加速的深度神经网络(DNN)编程。Torch 是一个经典的对多维矩阵数据进行操作的张量(tensor )库,在机器学习和其他数学密集型应用有广泛应用。与Tensorflow的静态计算图不同,pytorch的计算图是动态的,可以根据计算需要实时改变计算图。但由于Torch语言采用 Lua,导致在国内一直很小众,并逐渐被支持 Python 的 Tensorflow 抢走用户。作为经典机器学习库 Torch 的端口,PyTorch 为 Python 语言使用者提供了舒适的写代码选择。 一.Pytorch安装及环境配置1. Pytorch的安装1.1 Anaconda安装  此处我选用的是Anaconda3-5.2.0-Windows-x86_64.exe版本  Ananconda作用是为pytorch提供一个独立的虚拟环境,里面可以独立地配置环境,避免和系统环境等冲突,便于管理环境,同时配置过程中有没法解决的问题都可以删除虚拟环境重来 超详细Anaconda安装教程_极小白的博客-CSDN博客 1.2 pytorch的环境配置及安装  打开Anaconda Prompt 输入  conda create -n pytorch python=3.6  创建了一个新环境,然后使用如下命令进入环境  conda activate pytorch  之后需要安装gpu版本的pytorch,按照视频教程去pytorch.org使用官方命令安装pytorch怎么样安装的都是cpu版本,找了很多教程,都没有用,最直接的解决方案是自己下载离线文件,此处按照下面这个教程安装 pytorch gpu版安装(普适稳妥,亲测有效) - 知乎 (zhihu.com)   在环境里安装torchvision时报错ReadTimeout,可能是安装依赖的问题,解决方法:设置pip全局设置镜像源 12pip3 config --global set global.index-url https://mirrors.aliyun.com/pypi/simple/pip3 config --global set install.trusted-host   检验是否安装成功,依次输入 123pythonimport torchtorch.cuda.is_available()   返回True代表成功  配置和安装pytorch遇到了很多报错,终于是成功了,如果觉得有问题,可以把环境文件夹删了重来一遍(我的文件夹路径为C:\\Users\\Frelar\\Anaconda3\\envs\\) 2. python的编辑器及配置2.1 PyCharm  此处建议下载新版本  编译器选择你对应虚拟环境即可 2.2 Jupyter Notebook(可选)  按照教程下载报错,改为在Anaconda Promot(pytorch环境)里输入conda install jupyter notebook,下载后new选项里没有当前环境,依次输入 12conda install ipykernelpython -m ipykernel install --user --name pytorch --display-name "Python (pytorch)" 成功解决 3. 其它相关库3.1 opencv  这里使用pip install opencv-python安装报错Failed building wheel for opencv-python,然后尝试用conda安装,报错找不到库,所以还是采用离线下载方式,进入网址Archived: Python Extension Packages for Windows - Christoph Gohlke (uci.edu)找到你对应的python版本(cp36代表python3.6),使用迅雷下载会快很多,然后在环境下使用pip install your_path\\opencv_python-4.4.0-cp36-cp36m-win_amd64.whl安装成功   import cv2 1cv2.imread() #读取图片,返回ndarray类型图片及信息 3.2 os  import os 12os.listdir(dir_path) #读取获取目录下所有文件名称os.path.join(path1,path2) #拼接路径 二.Pytorch使用1.torch1.1 torch.utils.data.Dataset  from torch.utils.data import Dataset  包含自定义数据集方法的类,用户可以通过继承该类来自定义自己的数据集类,在继承时要求用户重载__len__()和__getitem__()这两个魔法方法 1234567891011121314151617181920212223242526from torch.utils.data import Dataset from PIL import Image import os class MyData(Dataset): def __init__(self,root_dir,label_dir): self.root_dir=root_dir #根目录 self.label_dir=label_dir #标签 self.path=os.path.join(root_dir,label_dir) #图片目录 self.image_path=os.listdir(self.path) #所有图片名称 def __getitem__(self, idx): img_name=self.image_path[idx] img_idx_path=os.path.join(self.path,img_name) img=Image.open(img_idx_path) label=self.label_dir return img,label #图片信息,标签 def __len__(self): return len(self.image_path) #图片个数 root_dir="dataSet/train" cat_label_dir="cat" cat_dataSet=MyData(root_dir,cat_label_dir) #实例 1.2 torch.utils.data. DataLoader  from torch.utils.data import DataLoader 1234train_loader=DataLoader(dataset=train_data,batch_size=64,shuffle=True,num_workers=0,drop_last=True)#数据集,每次打包的图片数,是否打乱,线程数,是否丢弃不够一组打包的图片#for data in train_loader# imgs,target=data #读取图片信息 1.3 torch.nn  from torch import nn  一个用于创建神经网络模型的类,可以作为参数、容器、子模块和函数 123456789101112131415161718192021222324252627#神经网络骨架class Name(nn.moudle): def __init__(self): super(Name,self).__init__() self.layer=layerFunc() #激活层 def forward(self,input) #前向传播 output=self.layer(input) return outputname=Name()output=name(input)name.train() #训练模式name.eval() #验证模式#卷积nn.Conv2d(in_channels=3,out_channels=6,kernel_size=3,stride=1,padding=0) #二维卷积(输入通道数,输出通道数,卷积核层数...)#池化nn.MaxPool2d(kernel_size=3,ceil_mode=True) #最大池化(核大小,步长(默认为核大小),是否填充是否舍弃不足核大小的图片部分)#非线性激活nn.sigmoid(inplace=False) #sigmoid激活(是否原地操作)#线性激活nn.Liner(input_features,output_features,bias=False) #线性激活(输入特征数,输出特征数,偏置)#序列nn.Sequential(fun1,fun2...,funN) #包装操作为序列,可读性更强#损失函数、反向传播loss=nn.CrossEntropyLoss()result_loss=loss(outputs,targets)result_loss.backword() #反向传播 将梯度记录在每层信息的weight里 1.4 torch.optim  import torch  用于参数优化的库(可以说是花式梯度下降) 12345optim=torch.optim.SGD(yourClass.parameters(),lr=0.01) #优化器(参数集,学习速率)#result.loss=loss(outputs,targets)#optim.zero_grad() #初始化梯度#result_loss.backword()#optim.step() 2.torchvision  import torchvision  处理图像的工具集 1234#获取数据集train_set=torchvision.datasets.dataname(root="./dataset",train=True,transforms=mytransform,download=True)#train_set.classes 数据集的各种类名(猫、狗...)#img,target=train_set[0] 图片信息,图片类名编号 2.1 torchvision.transforms  from torchvision import transforms  对图片进行操作  使用方法: 12mytool=transforms.tool(arguments1) #实例化mytool(arguments2) #使用   tool: 123456789101112myToTensor=transforms.ToTensor()myImg=myToTensor(img) #将PIL或numpy.ndarry类型图片转为tensor类型并返回#tensor类型:包装了神经网络的一些参数myCompose=transforms.Compose(n)myImg=myCompose(img) #中心裁剪myNorm=transforms.Normalize([mean1...meanN],[std1...stdN]) #每个通道的均值和方差myImg=myNorm(img) #归一化myResize=transforms.Resize((h,w))myImg=myResize(img) #缩放 3.利用GPU加速123456#方法一if torch.cuda.is_available(): #判断GPU加速是否可用 item=item.cuda() #目标加速(可用于:imgs/targets/yourMoudle/loss)#方法二device=torch.device("cuda" if torch.cuda.is_available() else "cpu") item=item.to(device) 遇到问题:1.pip insall或者conda install很多报错,如找不到库,超时等2. 安装cuda版本的pytorch总是安装成cpu(大坑)解决方案:1.采用离线下载方式,手动下载.whl文件 然后pip install yourPath.whl2.采用离线下载是唯一解决方法 结语:无  ","categories":[{"name":"笔记","slug":"笔记","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/"},{"name":"人工智能","slug":"笔记/人工智能","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"},{"name":"yolo","slug":"笔记/人工智能/yolo","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/yolo/"}],"tags":[{"name":"目标检测","slug":"目标检测","permalink":"http://example.com/tags/%E7%9B%AE%E6%A0%87%E6%A3%80%E6%B5%8B/"}]},{"title":"会议室预约系统(一)—QT开发数据库学习","slug":"会议室预约系统-一-—QT开发数据库学习","date":"2023-10-29T15:16:04.000Z","updated":"2023-11-28T09:06:00.462Z","comments":true,"path":"2023/10/29/会议室预约系统-一-—QT开发数据库学习/","link":"","permalink":"http://example.com/2023/10/29/%E4%BC%9A%E8%AE%AE%E5%AE%A4%E9%A2%84%E7%BA%A6%E7%B3%BB%E7%BB%9F-%E4%B8%80-%E2%80%94QT%E5%BC%80%E5%8F%91%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AD%A6%E4%B9%A0/","excerpt":"","text":"简介:  本篇博客用于记录QT软件结合数据库开发的学习过程 参考资料: 最新QT从入门到实战完整版|传智教育_哔哩哔哩_bilibili Qt零基础系列10:使用Qt如何操作数据库 - 知乎 (zhihu.com) 一.QT1.控件1.1 窗体12345678910myWidget w; //创建窗体//set类setWindowTitle("窗口名称") //设置窗口名称resize(w,h) //重置窗口大小setFixedSize(w.h) //设置固定窗口大小//print类show() //显示窗口 1.2 按钮1234567891011#include <QPushButton>QPushButton* btn=new QPushButton("按钮名称",父窗口); //创建按钮在父窗口内(不填默认新开窗口创建)setText();setParent();move(x,y); //移动按钮resize(w,h);//信号clicked(); 1.3 对话框12345#include <QDialog>exec() //阻塞其他操作(模态)//消息对话框QMessageBox::critical(父类,标题,信息) //错误对话框QMessageBox::information(父类,标题,信息) //信息提示对话框 1.4 编辑框1234#include <QLineEdit>text();setText();setReadOnly(true); 1.5 文本编辑框12#include <QtextEdit>toPlainText(); //获取文本内容 1.6 下拉式选项框12345#include <QComboBox>currentIndex(); //当前选项编号//信号currentIndexChanged(); 2.信号和槽信号和槽是指,当某一控件被操作时,发出信号,然后由信号接收者接收信号,并通过槽函数处理信号 1connect(信号发送者,信号(函数地址),信号接收者,处理信号函数(槽函数地址); 自定义信号:无返回值写在signal下需要声明不需要实现可以带参数,可以重载(参数要多于槽函数参数) 自定义槽:无返回值一般的声明在public slots下需要实现可以带参数,可以重载 重载后 connect需要如下实现: 12void(ClassName:: *p)(参数)=&ClassName::函数名;connect(class,p,...) 触发信号 1emit 函数名; 断开信号 1discount(); 3.QT设计联合代码开发点击界面文件中的mainwindow.ui,然后进行界面设计,完成后打开Qt 6.4.3(MinGW 11.2.0 64-bit) (打开你对应版本即可),cd 到你界面文件所在的文件夹,输入:uic -o ui.h mainwindow.ui即可在该文件夹生成ui.h头文件 使用时先实例化ui.h类:Ui_mainWindow ui=Ui_mainWindow();然后调用setupUi()方法:ui.setupUi(yourWindow_address);这时你的界面就和你设计的界面一样了 获取你的界面的控件的方法:QType* a= yourWindow->findChild<QType*>("itemName"); 二.数据库qt使用数据库步骤: 下载Mysql(网上很多教程) 本地/服务器创建数据库 建立数据库链接(和你创建的数据库一致)(详细代码见参考资料) 本地/服务器创建表 qt中执行sql语句对数据库增删查改 qt执行sql语句步骤: 1234567891011121314151617//1.在建立好数据库链接后获取数据库链接QSqlDatabase db=QSqlDatabase::database(connectionName);//2.传入数据库链接QSqlQuery query(db);//3.设计sql语句query.prepare("SELECT * FROM conference_users WHERE users_name=:users_name");query.bindValue(":users_name",name);//或者QString sql = "SELECT * FROM conference_users WHERE users_name='" + name + "'";//4.执行sql语句if(!query.exec(sql)) qDebug()<<"查找失败";//5.获取结果if(query.size()==0) return NULL;while (query.next()){ QString str=query.value(1).toString(); //...} 结语:熟练使用QT设计和代码联合开发很重要  ","categories":[{"name":"项目","slug":"项目","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/"},{"name":"程序设计","slug":"项目/程序设计","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/"},{"name":"会议室系统","slug":"项目/程序设计/会议室系统","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/%E4%BC%9A%E8%AE%AE%E5%AE%A4%E7%B3%BB%E7%BB%9F/"}],"tags":[{"name":"qt","slug":"qt","permalink":"http://example.com/tags/qt/"},{"name":"sql","slug":"sql","permalink":"http://example.com/tags/sql/"}]},{"title":"会议室预约系统(二)—代码实现","slug":"会议室预约系统-二-—代码实现","date":"2023-10-29T14:53:54.000Z","updated":"2023-11-28T09:06:00.463Z","comments":true,"path":"2023/10/29/会议室预约系统-二-—代码实现/","link":"","permalink":"http://example.com/2023/10/29/%E4%BC%9A%E8%AE%AE%E5%AE%A4%E9%A2%84%E7%BA%A6%E7%B3%BB%E7%BB%9F-%E4%BA%8C-%E2%80%94%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0/","excerpt":"","text":"简介:  本篇博客用于介绍使用QT软件、Mysql开发会议室预约系统的代码,代码已经发布到https://github.com/arrowonstr/conferenceRoomReservationSystem 一.环境该系统使用C++ QT开发,使用方法: 下载代码 将所有head、source中文件取出和其他文件放在同一目录下 使用QT软件打开项目打开.pro文件 配置对应数据库,具体数据库内容见本文 二. ,或修改代码符合自己的数据库 运行 二.系统流程图其中: 大矩形框代表界面 正方形框代表按钮 小矩形框代表下拉式选项 实线箭头代表按钮按下后打开/更新某一界面 虚线箭头代表按钮按下后向调用数据库 流程图仅表示大致流程,许多细节是被隐藏的,具体可以看代码 三.数据库配置数据库需要配置为下面对应配置,或者修改mycfsql.cpp文件中的createConnectionByName函数,以及signwindow.cpp构造函数中cf.createConnectionByName("conference")数据库名表需要完全相同配置 1.数据库服务器:本地数据库名:conference用户名:root密码:1234端口号:3306 2.表2.1 conference_users 字段名称 数据类型 是否为可空 默认值 含义 users_id int NO NULL id users_name varchar(20) NO NULL 用户名 users_password varchar(20) NO NULL 密码 users_author int NO NULL 权限 其中: users_id为主键 权限 0为用户 1为管理员 2.2 conference_rooms 字段名称 数据类型 是否为可空 默认值 含义 rooms_id int NO NULL id rooms_name varchar(20) NO NULL 名称 rooms_number int NO NULL 房号 rooms_capacity int NO NULL 最大人数 rooms_square int NO NULL 面积 rooms_describe varchar(50) NO NULL 描述 rooms_reserve varchar(12) NO NULL 无 其中: rooms_id为主键 rooms_reserve为废弃的字段 但是不想改代码了 所以保留在这里 其没有含义和作用 2.3 conference_reserves 字段名称 数据类型 是否为可空 默认值 含义 reserves_id int NO NULL id reserves_user varchar(20) NO NULL 预约用户 reserves_number int NO NULL 预约房号 reserves_stime datetime NO NULL 开始时间 reserves_etime datetime NO NULL 结束时间 reserves_subject varchar(20) NO NULL 主题 reserves_capacity int NO NULL 人数 reserves_state int NO NULL 状态 reserves_describe varchar(50) NO NULL 描述 其中:reserves_id为主键状态 0未审批 1未审批 2为驳回 3为取消 结语:无  ","categories":[{"name":"项目","slug":"项目","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/"},{"name":"程序设计","slug":"项目/程序设计","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/"},{"name":"会议室系统","slug":"项目/程序设计/会议室系统","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/%E4%BC%9A%E8%AE%AE%E5%AE%A4%E7%B3%BB%E7%BB%9F/"}],"tags":[{"name":"qt","slug":"qt","permalink":"http://example.com/tags/qt/"},{"name":"sql","slug":"sql","permalink":"http://example.com/tags/sql/"}]},{"title":"K210开发板(六)-yolo目标检测","slug":"K210开发板-六-yolo目标检测","date":"2023-09-23T08:28:11.000Z","updated":"2023-11-28T09:06:00.483Z","comments":true,"path":"2023/09/23/K210开发板-六-yolo目标检测/","link":"","permalink":"http://example.com/2023/09/23/K210%E5%BC%80%E5%8F%91%E6%9D%BF-%E5%85%AD-yolo%E7%9B%AE%E6%A0%87%E6%A3%80%E6%B5%8B/","excerpt":"","text":"简介:   一、实验准备1.Pytorch  Pytorch是torch的python版本,是由Facebook开源的神经网络框架,专门针对 GPU 加速的深度神经网络(DNN)编程。Torch 是一个经典的对多维矩阵数据进行操作的张量(tensor )库,在机器学习和其他数学密集型应用有广泛应用。与Tensorflow的静态计算图不同,pytorch的计算图是动态的,可以根据计算需要实时改变计算图。但由于Torch语言采用 Lua,导致在国内一直很小众,并逐渐被支持 Python 的 Tensorflow 抢走用户。作为经典机器学习库 Torch 的端口,PyTorch 为 Python 语言使用者提供了舒适的写代码选择。 1.1 Pytorch的安装1.1.1 Anaconda安装  此处我选用的是Anaconda3-5.2.0-Windows-x86_64.exe版本 超详细Anaconda安装教程_极小白的博客-CSDN博客 1.1.2 pytorch的环境配置及安装  打开Anaconda Prompt 输入  conda create -n pytorch python=3.6  创建了一个新环境,然后使用如下命令进入环境  conda activate pytorch  之后需要安装gpu版本的pytorch,按照视频教程去pytorch.org使用官方命令安装pytorch怎么样安装的都是cpu版本,找了很多教程,都没有用,最直接的解决方案是自己下载离线文件,此处按照下面这个教程安装 pytorch gpu版安装(普适稳妥,亲测有效) - 知乎 (zhihu.com)   在环境里安装torchvision时报错ReadTimeout,可能是安装依赖的问题,解决方法:设置pip全局设置镜像源 12pip3 config --global set global.index-url https://mirrors.aliyun.com/pypi/simple/pip3 config --global set install.trusted-host   检验是否安装成功,依次输入 123pythonimport torchtorch.cuda.is_available()   返回True代表成功  配置和安装pytorch遇到了很多报错,终于是成功了,如果觉得有问题,可以把环境文件夹删了重来一遍(我的文件夹路径为C:\\Users\\Frelar\\Anaconda3\\envs\\) 1.2 python的编辑器及配置1.2.1 PyCharm  下载2023版使用import torch报错了 下回2019版成功 1.2.2 Jupyter Notebook  按照教程下载报错,改为在Anaconda Promot(pytorch环境)里输入conda install jupyter notebook,下载后new选项里没有当前环境,依次输入 12conda install ipykernelpython -m ipykernel install --user --name pytorch --display-name "Python (pytorch)" 成功解决 1.3 一些库1.3.1 opencv  这里使用pip install opencv-python安装报错Failed building wheel for opencv-python,然后尝试用conda安装,报错找不到库,所以还是采用离线下载方式,进入网址Archived: Python Extension Packages for Windows - Christoph Gohlke (uci.edu)找到你对应的python版本(cp36代表python3.6),使用迅雷下载会快很多,然后在环境下使用pip install your_path\\opencv_python-4.4.0-cp36-cp36m-win_amd64.whl安装成功   import cv2 1cv2.imread() #读取图片,返回ndarray类型图片及信息 1.3.2 os  import os 12os.listdir(dir_path) #读取获取目录下所有文件名称os.path.join(path1,path2) #拼接路径 1.3.3 torch1.3.3.1 torch.utils.data.Dataset  from torch.utils.data import Dataset  包含自定义数据集方法的类,用户可以通过继承该类来自定义自己的数据集类,在继承时要求用户重载__len__()和__getitem__()这两个魔法方法 1234567891011121314151617181920212223242526from torch.utils.data import Dataset from PIL import Image import os class MyData(Dataset): def __init__(self,root_dir,label_dir): self.root_dir=root_dir #根目录 self.label_dir=label_dir #标签 self.path=os.path.join(root_dir,label_dir) #图片目录 self.image_path=os.listdir(self.path) #所有图片名称 def __getitem__(self, idx): img_name=self.image_path[idx] img_idx_path=os.path.join(self.path,img_name) img=Image.open(img_idx_path) label=self.label_dir return img,label #图片信息,标签 def __len__(self): return len(self.image_path) #图片个数 root_dir="dataSet/train" cat_label_dir="cat" cat_dataSet=MyData(root_dir,cat_label_dir) #实例 1.3.3.2 torch.utils.data. DataLoader  from torch.utils.data import DatLoader 1234train_loader=DataLoader(dataset=train_data,batch_size=64,shuffle=True,num_workers=0,drop_last=True)#数据集,每次打包的图片数,是否打乱,线程数,是否丢弃不够一组打包的图片#for data in train_loader# imgs,target=data #读取图片信息 1.3.3.3 torch.nn  from torch import nn  一个用于创建神经网络模型的类,可以作为参数、容器、子模块和函数 123456789101112131415161718192021222324252627#神经网络骨架class Name(nn.moudle): def __init__(self): super(Name,self).__init__() self.layer=layerFunc() #激活层 def forward(self,input) #前向传播 output=self.layer(input) return outputname=Name()output=name(input)name.train() #训练模式name.eval() #验证模式#卷积nn.Conv2d(in_channels=3,out_channels=6,kernel_size=3,stride=1,padding=0) #二维卷积(输入通道数,输出通道数,卷积核层数...)#池化nn.MaxPool2d(kernel_size=3,ceil_mode=True) #最大池化(核大小,步长(默认为核大小),是否填充是否舍弃不足核大小的图片部分)#非线性激活nn.sigmoid(inplace=False) #sigmoid激活(是否原地操作)#线性激活nn.Liner(input_features,output_features,bias=False) #线性激活(输入特征数,输出特征数,偏置)#序列nn.Sequential(fun1,fun2...,funN) #包装操作为序列,可读性更强#损失函数、反向传播loss=nn.CrossEntropyLoss()result_loss=loss(outputs,targets)result_loss.backword() #反向传播 将梯度记录在每层信息的weight里 1.3.3.4 torch.optim  import torch  用于参数优化的库(可以说是花式梯度下降) 12345optim=torch.optim.SGD(yourClass.parameters(),lr=0.01) #优化器(参数集,学习速率)#result.loss=loss(outputs,targets)#optim.zero_grad() #初始化梯度#result_loss.backword()#optim.step() 1.3.4 torchvision  import torchvision  处理图像的工具集 1234#获取数据集train_set=torchvision.datasets.dataname(root="./dataset",train=True,transforms=mytransform,download=True)#train_set.classes 数据集的各种类名(猫、狗...)#img,target=train_set[0] 图片信息,图片类名编号 1.3.4.1 torchvision.transforms  from torchvision import transforms  对图片进行操作  使用方法: 12mytool=transforms.tool(arguments1) #实例化mytool(arguments2) #使用   tool: 123456789101112myToTensor=transforms.ToTensor()myImg=myToTensor(img) #将PIL或numpy.ndarry类型图片转为tensor类型并返回#tensor类型:包装了神经网络的一些参数myCompose=transforms.Compose(n)myImg=myCompose(img) #中心裁剪myNorm=transforms.Normalize([mean1...meanN],[std1...stdN]) #每个通道的均值和方差myImg=myNorm(img) #归一化myResize=transforms.Resize((h,w))myImg=myResize(img) #缩放 1.4 利用GPU加速123456#方法一if torch.cuda.is_available(): #判断GPU加速是否可用 item=item.cuda() #目标加速(可用于:imgs/targets/yourMoudle/loss)#方法二device=torch.device("cuda" if torch.cuda.is_available() else "cpu") item=item.to(device) 遇到问题:解决方案: Tips: 结语:  ","categories":[{"name":"项目","slug":"项目","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/"},{"name":"嵌入式","slug":"项目/嵌入式","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/"},{"name":"K210","slug":"项目/嵌入式/K210","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/K210/"}],"tags":[{"name":"K210","slug":"K210","permalink":"http://example.com/tags/K210/"}]},{"title":"K210开发板(五)—拍摄和KEYPAD照片查看","slug":"K210开发板-五-—拍摄和KEYPAD照片查看","date":"2023-09-18T16:16:58.000Z","updated":"2023-11-28T09:06:00.477Z","comments":true,"path":"2023/09/19/K210开发板-五-—拍摄和KEYPAD照片查看/","link":"","permalink":"http://example.com/2023/09/19/K210%E5%BC%80%E5%8F%91%E6%9D%BF-%E4%BA%94-%E2%80%94%E6%8B%8D%E6%91%84%E5%92%8CKEYPAD%E7%85%A7%E7%89%87%E6%9F%A5%E7%9C%8B/","excerpt":"","text":"简介:  本篇博客用于记录K210的学习和自己遇到的问题,主要内容为实现拍摄和照片查看功能,摄像头实时将画面传到LCD上,按下BOOT拍照记录当前照片,按下KEYPAD进入照片查看,左滚KEYPAD查看上一张照片,右滚KEYPAD查看下一张照片,再次按下KEYPAD退出照片查看,重新恢复摄像功能。由于参考资料的教程比较齐全,本文会着重于讲述自己遇到的问题和解决方案 参考资料:http://www.yahboom.com/study/K210-Developer-Kit提取码:moj1 一.实验准备1.函数介绍1.1 lcd_draw_picture  设置GPIO中断触发模式 参数成员 描述 输入输出 x 起始坐标 输入 y 起始坐标 输入 width 图像宽度 输入 height 图像长度 输入 *ptr 图像数据存放地址 输入 二.实验步骤1.配置  过多了,这里就不贴了,见参考资料摄像头显示实验、按键中断实验、keypad控制RGB灯实验 2.初始化见参考文件就行,注意引脚定义不要冲突了 3.主体3.1 中断回调函数3.1.1 boot中断函数  下降沿触发,每次按下boot时触发中断,申请一片320*240的空间,存放照片数据,最后保存地址 12345678910111213141516171819202122232425262728//button中断函数/*uint32_t phoNum=0;uint32_t g_count=-1;uint32_t *keyptrs[5]={NULL,NULL,NULL,NULL,NULL};uint32_t keyptr_addrs[5]={};*/int key_irq_cb(void* ctx){ phoNum++; //照片数量 uint32_t *tmp = (uint32_t *)(ctx); //button使用次数 (*tmp)++; if(*tmp<5){ //存五张照片 uint32_t * keyptr=keyptrs[phoNum-1]; keyptr=(uint32_t*)iomem_malloc(CAM_WIDTH_PIXEL * CAM_HIGHT_PIXEL * 2); uint32_t *display_buf=display_buf_addr; for(int i=0;i<76800;i++){ *(keyptr+i)=*(display_buf+i); //写入数据 } uint32_t keyptr_addr=keyptr; keyptr_addrs[phoNum-1]=keyptr_addr; //存放照片地址 lcd_draw_picture(0, 0, 320, 240,keyptr_addr); //拍摄显示 sleep(1); }else{ printf("Photo space is full\\n"); } return 0;} 3.1.1 boot中断函数  下降沿触发,每次按下boot时触发中断,申请一片320*240的空间,存放照片数据,最后保存地址//keypad中断函数 3.1.2 keypad中断函数  分为左滚动中断、右滚动中断、中键中断 1234567/*uint32_t keypad_idx=1; //当前照片索引//是否处于中断标志uint32_t keypad_left_icf_flag=0;uint32_t keypad_middle_icf_flag=0;uint32_t keypad_right_icf_flag=0;*/ 3.1.2.1 左滚动中断  用互斥锁防止由于抖动而导致相同中断同时发生好几起,当向左滚动时keypad_idx减一 12345678910111213141516171819202122232425262728int keypad_left_icf(void* ctx){ msleep(10); //消抖 keypad_left_icf_flag++; //使用中断 if(keypad_left_icf_flag>1){ //退出其它中断 keypad_left_icf_flag--; return 0; } gpio_pin_value_t state_keypad_left = gpiohs_get_pin(KEYPAD_LEFT_GPIONUM); if(state_keypad_left!=0){ keypad_left_icf_flag=0; return 0; }; //检查 if(keypad_left_icf_flag>1){ keypad_left_icf_flag--; return 0; } uint32_t *tmp = (uint32_t *)(ctx); printf("Enter keypad left interrupt%d\\n",keypad_left_icf_flag); if(phoNum==0){ //如果没有照片 printf("There are currently no photos available\\n"); }else if((*tmp)==1){ printf("This is currently the first photo, there are no more previous photos available\\n"); }else{ (*tmp)--; } keypad_left_icf_flag=0; //释放中断 return 0;} 3.1.2.2 左滚动中断  用互斥锁防止由于抖动而导致相同中断同时发生好几起,当向左滚动时keypad_idx加一 123456789101112131415161718192021222324int keypad_right_icf(void* ctx){ msleep(10); keypad_right_icf_flag++; //使用中断 if(keypad_right_icf_flag>1){ //退出其它中断 keypad_right_icf_flag--; return 0; } gpio_pin_value_t state_keypad_right = gpiohs_get_pin(KEYPAD_RIGHT_GPIONUM); if(state_keypad_right!=0){ keypad_right_icf_flag=0; return 0; }; //检查 uint32_t *tmp = (uint32_t *)(ctx); printf("Enter keypad right interrupt\\n"); if(phoNum==0){ //如果没有照片 printf("There are currently no photos available\\n"); }else if((*tmp)==phoNum){ printf("This is currently the last photo, there are no further photos available\\n"); }else{ (*tmp)++; } keypad_right_icf_flag=0; //释放中断 return 0;} 3.1.2.3 中键中断  中键按下时,如果是第一次按下,允许进入中断,如果此时有照片,就在while循环内查看当前第keypad_idx张照片,左滚动右滚动优先级都大于中键,可以用于切换照片,boot按键优先级小于中键,需要等待再次按下中键,此时退出照片查看,可以重新使用拍照功能 123456789101112131415161718192021222324252627int keypad_middle_icf(void* ctx){ msleep(10); keypad_middle_icf_flag++; if(keypad_middle_icf_flag>1){ keypad_middle_icf_flag--; return 0; } gpio_pin_value_t state_keypad_middle = gpiohs_get_pin(KEYPAD_MIDDLE_GPIONUM); if(state_keypad_middle!=0){ keypad_middle_icf_flag--; return 0; }; //检查 uint32_t *tmp = (uint32_t *)(ctx); if(phoNum==0){ //如果没有照片 printf("There are currently no photos available\\n"); }else{ printf("enter photo view\\n"); while(gpiohs_get_pin(KEYPAD_MIDDLE_GPIONUM)!=1); //等待此次按下结束 while(gpiohs_get_pin(KEYPAD_MIDDLE_GPIONUM)==1){ //等待下次按下离开查看照片 lcd_draw_picture(0, 0, 320, 240, keyptr_addrs[keypad_idx-1]); } } while(gpiohs_get_pin(KEYPAD_MIDDLE_GPIONUM)!=1); //防止离开时按下的按钮继续触发查看照片 keypad_middle_icf_flag--; printf("exit keypad_enter_mid_icf%d %d\\n",keypad_middle_icf_flag,keypad_middle_icf_exit_flag); return 0;} 3.1.3 摄像头中断1234567891011121314151617static int on_dvp_irq_cb(void *ctx){ //读取DVP中断状态,如果完成则刷新显示地址的数据,并清除中断标志,否则读取摄像头数据 if (dvp_get_interrupt(DVP_STS_FRAME_FINISH)) { dvp_set_display_addr((uint32_t)display_buf_addr); dvp_clear_interrupt(DVP_STS_FRAME_FINISH); g_dvp_finish_flag = 1; } else { if (g_dvp_finish_flag == 0) dvp_start_convert(); dvp_clear_interrupt(DVP_STS_FRAME_START); } return 0;} 3.2 while循环1234while (g_dvp_finish_flag == 0);g_dvp_finish_flag = 0;//显示画面lcd_draw_picture(0, 0, 320, 240, display_buf_addr); 三.实验总结遇到问题: KEYPAD相同中断函数同时触发两次,尽管用了互斥变量还是没能解决 LCD显示已经拍摄的照片时(除最后一张),显示花纹图像,未正确显示 LCD显示偶尔会黑屏、失真 终端中文乱码,打印出问题 KEYPAD中键检测不灵敏,需要按一小段时间才能检测到 解决方案: 未解决 未解决,估计和内存存放地址有关 Reset可解决,但原因未找明,仍会复发 改用英文,每行加回车 未解决 Tips:引脚过多,注意软件引脚定义,不要冲突 结语:  第一次功能比较多的复合实验,遇到的冲突不少,需要注意不同功能间的交互、变量统一、冲突问题","categories":[{"name":"项目","slug":"项目","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/"},{"name":"嵌入式","slug":"项目/嵌入式","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/"},{"name":"K210","slug":"项目/嵌入式/K210","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/K210/"}],"tags":[{"name":"K210","slug":"K210","permalink":"http://example.com/tags/K210/"}]},{"title":"K210开发板(四)—按键独立中断","slug":"K210开发板-四-—按键独立中断","date":"2023-09-17T10:20:11.000Z","updated":"2023-11-28T09:06:00.476Z","comments":true,"path":"2023/09/17/K210开发板-四-—按键独立中断/","link":"","permalink":"http://example.com/2023/09/17/K210%E5%BC%80%E5%8F%91%E6%9D%BF-%E5%9B%9B-%E2%80%94%E6%8C%89%E9%94%AE%E7%8B%AC%E7%AB%8B%E4%B8%AD%E6%96%AD/","excerpt":"","text":"简介:  本篇博客用于记录K210的学习和自己遇到的问题,主要内容为按键独立中断,按键未按下时播放音乐,按键按下时暂停播放。由于参考资料的教程比较齐全,本文会着重于讲述自己遇到的问题和解决方案 参考资料:http://www.yahboom.com/study/K210-Developer-Kit提取码:moj1 一.实验准备1.外部中断  普通的按键的实验,虽然能实现 IO 口输入输出功能,但代码是一直在检测 IO 输入口的变化,因此效率不高,特别是在一些特定的场合,比如某个按键,可能 1 天才按下一次去执行相关功能,这样我们就浪费大量时间来实时检测按键的情况。为了解决这样的问题,我们引入外部中断概念,顾名思义,就是当按键被按下(产生中断)时,我们才去执行相关功能。这大大节省了 CPU 的资源,因此中断的在实际项目的应用非常普遍。 2.按键原理  BOOT 按键按下的时候会把电平拉低,松开的时候会把电平拉高,只需要检测 BOOT 按键的 IO 口的电平,如果是按下则会产生下降沿,松开会产生上升沿,以 此的方式来检测并触发系统的中断 3.函数介绍4.1 gpiohs_set_pin_edge  设置GPIO中断触发模式 参数成员 描述 输入输出 pin GPIO管脚 输入 edge 中断触发方式 输入 4.2 gpiohs_irq_register  设置GPIO中断回调函数 参数成员 描述 输入输出 pin GPIO管脚 输入 priority 中断优先级 输入 plic_irq_callback_t 中断回调函数 输入 *ctx 回调函数的形参 输入 二.实验步骤1.配置1.1 引脚12345678910111213141516171819202122//定义硬件引脚(见原理图)#define PIN_SPK_WS (30)#define PIN_SPK_DATA (31)#define PIN_SPK_BCK (32)#define PIN_KEY (16)//定义软件GPIO口#define KEY_GPIONUM (3)//将硬件引脚映射到软件GPIO功能#define FUNC_SPK_WS FUNC_I2S2_WS#define FUNC_SPK_DATA FUNC_I2S2_OUT_D0#define FUNC_SPK_BCK FUNC_I2S2_SCLK#define FUNC_KEY (FUNC_GPIOHS0 + KEY_GPIONUM)void hardware_init(void){ fpioa_set_function(PIN_SPK_WS, FUNC_SPK_WS); fpioa_set_function(PIN_SPK_DATA, FUNC_SPK_DATA); fpioa_set_function(PIN_SPK_BCK, FUNC_SPK_BCK); fpioa_set_function(PIN_KEY, FUNC_KEY);} 1.2 函数12345678int key_irq_cb(void* ctx){ gpio_pin_value_t key_state = gpiohs_get_pin(KEY_GPIONUM); //这里只是为了测试才在中断回调打印数据,正常情况下是不建议这么做的 printf("IRQ The PIN is %d\\n", key_state); uint32_t *tmp = (uint32_t *)(ctx); printf("count is %d\\n", (*tmp)++); return 0;} 2.初始化123456789101112131415161718192021222324252627// 硬件引脚初始化hardware_init();//外部中断初始化plic_init();///使能全局中断sysctl_enable_irq();//设置系统时钟sysctl_pll_set_freq(SYSCTL_PLL0, 320000000UL);sysctl_pll_set_freq(SYSCTL_PLL1, 160000000UL);sysctl_pll_set_freq(SYSCTL_PLL2, 45158400UL);uarths_init();//初始化I2Si2s_init(I2S_DEVICE_2, I2S_TRANSMITTER, 0x03);i2s_tx_channel_config( I2S_DEVICE_2, // I2S设备号 I2S_CHANNEL_0, // I2S通道 RESOLUTION_16_BIT, // 接收数据位数 SCLK_CYCLES_32, // 单个数据时钟数 TRIGGER_LEVEL_4, // DMA触发时FIFO深度 RIGHT_JUSTIFYING_MODE); // 工作模式// 初始化按键key//设置按键的GPIO模式为上拉输入gpiohs_set_drive_mode(KEY_GPIONUM,GPIO_DM_INPUT_PULL_UP);//设置按键的GPIO电平触发模式为下降沿触发gpiohs_set_pin_edge(KEY_GPIONUM,GPIO_PE_RISING);//设置按键GPIO口的中断回调gpiohs_irq_register(KEY_GPIONUM,1,key_irq_cb,&g_count); 3.主体123456789101112gpio_pin_value_t key_state = gpiohs_get_pin(KEY_GPIONUM);//检测按键是否未被按下if(key_state==1){ i2s_play( I2S_DEVICE_2, // I2S设备号 DMAC_CHANNEL0, // DMA通道号 (uint8_t *)test_pcm, // 播放的PCM数据 sizeof(test_pcm), // PCM数据的长度 1024, // 单次发送数量 16, // 单次采样位宽 2); // 声道数 } 三.实验总结遇到问题:音乐暂停没有实现真正意义上的暂停,松开按键只会重新播放解决方案:未解决,需要了解如何暂停I2S的传输然后恢复 结语:  无","categories":[{"name":"项目","slug":"项目","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/"},{"name":"嵌入式","slug":"项目/嵌入式","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/"},{"name":"K210","slug":"项目/嵌入式/K210","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/K210/"}],"tags":[{"name":"K210","slug":"K210","permalink":"http://example.com/tags/K210/"}]},{"title":"K210开发板(三)—扬声器播放","slug":"K210开发板-三-—扬声器播放","date":"2023-09-16T14:59:34.000Z","updated":"2023-11-28T09:06:00.476Z","comments":true,"path":"2023/09/16/K210开发板-三-—扬声器播放/","link":"","permalink":"http://example.com/2023/09/16/K210%E5%BC%80%E5%8F%91%E6%9D%BF-%E4%B8%89-%E2%80%94%E6%89%AC%E5%A3%B0%E5%99%A8%E6%92%AD%E6%94%BE/","excerpt":"","text":"简介:  本篇博客用于记录K210的学习和自己遇到的问题,主要内容为扬声器的播放。由于参考资料的教程比较齐全,本文会着重于讲述自己遇到的问题和解决方案 参考资料:http://www.yahboom.com/study/K210-Developer-Kit提取码:moj1 一.实验准备1.音频转化过程  模拟量转成数字量过程,一般可以分为三个过程,分别为采样、量化、编码, 用一个比源声音频率高的采样信号去量化源声音,记录每个采样点的值, 最后如果把所有采样点数值连接起来与源声音曲线是互相吻合的,只是它不是连续的。 在图中两条蓝色虚线距离就是采样信号的周期,即对应一个采样频率(FS), 可以想象得到采样频率越高最后得到的结果就与源声音越吻合,但此时采样数据量越越大, 一般使用44.1KHz采样频率即可得到高保真的声音。每条蓝色虚线长度决定着该时刻源声音的量化值, 该量化值有另外一个概念与之挂钩,就是量化位数。量化位数表示每个采样点用多少位表示数据范围, 常用有16bit、24bit或32bit,位数越高最后还原得到的音质越好,数据量也会越大。 2.I2SI2S 有 3 个主要信号: 串行时钟 SCLK,也叫位时钟(BCLK),即对应数字音频的每一位数据,SCLK 都有 1 个脉冲。SCLK 的频率=2×采样频率×采样位数 帧时钟 LRCK,用于切换左右声道的数据。LRCK 为“1”表示正在传输的 是左声道的数据,为“0”则表示正在传输的是右声道的数据。LRCK 的频率等于 采样频率。 串行数据 SDATA,就是用二进制补码表示的音频数据。   DAC 芯片将 I2S 信号转化成 RCH 信号输送给功放,功放将 RCH 信号发大,然 后扬声器播放出对应的声音。 3.PCM  将每个数字化声音数据转换成二进制数据,该数据就是 PCM 数据。PCM 数据可以直接存储在介质上,也可以在经过编解码处理后进行存储或传输。   PCM 数据常用量化指标:采样率位深、声道数、采样数据是否有符号(有符号的采样数据不能使用无符号的方式播放)、字节序(表示 PCM 数据是用大端存储还是小端存储,通常是小端存储)   对于 8-bit PCM 数据而言,存储结构可能是左声道数据右声道数据交替存储   PCM数据的音量调整由于数据溢出的可能性,不应当直接将数据*n,可通过对数变换处理x * tan(x/n)达到调整音量的效果 4.函数介绍4.1 i2s_tx_channel_config  设置I2S发送数据的通道参数 参数成员 描述 输入输出 device_num I2S号 输入 channel_num 通道号 输入 word_length 接收数据位数 输出 word_select_size 单个数据时钟数 输入 trigger_level DMA触发时FIFO深度 输入 word_mode 工作模式 输入 4.2 i2s_play  发送 PCM 数据, 比如播放音乐 参数成员 描述 输入输出 device_num I2S号 输入 channel_num 通道号 输入 buf PCM PCM数据 输入 buf_len PCM数据长度 输入 frame 单次发送数量 输入 bits_per_sample 单次采样位宽 输入 track_num 声道数 输入 二.实验步骤1.配置1.1.头文件12//部分头文件#include "pcm.h" 头文件pcm.h包含了变量uint16_t test_pcm[],其记录了要播放音频的PCM数据,参考资料直接给出了数据。这里我通过matlab将.wav文件转为PCM数据的方式实现播放任意音频。 12345678910%读取.wav文件filename='name.wav';[data,fs]=audioread(filename);data=data(:,2);%将数据归一化并转换为int16类型pcm_data=int16(data * (2^15 - 1));%将int16数据转换为uint16类型pcm_data=typecast(pcm_data, 'uint16');%将数据写入文件 以逗号分隔dlmwrite('data.txt',pcm_data,'delimiter',','); Tips:由于C语言不支持数组元素用空格分开,所以数据需要用逗号分隔再复制到pcm.h中 1.2.引脚配置123456789101112131415161718//定义硬件引脚(见原理图)#define PIN_SPK_WS (30)#define PIN_SPK_DATA (31)#define PIN_SPK_BCK (32)//定义软件GPIO口/*FUNC_I2S2_WSFUNC_I2S2_OUT_D0FUNC_I2S2_SCLK*///将硬件引脚映射到软件GPIO功能#define FUNC_SPK_WS FUNC_I2S2_WS#define FUNC_SPK_DATA FUNC_I2S2_OUT_D0#define FUNC_SPK_BCK FUNC_I2S2_SCLKvoid hardware_init(void){ fpioa_set_function(PIN_SPK_WS, FUNC_SPK_WS); fpioa_set_function(PIN_SPK_DATA, FUNC_SPK_DATA); fpioa_set_function(PIN_SPK_BCK, FUNC_SPK_BCK);} 2.初始化1234567891011121314151617//硬件引脚初始化hardware_init();//设置系统时钟sysctl_pll_set_freq(SYSCTL_PLL0, 320000000UL);sysctl_pll_set_freq(SYSCTL_PLL1, 160000000UL);sysctl_pll_set_freq(SYSCTL_PLL2, 45158400UL);uarths_init();//初始化I2S,第三个参数为设置通道掩码,通道0:0x03,通道1:0x0C,通道2:0x30,通道3:0xC0i2s_init(I2S_DEVICE_2, I2S_TRANSMITTER, 0x03);//设置I2S发送数据的通道参数i2s_tx_channel_config( I2S_DEVICE_2, // I2S设备号 I2S_CHANNEL_0, // I2S通道 RESOLUTION_16_BIT, // 接收数据位数 SCLK_CYCLES_32, // 单个数据时钟数 TRIGGER_LEVEL_4, // DMA触发时FIFO深度 RIGHT_JUSTIFYING_MODE); // 工作模式 3.主体12345678i2s_play( I2S_DEVICE_2, //I2S设备号 DMAC_CHANNEL0, //DMA通道号 (uint8_t *)test_pcm, //播放的PCM数据 sizeof(test_pcm), //PCM数据的长度 1024, //单次发送数量 16, //单次采样位宽 2); //声道数 三.实验总结遇到问题:PCM数据的获取解决方案:见二.1.1 结语:  仅仅按照参考资料的程序实验是不够的,需要自己理解代码的含义,做出可拓展的尝试","categories":[{"name":"项目","slug":"项目","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/"},{"name":"嵌入式","slug":"项目/嵌入式","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/"},{"name":"K210","slug":"项目/嵌入式/K210","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/K210/"}],"tags":[{"name":"K210","slug":"K210","permalink":"http://example.com/tags/K210/"}]},{"title":"K210开发板(二)—点亮LED灯","slug":"K210开发板-二-—点亮LED灯","date":"2023-09-16T05:40:42.000Z","updated":"2023-11-28T09:06:00.467Z","comments":true,"path":"2023/09/16/K210开发板-二-—点亮LED灯/","link":"","permalink":"http://example.com/2023/09/16/K210%E5%BC%80%E5%8F%91%E6%9D%BF-%E4%BA%8C-%E2%80%94%E7%82%B9%E4%BA%AELED%E7%81%AF/","excerpt":"","text":"简介:  本篇博客用于记录K210的学习和自己遇到的问题,主要内容为LED灯的交替闪烁。由于参考资料的教程比较齐全,本文会着重于讲述自己遇到的问题和解决方案 参考资料:http://www.yahboom.com/study/K210-Developer-Kit提取码:moj1 一.实验准备1.GPIO  我个人理解是将芯片的的操作封装成函数,使微控制器成为微处理器 好比你告诉家里的保姆说“去吧客厅的灯关上”,他就走过去按动灯的开关,然后灯就灭了。你下的这个指令的动作相当于调用了GPIO操作的函数,保姆去按开关这个动作相当于函数配置寄存器。 2.K210的GPIO—FPIOA  FPIOA (现场可编程 IO 阵列)允许用户将 255 个内部功能映射到芯片外围的 48 个自由 IO 上 二.实验步骤1.配置1234567891011121314//定义硬件引脚(见原理图)#define PIN_LED_0 (0)#define PIN_LED_1 (17)//定义软件GPIO口#define LED0_GPIONUM (0)#define LED1_GPIONUM (1)//将硬件引脚映射到软件GPIO功能#define FUNC_LED0 (FUNC_GPIO0 + LED0_GPIONUM)#define FUNC_LED1 (FUNC_GPIO0 + LED1_GPIONUM)void hardware_init(void){ fpioa_set_function(PIN_LED_0, FUNC_LED0); fpioa_set_function(PIN_LED_1, FUNC_LED1);} 2.初始化123456789hardware_init(); //硬件引脚初始化gpio_init(); // 使能GPIO的时钟// 设置LED0和LED1的GPIO模式为输出gpio_set_drive_mode(LED0_GPIONUM, GPIO_DM_OUTPUT);gpio_set_drive_mode(LED1_GPIONUM, GPIO_DM_OUTPUT);// 先关闭LED0和LED1gpio_pin_value_t value = GPIO_PV_HIGH;gpio_set_pin(LED0_GPIONUM, value);gpio_set_pin(LED1_GPIONUM, value); 3.主体123sleep(1); //延时1sgpio_set_pin(LED0_GPIONUM, value); //设置引脚值gpio_set_pin(LED1_GPIONUM, value = !value); 三.实验总结遇到问题:未能理解FUNC_GPIO0的作用解决方案:暂未解决 结语:  无","categories":[{"name":"项目","slug":"项目","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/"},{"name":"嵌入式","slug":"项目/嵌入式","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/"},{"name":"K210","slug":"项目/嵌入式/K210","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/K210/"}],"tags":[{"name":"K210","slug":"K210","permalink":"http://example.com/tags/K210/"}]},{"title":"K210开发板(一)—搭建开发环境","slug":"K210开发板-一-—搭建开发环境","date":"2023-09-15T13:53:59.000Z","updated":"2023-11-28T09:06:00.467Z","comments":true,"path":"2023/09/15/K210开发板-一-—搭建开发环境/","link":"","permalink":"http://example.com/2023/09/15/K210%E5%BC%80%E5%8F%91%E6%9D%BF-%E4%B8%80-%E2%80%94%E6%90%AD%E5%BB%BA%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/","excerpt":"","text":"简介:  本篇博客用于记录K210的学习和自己遇到的问题,主要内容为开发环境的搭建。由于参考资料的教程比较齐全,本文会着重于讲述自己遇到的问题和解决方案 参考资料:http://www.yahboom.com/study/K210-Developer-Kit提取码:moj1 一.安装串口驱动  使用参考资料中的程序安装即可 遇到问题:设备管理器中找不到端口选项解决方案:点击 查看->显示隐藏的设备即可 Tips:连接开发板需要用数据线,连接仅能充电的线电脑是没有反应的 二.配置VSCode开发环境  按照参考资料中的教程即可 创建新项目 把项目文件(包含main.c)复制到 SDK 中的src目录下 进入 build 目录,运行命令 cmake .. -DPROJ=name -G “MinGW Makefiles” make 打开kfalsh把生成的.bin文件(填入路径)烧录到开发板上 遇到问题:解压参考资料里的toolchain时报错:路径太长无法复制解决方案:进入https://www.canaan-creative.com/developer 下载压缩包即可 结语:  搭建过程友好,不要气馁","categories":[{"name":"项目","slug":"项目","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/"},{"name":"嵌入式","slug":"项目/嵌入式","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/"},{"name":"K210","slug":"项目/嵌入式/K210","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/K210/"}],"tags":[{"name":"K210","slug":"K210","permalink":"http://example.com/tags/K210/"}]},{"title":"K210开发板(零)","slug":"K210开发板-零","date":"2023-09-15T10:44:02.000Z","updated":"2023-11-28T09:06:00.483Z","comments":true,"path":"2023/09/15/K210开发板-零/","link":"","permalink":"http://example.com/2023/09/15/K210%E5%BC%80%E5%8F%91%E6%9D%BF-%E9%9B%B6/","excerpt":"","text":"一.介绍  本类博客categories:嵌入式->K210 tags:K210  本类博客用于记录自己的K210开发板学习过程,由于参考资料的教程比较齐全,本类博客会着重于讲述自己遇到的问题和解决方案 参考资料:http://www.yahboom.com/study/K210-Developer-Kit提取码:moj1 二.本类博客风格 第一篇为开发环境的搭建,之后为记录程序实践,第一篇不适用于下述风格 二级标题必选为实验准备、实验步骤、实验总结 实验步骤中三级标题可选为代码编写、烧录结果 代码编写中四级标题可选为  配置(main函数前部分)  初始化(main函数至while循环部分  主体(while循环中和中断回调函数中部分)  其它(while循环后部分) 配置中五级标题可选为头文件、引脚、函数 主题中五级标题可选为while循环和中断回调函数","categories":[{"name":"项目","slug":"项目","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/"},{"name":"嵌入式","slug":"项目/嵌入式","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/"},{"name":"K210","slug":"项目/嵌入式/K210","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/K210/"}],"tags":[{"name":"K210","slug":"K210","permalink":"http://example.com/tags/K210/"}]},{"title":"我的第一篇博客","slug":"我的第一篇博客","date":"2023-09-15T07:44:02.000Z","updated":"2023-09-16T13:27:42.216Z","comments":true,"path":"2023/09/15/我的第一篇博客/","link":"","permalink":"http://example.com/2023/09/15/%E6%88%91%E7%9A%84%E7%AC%AC%E4%B8%80%E7%AF%87%E5%8D%9A%E5%AE%A2/","excerpt":"","text":"简介:  本篇博客用于记录自己的第一篇博客,主要内容为Hexo的主要指令和markdown的主要语法 一.Hexo主要指令 hexo -n “xxx” %创建一个新文章 hexo clean %清理 hexo -g %生成 hexo -s %预览 hexo -d %部署 %http://localhost:4000/ #本地访问地址%https://arrowonstr.github.io/ #查看博客 二.markdown主要语法 %n级标题 #*n %引用 > %列表 - %代码块 ```代码语言 %脚注 [^] %横线 — %网址 [网站](地址”注释”) %超链接 [标题](#标题) %图片  %斜体 ** %加粗 ** ** %行内代码/使符号不起格式作用 `` %下划线 <u>下划线</u> %高亮 == == 结语:  博客之旅由此开始","categories":[{"name":"日记","slug":"日记","permalink":"http://example.com/categories/%E6%97%A5%E8%AE%B0/"}],"tags":[{"name":"日记","slug":"日记","permalink":"http://example.com/tags/%E6%97%A5%E8%AE%B0/"}]},{"title":"封面","slug":"封面","date":"2023-09-15T07:00:00.000Z","updated":"2024-01-13T06:31:58.345Z","comments":true,"path":"2023/09/15/封面/","link":"","permalink":"http://example.com/2023/09/15/%E5%B0%81%E9%9D%A2/","excerpt":"","text":"我的博客风格%日记类的博客可以不按照任意风格格式 1.博客框架 每篇博客使用tags归类 类内博客标题使用xxx(一)—xxx的格式 类内博客第一篇标题为xxx(零),内容为:  该类博客的主要介绍  该类博客的风格 类内博客简介包含该类的主要介绍 类内博客最后一篇标题为xxx(END) tags包含”日记” 2.行文框架 行文第一块为简介:xxx 字体为灰色 行文第二块为参考资料 正文从二级标题开始 %一级标题为文章标题  二级标题使用 一. 二. 三. …  三级标题使用 1. 2. 3. …  四级标题使用 1.1. 1.2. 1.3.  以此类推… 建议在内容下添加  遇到问题&解决方案 颜色为#3399ff  Tips 颜色为#ff6600 行文结尾为结语:xxx 颜色为灰色 3.其它 正文注释使用% 代码注释依据语言决定   早祷的钟声突然响了,无数的钟声一下子都惊醒了。天又黎明!黑沉沉的危崖后面,看不见的太阳在金色的天空升起。快要倒下来的克利斯朵夫终于到了彼岸。于是他对孩子说:   “咱们到了!唉,你多重啊!孩子,你究竟是谁呢?”   孩子回答说:   “我是即将来到的日子。” ***","categories":[{"name":"日记","slug":"日记","permalink":"http://example.com/categories/%E6%97%A5%E8%AE%B0/"}],"tags":[{"name":"日记","slug":"日记","permalink":"http://example.com/tags/%E6%97%A5%E8%AE%B0/"}]}],"categories":[{"name":"项目","slug":"项目","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/"},{"name":"笔记","slug":"笔记","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/"},{"name":"课程","slug":"笔记/课程","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/%E8%AF%BE%E7%A8%8B/"},{"name":"人工智能","slug":"笔记/人工智能","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"},{"name":"yolo","slug":"笔记/人工智能/yolo","permalink":"http://example.com/categories/%E7%AC%94%E8%AE%B0/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/yolo/"},{"name":"程序设计","slug":"项目/程序设计","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/"},{"name":"会议室系统","slug":"项目/程序设计/会议室系统","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/%E4%BC%9A%E8%AE%AE%E5%AE%A4%E7%B3%BB%E7%BB%9F/"},{"name":"嵌入式","slug":"项目/嵌入式","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/"},{"name":"K210","slug":"项目/嵌入式/K210","permalink":"http://example.com/categories/%E9%A1%B9%E7%9B%AE/%E5%B5%8C%E5%85%A5%E5%BC%8F/K210/"},{"name":"日记","slug":"日记","permalink":"http://example.com/categories/%E6%97%A5%E8%AE%B0/"}],"tags":[{"name":"编译原理","slug":"编译原理","permalink":"http://example.com/tags/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/"},{"name":"编译器","slug":"编译器","permalink":"http://example.com/tags/%E7%BC%96%E8%AF%91%E5%99%A8/"},{"name":"计网","slug":"计网","permalink":"http://example.com/tags/%E8%AE%A1%E7%BD%91/"},{"name":"数据库","slug":"数据库","permalink":"http://example.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"},{"name":"计算机组成原理","slug":"计算机组成原理","permalink":"http://example.com/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/"},{"name":"目标检测","slug":"目标检测","permalink":"http://example.com/tags/%E7%9B%AE%E6%A0%87%E6%A3%80%E6%B5%8B/"},{"name":"qt","slug":"qt","permalink":"http://example.com/tags/qt/"},{"name":"sql","slug":"sql","permalink":"http://example.com/tags/sql/"},{"name":"K210","slug":"K210","permalink":"http://example.com/tags/K210/"},{"name":"日记","slug":"日记","permalink":"http://example.com/tags/%E6%97%A5%E8%AE%B0/"}]}