首页 > 其他分享 >【JVM】一、从class文件开始

【JVM】一、从class文件开始

时间:2022-09-18 19:02:02浏览次数:81  
标签:info 文件 00 常量 指令 JVM 方法 class 属性

image-20220917203645839

一、从class文件开始

1、手撕字节码文件

一个简单的Java源文件

package jvmClass;

public class jvmClass{
	private String name;
	
	public String getName() {
		return name;
	}
}

javac编译后得到字节码文件

image-20220917204005989

字节码文件结构
字节码文件结构中存在两种数据类型
(1)无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。

(2)表是由多个无符号数或者其他表作为数据项构成的复合数据类型,为了便于区分,所有表的命名都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上也可以视作是一张表

image-20220917213907237

JDK版本号

image-20220917215020941

1.1 常量池计数器

字节码文件中,魔数和版本号之后是常量池入口。

概述:

常量池可以比喻为class文件里的资源仓库,是class文件结构中于其他项目关联最多的数据。通常也是占用Class文件空间最大的数据项目之一,另外,它还是在Class文件中第一个出现的表类型数据项目。
由于常量池中的常量的数量是不固定的,所以需要在常量池入口处放置一项u2类型的数据,代表常量池容量计数值。

class文件中常量计数器从1开始

	与Java中语言习惯不同,这个容量计数是从1而不是0开始的。在Class文件格式规范制定之时,设计者将第0项常量空出来是有特殊考虑的,这样做的目的在于,如果后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,可以把索引值设置为0来表示。Class文件结构中只有常量池的容量计数是从1开始,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都与一般习惯相同,是从0开始。
	
例子1 :Object类没有父类,他的父类索引指向哪里呢?指向 00 00 (指向常量池里的第 0 个常量,第0个常量什么都没有,这个第 0个,就是为了给所有无法指向的情况提供的一个空常量指向)
例子2: 匿名内部类。 (类名称指向哪里?指向 00 00)

1.2 常量池

常量池主要存放两大类常量:

  • 字面量(Literal)
  • 符号引用(Symbolic References)
字面量:
	看上去是啥就存储啥
	比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。

符号引用
	则属于编译原理方面的概念,主要包括下面几类常量:
·被模块导出或者开放的包(Package)
·类和接口的全限定名(Fully Qualified Name)
·字段的名称和描述符(Descriptor)
·方法的名称和描述符
·方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
·动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)

在Class文件中不会保存各个方法、字段最终在内存中的布局信息,这些字段、方法的符号引用不经过虚拟机在运行期转换的话是无法得到真正的内存入口地址(类加载说。你的虚拟机不运行,你的类就是无用的。一切都要基于jvm运行的时候,类才有他的意义),也就无法直接被虚拟机使用的。当虚拟机做类加载时,将会从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。

常量池的项目类型

image-20220917221400002

常量池的项目类型的结构

image-20220917223059576

(1)CONSTANT_Class_info型常量的结构

image-20220917221450292

(2)CONSTANT_Utf8_info型常量的结构

image-20220917221644369

1.3 访问标志

常量池结束后,是2个字节的访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息。

包括:这个class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话是否被声明为final等等

类或接口访问标志表

image-20220917233130934

1.4 类索引、父类索引与接口索引集合

类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定该类型的继承关系。

类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。

由于Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为0

接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements关键字(如果这个Class文件表示的是一个接口,则应当是extends关键字)后的接口顺序从左到右排列在接口索引集合中。

类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串

image-20220917235428404

1.5 字段表集合

image-20220917235639103

字段表(field_info)用于描述接口或者类中声明的变量

Java语言中的“字段”(Field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量

字段可以包括的修饰符有字段的作用域(public、private、protected修饰符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称。上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。而字段叫做什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。

字段访问标志(access_flages)

access_flags:访问标志

image-20220917235947641

name_index:字段的简单名称的引用

descriptor_index:字段和方法的描述符

attributes_count:属性表属性的数量

1.6 方法表集合

Class文件存储格式中对方法的描述与对字段的描述采用了几乎完全一致的方式,方法表的结构如同字段表一样,依次包括访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项

image-20220918001038621

方法里面的代码去哪里了?

	方法里的Java代码,经过Javac编译器编译成字节码指令之后,存放在方法属性表集合中一个名为“Code”的属性里面,属性表作为Class文件格式中最具扩展性的一种数据项目。

1.7 属性表集合

属性表(attribute_info)在前面的讲解之中已经出现过数次,Class文件、字段表、方法表都可以携带自己的属性表集合,以描述某些场景专有的信息。

	与Class文件中其他的数据项目要求严格的顺序、长度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格顺序,并且《Java虚拟机规范》允许只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。

对于每一个属性,它的名称都要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性去说明属性值所占用的位数即可。一个符合规则的属性表应该满足表中所定义的结构。

image-20220918001506471

1.7.1 Code属性

Java程序方法体里面的代码经过Javac编译器处理之后,最终变为字节码指令存储在Code属性内。Code属性出现在方法表的属性集合之中,但并非所有的方法表都必须存在这个属性,譬如接口或者抽象类中的方法就不存在Code属性,如果方法表有Code属性存在,那么它的结构将如表

image-20220918001558444

  • attribute_name_index:是一项指向CONSTANT_Utf8_info型常量的索引,此常量值固定为“Code”,它代表了该属性的属性名称

  • attribute_length:指示了属性值的长度,由于属性名称索引与属性长度一共为6个字节,所以属性值的长度固定为整个属性表长度减去6个字节。

  • max_stack:代表了操作数栈(Operand Stack)深度的最大值。在方法执行的任意时刻,操作数栈都不会超过这个深度。虚拟机运行的时候需要根据这个值来分配栈帧(Stack Frame)中的操作栈深度(stackOverflow 异常)。

  • max_locals代表了局部变量表所需的存储空间。在这里,max_locals的单位是变量槽(Slot),变量槽是虚拟机为局部变量分配内存所使用的最小单位。对于byte、char、float、int、short、boolean和returnAddress等长度不超过32位的数据类型,每个局部变量占用一个变量槽,而double和long这两种64位的数据类型则需要两个变量槽来存放

    方法参数(包括实例方法中的隐藏参数“this”)、显式异常处理程序的参数(Exception Handler Parameter,就是try-catch语句中catch块中所定义的异常)、方法体中定义的局部变量都需要依赖局部变量表来存放。
    注意:

    并不是在方法中用了多少个局部变量,就把这些局部变量所占变量槽数量之和作为max_locals的值,操作数栈和局部变量表直接决定一个该方法的栈帧所耗费的内存,不必要的操作数栈深度和变量槽数量会造成内存的浪费。Java虚拟机的做法是将局部变量表中的变量槽进行重用,当代码执行超出一个局部变量的作用域时,这个局部变量所占的变量槽可以被其他局部变量所使用,Javac编译器会根据变量的作用域来分配变量槽给各个变量使用,根据同时生存的最大局部变量数量和类型计算出max_locals的大小

  • code_lengthcode用来存储Java源程序编译后生成的字节码指令。code_length代表字节码长度,code是用于存储字节码指令的一系列字节流。既然叫字节码指令,那顾名思义每个指令就是一个u1类型的单字节,当虚拟机读取到code中的一个字节码时,就可以对应找出这个字节码代表的是什么指令,并且可以知道这条指令后面是否需要跟随参数,以及后续的参数应当如何解析。

	关于code_length,有一件值得注意的事情,虽然它是一个u4类型的长度值,理论上最大值可以达到2的32次幂,但是《Java虚拟机规范》中明确限制了一个方法不允许超过65535条字节码指令,即它实际只使用了u2的长度,如果超过这个限制,Javac编译器就会拒绝编译。一般来讲,编写Java代码时只要不是刻意去编写一个超级长的方法来为难编译器,是不太可能超过这个最大值的限制的。但是,某些特殊情况,例如在编译一个很复杂的JSP文件时,某些JSP编译器会把JSP内容和页面输出的信息归并于一个方法之中,就有可能因为方法生成字节码超长的原因而导致编译失败。

	Code属性是Class文件中最重要的一个属性,如果把一个Java程序中的信息分为代码(Code,方法体里面的Java代码)和元数据(Metadata,包括类、字段、方法定义及其他信息)两部分,那么在整个Class文件里,Code属性用于描述代码,所有的其他数据项目都用于描述元数据。

1.7.2 ConstantValue属性

ConstantValue属性的作用是通知虚拟机自动为静态变量赋值(提前赋值,不需要运行时赋值,加快运行时的获取速度)

只有被static关键字修饰的变量(类变量)才可以使用这项属性。类似“int x=123”和“static int x=123”这样的变量定义在Java程序里面是非常常见的事情,但虚拟机对这两种变量赋值的方式和时刻都有所不同。对非static类型的变量(也就是实例变量)的赋值是在实例构造器<init>()方法中进行的;而对于类变量,则有两种方式可以选择:在类构造器<clinit>()方法中或者使用ConstantValue属性。

目前Oracle公司实现的Javac编译器的选择是,如果同时使用final和static来修饰一个变量(按照习惯,这里称“常量”更贴切),并且这个变量的数据类型是基本类型或者java.lang.String的话,就将会生成ConstantValue属性来进行初始化;如果这个变量没有被final修饰,或者并非基本类型及字符串,则将会选择在<clinit>()方法中进行初始化。

image-20220918162335612

从数据结构中可以看出ConstantValue属性是一个定长属性,它的attribute_length数据项值必须固定为2。constantvalue_index数据项代表了常量池中一个字面量常量的引用,根据字段类型的不同,字面量可以是CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、CONSTANT_Integer_info和CONSTANT_String_info常量中的一种。

1.8 jvm字节码指令

在Java虚拟机的指令集中,大多数指令都包含其操作所对应的数据类型信息。举个例子,iload指令用于从局部变量表中加载int型的数据到操作数栈中,而fload指令加载的则是float类型的数据。这两条指令的操作在虚拟机内部可能会是由同一段代码来实现的,但在Class文件中它们必须拥有各自独立的操作码。

对于大部分与数据类型相关的字节码指令,它们的操作码助记符中都有特殊的字符来表明专门为哪种数据类型服务:i代表对int类型的数据操作,l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。也有一些指令的助记符中没有明确指明操作类型的字母,例如arraylength指令,它没有代表数据类型的特殊字符,但操作数永远只能是一个数组类型的对象。还有另外一些指令,例如无条件跳转指令goto则是与数据类型无关的指令。

1.8.1 加载和存储

加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输,这类指令包括:

  • 将一个局部变量加载到操作栈:iload、iload_、lload、lload_fload、fload_、dload、dload_、aload、aload_

  • 将一个数值从操作数栈存储到局部变量表:istore、istore_、lstore、lstore_、fstore、fstore_、dstore、dstore_、astore、astore_

  • 将一个常量加载到操作数栈:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_、lconst_、fconst_、dconst_

  • 扩充局部变量表的访问索引的指令:wide

1.8.2 运算

  • 加法指令:iadd、ladd、fadd、dadd

  • 减法指令:isub、lsub、fsub、dsub

  • 乘法指令:imul、lmul、fmul、dmul

  • 除法指令:idiv、ldiv、fdiv、ddiv

  • 求余指令:irem、lrem、frem、drem

  • 取反指令:ineg、lneg、fneg、dneg

  • 位移指令:ishl、ishr、iushr、lshl、lshr、lushr

  • 按位或指令:ior、lor

  • 按位与指令:iand、land

  • 按位异或指令:ixor、lxor

  • 局部变量自增指令:iinc

  • 比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp

1.8.3 类型转换指令

Java虚拟机直接支持(即转换时无须显式的转换指令)以下数值类型的宽化类型转换(Widening Numeric Conversion,即小范围类型向大范围类型的安全转换):

  • int类型到long、float或者double类型
  • long类型到float、double类型
  • float类型到double类型

与之相对的,处理窄化类型转换(Narrowing Numeric Conversion)时,就必须显式地使用转换指令来完成,这些转换指令包括i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f。窄化类型转换可能会导致转换结果产生不同的正负号、不同的数量级的情况,转换过程很可能会导致数值的精度丢失。

1.8.4 对象创建与访问指令

虽然类实例和数组都是对象,但Java虚拟机对类实例和数组的创建与操作使用了不同的字节码指令。对象创建后,就可以通过对象访问指令获取对象实例或者数组实例中的字段或者数组元素,这些指令包括:

  • 创建类实例的指令:new

  • 创建数组的指令:newarray、anewarray、multianewarray

  • 访问类字段(static字段,或者称为类变量)和实例字段(非static字段,或者称为实例变量)的指令:getfield、putfield、getstatic、putstatic

  • 把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload

  • 将一个操作数栈的值储存到数组元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore

  • 取数组长度的指令:arraylength

  • 检查类实例类型的指令:instanceof、checkcast

1.8.5 方法调用和返回指令

方法调用,这里仅列举以下五条指令用于方法调用:

  • invokevirtual指令:用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式。

  • invokeinterface指令:用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。

  • invokespecial指令:用于调用一些需要特殊处理的实例方法,包括实例初始化方法(init)、私有方法(private)和父类方法(父类方调用)。

  • invokestatic指令:用于调用类静态方法(static方法)。

  • invokedynamic指令:用于在运行时动态解析出调用点限定符所引用的方法。并执行该方法。前面四条调用指令的分派逻辑都固化在Java虚拟机内部,用户无法改变,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。

方法调用指令与数据类型无关,而方法返回指令是根据返回值的类型区分的,包括ireturn(当返回值是boolean、byte、char、short和int类型时使用)、lreturn、freturn、dreturn和areturn,另外还有一条return指令供声明为void的方法、实例初始化方法、类和接口的类初始化方法使用。

1.9 得到的字节码文件

CA FE BA BE //魔数(咖啡宝贝)
00 00 次版本
00 3C 主版本 60

//常量池入口
00 13 常量池常量个数 19个(真正有18个)

//第1个常量
0A 表示方法类型 CONSTANT_Methoder_info
00 04 指向第4个常量 java/lang/Object
// 一旦有一个类需要初始化,必先初始化其父类
00 0F 指向第15个常量 CONSTANT_NameAndType_info

//第2个常量 
09 字段的符号引用 CONSTANT_Fieldref_info
00 03 指向第3个常量 jvmClass/jvmClass
00 10 指向第16个常量 CONSTANT_NameAndType_info

//第3个常量 jvmClass/jvmClass
07 类或接口的符号引用 CONSTANT_Class_info
00 11 指向第17个常量

//第4个常量 java/lang/Object
07 类或接口的符号引用 CONSTANT_Class_info
00 12 指向第18个常量

//第5个常量
01 UTF-8编码的字符串 CONSTANT_Utf8_info
00 04 length:字符串长度为4个字节
6E 61 6D 65 代表"name"

//第6个常量
01 UTF-8编码的字符串 CONSTANT_Utf8_info
00 12 length:字符串长度为18个直接
4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 代表"Ljava/lang/string;"
//L:代表后边是类描述符
//以分号结尾

//第7个常量
01 UTF-8编码的字符串 CONSTANT_Utf8_info
00 06 长度为6
3C 69 6E 69 74 3E //"init"方法,Java文件编译为class文件jvm自动生成

//第8个常量
01 UTF-8编码的字符串 CONSTANT_Utf8_info
00 03
28 29 56 //"()V" 方法的返回值为void(init方法的返回值)

//第9个常量
01 UTF-8编码的字符串 CONSTANT_Utf8_info
00 04
43 6F 64 65 //"Code"属性(方法中都有Code属性)

//第10个常量
01 UTF-8编码的字符串 CONSTANT_Utf8_info
00 0F //15
4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 //"LineNumberTable" 代码的行号,为显示错误的行数

//第11个常量
01
00 07
67 65 74 4E 61 6D 65 //"getName" 方法名称

//第12个常量
01
00 14
28 29 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B //"java/lang/string;"

//第13个常量
01
00 0A
53 6F 75 72 63 65 46 69 6C 65 //"SourceFile" 源文件标识

//第14个常量
01
00 0D
6A 76 6D 43 6C 61 73 73 2E 6A 61 76 61 //"jvmClass.java"真正的文件名称

//第15个常量(有一个常量,代表了一个init名称和void名称)
0C 字段或方法的部分符号引用 CONSTANT_NameAndType_info
00 07 指向第7个常量——init
00 08 指向第8个常量——void返回值

//第16个常量(有一个常量,名称叫name,类型是Ljava/lang/string))
0C 字段或方法的符号引用 CONSTANT_NameAndType_info
00 05 指向第5个常量——name
00 06 指向第6个常量——Ljava/lang/string

//第17个常量
01 UTF-8编码的字符串 CONSTANT_Utf8_info
00 11
6A 76 6D 43 6C 61 73 73 2F 6A 76 6D 43 6C 61 73 73 //"jvmClass/jvmClass" 全包名 + 类名

//第18个常量
01
00 10
6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 //"Ljava/lang/Object"

//访问标志
00 21 // ACC_PUBLIC + ACC_SUPER

//类索引
00 03 指向第3个常量 jvmClass

//父类索引
00 04 指向第4个常量 Object

//接口索引
00 00 没有实现任何接口

//字段表计数器
00 01 1个

//第1个字段 ===> private String name;
00 02 private
00 05 指向第5个常量——name
00 06 指向第6个常量——Ljava/lang/string
00 00 没有其他属性

//方法计数器
00 02 //两个方法(init, getName)

//第1个方法
00 01 public
00 07 指向第7个常量——init
00 08 指向第8个常量——void返回值
00 01 有一个属性(Code)
00 09 指向第9个常量——>Code
00 00 00 1D 属性的长度为29
00 01 最大栈深度
00 01 最大局部变量表槽的数量
00 00 00 05 代码编译后所生成的jvm指令码长度code_length
2A //aload_0指令,将第一个属性压入栈顶
B7 //invokespecial,调用所引用对象的构造方法
00 01 //调用第一个方法(init) 
B1 //return

00 00 异常信息为空

00 01 Code属性有一个属性
00 0A 指向第10个属性——>"LineNumberTable" 代码的行号
00 00 00 06 属性的长度为6
00 01 有一个属性
00 00 字节码的行号
00 03 代码的行号

//第2个方法
00 01 public
00 0B 指向第11个常量——>getName
00 0C 指向第12个常量——>java/lang/string;
00 01 有一个属性
00 09 指向第9个常量——>Code
00 00 00 1D 属性的长度为29
00 01 最大栈深度
00 01 最大局部变量表槽的数量
00 00 00 05 code属性长度为5(指令码长度)
2A //aload_0指令,将第一个属性压入栈顶
B4 //getfield指令,获取name并return
00 02 //指向第2个常量 Ljava/lang/string;name
B0 //areturn,返回name对象的引用

00 00 //异常信息为空

00 01 //code属性有一个属性
00 0A //指向第10个属性——>"LineNumberTable" 代码的行号
00 00 00 06 属性的长度为6
00 01 有一个属性
00 00 字节码的行号
00 07 代码的行号

00 01 属性表计数器——一个属性
00 0D 指向第13个常量——>SourceFile
00 00 00 02 两个长度
00 0E 指向"jvmClass.java"

标签:info,文件,00,常量,指令,JVM,方法,class,属性
From: https://www.cnblogs.com/DarkSki/p/16705391.html

相关文章

  • xampp相关配置文件所在路径
    环境:Xampp3.2.4一般来讲,可以在xampp的控制面板打开先关的配置文件,但是出现有些问题的时候,还是需要到别的路径下找到相关配置文件修改才行。Xampp的配置文件propertie......
  • .mat文件批量转存为.json文件的方法
    记录下.mat文件批量转存为.json文件的方法matlab本身是没有json解析包的,因此需要加载一个jsonlab工具包,可以用git命令下载gitclonehttps://github.com/fangq/jsonlab.g......
  • testbench (verilog)读取文件的细节问题
    为什么要用?在使用数字图像IC设计中,往往需要测试所设计的图像处理模块的功能,此时模块的输入端数据时序要求比较复杂,因此需要通过testbench按照一定的时序关系读取外部的文......
  • 创建一个很棒的 GitHub 个人资料自述文件!!
    创建一个很棒的GitHub个人资料自述文件!!啊,自述文件——当你开始一个新的GitHub存储库或项目时生成的那些Markdown文件。也许你给你的项目一些描述,或者你是一个偶尔更......
  • 使用 CSS 的 Glassmorphic 配置文件卡
    使用CSS的Glassmorphic配置文件卡在这篇博客中,我们使用css创建了一个glassmorphic配置文件卡。获取完整的源代码编码扭矩.com版权声明:本文为博主原创文章,遵循......
  • 文件操作
    程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放通过文件可以将数据持久化C++中对文件操作需要包含头文件==<fstream>==文件类型分为两种:文本文件......
  • 操作系统学习笔记13 | 目录与文件系统
    本文完成磁盘管理4层抽象中的最后一层抽象:目录与文件系统。达成的效果是整个磁盘抽象为我们日常所熟悉的目录树,这个树应当能够适配不同的操作系统(是一个独立子系统),通过目......
  • 1-STM32+Air724UG远程升级篇OTA(自建物联网平台)-STM32通过Air724UG使用http或https下
    <p><iframename="ifd"src="https://mnifdv.cn/resource/cnblogs/ZLAir724UGA/myota.html"frameborder="0"scrolling="auto"width="100%"height="1500"></iframe></p>......
  • shell编程-文件归档
    需求说明:设置定时任务,每天凌晨1点进行将指定目录(/root/scripts)下文件按照archive_目录名_年月日.tar.gz的格式归档存放到/root/archive路径下。1、编写脚本文件archive_s......
  • redis的配置文件
    redis的配置文件开头INCLUDES(包含)当redis有多个其他配置时就可以使用include来引入,类似spring中的import,如果想要覆盖其中的配置参数需要把include放到最后来设置。......