Linux第66步_linux字符设备驱动_挂载和卸载

news2025/1/19 17:21:32

1、了解linux中的驱动类型:

1)、字符设备驱动

字符设备是limnux驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。如:GPIO输入输出、UART、I2C、SPI、USB、LCD、音频等都属于字符设备驱动。

2)、块设备驱动

块设备驱动就是存储器设备的驱动,比如EMMC、NAND、SD卡和U盘等存储设备,因为这些存储设备的特点是以存储块为基础,因此叫做“块设备

3)、网络设备驱动网络设备就是网络驱动,不管是有线的,还是无线的,都属于网络设备驱动的范畴。

注意:

一个设备可以属于多种设备驱动类型,比如:USB WIFI,其使用USB接口,所以属于字符设备,但是其又能上网,所以它又属于网络设备驱动。

2、了解Limux应用程序调用驱动程序的流程

在Linux中一切皆为文件。驱动加载成功以后会在“/dev”目录下生成一个相应的驱动文件,如“xxx”的驱动文件名。应用程序通过对“/dev/xxx”的文件进行相应的操作,就可实现对硬件的操作。比如:现在有个叫做“/dev/led ”的驱动文件,它是1ed灯的驱动文件。应用程序使用“open函数”打开驱动文件“/dev/led ”,然后使用“close函数”关闭驱动文件“/dev/led”。open和close 就是打开和关闭led驱动的函数,如果要点亮或关闭1ed,那么就使用write函数来操作,也就是向此驱动写入数据,这个数据就是要关闭还是要打开led的控制参数。如果要获取led灯的状态,就用read函数从驱动中读取相应的状态。应用程序运行在用户空间,而limux驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核进行操作,比如使用open函数打开/dev/led这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作。open、close、write和read等这些函数是由C库提供的,在Limux系统中,系统调用作为C库的一部分。

我们重点关注的时应用程序调用的函数驱动调用的函数,至于C库和内核调用的函数,我们不用去关心。

3、了解“file_operations”结构体

打开虚拟机上VSCode,点击“文件”,点击“打开文件夹”,点击“zgq”,点击“linux”,点击“atk-mp1”,点击“linux”,点击“my_linux”,点击“linux-5.4.31”,见下图:

点击“确定

点击“查看”,点击“搜索”,输入“struct file_operations

随便点击其中一个struct file_operations”,得到下面的界面:

在“file_operations”点击“鼠标右键”,点击“转到定义”,见下图:

得到下图:

字符设备驱动重点是“file_operations”结构体

“fs.h”定义“file_operations”结构体如下:

struct file_operations {

struct module *owner;

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);

ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);

int (*iopoll)(struct kiocb *kiocb, bool spin);

int (*iterate) (struct file *, struct dir_context *);

int (*iterate_shared) (struct file *, struct dir_context *);

__poll_t (*poll) (struct file *, struct poll_table_struct *);

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct *);

unsigned long mmap_supported_flags;

int (*open) (struct inode *, struct file *);

int (*flush) (struct file *, fl_owner_t id);

int (*release) (struct inode *, struct file *);

int (*fsync) (struct file *, loff_t, loff_t, int datasync);

int (*fasync) (int, struct file *, int);

int (*lock) (struct file *, int, struct file_lock *);

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

int (*check_flags)(int);

int (*flock) (struct file *, int, struct file_lock *);

ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

int (*setlease)(struct file *, long, struct file_lock **, void **);

long (*fallocate)(struct file *file, int mode, loff_t offset,

  loff_t len);

void (*show_fdinfo)(struct seq_file *m, struct file *f);

#ifndef CONFIG_MMU

unsigned (*mmap_capabilities)(struct file *);

#endif

ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,

loff_t, size_t, unsigned int);

loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,

   struct file *file_out, loff_t pos_out,

   loff_t len, unsigned int remap_flags);

int (*fadvise)(struct file *, loff_t, loff_t, int);

} __randomize_layout;

/* 设备操作函数结构体 */

static struct file_operations chrdevbase_fops = {

    .owner = THIS_MODULE,  

    .open = chrdevbase_open,

    .read = chrdevbase_read,

    .write = chrdevbase_write,

    .release = chrdevbase_release,

};

4、了解“linux-5.4.31”中的驱动

下面我们通过“crc-vpmsum_test.c”来学习linux驱动的编写:

1)、点击“转到”,点击“转到文件”,在输入框中输入“crc-vpmsum_test.c”,按“回车键”打开“crc-vpmsum_test.c

见下图:

crc-vpmsum_test.c”内容如下:

// SPDX-License-Identifier: GPL-2.0-only

/*

 * CRC vpmsum tester

 * Copyright 2017 Daniel Axtens, IBM Corporation.

 */

#include <linux/crc-t10dif.h>

#include <linux/crc32.h>

#include <crypto/internal/hash.h>

#include <linux/init.h>       //必须要包含的头文件

#include <linux/module.h>     //必须要包含的头文件

#include <linux/string.h>     //下面要用到字符串,显然也要包含

#include <linux/kernel.h>     //必须要包含的头文件

#include <linux/cpufeature.h>

#include <asm/switch_to.h>

static unsigned long iterations = 10000;

#define MAX_CRC_LENGTH 65535

/*入口函数初始化*/

static int __init crc_test_init(void)

{

u16 crc16 = 0, verify16 = 0;

u32 crc32 = 0, verify32 = 0;

__le32 verify32le = 0;

unsigned char *data;

unsigned long i;

int ret;

struct crypto_shash *crct10dif_tfm;

struct crypto_shash *crc32c_tfm;

if (!cpu_has_feature(CPU_FTR_ARCH_207S))

return -ENODEV;

data = kmalloc(MAX_CRC_LENGTH, GFP_KERNEL);

if (!data)

return -ENOMEM;

crct10dif_tfm = crypto_alloc_shash("crct10dif", 0, 0);

if (IS_ERR(crct10dif_tfm)) {

pr_err("Error allocating crc-t10dif\n");

goto free_buf;

}

crc32c_tfm = crypto_alloc_shash("crc32c", 0, 0);

if (IS_ERR(crc32c_tfm)) {

pr_err("Error allocating crc32c\n");

goto free_16;

}

do {

SHASH_DESC_ON_STACK(crct10dif_shash, crct10dif_tfm);

SHASH_DESC_ON_STACK(crc32c_shash, crc32c_tfm);

crct10dif_shash->tfm = crct10dif_tfm;

ret = crypto_shash_init(crct10dif_shash);

if (ret) {

pr_err("Error initing crc-t10dif\n");

goto free_32;

}

crc32c_shash->tfm = crc32c_tfm;

ret = crypto_shash_init(crc32c_shash);

if (ret) {

pr_err("Error initing crc32c\n");

goto free_32;

}

pr_info("crc-vpmsum_test begins, %lu iterations\n", iterations);

for (i=0; i<iterations; i++) {

size_t offset = prandom_u32_max(16);

size_t len = prandom_u32_max(MAX_CRC_LENGTH);

if (len <= offset)

continue;

prandom_bytes(data, len);

len -= offset;

crypto_shash_update(crct10dif_shash, data+offset, len);

crypto_shash_final(crct10dif_shash, (u8 *)(&crc16));

verify16 = crc_t10dif_generic(verify16, data+offset, len);

if (crc16 != verify16) {

pr_err("FAILURE in CRC16: got 0x%04x expected 0x%04x (len %lu)\n",

       crc16, verify16, len);

break;

}

crypto_shash_update(crc32c_shash, data+offset, len);

crypto_shash_final(crc32c_shash, (u8 *)(&crc32));

verify32 = le32_to_cpu(verify32le);

        verify32le = ~cpu_to_le32(__crc32c_le(~verify32, data+offset, len));

if (crc32 != (u32)verify32le) {

pr_err("FAILURE in CRC32: got 0x%08x expected 0x%08x (len %lu)\n",

       crc32, verify32, len);

break;

}

}

pr_info("crc-vpmsum_test done, completed %lu iterations\n", i);

} while (0);

free_32:

crypto_free_shash(crc32c_tfm);

free_16:

crypto_free_shash(crct10dif_tfm);

free_buf:

kfree(data);

return 0;

}

/*出口函数初始化*/

static void __exit crc_test_exit(void) {}

module_init(crc_test_init);/*将crc_test_init()指定为入口函数*/

module_exit(crc_test_exit); /*将crc_test_exit()指定为出口函数*/

module_param(iterations, long, 0400);

MODULE_AUTHOR("Daniel Axtens <dja@axtens.net>");//添加作者名字

MODULE_DESCRIPTION("Vector polynomial multiply-sum CRC tester");

//从字面意思上看,指的是模块介绍

MODULE_LICENSE("GPL");//LICENSE采用“GPL协议”

上面的程序用到“pr_err()和pr_info()”,通过查找,它们都是调用“printk()”,内容如下:

#define pr_err(fmt, ...) \

printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)

#define pr_info(fmt, ...) \

printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)

因此,我们知道linux的串口输出是调用printk()来实现的,相当于C语言的printf()函数。

Linux驱动的运行方式:

1)、将驱动源码编译进Linux内核中,生成uImage/zImage系统镜像里面。

2)、将驱动源码编译成模块生成以“.ko结尾的文件然后在Linux内核加载驱动模块。也可以编译进内核,最终集成到uImage里面。

4、加载模块使用modprobe命令卸载模块使用insmod命令

比如执行“insmod drv.ko”加载“drv.ko”模块,由于drv.ko模块需要依赖first.ko模块,因此,要先执行“insmod first.ko”,然后再执行“insmod drv.ko”,才可以加载。而执行“modprobe drv.ko”加载“drv.ko”模块,它会分析first.kodrv.ko模块之间的依赖关系,自动将“first.kodrv.ko”加载到内核中。因此加载驱动使用modprobe命令

在卸载的时候,执行“rmmod drv.ko”就可以卸载了,而modprobe命令在分析出“first.kodrv.ko”模块之间有依赖关系,若first.ko被其它模块使用le,就不能执行“modprobe -r drv;若first.ko没有被其它模块使用,就可以执行“modprobe -r drv;。因此卸载模块使用insmod命令

注意:

Limnux kemnel的版本号为5.4.31modprobe命令会到“/lib/modules/5.4.31”目录中查找相应的驱动模块,因此我们需要将编写好的驱动会放到“/lib/modules/5.4.31”目录中

5、通过“crc-vpmsum_test.c”学习,我们仿写一个驱动,命名为My_TestDriver.c。

1)、创建“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录

输入“cd /home/zgq/linux/Linux_Drivers/

切换到“/home/zgq/linux/Linux_Drivers/”目录

输入“mkdir 00_My_TestDriver

创建“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录

输入“ls”,查询“/home/zgq/linux/Linux_Drivers/”目录下的文件和文件夹

输入“cd 00_My_TestDriver/

切换到“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录

输入“vi My_TestDriver.c”打开“My_TestDriver.c”

输入内容如下:

#include <linux/init.h>       //必须要包含的头文件

#include <linux/module.h>     //必须要包含的头文件

#include <linux/string.h>     //下面要用到字符串,显然也要包含

#include <linux/kernel.h>     //必须要包含的头文件

/*入口函数初始化*/

static int __init My_TestDriver_init(void)

{

int ret = 0;

    printk("My_TestDriver_init\r\n");

return ret;

}

/*出口函数初始化*/

static void __exit My_TestDriver_exit(void)

{

printk("My_TestDriver_exit\r\n");

}

module_init(My_TestDriver_init);/*将My_TestDriver_init()指定为入口函数*/

module_exit(My_TestDriver_exit); /*将My_TestDriver_exit()指定为出口函数*/

MODULE_AUTHOR("Zhanggong");//添加作者名字

MODULE_DESCRIPTION("This is test module!");//模块介绍

MODULE_LICENSE("GPL");//LICENSE采用“GPL协议”

MODULE_INFO(intree,"Y");//去处显示“loading out-of-tree module taints kernel.”

按“ESC键”,输入“:wq回车

输入“vim Makefile

添加内容如下:

KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31

#使用“:=”将其后面的字符串赋值给KERNELDIR

CURRENT_PATH := $(shell pwd)

#采用“shell pwd”获取当前打开的路径

#使用“$(变量名)”引用“变量的值”

obj-m := My_TestDriver.o

#生成“obj-m”需要依赖“My_TestDriver.o”

build: kernel_modules

#生成“build”需要依赖“kernel_modules”

kernel_modules:

        $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

#后面的"modules"表示编译成模块

#“KERNELDIR”上面定义为“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”,即“指定的工作目录”

#“CURRENT_PATH”上面定义为“当前的工作目录”

#“-C $(KERNELDIR) M=$(CURRENT_PATH) ”表示将“当前的工作目录”切换到“指定的目录”中

#即切换到“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”。

#M表示模块源码目录

#在“make和modules”之间加入“M=$(CURRENT_PATH)”,表示切换到由“CURRENT_PATH”指定的目录中读取源码,同时将其编>译为.ko 文件

clean:

        $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

#“KERNELDIR”上面定义为“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”,即“指定的工作目录”

#“CURRENT_PATH”上面定义为“当前的工作目录”

按“ESC键”,输入“:wq回车

在linx驱动中的Makefile文件,有点像“八股文”,在格式上基本是固定的。

驱动测试

在连接开发板之前,需要将“My_TestDriver.ko”拷贝到“/home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/

输入“cd /home/zgq/linux/Linux_Drivers/00_My_TestDriver/

切换到“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录

输入“ls”,查询“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录下的文件和文件夹

输入“make clean回车”清除工程

输入“make回车”执行编译

输入“ls”,查询“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录下的文件和文件夹

输入“sudo cp  My_TestDriver.ko  /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31

将“My_TestDriver.ko”拷贝到“/home/zgq/linux/nfs/rootfs/lib/modules/5.4.31

启动开发板,从网络下载程序

输入“root”

输入“cd /lib/modules/5.4.31/

在nfs挂载中,切换到“/home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/”目录

输入“ls

输入“depmod”,驱动在第一次执行时,需要运行“depmod”

输入“modprobe My_TestDriver.ko”,加载“My_TestDriver.ko”模块

输入“lsmod”查看有哪些驱动在工作

输入“rmmod My_TestDriver.ko”,卸载“My_TestDriver.ko”模块

注意:输入“rmmod My_TestDriver也可以卸载“My_TestDriver.ko模块

输入“lsmod”查看有哪些驱动在工作

至此,我们完成了linux字符设备驱动的挂载和卸载。

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

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

相关文章

Apache Doris 发展历程、技术特性及云原生时代的未来规划

文章目录 每日一句正能量前言作者介绍Apache Doris 特性极简架构高效自运维高并发场景支持MPP 执行引擎明细与聚合模型的统一便捷数据接入Apache Doris 极速 1.0 时代极速列式内存布局向量化的计算框架Cache 亲和度虚函数调用SIMD 指令集 稳定多源基于云原生向量数据库Milvus 的…

Java学习笔记------继承

继承 Java中提供了一个关键字extends&#xff0c;用这个关键字&#xff0c;我们可以让一个类和另一个类建立继承关系 如图&#xff0c;Student和Teacher类中除了study&#xff08;&#xff09;和teacher&#xff08;&#xff09;两个成员函数不同&#xff0c;其他重复了&…

万界星空科技商业开源MES

一、万界星空科技商业开源MES系统概述&#xff1a; 万界星空科技免费MES、开源MES、商业开源MES、市面上最好的开源MES、MES源代码、适合二开的开源MES。 1.万界星空开源MES制造执行系统的Java开源版本。 开源mes系统包括系统管理&#xff0c;车间基础数据管理&#xff0c;计…

ArcgisForJS如何使用ArcGIS Server的缓冲区几何服务?

文章目录 0.引言1.使用geometryService生成缓冲区2.使用geometryEngine生成缓冲区 0.引言 ArcGIS For JS是一款强大的JavaScript库&#xff0c;它提供了许多功能&#xff0c;包括使用ArcGIS Server的缓冲区几何服务。缓冲区几何服务是一种服务&#xff0c;它允许你在地理空间数…

网络原理TCP之“三次握手“

TCP内核中的建立连接 众所周知,TCP是有连接的. 当我们在客户端敲出socket new Socket(serverIp,severPort)时,就在系统内核就在建立连接 真正建立连接是在系统内核中建立的,我们程序员只是调用相关的api. 在此处,我们把TCP的建立连接称为三次握手. 系统在内核建立连接时如上…

广联达Linkworks GetAllData 信息泄露漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

Spring 容器、核心容器总结

目录 创建容器获取 bean容器类层次结构图核心容器总结容器相关bean 相关依赖注入相关 创建容器 方式一&#xff1a; 类路径加载配置文件 ApplicationContext ctx new ClassPathXmlApplicationContext("applicationContext.xml");方式二&#xff1a; 文件路径加载配…

MySQL数据库进阶第四篇(视图/存储过程/触发器)

文章目录 一、视图简单介绍与基础语法二、视图的检查选项三、视图的更新四、视图的作用五、存储过程的概念与特点六、存储过程的 创建&#xff0c;调用&#xff0c;查看&#xff0c;删除七、存储过程 — 系统变量八、存储过程 — 用户定义变量九、存储过程 — 局部变量十、存储…

<网络安全>《51 网络攻防专业课<第十四课 - 华为防火墙的使用(4)>

8 防火墙的防范技术&#xff08;3&#xff09; 8.1 IP spoofing攻击防范 攻击介绍 为了获得访问权&#xff0c;或隐藏入侵者的身份信息&#xff0c;入侵者生成带有伪造源地址的报文。 处理方法 检测每个接口流入的IP报文的源地址与目的地址&#xff0c;并对报文的源地址反查路…

C#与VisionPro联合开发——串口通信

串口通信 串口通信是一种常见的数据传输方式&#xff0c;通过串行接口&#xff08;串口&#xff09;将数据以串行比特流的形式进行传输。在计算机和外部设备之间&#xff0c;串口通信通常是通过串行通信标准&#xff08;如RS-232&#xff09;来实现的。串口通信可以用于连接各…

【HarmonyOS】低代码开发—使用低代码开发服务卡片

DevEco Studio还支持使用低代码开发功能开发服务卡片&#xff0c;目前只支持JS语言&#xff0c;且compileSdkVersion必须为7或以上。 下面以创建一个新的服务卡片为例进行说明。 1.打开一个工程&#xff0c;创建服务卡片&#xff0c;创建方法包括如下两种方式&#xff1a; 选…

vue3前端项目开发,具备纯天然的防止爬虫采集的特征

vue3前端项目开发,具备纯天然的防止爬虫采集的特征&#xff01;众所周知&#xff0c;网络爬虫可以在网上爬取到一些数据&#xff0c;很多公司&#xff0c;为了自己公司的数据安全&#xff0c; 尤其是web端项目&#xff0c;不希望被爬虫采集。那么&#xff0c;您可以使用vue技术…

[c++] char * 和 std::string

1 char * 和 std::string 的区别 char * 字符串是常量字符串&#xff0c;不能修改&#xff1b;std::string 指向的字符串可以修改 实例代码如下图所示&#xff0c;s1 和 s2 均是常量字符串&#xff0c;字符串常量保存在只读数据区&#xff0c;是只读的&#xff0c;不能写&…

【人工智能高频面试题--基础篇】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;人工智能高频面试题 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; 人工智能高频面试题 1.什么是人工智能&#xff1f;什么是人工智能神经网络&#xff1f;2.解释…

Linux之ACL权限chmod命令

一. chmod命令 chmod命令来自英文词组change mode的缩写&#xff0c;其功能是改变文件或目录权限的命令。默认只有文件的所有者和管理员可以设置文件权限&#xff0c;普通用户只能管理自己文件的权限属性。 设置权限时可以使用数字法&#xff0c;亦可使用字母表达式&#xff0…

C++ //练习 8.8 修改上一题的程序,将结果追加到给定的文件末尾。对同一个输出文件,运行程序至少两次,检验数据是否得以保留。

C Primer&#xff08;第5版&#xff09; 练习 8.8 练习 8.8 修改上一题的程序&#xff0c;将结果追加到给定的文件末尾。对同一个输出文件&#xff0c;运行程序至少两次&#xff0c;检验数据是否得以保留。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工…

readproc.h

Ubuntu22.04系统中 编译自己写的程序的时候&#xff0c;报错&#xff0c;显示找不到readproc.h文件&#xff0c;通过安装libprocps-dev解决 sudo apt install libprocps-dev

5.1 Ajax数据爬取之初介绍

目录 1. Ajax 数据介绍 2. Ajax 分析 2.1 Ajax 例子 2.2 Ajax 分析方法 &#xff08;1&#xff09;在网页页面右键&#xff0c;检查 &#xff08;2&#xff09;找到network&#xff0c;ctrl R刷新 &#xff08;3&#xff09;找 Ajax 数据包 &#xff08;4&#xff09;…

美联储突然降息无望

作者&#xff1a;秦晋 我们知道&#xff0c;影响比特币未来1-2年市场走向的重要三因素是比特币ETF、比特币减半以及美联储降息。 如果说前两者是影响比特币市场比较紧密的微观因素。那么美联储降息就是影响比特币市场的重要宏观因素。如何看懂宏观因素&#xff1f;尽量倾听和观…

从源码学习static的使用

从源码学习static的使用 前言 ​ static意味静态的&#xff0c;在Java中&#xff0c;主要用来修饰类级别的变量或方法等&#xff0c;被修饰的内容&#xff0c;表示随着类的加载而加载&#xff0c;而不是具体的实例级别。 ​ 具体到static的使用场景&#xff0c;主要有以下用…