操作系统之保护模式

news2024/11/19 2:43:05

保护模式

  • 保护模式概述
  • 初见保护模式
    • 保护模式之寄存器扩展
    • 保护模式之寻址扩展
  • 全局描述符表
    • 段描述符
  • 全局描述符GDT,局部描述符LDT级选择子
    • 保护模式的开关,CR0寄存器的PE位
  • 进入保护模式

保护模式概述

** 问题1:为什么会有保护模式**

  • 实模式下操作系统和用户程序属于同一特权级
  • 用户程序所引用的地址都是指向真实的物理地址
  • 用户程序可以自由修改段基址,可以访问所有内存
    以上三个原因属于安全缺陷
  • 访问超过64KB的内存区域时要切换段基址
  • 一次只能运行一个程序,无法充分利用计算机资源
  • 共20条地址线,最大可用内存为1MB
    为了克服这种恶劣的内存管理方式,处理器厂商开发出保护模式,这样,物理内存地址不能直接被程序访问,程序内部的地址(虚拟地址)需要被转化为物理地址后再去访问

注意:实模式不是32位CPU,变成了16位

  • 我们说实模式时,指的是32位的CPU运行在16位模式下的状态,就像大学生去做小学生的题一样,无非是大马拉小车

初见保护模式

保护模式之寄存器扩展

为了让一个寄存器就能访问4GB空间,需要寄存器宽度提升到32位
寄存器要保持向下兼容,不能推翻之前的方案从头再来,必须在原有的基础上扩展,各寄存器在原有16位的基础上,再次向高位扩展了16位,成为了32位寄存器
在这里插入图片描述
偏移地址还在和实模式下的一样,但段基址可不是简单的一个地址的事了,为了更加安全,怎么也得多添加点约束条件才靠谱,这些"约束条件"便是对内存段的描述信息,由于信息比较多,所以专门找了个数据结构——全局描述符表,既然叫表,就说明里面有表项,表中至少有一个表项,其中每一个表项称为段描述符,其大小为64字节,用来描述各个内存段的起始地址,大小,权限等信息
段寄存器保存的再也不是段基址了,里面保存的内容叫"选择子",该选择子其实就是个数,用这个数来索引全局描述符表中的段描述符
访问段描述符非常耗费时间,所以在80286的保护模式下,为了提高获取段信息的效率,对段寄存器率先应用了缓存技术,将段信息用一个寄存器来缓存,这就是段描述符缓冲寄存器,对程序员而言它是不可见的,CPU每次将获取到的内存段信息,整理后,存入段描述符缓冲寄存器,以后每次访问相同的段时,就直接读取该段寄存器对应的段描述符缓冲寄存器

保护模式之寻址扩展

进入保护模式后,寻址方式也有了很大进步,基址,变址,寻址变得更加灵活了
实模式下对于内存寻址来说,其中的基址寻址,变址寻址,基址变址寻址,这三种形式中的基址寄存器只能是bx,bp,变址寄存器只能是si,di,其中bx默认的段寄存器是ds,它经常用于访问数据段,bp默认的段寄存器是ss,它经常用于访问栈
在保护模式下,同样是内存寻址中,基址寄存器不再只是bx,bp,而是所有32位的通用寄存器,变址寄存器不再只是si,di,而是除esp之外的所有32位通用寄存器,偏移量由实模式的16位变成了32位,还可以对变址寄存器乘以一个比例因子,比例因子,只能是1,2,4,8
在这里插入图片描述

全局描述符表

保护模式下,内存段不再是简单地用段寄存器记载一下段基址就能用了,段的信息增加了很多,需要提前把段定义好才能使用
全局描述符表是保护模式下内存段的登记表,这是不同于实模式的显著特征之一

段描述符

对于IA32架构的处理器,访问内存采用"段基址:段内偏移量”形式,即使到了保护模式,也绕不开这个限制
现在为了安全性,需要给内存段添加一些额外的安全属性,这些安全属性存放在内存当中
** 问题2:需要添加哪些属性来描述内存段**

  • 实模式下的用户程序可以破坏存储代码的内存区域,所以要添加个内存段类型属性来阻止这种行为
  • 实模式下的用户程序和操作系统是同一级别的,所以要添加个特权级属性来区分用户程序和操作系统的地位
  • 内存段时一片内存区域,访问内存就要提供段基址,所以要有段基址属性
  • 为了限制程序访问内存的范围,还要对段大小进行约束,所以要有段界限属性
    这些用来描述内存段的属性,被放到了一个称为段描述符的结构中,该结构专门用来描述一个内存段,该结构是8字节大小
    在这里插入图片描述
    段界限表示段边界的扩展最值,扩展方向只有上下两种,对于数据段和代码段,段的扩展方向是向上的,即地址越来越高,此时的段界限用来表示段内偏移的最大值,对于栈段,段的扩展方向是向下,即地址越来越低,此时的段界限用来表示段内偏移的最小值
    段界限用20个二进制来表示,只不过此段界限只是个单位量,它的单位要么是字节,要么是4KB,这是由描述符中的G位来指定的,最终段的边界是此段界限值*单位,故段的大小要么是2的20次方等于1MB,要么是2的32次方(4KB等于2的12次方,12+20=32)等于4GB
    上面说的1MB和4GB只是个范围,由于段界限只是个偏移量,是从0算起的,所以实际的段界限边界值=(描述符中段界限+1) x (段界限的粒度大小:4KB或者1) -1
    如果G位为0,表示段界限粒度大小为1字节,实际段界限=(描述符中段界限+1)*1-1=描述符中段界限,段界限实际大小就等于描述符中的段界限值
    段界限用来限制段内偏移地址的,段内偏移地址必须位于段的范围之内,否则CPU会抛异常,根据段的扩展方向,此"段界限x单位“”便是段内偏移地址的最大值(向上扩展)或最小值(向下扩展),任何超过此值的偏移地址都被认为是非法访问
    从图上看,20位的段界限属性,被拆分成两部分,这属于历史遗留问题,为了兼容CPU不得不兼顾过去的产品
    不过不需要太担忧这样会影响CPU获取段信息的效率,因为段信息会被CPU缓存到段描述符缓冲寄存器中,此缓冲寄存器的内容便是段描述符中的内容,它是经过CPU整理后的,段界限和段基址已经被拼接到一起了,CPU下次会自动到段描述符缓冲器中取段数据
    0-7位是段基址的16-23,24-31位是段基址的24-31位,加上在段描述符低32位中的段基址0-15位,这下32位基地址才算齐全
    8-11位是type字段,共4位,用来指定本描述符的类型,一个段描述符,在CPU眼里分为两大类,要么描述的是系统段,要么描述的是数据段,这是由段描述符的S位决定的
    在CPU眼里,凡是硬件运行需要的东西都称为系统,凡是软件(操作系统也属于软件,CPU眼里,它与用户程序无区别)需要的东西都称为数据,所以代码段在段描述符中也属于数据段,S为0时表示系统段,S为1时表示数据段

问题三:什么是系统段
各种称为"门"的结构便是系统段,也就是硬件系统需要的结构,非软件使用的,如调度门,任务门,简而言之,门的元素就是入口,它通往一段程序
type字段,该字段共4位,用来表示内存段或门的子类型
在这里插入图片描述
表中A位表示Accessed位,这是CPU来设置的,没当该段被CPU访问过后,CPU就将此位置1,所以,创建一个新段描述符时,应该将此位置0,在调试时,根据该为便能判断该描述符是否可用了
C表示一致性代码段,C为1时表示该段时一致性代码段,C为0时表示该段为非一致性代码段
R表示可读,R为1表示可读,R为0表示不可读
X表示该段是否可执行
E是用来标识段的扩展方向,E为0表示向上扩展,E为1表示向下扩展
W是指段是否可写,W为1表示可写,通常用于数据段,W为0
段描述符的第13-14位是DPL字段,即描述符特权级
这两位能表示4种特权级,分别是0,1,2,3,级特权,数字越小,特权级越大
段描述符的第15位是P字段,即段是否存在,如果段存在于内存中,P为1,否则P为0

全局描述符GDT,局部描述符LDT级选择子

一个段描述符只用来定义一个内存段
这些描述符放在全局描述表中,全局描述表GDT相当于是描述符的数组,数组中的每个元素都是8字节的描述符
全局描述符表位于内存中,需要用专门的寄存器指向它,这个专门的寄存器便是GDTR,专门用来存储GDT的内存地址及大小
段描述符有了,描述符表也有了,我们该如何使用它,下面我们引出新的概念:段的选择子
段寄存器CS,DS,ES,FS,GS,SS,在实模式下,段中存储的是段基地址,即内存段的起始地址,而在保护模式下,由于段基址已经存入到段描述符中,所以段寄存器中再放段基址没有意义,在段基址中存入的是一个叫作选择子的东西——selector
由于段寄存器是16位,所以选择子也是16位,在其低2位即第0-1位,用于存储RPL,即请求特权级,可以表示0,1,2,3四种特权级,在选择子的第2位是TI位,用来指示选择子是在GDT中,还是在LDT中索引描述符,1表示在LDT中索引描述符,0表示在GDT中缩影描述符,选择子的高13位是描述符的索引值,用此值在GDT中索引描述符,前面说过GDT相当于一个描述符数组,所以此选择子中的索引值就是GDT中的下标
选择子的作用主要是确定段描述符,确定段描述的目的,一是为了特权级,权限等安全考虑,最主要的还是确认段的基地址
段基址在段描述符中,用给出的选择子缩影到描述符后,CPU自动从段描述符中取出段基址,这样再加上段内偏移地址,便凑成了段基址:段内偏移地址的形式
如图展示段描述符与内存段的关系
在这里插入图片描述
选择子的结构如图·所示:
在这里插入图片描述
需要注意的是GDT的第0个段描述符时不可用的,原因是定义在GDT中的段描述符是要用选择子来访问的,如果使用的选择子忘记初始化,选择子的值便会是0,这便会访问到第0个段描述符,为了避免出现这种因忘记初始化选择子而选择到第0个段描述符的情况,GDT中的第0个段描述符不可用
为了突破1MB内存的束缚,IBM在键盘控制器上的一些输出线来控制第21根地址(A20)的有效性,故被称为A20Gate

  • 如果A20Gate被打开,当访问到0x10000-0x10FFEF之间的地址时,CPU将真正访问这块物理内存
  • 如果A20Gate被禁用,当访问0x100000-0x10FFEF之间的地址时,CPU将采用8086/8088的地址回绕

打开A20Gate的方式是及其简单的,将端口0x92的第一位置1就可以了

保护模式的开关,CR0寄存器的PE位

控制寄存器是CPU的窗口,既可以用来展示CPU的内部控制,也可用于控制CPU的运行机制,我们将用到CR0寄存器的第0位,即PE位,此位用于启动保护模式,是保护模式的开关,当打开此位后,CPU才真正进入保护模式

进入保护模式

boot.inc文件,里面是一些配置信息,loader.S中用到的配置都是定义在boot.inc中的符号

;-------------	 loader和kernel   ----------

LOADER_BASE_ADDR equ 0x900 
LOADER_START_SECTOR equ 0x2

;--------------   gdt描述符属性  -------------
DESC_G_4K   equ	  1_00000000000000000000000b   
DESC_D_32   equ	   1_0000000000000000000000b
DESC_L	    equ	    0_000000000000000000000b	;  64位代码标记,此处标记为0便可。
DESC_AVL    equ	     0_00000000000000000000b	;  cpu不用此位,暂置为0  
DESC_LIMIT_CODE2  equ 1111_0000000000000000b
DESC_LIMIT_DATA2  equ DESC_LIMIT_CODE2
DESC_LIMIT_VIDEO2  equ 0000_000000000000000b
DESC_P	    equ		  1_000000000000000b
DESC_DPL_0  equ		   00_0000000000000b
DESC_DPL_1  equ		   01_0000000000000b
DESC_DPL_2  equ		   10_0000000000000b
DESC_DPL_3  equ		   11_0000000000000b
DESC_S_CODE equ		     1_000000000000b
DESC_S_DATA equ	  DESC_S_CODE
DESC_S_sys  equ		     0_000000000000b
DESC_TYPE_CODE  equ	      1000_00000000b	;x=1,c=0,r=0,a=0 代码段是可执行的,非依从的,不可读的,已访问位a清0.  
DESC_TYPE_DATA  equ	      0010_00000000b	;x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写的,已访问位a清0.

DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0x00
DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00
DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0b

;--------------   选择子属性  ---------------
RPL0  equ   00b
RPL1  equ   01b
RPL2  equ   10b
RPL3  equ   11b
TI_GDT	 equ   000b
TI_LDT	 equ   100b

loader.S文件

  %include "boot.inc"
   section loader vstart=LOADER_BASE_ADDR
   LOADER_STACK_TOP equ LOADER_BASE_ADDR
   jmp  near loader_start 					; 此处的物理地址是:
   
;构建gdt及其内部的描述符
   GDT_BASE:   dd    0x00000000 
	       dd    0x00000000

   CODE_DESC:  dd    0x0000FFFF 
	       dd    DESC_CODE_HIGH4

   DATA_STACK_DESC:  dd    0x0000FFFF
		     dd    DESC_DATA_HIGH4

   VIDEO_DESC: dd    0x80000007	       ;limit=(0xbffff-0xb8000)/4k=0x7
	       dd    DESC_VIDEO_HIGH4  ; 此时dpl已改为0

   GDT_SIZE   equ   $ - GDT_BASE
   GDT_LIMIT   equ   GDT_SIZE -	1 
  
   dq 50 dup(0)             ;不能预留 60个 ,60 * 8B 太大了(只有1M内存),50可以运行正常。
    ; times 60 dq 0					 ; 此处预留60个描述符的slot
   SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0         ; 相当于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0
   SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0	 ; 同上
   SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0	 ; 同上 

   ;以下是定义gdt的指针,前2字节是gdt界限,后4字节是gdt起始地址

   gdt_ptr  dw  GDT_LIMIT 
	    dd  GDT_BASE
   loadermsg db '2 loader in real.'

loader_start:

;------------------------------------------------------------
;INT 0x10    功能号:0x13    功能描述:打印字符串
;------------------------------------------------------------
;输入:
;AH 子功能号=13H
;BH = 页码
;BL = 属性(若AL=00H或01H)
;CX=字符串长度
;(DH、DL)=坐标(行、列)
;ES:BP=字符串地址 
;AL=显示输出方式
;   0——字符串中只含显示字符,其显示属性在BL中。显示后,光标位置不变
;   1——字符串中只含显示字符,其显示属性在BL中。显示后,光标位置改变
;   2——字符串中含显示字符和显示属性。显示后,光标位置不变
;   3——字符串中含显示字符和显示属性。显示后,光标位置改变
;无返回值
     
   mov	 sp, LOADER_BASE_ADDR
   mov	 bp, loadermsg           ; ES:BP = 字符串地址
   mov	 cx, 17			 ; CX = 字符串长度
   mov	 ax, 0x1301		 ; AH = 13,  AL = 01h
   mov	 bx, 0x001f		 ; 页号为0(BH = 0) 蓝底粉红字(BL = 1fh)
   mov	 dx, 0x1800		 ;
   int	 0x10                    ; 10h 号中断

;----------------------------------------   准备进入保护模式   ------------------------------------------
									;1 打开A20
									;2 加载gdt
									;3 将cr0的pe位置1


   ;-----------------  打开A20  ----------------
   in al,0x92
   or al,0000_0010B
   out 0x92,al

   ;-----------------  加载GDT  ----------------
   lgdt [gdt_ptr]


   ;-----------------  cr0第0位置1  ----------------
   mov eax, cr0
   or eax, 0x00000001
   mov cr0, eax
   

   ;jmp dword SELECTOR_CODE:p_mode_start	     ; 刷新流水线,避免分支预测的影响,这种cpu优化策略,最怕jmp跳转,
   jmp  dword SELECTOR_CODE:p_mode_start	     ; 刷新流水线,避免分支预测的影响,这种cpu优化策略,最怕jmp跳转,
					     ; 这将导致之前做的预测失效,从而起到了刷新的作用。

[bits 32]
p_mode_start:
   mov ax, SELECTOR_DATA
   mov ds, ax
   mov es, ax
   mov ss, ax
   mov esp,LOADER_STACK_TOP
   mov ax, SELECTOR_VIDEO
;    mov gs, ax

   mov byte [gs:160], 'P'

   jmp $

编译:

nasm -I include/ -o mbr.bin mbr.S
nasm -I include/ -o loader.bin loader.S

复制到硬盘:

dd if=./mbr.bin of=hd60M.img bs=512 count=1 conv=notrunc
dd if=./loader.bin of=hd60M.img bs=512 count=4 seek=2 conv=notrunc

最后的效果为:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/767.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

D-逃亡的贝贝(二分+有限制最小边权)

D-逃亡的贝贝_牛客练习赛104 (nowcoder.com) 题意:给你一个n个点,m条双向边的图(有边权),再给你起点s与终点t,以及有k个药水可以使某一条边,减小,求起点到终点经历边权最小值为多少. 题解: 首先建图,然后看到题中让我们找的是一个最小,或最大的值,是一个线性的值,我们就可以想…

整理了几个100%提高Python代码质量的技巧,直呼过瘾

B站|公众号&#xff1a;啥都会一点的研究生 相关阅读 整理了几个100%会踩的Python细节坑&#xff0c;提前防止脑血栓 整理了十个100%提高效率的Python编程技巧&#xff0c;更上一层楼 Python-列表&#xff0c;从基础到进阶用法大总结&#xff0c;进来查漏补缺 Python-元组&…

类和对象下篇

目录 再谈构造函数 构造函数体赋值 初始化列表 explicit关键字 static成员 访问静态成员变量的方法 C11中成员初始化的新玩法 友元 友元函数 友元类 内部类 再次理解封装 再次理解面向对象 内容大纲&#xff1a; 再谈构造函数 构造函数体赋值 在对象创建时&…

【牛客刷题专栏】0x08:C数据结构栈实现反转链表

前言 个人推荐在牛客网刷题(点击可以跳转)&#xff0c;它登陆后会保存刷题记录进度&#xff0c;重新登录时写过的题目代码不会丢失。个人刷题练习系列专栏&#xff1a;个人CSDN牛客刷题专栏。 牛客数据结构题目位置如下&#xff1a; 目录前言问题描述&#xff1a;举例&#x…

Pytest+Unittest+Git+Jenkins企业级CICD自动化测试平台建设方案

随着持续集成的引入&#xff0c;项目中的自动化测试用例越来越多&#xff0c;每轮执行所消耗的时间也越来越久。 要提高自动化测试用例执行的效率&#xff0c;以下几点是需要考虑的根本点。 &#xff08;1&#xff09;公司项目的交付策略如何 首先&#xff0c;测试团队服务于…

Autosar MCAL-ICU输入捕获

文章目录前言ICUIcuChannelIcuChannelIdIcuDefaultStartEdgeIcuMeasurementModeIcuSignalTypeIcuWakeupCapability子配置项IcuSignalMeasurementIcuSignalMeasurementPropertyIcuDutycycleBufferMarkerIcuOptionalApisIcuGetDutyCycleValuesApiIcuSetModeApiIcuSignalMeasureme…

尝试模仿现实通讯录

学习C语言已有一段时间&#xff0c;我虽不敢说代码水平尚可&#xff0c;但学习一段时间总需要有些成果。下半年开学后&#xff0c;专业课程增多&#xff0c;我需要每天完成学校任务&#xff0c;所花的时间也不算少&#xff0c;但还是有些空闲的。想起自己之前学过用C语言写通讯…

小程序开发必备功能的吐血整理【个人中心界面样式大全】

&#x1f4cc;个人主页&#xff1a;个人主页 ​&#x1f9c0; 推荐专栏&#xff1a;小程序开发成神之路 --(这是一个为想要入门和进阶小程序开发专门开启的精品专栏&#xff01;从个人到商业的全套开发教程&#xff0c;实打实的干货分享&#xff0c;确定不来看看&#xff1f; &…

FPGA学习笔记(七)verilog的深入学习之任务与函数(语法篇3)

目录任务与函数任务任务的基本概况自动&#xff08;可重入&#xff09;任务函数函数概述自动&#xff08;递归&#xff09;函数常量函数带符号函数条件编译在之前学习的基础上&#xff0c;继续加深对Verilog HDl的学习 前两个见&#xff1a; FPGA学习笔记&#xff08;二&#…

深度学习(python)——神经网络(Artificial Neural Networks)激活函数代码

目录 1.概述 2.激活函数 &#xff08;1&#xff09;基本概念 &#xff08;2&#xff09;sigmoid函数&#xff08;常用&#xff09; 代码(sigmoid) &#xff08;3&#xff09;阶跃函数 代码&#xff08;阶跃函数&#xff09; &#xff08;4&#xff09;ReLU函数&#xff08;目…

【零基础算法】C语言实现二叉搜索树

目录 一&#xff0c;定义 二&#xff0c;性质 三&#xff0c;基本操作 1&#xff0c;初始化 2&#xff0c;查找 3&#xff0c;插入&#xff08;建树&#xff09; 4.删除结点 四&#xff0c;总结 一&#xff0c;定义 二叉查找树&#xff08;Binary Search Tree&#…

记一次网络安全渗透测试实战指南

1.信息收集 网址已无法访问&#xff0c;就不贴了 可以使用Fofa&#xff0c;火线&#xff0c;zoomeye&#xff0c;searchcode.com等爬取相关的资产&#xff0c;重点关注一些有漏洞暴露的框架和服务例如&#xff1a;泛微&#xff0c;PHP&#xff0c;Tomca&#xff0c;后台&#x…

71.(后端)角色接口携带权限数据——flask两张表之间多对多关系的使用与层级关系的输出

1.ORM关系之多对多 1.1 什么时候使用多对多关系 例如&#xff0c;我们我们的项目中&#xff0c;一个用户可以拥有多个角色&#xff0c;同样的&#xff0c;一个角色可以给多个用户。通俗来说&#xff0c;一个用户可以购买多个商品&#xff0c;多个商品可以被一个用户购买 1.2…

【FPGA】Verilog编程实现SDRAM读写(一) ----- 初始SDRAM

文章目录一. 存储器及SDRAM分类1. 存储器分类2. 半导体存储器分类3. SDRAM分类二. 什么是SDRAM&#xff1f;1. SDRAM基本概念2. SDRAM存储阵列3. SDRAM基本存储单元4. BANK概念5. SDRAM容量计算6. SDRAM功能框图7. SDRAM信号引脚8. SDRAM操作命令8.1 禁止命令&#xff08; INHI…

mysql做查询时,第一次很慢,第二三次就会很快?

前言 sql语句第一次查询慢的原因不仅仅是因为执行计划没有被缓存这么简单,有时候你会发现sql语句重用了执行计划,但是第一次查询时还是很慢. 最主要的原因是第一次查询的时候,mysql会将查询出的部分数据和索引从磁盘加载到内存作为缓存,而第二三此查询的时候就直接从内存缓存…

Spring - 手把手分析 IoC 容器创建过程

概述 这里我们以 Spring 5.2.8.RELEASE 版本为例&#xff0c;看一下 IoC 容器 的创建过程。同时我们使用 java-based 的方式实例化 IoC 容器&#xff0c;即使用 AnnotationConfigApplicationContext Configuration 的方式配置容器需要的组件。 ApplicationContext ctx new …

【云IDE初体验】与君相逢恨晚,真正的轻量级开发工具

云IDE产品介绍 云IDE使用教程 免费使用地址&#xff1a;点击【云IDE】&#xff0c;即可开始创建工作空间啦~ 云IDE1.云IDE介绍2. 使用流程3. 体验总结作为一名大学生&#xff0c;学习的地点很关键&#xff0c;尤其是我们计算机系&#xff0c;更是离不开电脑&#xff0c;去图书馆…

调度线程池 ScheduledThreadPoolExecutor 的正确使用姿势

前言 项目中经常会遇到一些非分布式的调度任务&#xff0c;需要在未来的某个时刻周期性执行。实现这样的功能&#xff0c;我们有多种方式可以选择&#xff1a; Timer类&#xff0c; jdk1.3 引入&#xff0c;不推荐 它所有任务都是串行执行的&#xff0c;同一时间只能有一个任…

【C语言】移位操作符 位操作符 - 对二进制位进行精准操作【+面试题目】_[初阶篇]

快速导航 【前言】 1.移位操作符 1.1左移操作符(<<) 1.2右移操作符(>>) 2.位操作符 2.1 & 按位与 2.2 | (按位或) 2.3 ^ (按位异或) 3.面试题目 3.1 交换两个变量&#xff08;不创建临时变量&#xff09; 3.2统计二进制中1的个数 3.2.1 方法一&#xff1a;…

复合事件归因分析

1 复合事件 1.1 概述 1.2 类型 1.2.1 先决条件事件&#xff08;preconditioned events&#xff09; 1.2.2 多变量事件&#xff08;multivariate CEs&#xff09; eg.高温干旱 1.2.3 时间复合事件&#xff08;temporally CEs&#xff09; eg.旱涝急转 1.2.4 空间复合事件…