STM32MP157驱动开发——Regmap API

news2024/11/28 22:39:01

STM32MP157驱动开发——Regmap API

  • 0.前言
  • 一、Regmap API 简介
    • 1.Regmap 驱动框架
    • 2.regmap 结构体
    • 3.regmap_config 结构体
    • 4.Regmap 操作函数
      • ①Regmap 申请与初始化
      • ②Regmap释放
      • ③regmap 设备访问 API 函数
    • 5. regmap_config 掩码设置
  • 二、驱动开发
    • 1.修改设备结构体,添加 regmap 和 regmap_config
    • 2.初始化 regmap
    • 3.卸载驱动时移除regmap
    • 4.读写设备内部寄存器
  • 三、运行测试
  • 四、I2CRegmap API开发


0.前言

  在前面学习 I2C 和 SPI 驱动的时候,针对 I2C 和 SPI 设备寄存器的操作都是通过相关的 API 函数进行操作的,这样 Linux 内核中就会充斥着大量的重复、冗余代码。但本质上都是对外设寄存器的操作,所以为了方便内核开发人员统一访问 I2C/SPI 设备,引入了 Regmap 子系统。这节就学习一下如何使用。

一、Regmap API 简介

  Linux 下大部分设备的驱动开发都是操作其内部寄存器,比如 I2C/SPI 设备的本质都是一样,通过 I2C/SPI 接口读写芯片内部寄存器。主控芯片内部寄存器也是同样的道理,比如 STM32MP157 的 PWM、TIM 等外设初始化等。
  Linux 下使用 i2c_transfer 来读写 I2C 设备中的寄存器,SPI 接口的话使用 spi_write/spi_read 等。但由于 I2C/SPI 芯片有很多,这种操作会产生大量的冗余代码,除此之外,一些芯片既支持 I2C 接口,也支持 SPI 接口,比如 icm20608 芯片,如果在开发过程中,需要更换通信接口,那么就需要大幅修改相关驱动。
  基于代码复用的原则, Linux 内核引入了 regmap 模型,regmap 将寄存器访问的共同逻辑抽象出来,驱动开发人员不需要再去纠结使用 SPI 或者 I2C 接口 API 函数,统一使用 regmap API 函数。这样的好处就是统一使用 regmap,降低了代码冗余,提高了驱动的可以移植性。此外,通过 regmap 模型提供的统一接口函数来访问器件的寄存器,SOC 内部的寄存器也可以使用 regmap 接口函数来访问。
  regmap 在驱动和硬件之间添加了 cache,降低了低速 I/O 的操作次数,提高了访问效率,缺点是实时性会降低。一般在如下几种情况会使用 regmap:

① 硬件寄存器操作,比如选用通过 I2C/SPI 接口来读写设备的内部寄存器,或者需要读写 SOC 内部的硬件寄存器。
② 提高代码复用性和驱动一致性,简化驱动开发过程。
③ 减少底层 I/O 操作次数,提高访问效率

1.Regmap 驱动框架

在这里插入图片描述
regmap 框架分为三层:

① 底层物理总线:regmap 就是对不同的物理总线进行封装,目前 regmap 支持的物理总线有 i2c、i3c、spi、mmio、sccb、sdw、slimbus、irq、spmi 和 w1
② regmap 核心层,用于实现 regmap,此处不用关心具体实现
③ regmap API 抽象层,regmap 向驱动编写人员提供的 API 接口,驱动编写人员使用这些 API 接口来操作具体的芯片设备

2.regmap 结构体

Linux 内核将 regmap 框架抽象为 regmap 结构体,这个结构体定义在文件 include/linux/regmap.h 中,部分内容如下:

49 struct regmap {
50   union {
51 		struct mutex mutex;
52 		struct {
53 			spinlock_t spinlock;
54 			unsigned long spinlock_flags;
55   	};
56   };
57   regmap_lock lock;
58   regmap_unlock unlock;
59   void *lock_arg; /* This is passed to lock/unlock functions */
60   gfp_t alloc_flags;
......
89   unsigned int max_register;
90   bool (*writeable_reg)(struct device *dev, unsigned int reg);
91   bool (*readable_reg)(struct device *dev, unsigned int reg);
92   bool (*volatile_reg)(struct device *dev, unsigned int reg);
93   bool (*precious_reg)(struct device *dev, unsigned int reg);
94   bool (*writeable_noinc_reg)(struct device *dev, unsigned int reg);
95   bool (*readable_noinc_reg)(struct device *dev, unsigned int reg);
96   const struct regmap_access_table *wr_table;
97   const struct regmap_access_table *rd_table;
98   const struct regmap_access_table *volatile_table;
99   const struct regmap_access_table *precious_table;
100  const struct regmap_access_table *wr_noinc_table;
101  const struct regmap_access_table *rd_noinc_table;
102
103  int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
104  int (*reg_write)(void *context, unsigned int reg, unsigned int val);
105  int (*reg_update_bits)(void *context, unsigned int reg,
106  unsigned int mask, unsigned int val);
......
159
160  struct rb_root range_tree;
161  void *selector_work_buf; /* Scratch buffer used for selector */
162
163  struct hwspinlock *hwlock;
164 };

第 90~101 行有很多的函数以及 table,这些需要驱动编写人员根据实际情况选择性的初始化,regmap 的初始化通过结构体 regmap_config 来完成。

3.regmap_config 结构体

regmap_config 结构体就是用来初始化 regmap 的,这个结构体也定义在 include/linux/regmap.h 文件中:

352 struct regmap_config {
353   const char *name;
354
355   int reg_bits;
356   int reg_stride;
357   int pad_bits;
358   int val_bits;
359
360   bool (*writeable_reg)(struct device *dev, unsigned int reg);
361   bool (*readable_reg)(struct device *dev, unsigned int reg);
362   bool (*volatile_reg)(struct device *dev, unsigned int reg);
363   bool (*precious_reg)(struct device *dev, unsigned int reg);
364   bool (*writeable_noinc_reg)(struct device *dev, unsigned int reg);
365   bool (*readable_noinc_reg)(struct device *dev, unsigned int reg);
366
367   bool disable_locking;
368   regmap_lock lock;
369   regmap_unlock unlock;
370   void *lock_arg;
371
372   int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
373   int (*reg_write)(void *context, unsigned int reg, unsigned int val);
374
375   bool fast_io;
376
377   unsigned int max_register;
378   const struct regmap_access_table *wr_table;
379   const struct regmap_access_table *rd_table;
380   const struct regmap_access_table *volatile_table;
381   const struct regmap_access_table *precious_table;
382   const struct regmap_access_table *wr_noinc_table;
383   const struct regmap_access_table *rd_noinc_table;
384   const struct reg_default *reg_defaults;
385   unsigned int num_reg_defaults;
386   enum regcache_type cache_type;
387   const void *reg_defaults_raw;
388   unsigned int num_reg_defaults_raw;
389
390   unsigned long read_flag_mask;
391   unsigned long write_flag_mask;
392   bool zero_flag_mask;
393
394   bool use_single_read;
395   bool use_single_write;
396   bool can_multi_write;
397
398   enum regmap_endian reg_format_endian;
399   enum regmap_endian val_format_endian;
400
401   const struct regmap_range_cfg *ranges;
402   unsigned int num_ranges;
403
404   bool use_hwlock;
405   unsigned int hwlock_id;
406   unsigned int hwlock_mode;
407 };

在内核源码中,对这些变量都进行了详细解释,这里主要看几个重要属性。
name:名字
reg_bits:寄存器地址位数,必填字段
reg_stride:寄存器地址步长
pad_bits:寄存器和值之间的填充位数
val_bits:寄存器值位数,必填字段
writeable_reg:可选的可写回调函数,寄存器可写的话此回调函数就会被调用,并返回 true
readable_reg:可选的可读回调函数,寄存器可读的话此回调函数就会被调用,并返回 true
volatile_reg:可选的回调函数,当寄存器值不能缓存的时候此回调函数就会被调用,并返回 true
precious_reg:当寄存器值不能被读出来的时候此回调函数会被调用,比如很多中断状态寄存器读清零,读这些寄存器就可以清除中断标志位,但是并没有读出这些寄存器内部的值
reg_read:可选的读操作回调函数,所有读寄存器的操作此回调函数就会执行
reg_write:可选的写操作回调函数,所有写寄存器的操作此回调函数就会执行
fast_io:快速 I/O,使用 spinlock 替代 mutex 来提升锁性能
max_register:有效的最大寄存器地址,可选
wr_table:可写的地址范围,为 regmap_access_table 结构体类型。后面的 rd_table、volatile_table、precious_table、wr_noinc_table 和 rd_noinc_table 同理
reg_defaults:寄存器模式值,为 reg_default 结构体类型,此结构体有两个成员变量:reg 和 def,reg 是寄存器地址,def 是默认值
num_reg_defaults:默认寄存器表中的元素个数
read_flag_mask:读标志掩码
write_flag_mask:写标志掩码

4.Regmap 操作函数

①Regmap 申请与初始化

regmap 支持多种物理总线,比如 I2C 和 SPI,需要根据所使用的接口来选择合适的 regmap 初始化函数。一般会在 probe 函数中初始化 regmap_config,然后申请并初始化 regmap。Linux 内核提供了针对不同接口的 regmap 初始化函数,SPI 接口初始化函数为 regmap_init_spi:
原型

struct regmap * regmap_init_spi(struct spi_device *spi,
								const struct regmap_config *config)

参数
spi:需要使用 regmap 的 spi_device
config:regmap_config 结构体,由开发人员初始化一个 regmap_config 实例,然后将其地址赋值给此参数
返回值:申请到的并进过初始化的 regmap

I2C 接口的 regmap 初始化函数为 regmap_init_i2c:
原型

struct regmap * regmap_init_i2c(struct i2c_client *i2c,
								const struct regmap_config *config)

参数
spi:需要使用 regmap 的 i2c_client
config:regmap_config 结构体,由开发人员初始化一个 regmap_config 实例,然后将其地址赋值给此参数
返回值:申请到的并进过初始化的 regmap

还有很多其他物理接口对应的 regmap 初始化函数,这里就不介绍了,直接查阅 Linux内核即可,基本和 SPI/I2C 的初始化函数相同。

②Regmap释放

在退出驱动的时候需要释放掉申请到的 regmap,不管是什么接口,全部使用 regmap_exit 函数:
原型

void regmap_exit(struct regmap *map)

参数
map:需要释放的 regmap
返回值:无

③regmap 设备访问 API 函数

不管是 I2C 还是 SPI 等接口,还是 SOC 内部的寄存器,对于寄存器的操作就两种:读和写。regmap 提供了最核心的两个读写操作:regmap_read 和 regmap_write。
原型

int regmap_read(struct regmap *map,
				unsigned int reg,
				unsigned int *val)
int regmap_write(struct regmap *map,
				unsigned int reg,
				unsigned int val)

参数
map:要操作的 regmap。
reg:要读/写的寄存器。
val:读/写的寄存器值。
返回值
0:读取/写入成功
其他:读取/写入失败

在 regmap_read 和 regmap_write 的基础上还衍生出了其他一些 regmap 的 API 函数:

regmap_update_bits 函数
原型

int regmap_update_bits (struct regmap *map,
						unsigned int reg,
						unsigned int mask,
						unsigned int val)

功能:用来修改寄存器指定的 bit 位的值。
参数
map:要操作的 regmap。
reg:要操作的寄存器。
mask:掩码,需要更新的位必须在掩码中设置为 1。
val:需要更新的位值。
返回值
0:写入成功
其他:写入失败

regmap_bulk_read函数 和 regmap_bulk_write函数
原型

int regmap_bulk_read(struct regmap *map,
					 unsigned int reg,
					 void *val,
					 size_t val_count)
int regmap_bulk_write(struct regmap *map,
					  unsigned int reg,
					  const void *val,
					  size_t val_count)

功能:读取/写入多个寄存器的值。
参数
map:要操作的 regmap。
reg:要读取/写入的第一个寄存器。
val:要读取/写入的数据缓冲区。
val_count:要读取/写入的寄存器数量。
返回值
0:写入成功
其他:写入失败

5. regmap_config 掩码设置

  结构体 regmap_config 中有三个关于掩码的成员变量:read_flag_mask、write_flag_mask 和 zero_flag_mask。
  以之前学习的 icm20608 为例,该芯片支持 i2c 和 spi 接口,但是当使用 spi 接口时,如果需要读取 icm20608 寄存器数据,地址最高位必须置 1,写内部寄存器的时,地址最高位要设置为 0,这里就涉及到对寄存器地址最高位的操作。在之前编写的驱动代码中,使用手动修改该位置数据的方式:

1 static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
2 {
3
......
21 		txdata[0] = reg | 0x80; /* 写数据的时候首寄存器地址 bit7 要置 1 */
22 		t->tx_buf = txdata; /* 要发送的数据 */
23 		t->rx_buf = rxdata; /* 要读取的数据 */
24 		t->len = len+1; /* t->len=发送的长度+读取的长度 */
25 		spi_message_init(&m); /* 初始化 spi_message */
26 		spi_message_add_tail(t, &m);
27 		ret = spi_sync(spi, &m); /* 同步发送 */
......
39 		return ret;
40 }

其中第 21 行,将寄存器的地址 bit7 置 1,表示这是一个读操作。
如果使用 regmap,在初始化 regmap_config 的时候直接将 read_flag_mask 设置为 0X80 即可,这样通过 regmap 读取 SPI 内部寄存器时就会将寄存器地址与 read_flag_mask 进行或运算,结果就是将 bit7 置 1。
同理 write_flag_mask 用法也是一样,只是 write_flag_mask 用于写寄存器操作。

打开 regmap-spi.c 文件,这个文件就是 regmap 的 spi 总线文件,其中有以下内容:

101 static const struct regmap_bus regmap_spi = {
102 		.write = regmap_spi_write,
103 		.gather_write = regmap_spi_gather_write,
104 		.async_write = regmap_spi_async_write,
105 		.async_alloc = regmap_spi_async_alloc,
106 		.read = regmap_spi_read,
107 		.read_flag_mask = 0x80,
108 		.reg_format_endian_default = REGMAP_ENDIAN_BIG,
109 		.val_format_endian_default = REGMAP_ENDIAN_BIG,
110 };
111
112 struct regmap *__regmap_init_spi(struct spi_device *spi,
113 								 const struct regmap_config *config,
114 								 struct lock_class_key *lock_key,
115 								 const char *lock_name)
116 {
117 		return __regmap_init(&spi->dev, &regmap_spi, &spi->dev, config, lock_key, lock_name);
118 }

第 101~110 行初始化了一个 regmap_bus 实例:regmap_spi,第 107 行中 read_flag_mask 默认为 0X80。注:这里是将 regmap_bus 的 read_flag_mask 成员变量设置为 0X80
第 112~119 行__regmap_init_spi 函数,被 regmap_init_spi 函数调用,用于并申请一个 SPI 总线的 regmap,该函数也只是__regmap_init 函数的简单封装。在__regmap_init 函数中有以下内容:

812 if (config->read_flag_mask ||
813     config->write_flag_mask ||
814     config->zero_flag_mask) 
815 {
816   	map->read_flag_mask = config->read_flag_mask;
817   	map->write_flag_mask = config->write_flag_mask;
818 } else if (bus) {
819 	map->read_flag_mask = bus->read_flag_mask;
820 }

第 812~817 行就是用 regmap_config 中的读写掩码来初始化 regmap_bus 中的掩码。注意:如果所使用的 SPI 设备不需要读掩码,在初始化 regmap_config 的时候一定要将 read_flag_mask 设置为 0X00,并且也要将 zero_flag_mask 设置为 true。

二、驱动开发

本节就对之前开发的 icm20608 驱动进行改写,所以不需要修改有关的设备树。

1.修改设备结构体,添加 regmap 和 regmap_config

修改完后的 icm20608_dev 结构体如下:

#include <linux/regmap.h>

struct icm20608_dev {
	struct spi_device *spi;		/* spi设备 */
	dev_t devid;				/* 设备号 	 */
	struct cdev cdev;			/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;		/* 设备 	 */
	struct device_node	*nd; 	/* 设备节点 */
	signed int gyro_x_adc;		/* 陀螺仪X轴原始值 	 */
	signed int gyro_y_adc;		/* 陀螺仪Y轴原始值		*/
	signed int gyro_z_adc;		/* 陀螺仪Z轴原始值 		*/
	signed int accel_x_adc;		/* 加速度计X轴原始值 	*/
	signed int accel_y_adc;		/* 加速度计Y轴原始值	*/
	signed int accel_z_adc;		/* 加速度计Z轴原始值 	*/
	signed int temp_adc;		/* 温度原始值 			*/

	struct regmap *regmap;
	struct regmap_config regmap_config;
};

添加了regmap 指针变量,regmap 需要使用 regmap_init_spi 函数来申请和初始化,所以这里是指针类型。
regmap_config 结构体成员变量用来配置 regmap。

2.初始化 regmap

一般在 probe 函数中初始化 regmap,修改后内容如下:

/*probe函数*/
static int icm20608_probe(struct spi_device *spi)
{
	int ret;
	struct icm20608_dev *icm20608dev;
	
	/* 分配icm20608dev对象的空间 */
	icm20608dev = devm_kzalloc(&spi->dev, sizeof(*icm20608dev), GFP_KERNEL);
	if(!icm20608dev)
		return -ENOMEM;

	/* 初始化 regmap_config 设置 */
	icm20608dev->regmap_config.reg_bits = 8; /* 寄存器长度 8bit */
	icm20608dev->regmap_config.val_bits = 8; /* 值长度 8bit */
	icm20608dev->regmap_config.read_flag_mask = 0x80; /* 读掩码 */
	/* 初始化 IIC 接口的 regmap */
	icm20608dev->regmap = regmap_init_spi(spi, &icm20608dev->regmap_config);
	if (IS_ERR(icm20608dev->regmap)) {
		return PTR_ERR(icm20608dev->regmap);
	}

	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	ret = alloc_chrdev_region(&icm20608dev->devid, 0, ICM20608_CNT, ICM20608_NAME);
	if(ret < 0) {
		pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", ICM20608_NAME, ret);
        goto del_regmap;
	}
......
	return 0;
	
destroy_class:
	device_destroy(icm20608dev->class, icm20608dev->devid);
del_cdev:
	cdev_del(&icm20608dev->cdev);
del_unregister:
	unregister_chrdev_region(icm20608dev->devid, ICM20608_CNT);
	return -EIO;
del_regmap:
	regmap_exit(icm20608dev->regmap);
	return -EIO;
}

①regmap_config 的初始化,icm20608 的寄存器地址地址长度为 8bit,寄存器值也是 8bit,因此 reg_bits 和 val_bits 都设置为 8。由于 icm20608 通过 SPI 接口读取的时候地址寄存器最高位要设置为 1,因此 read_flag_mask 设置为 0X80。
②通过 regmap_init_spi 函数来申请并初始化 SPI 总线的 regmap。
③如果要删除 regmap 就使用 regmap_exit 函数。

3.卸载驱动时移除regmap

在 remove 函数中删除 probe 里申请的 regmap,修改后内容如下:

static int icm20608_remove(struct spi_device *spi)
{
	struct icm20608_dev *icm20608dev = spi_get_drvdata(spi);
	/* 注销字符设备驱动 */
	/* 1、删除cdev */
	cdev_del(&icm20608dev->cdev);
	/* 2、注销设备号 */
	unregister_chrdev_region(icm20608dev->devid, ICM20608_CNT); 
	/* 3、注销设备 */
	device_destroy(icm20608dev->class, icm20608dev->devid);
	/* 4、注销类 */
	class_destroy(icm20608dev->class); 
	/* 5、删除 regmap */
	regmap_exit(icm20608dev->regmap);
	
	return 0;
}

使用 regmap_exit 删除掉 probe 函数中申请的 regmap。

4.读写设备内部寄存器

以前使用 spi 驱动框架编写读写函数,现在直接使用 regmap_read、regmap_write 的函数即可:

/*读取icm20608指定寄存器值,读取一个寄存器*/
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
	u8 ret;
	unsigned int data;

	ret = regmap_read(dev->regmap, reg, &data);
	return (u8)data;
}
/*写入icm20608指定寄存器值,读取一个寄存器*/
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
    regmap_write(dev->regmap, reg, &value);
}

/*读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、三轴加速度计和内部温度*/
void icm20608_readdata(struct icm20608_dev *dev)
{
	u8 ret;
	unsigned char data[14];

	ret = regmap_bulk_read(dev->regmap, ICM20_ACCEL_XOUT_H, data, 14);

	dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]); 
	dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]); 
	dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]); 
	dev->temp_adc    = (signed short)((data[6] << 8) | data[7]); 
	dev->gyro_x_adc  = (signed short)((data[8] << 8) | data[9]); 
	dev->gyro_y_adc  = (signed short)((data[10] << 8) | data[11]);
	dev->gyro_z_adc  = (signed short)((data[12] << 8) | data[13]);
}

①icm20608_read_onereg 函数用于读取 icm20608 内部单个寄存器,这里使用 regmap_read 函数来完成寄存器读取操作
②icm20608_write_onereg 函数用于向 icm20608 指定寄存器写入数据,使用regmap_write 函数来完成
③icm20608_readdata 函数用于读取 icm20608 内部陀螺仪、加速度计和温度计的数据,从 ICM20_ACCEL_XOUT_H 寄存器开始,连续读取 14 个寄存器。这里直接使用 regmap_bulk_read 函数来读取多个寄存器。

三、运行测试

与之前的 SPI 总线测试方式基本相同,编译出.ko模块文件,放入开发板的相应目录,然后使用之前的测试程序进行测试即可。
在这里插入图片描述

四、I2CRegmap API开发

IIC 总线的 regmap 框架基本和 SPI 一样,只是需要使用 regmap_init_i2c 来申请并初始化对应的 regmap,同样都是使用 regmap_read 和 regmap_write 来读写 I2C 设备内部寄存器。
将 SPI 相关的 API 更换成 IIC 的 API 即可,这里就不再赘述。

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

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

相关文章

RxJava VS kotlin flow

1.基础概念介绍 1.1 观察者模式 观察者模式&#xff0c;其实对于Android开发者而言&#xff0c;并不陌生&#xff0c;button的setOnClickListener&#xff0c;就是一个典型的观察者模式。控件button是被观察者&#xff0c;它产生一个事件(点击)&#xff0c;观察者OnClickList…

量化策略——准备2 量化技能树量化术语

文章目录量化技能树量化/金融术语1. 俗语2. 持仓术语3. 资金术语4. 策略术语5. 股票软件界面实用术语量化必然用到的核心价格数据其他数据/指标含义6. 委托单术语量化技能树 首先&#xff0c;量化金融&#xff08;Quantitative Finance&#xff0c;简称“量化”&#xff0c;Qu…

《小猫猫大课堂》三轮1——深度解析数据在内存中的存储

宝子&#xff0c;你不点个赞吗&#xff1f;不评个论吗&#xff1f;不收个藏吗&#xff1f; 最后的最后&#xff0c;关注我&#xff0c;关注我&#xff0c;关注我&#xff0c;你会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的很重…

学习网络协议

概述 先从知乎盗个图&#xff1a;怎么开始学习网络协议&#xff1f; - 知乎 1、应用层 作用&#xff1a;定义数据格式并按照对应的格式解读数据。 2、传输层 作用&#xff1a;定义端口&#xff0c;标识应用程序身份&#xff0c;实现端口到端口的通信。 3、网络层 作用&…

5个技巧提高项目领导技能

作为项目经理&#xff0c;一个典型的工作日涉及处理许多任务。监督不同的时间表和里程碑。解决问题&#xff0c;主动解决瓶颈。 这些后勤工作很重要&#xff0c;但您也知道成功的项目管理比协调这些细节要多得多。为了做好你的工作&#xff08;并且把它做好&#xff09;&am…

C语言模拟QT的信号与槽功能

文章目录前言一、Qt信号与槽的实现机理二、简化后的实现步骤1. 定义一些必要的宏2. 实现声明信号的宏3. 实现发射信号的宏4. 取代QObject类5. 实现connect函数6. 可有可无的slots三、完整的代码实现四、使用方法与QT中的区别1. SIG_SLOT_OBJ取代QObject2. 定义信号不同3. 发射信…

【NI Multisim 14.0原理图环境设置——原理图的组成】

目录 序言 一、原理图的组成 &#x1f46c; 1. 元器件 &#x1f46c;2. 仪表 &#x1f46c;3.导线 &#x1f46c;4.丝印层 &#x1f46c;5. 端口 &#x1f46c;6.网络标号 &#x1f46c;7.电源符号 序言 NI Multisim最突出的特点之一就是用户界面友好。它可以使电路设…

为iframe正名,你可能并不需要微前端

作者&#xff1a;刘显安(码怪) 任何新技术、新产品都是有一定适用场景的&#xff0c;它可能在当下很流行&#xff0c;但它不一定在任何时候都是最优解。 前言 最近几年微前端很火&#xff0c;火到有时候项目里面用到了iframe还要偷偷摸摸地藏起来生怕被别人知道了&#xff0c;…

Linux学习笔记——Tomcat安装部署

5.2、Tomcat安装部署 5.2.1、简介 Tomcat是由Apache开发的一个Servlet容器&#xff0c;实现了对Servlet和JSP的支持&#xff0c;并提供了作为Web服务器的一些特有功能&#xff0c;如Tomcat管理和控制平台、安全域管理和Tomcat阀等。 简单来说&#xff0c;Tomcat是一个WEB应用…

内核解读之内存管理(3)内存管理三级架构之内存区域zone

文章目录1、zone类型2、zone结构体3、zone的初始化流程1、zone类型 NUMA结构下, 每个处理器CPU与一个本地内存直接相连, 而不同处理器之前则通过总线进行进一步的连接, 因此相对于任何一个CPU访问本地内存的速度比访问远程内存的速度要快, 而Linux为了兼容NUMA结构, 把物理内存…

Flink数据流类型之间的转换(WindowedStream、DataStream、KeyedStream、AllWindowStream之间的转换)

Flink提供了一些流API&#xff0c;其中包括WindowedStream、DataStream、KeyedStream和AllWindowStream。 &#x1f34a;WindowedStream是一种特殊的流&#xff0c;其中数据已按时间或数据元素的键进行分组&#xff0c;并且每个分组的数据都在窗口中按时间划分。这意味着&…

2023年出入境政策-喜忧参半

2023年已经到来&#xff0c;随着卫健委公布中国防控新冠措施调整优化以后&#xff0c;出入境政策相应也有了很大变化&#xff0c;知识人网小编概括为喜忧参半。喜的是从国外入境中国不再需要集中隔离&#xff1b;忧的是有些国家对于中国人入境增加了核酸检测要求。下面我们就这…

第一章 Java入门开发

第一章 Java入门开发 目录一&#xff0e; 概述二&#xff0e; JDK1. 概述2. 安装3. JDK目录一&#xff0e; 概述 Java是一门高级程序设计语言&#xff0c;是支持跨平台和完成面向对象的程序设计语言。针对不同的开发市场&#xff0c;sun公司将Java分为Java SE&#xff08;标准版…

关于clip通信架构设计的调研

网络上大部分关于clip-as-service的描述都是关于它如何使用&#xff0c;基于它的编码功能上去计算文本相似度&#xff0c;根据文字推荐图片等等&#xff0c;只有作者的创作思路里面提及通信架构的设计。 作者博客&#xff1a; 链接: link 如何解决多个客户端同时请求服务端的场…

STS4中MVC项目中把log4j从1.x升级到2.x中遇到的两个问题

文章目录问题一 升级后看Maven Dependencies中还是有依赖1.x的log4j问题二 web.xml配置不对项目原来的log4j版本是1.2.14&#xff0c;有漏洞需要升级到2.18.0.问题一 升级后看Maven Dependencies中还是有依赖1.x的log4j 原因是有关联依赖&#xff0c; 项目中别的jar库有依赖低…

【算法笔记】【专题】RMQ 问题:ST表/树状数组/线段树

0. 前言 好久没更算法笔记专栏了&#xff0c;正好学了新算法来更新…… 这也是本专栏的第一个专题问题&#xff0c;涉及到三种数据结构&#xff0c;如果写得有问题请各位大佬多多指教&#xff0c;谢谢&#xff01; 1. 关于 RMQ 问题 RMQ 的全称是 Range Minimum/Maximum Que…

《Linux运维实战:Centos7.6基于docker-compose一键离线部署单节点redis6.2.8 》

一、部署背景 由于业务系统的特殊性&#xff0c;我们需要面向不通的客户安装我们的业务系统&#xff0c;而作为基础组件中的redis针对不同的客户环境需要多次部署&#xff0c;作为一个运维工程师&#xff0c;提升工作效率也是工作中的重要一环。所以我觉得有必要针对redis6.2.8…

使用 .NET 标记游戏地图关键坐标点

本文以天涯明月刀 OL 游戏的云上之城探索玩法为例&#xff0c;介绍如何使用 .NET 在游戏地图中标记大量关键坐标点。 1. 背景 大概很多程序员都是喜欢玩游戏的吧&#xff0c;我也不例外。我们经常会看到电视剧中的各路游戏大神&#xff0c;要么是有只有他一个人会的骚操作&…

Linux--信号--信号的产生方式--核心转储--0104

1. 什么是信号 生活中的信号&#xff1a;红绿灯&#xff0c;狼烟&#xff0c;撤退、集合...。 我们认识这些信号&#xff0c;首先是因为自己记住了对应场景下的信号后续需要执行的动作。如果信号没有产生&#xff0c;我们依旧知道如何处理这个信号。收到信号&#xff0c;我们…

springboot学习(七十八) springboot中通过自定义注解实现数据脱敏的功能

文章目录前言一、引入hutools工具类二、定义常用需要脱敏的数据类型的枚举三、定义脱敏方式枚举四、自定义脱敏的注解五、自定义Jackson的序列化方式六、使用七、脱敏效果前言 对于某些接口返回的信息&#xff0c;涉及到敏感数据的必须进行脱敏操作&#xff0c;例如银行卡号、…