【Linux 驱动基础】Linux platform平台设备驱动

# 前置知识

总线驱动模型简介:

总线是处理器与一个或者多个设备之间的通道,在设备模型中,所有的设备都是通过总线相连,当然也包括虚拟的 platform 平台总线。

总线驱动模型中有三要素:

1. 总线

/*** struct bus_type - The bus type of the device** @name:	The name of the bus.* @dev_name:	Used for subsystems to enumerate devices like ("foo%u", dev->id).* @dev_root:	Default device to use as the parent.* @dev_attrs:	Default attributes of the devices on the bus.* @bus_groups:	Default attributes of the bus.* @dev_groups:	Default attributes of the devices on the bus.* @drv_groups: Default attributes of the device drivers on the bus.* @match:	Called, perhaps multiple times, whenever a new device or driver*		is added for this bus. It should return a nonzero value if the*		given device can be handled by the given driver.* @uevent:	Called when a device is added, removed, or a few other things*		that generate uevents to add the environment variables.* @probe:	Called when a new device or driver add to this bus, and callback*		the specific driver's probe to initial the matched device.* @remove:	Called when a device removed from this bus.* @shutdown:	Called at shut-down time to quiesce the device.** @online:	Called to put the device back online (after offlining it).* @offline:	Called to put the device offline for hot-removal. May fail.** @suspend:	Called when a device on this bus wants to go to sleep mode.* @resume:	Called to bring a device on this bus out of sleep mode.* @pm:		Power management operations of this bus, callback the specific*		device driver's pm-ops.* @iommu_ops:  IOMMU specific operations for this bus, used to attach IOMMU*              driver implementations to a bus and allow the driver to do*              bus-specific setup* @p:		The private data of the driver core, only the driver core can*		touch this.* @lock_key:	Lock class key for use by the lock validator** A bus is a channel between the processor and one or more devices. For the* purposes of the device model, all devices are connected via a bus, even if* it is an internal, virtual, "platform" bus. Buses can plug into each other.* A USB controller is usually a PCI device, for example. The device model* represents the actual connections between buses and the devices they control.* A bus is represented by the bus_type structure. It contains the name, the* default attributes, the bus' methods, PM operations, and the driver core's* private data.*/
struct bus_type {const char		*name;const char		*dev_name;struct device		*dev_root;struct device_attribute	*dev_attrs;	/* use dev_groups instead */const struct attribute_group **bus_groups;const struct attribute_group **dev_groups;const struct attribute_group **drv_groups;int (*match)(struct device *dev, struct device_driver *drv);int (*uevent)(struct device *dev, struct kobj_uevent_env *env);int (*probe)(struct device *dev);int (*remove)(struct device *dev);void (*shutdown)(struct device *dev);int (*online)(struct device *dev);int (*offline)(struct device *dev);int (*suspend)(struct device *dev, pm_message_t state);int (*resume)(struct device *dev);const struct dev_pm_ops *pm;const struct iommu_ops *iommu_ops;struct subsys_private *p;struct lock_class_key lock_key;
};

API: 

extern int __must_check bus_register(struct bus_type *bus);extern void bus_unregister(struct bus_type *bus);

平台总线定义: 

struct bus_type platform_bus_type = {.name		= "platform",.dev_groups	= platform_dev_groups,.match		= platform_match,.uevent		= platform_uevent,.pm		= &platform_dev_pm_ops,
};

平台总线的注册如下: 

int __init platform_bus_init(void)
{int error;early_platform_cleanup();error = device_register(&platform_bus);if (error)return error;error =  bus_register(&platform_bus_type);if (error)device_unregister(&platform_bus);of_platform_register_reconfig_notifier();return error;
}

2. 总线设备

struct platform_device {const char	*name;             /* 名字 */ int		id;                    /* 用于标识该设备的ID */bool		id_auto;           /* 指示在注册设备时,是否自动赋予ID值 */struct device	dev;           /* 真正的设备(Platform设备只是一个特殊的设备,因此其核心逻辑还是由底层的模块实现) */u32		num_resources;        /* 资源个数 */struct resource	*resource;    /* 该设备的资源描述 */const struct platform_device_id	*id_entry;char *driver_override; /* Driver name to force a match *//* MFD cell pointer */struct mfd_cell *mfd_cell;/* arch specific additions */struct pdev_archdata	archdata;    /* 私有数据 */
};struct resource {resource_size_t start;    /* 起始地址 */resource_size_t end;      /* 结束地址 */const char *name;         /* 资源名字 */unsigned long flags;      /* 资源标识 */struct resource *parent, *sibling, *child;
};

 API:

/* 注册平台设备 */
int platform_device_register(struct platform_device *);/* 注销平台设备 */
void platform_device_unregister(struct platform_device *);/* 设置platform_device变量中的archdata指针 */
void arch_setup_pdev_archdata(struct platform_device *);/* 通过资源类型获取platform_device变量中的resource信息 */
struct resource *platform_get_resource(struct platform_device *,unsigned int, unsigned int);/* 通过资源名字获取platform_device变量中的resource信息 */
struct resource *platform_get_resource_byname(struct platform_device *,unsigned int,const char *);

3. 总线驱动

struct platform_driver {int (*probe)(struct platform_device *);        /* 当驱动和硬件信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中 */int (*remove)(struct platform_device *);        /* 硬件信息被移除了,或者驱动被卸载了,全部要释放,释放资源的操作就放在该函数中 */void (*shutdown)(struct platform_device *);int (*suspend)(struct platform_device *, pm_message_t state);int (*resume)(struct platform_device *)    ;struct device_driver driver;        /* 内核维护的所有的驱动必须包含该成员,通常driver->name用于和设备进行匹配 */const struct platform_device_id *id_table;    /* 往往一个驱动可能能同时支持多个硬件,这些硬件的名字都放在该结构体数组中 */bool prevent_deferred_probe;
};struct device_driver {const char		*name;struct bus_type		*bus;struct module		*owner;const char		*mod_name;	/* used for built-in modules */bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */const struct of_device_id	*of_match_table;const struct acpi_device_id	*acpi_match_table;int (*probe) (struct device *dev);int (*remove) (struct device *dev);void (*shutdown) (struct device *dev);int (*suspend) (struct device *dev, pm_message_t state);int (*resume) (struct device *dev);const struct attribute_group **groups;const struct dev_pm_ops *pm;struct driver_private *p;
};

API:

/** use a macro to avoid include chaining to get THIS_MODULE*/
/* 注册设备 */
#define platform_driver_register(drv) \__platform_driver_register(drv, THIS_MODULE)
extern int __platform_driver_register(struct platform_driver *,struct module *);/* 注销设备 */
extern void platform_driver_unregister(struct platform_driver *);/* non-hotpluggable platform devices may use this so that probe() and* its support may live in __init sections, conserving runtime memory.*/
#define platform_driver_probe(drv, probe) \__platform_driver_probe(drv, probe, THIS_MODULE)
extern int __platform_driver_probe(struct platform_driver *driver,int (*probe)(struct platform_device *), struct module *module);/* 获取设备私有资源 */
static inline void *platform_get_drvdata(const struct platform_device *pdev)
{return dev_get_drvdata(&pdev->dev);
}

设备与驱动匹配过程

 来自:https://blog.csdn.net/qq_16504163/article/details/118562670

# 示例代码

platform_device:

#include <linux/init.h>
#include <linux/file.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>#define CCM_CCGR1_ADDR                              0x20C406C
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03_ADDR       0x20E0068
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03_ADDR       0x20E02F4
#define GPIO1_BASE_ADDR                             0x209C000     static struct resource atk_led_resource[] = {[0] = {.start  = CCM_CCGR1_ADDR,.end    = CCM_CCGR1_ADDR + 4,.name   = "led_clock",.flags  = IORESOURCE_MEM,},[1] = {.start  = IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03_ADDR,.end    = IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03_ADDR + 4,.name   = "led_mux_ctrl",.flags  = IORESOURCE_MEM,},[2] = {.start  = IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03_ADDR,.end    = IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03_ADDR + 4,.name   = "led_pad_ctrl",.flags  = IORESOURCE_MEM,},[3] = {.start  = GPIO1_BASE_ADDR,.end    = GPIO1_BASE_ADDR + 4,.name   = "led_gpio_ctrl",.flags  = IORESOURCE_MEM,}
};static void	atk_led_release(struct device *dev)
{}static struct platform_device atk_led_dev = {.name = "atk_led",.id = -1,.resource = atk_led_resource,.num_resources = ARRAY_SIZE(atk_led_resource),.dev    = {.release = atk_led_release,},
};static int __init led_device_init(void)
{int err;err = platform_device_register(&atk_led_dev);return 0;
}static void __exit led_device_exit(void)
{platform_device_unregister(&atk_led_dev);
}module_init(led_device_init);
module_exit(led_device_exit);MODULE_LICENSE("GPL");

platform_driver: 

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/io.h>
#include <linux/platform_device.h>typedef struct
{volatile uint32_t GPIO_DR;volatile uint32_t GPIO_GDIR;volatile uint32_t GPIO_PSR;volatile uint32_t GPIO_ICR1;volatile uint32_t GPIO_ICR2;volatile uint32_t GPIO_IMR;volatile uint32_t GPIO_ISR;volatile uint32_t GPIO_EDGE_SEL;
}GPIO_TypeDef;static GPIO_TypeDef *GPIO;static volatile uint32_t *CCM_CCGR;
static volatile uint32_t *IOMUXC_SW_MUX_CTL_PAD;
static volatile uint32_t *IOMUXC_SW_PAD_CTL_PAD;struct pri_led_TypeDef
{char drv_name[50];  /* 驱动名称 */int major;      /* 主设备号 */int minor;      /* 次设备号 */dev_t devt;     /* 设备号 */struct device *device;   /* 设备 */char device_name[50];    /* 设备名称 */struct class *class;    /* 类 */char class_name[50];    /* 类名称 */
};static struct pri_led_TypeDef pri_led = {.drv_name = "led_drv",.major  = 0,.minor  = 0,.devt   = 0,.device = NULL,.device_name = "led_dev",.class  = NULL,.class_name  = "led_class",
};static int led_open(struct inode *inode, struct file *file)
{uint32_t val;/* 使能GPIO1时钟 */*CCM_CCGR |= (3 << 26);/* 设置IO复用 */val = *IOMUXC_SW_MUX_CTL_PAD;val &= ~(0x0F);val |= 5;*IOMUXC_SW_MUX_CTL_PAD = val;/* 设置IO属性 *//* 设置IO方向,设置GPIO1_IO03为输出 */GPIO->GPIO_GDIR |= (1 << 3);return 0;
}static int led_release(struct inode *inode, struct file *file)
{return 0;
}static ssize_t led_write(struct file *file, const char __user *buff, size_t size, loff_t *ppos)
{uint8_t status;int err;err = copy_from_user(&status, buff, 1);if(status == 1){GPIO->GPIO_DR &= ~(1<<3);}else{GPIO->GPIO_DR |= (1<<3);}return 1;
}static const struct file_operations led_op = {.owner      = THIS_MODULE,.open       = led_open,.release    = led_release,.write      = led_write,
};static int atk_led_drv_probe(struct platform_device *dev)
{struct resource *res;int err;printk("atk_led_drv_probe\r\n");res = platform_get_resource(dev, IORESOURCE_MEM, 0);CCM_CCGR = ioremap(res->start, (res->end - res->start));res = platform_get_resource(dev, IORESOURCE_MEM, 1);IOMUXC_SW_MUX_CTL_PAD = ioremap(res->start, (res->end - res->start));res = platform_get_resource(dev, IORESOURCE_MEM, 2);IOMUXC_SW_PAD_CTL_PAD = ioremap(res->start, (res->end - res->start));res = platform_get_resource(dev, IORESOURCE_MEM, 3);GPIO = ioremap(res->start, sizeof(GPIO_TypeDef));pri_led.major = register_chrdev(0, pri_led.drv_name, &led_op);pri_led.devt = MKDEV(pri_led.major, pri_led.minor);pri_led.class = class_create(THIS_MODULE, pri_led.class_name);if(IS_ERR(pri_led.class)){printk("class_create error\r\n");err = PTR_ERR(pri_led.class);goto err_class_create_out;}pri_led.device = device_create(pri_led.class, NULL, pri_led.devt, NULL, pri_led.device_name);if(IS_ERR(pri_led.device)){printk("device_create error\r\n");err = PTR_ERR(pri_led.device);goto err_device_create_out;}return 0;err_device_create_out:class_destroy(pri_led.class);
err_class_create_out:unregister_chrdev(pri_led.major, pri_led.drv_name);return err;
}static int atk_led_drv_remove(struct platform_device *dev)
{printk("atk_led_drv_remove\r\n");iounmap(GPIO);iounmap(CCM_CCGR);iounmap(IOMUXC_SW_MUX_CTL_PAD);iounmap(IOMUXC_SW_PAD_CTL_PAD);device_destroy(pri_led.class, pri_led.devt);class_destroy(pri_led.class);unregister_chrdev(pri_led.major, pri_led.drv_name);return 0;
}static struct platform_driver atk_led_driver = {.probe = atk_led_drv_probe,.remove = atk_led_drv_remove,.driver = {.name = "atk_led",},
};static int __init led_driver_init(void)
{int err;err = platform_driver_register(&atk_led_driver);return 0;
}static void __exit led_driver_exit(void)
{platform_driver_unregister(&atk_led_driver);
}module_init(led_driver_init);
module_exit(led_driver_exit);MODULE_LICENSE("GPL");

# 补充知识

1. platform_match 匹配规程

static int platform_match(struct device *dev, struct device_driver *drv)
{struct platform_device *pdev = to_platform_device(dev);struct platform_driver *pdrv = to_platform_driver(drv);/* When driver_override is set, only bind to the matching driver */if (pdev->driver_override)return !strcmp(pdev->driver_override, drv->name);/* Attempt an OF style match first */if (of_driver_match_device(dev, drv))return 1;/* Then try ACPI style match */if (acpi_driver_match_device(dev, drv))return 1;/* Then try to match against the id table */if (pdrv->id_table)return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */return (strcmp(pdev->name, drv->name) == 0);
}
  • 最先比较:platform_device.driver_override 和 platform_driver.driver.name,可以设置 platform_device 的 driver_override,强制选择某个 platform_driver
  • 然后比较:platform_device. name 和 platform_driver.id_table[i].name,Platform_driver.id_table 是“platform_device_id”指针,表示该 drv 支持若干个 device,它里面列出了各个 device 的{.name, .driver_data},其中的“ name”表示该drv 支持的设备的名字, driver_data 是些提供给该 device 的私有数据
  • 最后比较:platform_device.name 和 platform_driver.driver.name,platform_driver.id_table 可能为空,这时可以根据 platform_driver.driver.name 来寻找同名的 platform_device

2. struct platform_device 中 id 的作用

	switch (pdev->id) {default:dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);break;case PLATFORM_DEVID_NONE:    /* -1 */dev_set_name(&pdev->dev, "%s", pdev->name);break;case PLATFORM_DEVID_AUTO:    /* -2 *//** Automatically allocated device ID. We mark it as such so* that we remember it must be freed, and we append a suffix* to avoid namespace collision with explicit IDs.*/ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);if (ret < 0)goto err_out;pdev->id = ret;pdev->id_auto = true;dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);break;}
  • id = -1 时,名称直接为名字
  • id = -2时,名称为自动获取的id加上.auto
  • 其他为名称加上id

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vite 为什么比 Webpack 快?

目录 1. Webpack 的构建原理 2. Script 的模块化&#xff08;主流浏览器对 ES Modules 的支持&#xff09; 3. Webpack vs Vite 开发模式的差异 对 ES Modules 的支持 底层语言的差异 热更新的处理 1. Webpack 的构建原理 前端之所以需要类似于 Webpack 这样的构建工具&…

发票是扫码验真好,还是OCR后进行验真好?

随着科技的进步&#xff0c;电子发票的普及使得发票的验真方式也在不断演进。目前&#xff0c;我们常见的发票验真方式主要有两种&#xff1a;一种是扫描发票上的二维码进行验真&#xff0c;另一种是通过OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别…

线性代数 - 应该学啥 以及哪些可以交给计算机

AI很热&#xff0c;所以小伙伴们不免要温故知新旧时噩梦 - 线代。 &#xff08;十几年前&#xff0c;还有一个逼着大家梦回课堂的风口&#xff0c;图形学。&#xff09; 这个真的不是什么美好的回忆&#xff0c;且不说老师的口音&#xff0c;也不说教材的云山雾绕&#xff0c;单…

win10微软拼音输入法 - bug - 在PATH变量为空的情况下,无法输入中文

文章目录 win10微软拼音输入法 - bug - 在PATH变量为空的情况下&#xff0c;无法输入中文概述笔记实验前提条件100%可以重现 - 无法使用win10拼音输入法输入中文替代的输入法软件备注END win10微软拼音输入法 - bug - 在PATH变量为空的情况下&#xff0c;无法输入中文 概述 在…

linux 内存介绍

大致共有四类&#xff1a;VSS、RSS、PSS、USS &#xff0c;通常情况下&#xff0c;VSS > RSS > PSS > USS 1.VSS(Virtual Set Size)虚拟耗用内存&#xff08;包含共享库占用的内存&#xff09; VSS表示一个进程可访问的全部内存地址空间的大小。这个大小包括了进程已…

Linux:Jenkins:参数化版本回滚(6)

上几章我讲到了自动集成和部署 Linux&#xff1a;Jenkins全自动持续集成持续部署&#xff08;4&#xff09;-CSDN博客https://blog.csdn.net/w14768855/article/details/136977106 当我们觉得这个页面不行的时候&#xff0c;需要进行版本回滚&#xff0c;回滚方法我这里准备了…

Memcached非关系型数据库介绍

使用背景 Memcached 不是一个数据库&#xff0c;而是一个高性能的分布式内存对象缓存系统。它主要用于减轻数据库负载&#xff0c;提高动态Web应用的速度、可扩展性和性能。Memcached 的工作原理是将数据存储在内存中&#xff0c;以提供快速的数据访问。当应用程序需要访问数据…

基于springboot+vue+Mysql的家政服务管理平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

Rabbitmq消息顺序的问题以及解决方案

1.1消息顺序的场景 场景1&#xff1a;一个queue&#xff0c;多个consumer 一个queue&#xff0c;有多个consumer去消费&#xff0c;这样就会造成顺序的错误&#xff0c;consumer从MQ里面读取数据是有序的&#xff0c;但是每个consumer的执行时间是不固定的&#xff0c;无法保…

第二十一章 Jquery ajax

文章目录 1. jquery下载2. jquery的使用3. jquery页面加载完毕执行4. jquery属性控制6. 遍历器 2. ajax1. 准备后台服务器2. ajax发送get请求3. ajax发送post请求 1. jquery下载 点击下载 稳定版本1.9 2. jquery的使用 存放到html文件的同级目录 3. jquery页面加载完毕执行…

论文《Exploring to Prompt for Vision-Language Models》阅读

论文《Exploring to Prompt for Vision-Language Models》阅读 论文概况论文动机&#xff08;Intro&#xff09;MethodologyPreliminaryCoOp[CLASS]位置Context 是否跨 class 共享表示和训练 ExperimentsOverall ComparisonDomain GeneralizationContext Length (M) 和 backbon…

【C语言】linux内核tcp_push函数

一、讲解 这个 tcp_push 函数是在Linux内核的TCP网络栈实现中&#xff0c;用于推动TCP缓冲区中待发送数据包的传输。这段代码需要在具备操作系统和网络编程知识背景下来解释。下面我将分步骤用中文逐一讲解这个函数的作用&#xff1a; 1. struct tcp_sock *tp tcp_sk(sk);&am…

Docker搭建LNMP环境实战(07):安装nginx

1、模拟应用场景描述 假设我要搭建一个站点&#xff0c;假设虚拟的域名为&#xff1a;api.test.site&#xff0c;利用docker实现nginxphp-fpmmariadb部署。 2、目录结构 2.1、dockers根目录 由于目前的安装是基于Win10VMWareCentOS虚拟机&#xff0c;同时已经安装了VMWareT…

冒泡排序(六大排序)

冒泡排序 冒泡排序的特性总结&#xff1a; 1. 冒泡排序是一种非常容易理解的排序 2. 时间复杂度&#xff1a;O(N^2) 3. 空间复杂度&#xff1a;O(1) 4. 稳定性&#xff1a;稳定 动图分析&#xff1a; 代码实现&#xff1a; Swap(int*p1,int*p2) {int tmp *p1;*p1*p2…

网络稳定性(蓝桥省赛)

0网络稳定性 - 蓝桥云课 (lanqiao.cn) 知识点&#xff1a;克鲁斯卡尔生成树&#xff0c;lca&#xff0c;倍增 最小生成树的模板&#xff1a;最小生成树【模板】-CSDN博客 题解代码如下&#xff1a; #include<bits/stdc.h> using namespace std; const int N3e5100; co…

ssh 公私钥(github)

一、生成ssh公私钥 生成自定义名称的SSH公钥和私钥对&#xff0c;需要使用ssh-keygen命令&#xff0c;这是大多数Linux和Unix系统自带的标准工具。下面&#xff0c;简单展示如何使用ssh-keygen命令来生成具有自定义名称的SSH密钥对。 步骤 1: 打开终端 首先&#xff0c;打开我…

用Kimichat快速识别出图片中的表格保存到Excel

如果有一张图片格式的表格&#xff0c;想要快速复制到Excel表格中&#xff0c;那么一般要借助于OCR工具。之前试过不少在线OCR工具&#xff0c;识别效果差强人意。其实&#xff0c;kimichat就可以非常好的完成这个任务。 下面是一张研报中的表格&#xff0c;只能以图片形式保存…

RTOS线程切换的过程和原理

0 前言 RTOS中最重要的一个概念就是线程&#xff0c;线程的按需切换能够满足RTOS的实时性要求&#xff0c;同时能将复杂的需求分解成一个个线程执行减轻我们开发负担。 本文从栈的角度出发&#xff0c;详细介绍RTOS线程切换的过程和原理。 注&#xff1a;本文参考的RTOS是RT-T…

DataX-Oracle新增writeMode支持update

目录 前言 第一步下载源码 第二步修改源码 1、Oraclewriter 2、WriterUtil 2.1、修改getWriteTemplate方法 2.2、新增onMergeIntoDoString与getStrings方法 3、CommonRdbmsWriter 3.1、修改startWriteWithConnection 3.2、修改doBatchInsert 3.3、修改fillPreparedStatem…

单例模式如何保证实例的唯一性

前言 什么是单例模式 指一个类只有一个实例&#xff0c;且该类能自行创建这个实例的一种创建型设计模式。使用目的&#xff1a;确保在整个系统中只能出现类的一个实例&#xff0c;即一个类只有一个对象。对于频繁使用的对象&#xff0c;“忽略”创建时的开销。特点&#xff1a…