编写一个通用的i2c控制器驱动框架

往期内容

I2C子系统专栏:

  1. I2C(IIC)协议讲解-CSDN博客
  2. SMBus 协议详解-CSDN博客
  3. I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客
  4. 内核提供的通用I2C设备驱动I2c-dev.c分析:注册篇
  5. 内核提供的通用I2C设备驱动I2C-dev.c分析:file_ops篇

总线和设备树专栏:

  1. 总线和设备树_憧憬一下的博客-CSDN博客
  2. 设备树与 Linux 内核设备驱动模型的整合-CSDN博客

前言

本章需要用到的内核源码:

Linux内核真正的I2C控制器驱动程序

  • IMX6ULL: Linux-4.9.88\drivers\i2c\busses\i2c-imx.c📎i2c-imx.c

1.分辨

1.1 I2C总线和平台总线区别

i2c总线-设备-驱动平台的注册,i2c总线是真实存在的,而bus总线-设备-驱动平台中的bus则是虚拟的

i2c总线-设备-驱动平台管理着多条总线上的i2c_Controller And Devices

i2c控制器也有自己的驱动(I2c_Adapter_drv)

  • 可以自己通过注册万能的bus总线平台,让驱动程序platform_driver来和platform_device设备节点(有控制器的name、属性设置等)匹配,来代表着一个总线,创建i2c_adapter。创建平台只是为了让驱动和设备节点相匹配,使该总线上i2c控制器功能的实现
  • 一个i2c_adapter就代表着一个总线,代表着一个i2c controller
  • 核心函数是master_xfer函数,这个函数是将数据传给总线上的i2c设备

i2c_client上则记录着i2c设备自己被哪一个i2c Controller(i2c_adapter)管理着,也就是在哪一条总线上,以及设备地址,一般是有设备树节点转化而来

i2c_driver上则是为i2c设备注册驱动程序、生成设备节点、设备类等,为app提供打开设备的设备节点、提供对设备的操作函数,比如读取或写入。注意的是要先用i2c_add_driver函数将驱动添加进i2c总线平台中,并且读取和写入是要先经过i2c_controller的

  • 如i2c_transfer函数,会将device_driver的数据(来自app)转发i2c_client中记录的i2c_adapter,也就是i2c控制器,之后i2c控制器对应的驱动程序又会将数据转发给在总线上的I2C设备(i2c_client源)中的存储芯片等
  • i2c_driver和i2c_client之间的匹配则是有i2c总线平台来进行匹配的,匹配成功后才能实现数据传给i2c_controller,进而传给i2c device
  • 当然,也可以自己注册bus总线平台,使用GPIO模拟I2C来让platform_device和platform_driver进行匹配,也就是i2c设备信息client和驱动程序进行匹配,只需要将GPIO引脚模拟I2C引脚输出I2C协议就行。这一种在后面也会出文章讲解。

img

1.2 I2C驱动程序的层次

img

img

2. I2C_Adapter驱动框架

2.1 核心的结构体

i2c_adapter

img

I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客该结构体在之前的文章也有介绍过其相关成员。

i2c_algorithm

\Linux-4.9.88\include\uapi\linux\i2c.h
struct i2c_algorithm {/* If an adapter algorithm can't do I2C-level access, set master_xferto NULL. If an adapter algorithm can do SMBus access, setsmbus_xfer. If set to NULL, the SMBus protocol is simulatedusing common I2C messages *//* master_xfer should return the number of messages successfullyprocessed, or a negative value on error */int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);  int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,u8 command, int size, union i2c_smbus_data *data);/* To determine what the adapter supports */u32 (*functionality) (struct i2c_adapter *);#if IS_ENABLED(CONFIG_I2C_SLAVE)int (*reg_slave)(struct i2c_client *client);int (*unreg_slave)(struct i2c_client *client);
#endif
};

这个结构体在之前的文章也有讲过,这里简单提一下:

  • master_xfer:这是最重要的函数,它实现了一般的I2C传输,用来传输一个或多个i2c_msg

  • smbus_xfer:实现SMBus传输,如果不提供这个函数,SMBus传输会使用master_xfer来模拟

  • functionality: 数通常会返回一个由功能标志位组成的位掩码,表示适配器支持哪些 I2C 或 SMBus 操作(如 I2C_FUNC_I2C, I2C_FUNC_SMBUS_READ_BLOCK_DATA 等等)。

  • reg_slave:

    • 注册一个 I2C 从设备,使得该设备可以响应 I2C 主设备发起的请求。有些I2C Adapter也可工作与Slave模式,用来实现或模拟一个I2C设备
  • unreg_slave:

    • 注销一个 I2C 从设备,使其不再响应 I2C 主设备的请求。

2.2 I2C adapter的相关的API

I2C adapter定义好之后,要把它注册到kernel中去,相关的API如下:

\Linux-4.9.88\Linux-4.9.88\include\linux\i2c.h:
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
extern int i2c_add_adapter(struct i2c_adapter *);
extern void i2c_del_adapter(struct i2c_adapter *);
extern int i2c_add_numbered_adapter(struct i2c_adapter *);
extern struct i2c_adapter *i2c_get_adapter(int nr);
extern void i2c_put_adapter(struct i2c_adapter *adap);
extern unsigned int i2c_adapter_depth(struct i2c_adapter *adapter);
/* must call put_device() when done with returned i2c_adapter device */
extern struct i2c_adapter *of_find_i2c_adapter_by_node(struct device_node *node);/* Return the functionality mask */
static inline u32 i2c_get_functionality(struct i2c_adapter *adap)
{return adap->algo->functionality(adap);
}
/* Return 1 if adapter supports everything we need, 0 if not. */
static inline int i2c_check_functionality(struct i2c_adapter *adap, u32 func)
{return (func & i2c_get_functionality(adap)) == func;
}
/* Return the adapter number for a specific adapter */
static inline int i2c_adapter_id(struct i2c_adapter *adap)
{return adap->nr;
}

(1)i2c_add_adapter和i2c_add_numbered_adapter是I2C adapter的注册接口,它们的区别是:

  • 前者将一个 i2c_adapter 注册到 I2C 核心框架中,并自动为它分配一个 nr(adapter ID)。内核会为你选择一个合适的 ID,而不需要你手动指定。适用于不需要指定特定 ID 的情况,适合大多数情况下的控制器注册。 ID 自动分配。
  • 后者允许你指定 i2c_adapter 的 nr 字段,也就是适配器的 ID。你可以手动设置 adap->nr,然后注册时内核将使用你指定的这个 ID。如果 nr 不可用或者冲突,则返回错误。适用于需要在系统中保留固定编号的 I2C 适配器,比如为了与用户空间或其他设备进行明确绑定。 ID 可以手动指定。

(2)i2c_del_adapter(struct i2c_adapter *adap):

  • 用于从 I2C 核心中删除适配器。它会取消所有注册到该适配器的从设备,释放与之相关的资源。在驱动卸载或适配器不再需要时使用,确保适配器从系统中安全移除。

(3)i2c_get_functionality(struct i2c_adapter *adap) 和 i2c_check_functionality:

  • 前者 获取指定适配器所支持的功能。返回值是一个 32 位的位掩码,用于表示该适配器支持的特性。这些特性可以包括:标准的 I2C 功能(如 7 位地址、10 位地址等)、SMBus 功能、高级协议特性(如 I2C_FUNC_I2CI2C_FUNC_SMBUS_*)。 用于查询控制器支持的功能。
  • 后者 检查指定控制器是否支持某个特定的功能。传入的 func 是功能的位掩码。该函数会将传入的 func 与适配器实际支持的功能进行比较,如果适配器支持所有的请求功能,则返回 1,否则返回 0。 用于检查控制器是否支持某一特定功能。

(4)i2c_adapter_id(struct i2c_adapter *adap):

  • 返回给定的 i2c_adapter 的 nr 字段,也就是适配器的 ID。这个函数很简单,通常在需要识别适配器或者调试时使用

(5)i2c_get_adapter(int nr) 和 i2c_put_adapter(struct i2c_adapter *adap):

  • 前者: 根据指定的 nr(适配器的 ID)查找并返回对应的 i2c_adapter 结构指针。这个函数会增加适配器所依赖模块的引用计数(内部通过调用 try_module_get)。因此在调用这个函数后,需要确保适配器不会被卸载。 增加引用计数,获取适配器。
  • 后者:当使用完 i2c_adapter 后,必须调用这个函数以减少模块的引用计数。这样可以防止模块被错误卸载,同时释放相关资源。 增加引用计数,获取适配器。

(6)of_find_i2c_adapter_by_node(struct device_node *node):

  • 通过设备树节点(device_node)查找与之关联的 I2C 适配器。该函数通常用于通过设备树动态探测并找到某个 I2C 适配器。在调用该函数后,你还需要通过 put_device 来正确处理设备的引用计数,以确保资源释放。

2.3 驱动程序框架

分配、设置、注册一个i2c_adpater结构体:

  • i2c_adpater的核心是i2c_algorithm
  • i2c_algorithm的核心是master_xfer函数

所涉及的函数

  • 分配struct i2c_adpater *adap = kzalloc(sizeof(struct i2c_adpater), GFP_KERNEL);
  • 设置
adap->owner = THIS_MODULE;
adap->algo = &stm32f7_i2c_algo;
  • 注册:i2c_add_adapter/i2c_add_numbered_adapter
ret = i2c_add_adapter(adap);          // 不管adap->nr原来是什么,都动态设置adap->nr
ret = i2c_add_numbered_adapter(adap); // 如果adap->nr == -1 则动态分配nr; 否则使用该nr
  • 反注册
i2c_del_adapter(adap);

i2c_algorithm示例

img

大致步骤

了解了I2C adapter有关的数据结构和API之后,编写I2C控制器驱动就简单多了,主要步骤如下:

1)定义一个struct i2c_algorithm变量,并根据I2C controller的特性,实现其中的回调函数。

2)在DTS文件(一般都放到DTSI)中,定义I2C controller相关的DTS node

3)在drives/i2c/busses目录下,以i2c-xxx.c的命名方式,编写I2C controller的platform driver,并提供match id、probe、remove等接口。

4)在platform driver的probe接口中,分配一个adapter结构,并进行必要的初始化操作后,调用i2c_add_adapter或者i2c_add_numbered_adapter接口,将其注册到kernel中即可。

2.4 编写框架

这里使用的万能的总线平台:bus+dts 实现,platfrom_driver and platform_device

📎05_i2c_adapter_framework.zip – 里面有设备树

#include <linux/completion.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c-algo-bit.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_data/i2c-gpio.h>
#include <linux/platform_device.h>
#include <linux/slab.h>/* Global pointer to the virtual I2C adapter */
static struct i2c_adapter *g_adapter;/* * i2c_bus_virtual_master_xfer - I2C message transfer function* @i2c_adap: Pointer to I2C adapter* @msgs: Array of I2C messages* @num: Number of messages* * This function simulates the transfer of multiple I2C messages. * Here you would normally interact with the hardware.*/
static int i2c_bus_virtual_master_xfer(struct i2c_adapter *i2c_adap,struct i2c_msg msgs[], int num)
{int i;/* Loop through each message and simulate transfer */for (i = 0; i < num; i++) {/* Normally, the actual hardware transfer would occur here */// simulate transfer of msgs[i];}/* Return the number of messages processed */return num;
}/* * i2c_bus_virtual_func - Supported functionality of the virtual I2C bus* @adap: Pointer to I2C adapter* * This function reports the capabilities of the virtual I2C bus. * It supports standard I2C transactions and SMBus emulation.*/
static u32 i2c_bus_virtual_func(struct i2c_adapter *adap)
{/* Return a combination of supported functionalities */return I2C_FUNC_I2C | I2C_FUNC_NOSTART | I2C_FUNC_SMBUS_EMUL |I2C_FUNC_SMBUS_READ_BLOCK_DATA |I2C_FUNC_SMBUS_BLOCK_PROC_CALL |I2C_FUNC_PROTOCOL_MANGLING;
}/* * i2c_bus_virtual_algo - Virtual I2C bus algorithm structure* * This structure defines the transfer and functionality operations for* the virtual I2C bus. It is used to handle I2C operations for the* associated adapter.*/
const struct i2c_algorithm i2c_bus_virtual_algo = {.master_xfer   = i2c_bus_virtual_master_xfer,.functionality = i2c_bus_virtual_func,
};/* * i2c_bus_virtual_probe - Platform driver probe function* @pdev: Platform device structure* * This function is called when the driver is matched with a device.* It sets up and registers the I2C adapter.*/
static int i2c_bus_virtual_probe(struct platform_device *pdev)
{/* Allocate memory for the I2C adapter structure */g_adapter = kzalloc(sizeof(*g_adapter), GFP_KERNEL);if (!g_adapter)return -ENOMEM;/* Initialize the I2C adapter structure */g_adapter->owner = THIS_MODULE;g_adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;  // Adapter supports hardware monitor and SPD classesg_adapter->nr = -1;  // Dynamically assign adapter numbersnprintf(g_adapter->name, sizeof(g_adapter->name), "i2c-bus-virtual");  // Set adapter name/* Assign the algorithm to the adapter */g_adapter->algo = &i2c_bus_virtual_algo;/* Register the I2C adapter with the I2C core */i2c_add_adapter(g_adapter);  // Can also use i2c_add_numbered_adapter() for fixed numbersreturn 0;
}/* * i2c_bus_virtual_remove - Platform driver remove function* @pdev: Platform device structure* * This function is called when the driver is removed.* It unregisters the I2C adapter and cleans up.*/
static int i2c_bus_virtual_remove(struct platform_device *pdev)
{/* Unregister the I2C adapter */i2c_del_adapter(g_adapter);/* Free the allocated memory */kfree(g_adapter);return 0;
}/* * i2c_bus_virtual_dt_ids - Device tree compatible entries* * This array defines the compatible strings that match the virtual I2C bus* driver to the corresponding device in the device tree.*/
static const struct of_device_id i2c_bus_virtual_dt_ids[] = {{ .compatible = "yyy,i2c-bus-virtual", },{ /* sentinel */ }
};/* * i2c_bus_virtual_driver - Platform driver structure* * This structure defines the probe, remove, and other operations for the* virtual I2C bus driver.*/
static struct platform_driver i2c_bus_virtual_driver = {.driver		= {.name	= "i2c-gpio",  // Name of the driver.of_match_table	= of_match_ptr(i2c_bus_virtual_dt_ids),  // Match device tree compatible entries},.probe		= i2c_bus_virtual_probe,  // Probe function.remove		= i2c_bus_virtual_remove,  // Remove function
};/* * i2c_bus_virtual_init - Driver initialization function* * This function is called when the module is loaded. It registers* the platform driver with the Linux kernel.*/
static int __init i2c_bus_virtual_init(void)
{int ret;/* Register the platform driver */ret = platform_driver_register(&i2c_bus_virtual_driver);if (ret)printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);return ret;
}
module_init(i2c_bus_virtual_init);/* * i2c_bus_virtual_exit - Driver exit function* * This function is called when the module is unloaded. It unregisters* the platform driver from the kernel.*/
static void __exit i2c_bus_virtual_exit(void)
{/* Unregister the platform driver */platform_driver_unregister(&i2c_bus_virtual_driver);
}
module_exit(i2c_bus_virtual_exit);MODULE_AUTHOR("www.100ask.net");
MODULE_LICENSE("GPL");

设备树

在设备树里构造控制器的设备节点:

i2c-bus-virtual {compatible = "yyy,i2c-bus-virtual";//剩下的可以是挂载在该控制器上的i2c设备的设备树节点};

platform_driver

分配、设置、注册platform_driver结构体。

核心是probe函数,

它要做这几件事:

  • 根据设备树信息设置硬件(引脚、时钟等)
  • 分配、设置、注册i2c_apdater

这里注册platform只是为了将i2c_adapter_driver和控制器的设备节点能够匹配起来,实现创建i2c_adapter而已

master_xfer

i2c_apdater核心是master_xfer函数,它的实现取决于硬件,在内核提供控制器驱动文件i2c-m中:

static int i2c_imx_xfer(struct i2c_adapter *adapter,struct i2c_msg *msgs, int num)
{unsigned int i, temp;int result;bool is_lastmsg = false;          // 标志变量,表示当前消息是否是最后一个消息bool enable_runtime_pm = false;   // 用于标记是否启用了运行时电源管理struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);  // 获取适配器的私有数据结构// 输出调试信息,显示函数名dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);// 检查运行时电源管理是否已启用,如果没有则启用它if (!pm_runtime_enabled(i2c_imx->adapter.dev.parent)) {pm_runtime_enable(i2c_imx->adapter.dev.parent);enable_runtime_pm = true;  // 标记为启用了电源管理}// 增加运行时电源的引用计数,确保设备处于活动状态result = pm_runtime_get_sync(i2c_imx->adapter.dev.parent);if (result < 0)goto out;  // 如果获取电源失败,则跳转到 out 标签处理// 开始I2C传输,调用底层驱动启动传输过程/* Start I2C transfer */result = i2c_imx_start(i2c_imx);if (result) {// 如果启动失败且存在总线恢复功能,则尝试恢复总线if (i2c_imx->adapter.bus_recovery_info) {i2c_recover_bus(&i2c_imx->adapter);result = i2c_imx_start(i2c_imx);  // 尝试重新启动传输}}if (result)  // 如果传输启动仍然失败,跳转到失败处理goto fail0;// 开始处理消息队列/* read/write data */for (i = 0; i < num; i++) {if (i == num - 1)is_lastmsg = true;  // 如果是最后一条消息,标记为trueif (i) {// 如果不是第一条消息,则发送重复开始信号 (Repeated Start)dev_dbg(&i2c_imx->adapter.dev, "<%s> repeated start\n", __func__);temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);  // 读取I2C控制寄存器temp |= I2CR_RSTA;  // 设置重复开始位imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);  // 写回控制寄存器result = i2c_imx_bus_busy(i2c_imx, 1);  // 等待总线忙完成if (result)goto fail0;  // 如果总线忙碌超时,跳转到失败处理}// 输出调试信息,显示当前正在传输的消息编号dev_dbg(&i2c_imx->adapter.dev, "<%s> transfer message: %d\n", __func__, i);#ifdef CONFIG_I2C_DEBUG_BUS// 如果启用了I2C调试,输出控制寄存器和状态寄存器的调试信息temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);dev_dbg(&i2c_imx->adapter.dev,"<%s> CONTROL: IEN=%d, IIEN=%d, MSTA=%d, MTX=%d, TXAK=%d, RSTA=%d\n",__func__,(temp & I2CR_IEN ? 1 : 0), (temp & I2CR_IIEN ? 1 : 0),(temp & I2CR_MSTA ? 1 : 0), (temp & I2CR_MTX ? 1 : 0),(temp & I2CR_TXAK ? 1 : 0), (temp & I2CR_RSTA ? 1 : 0));temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);dev_dbg(&i2c_imx->adapter.dev,"<%s> STATUS: ICF=%d, IAAS=%d, IBB=%d, IAL=%d, SRW=%d, IIF=%d, RXAK=%d\n",__func__,(temp & I2SR_ICF ? 1 : 0), (temp & I2SR_IAAS ? 1 : 0),(temp & I2SR_IBB ? 1 : 0), (temp & I2SR_IAL ? 1 : 0),(temp & I2SR_SRW ? 1 : 0), (temp & I2SR_IIF ? 1 : 0),(temp & I2SR_RXAK ? 1 : 0));
#endif// 根据消息类型执行读或写操作if (msgs[i].flags & I2C_M_RD)  // 如果是读操作result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);  // 执行读操作else {  // 如果是写操作if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)  // 如果支持DMA且消息长度大于阈值result = i2c_imx_dma_write(i2c_imx, &msgs[i]);  // 使用DMA执行写操作elseresult = i2c_imx_write(i2c_imx, &msgs[i]);  // 否则使用常规方式写}if (result)  // 如果读写操作失败,跳转到失败处理goto fail0;}fail0:// 停止I2C传输/* Stop I2C transfer */i2c_imx_stop(i2c_imx);// 更新运行时电源状态并减少引用计数pm_runtime_mark_last_busy(i2c_imx->adapter.dev.parent);pm_runtime_put_autosuspend(i2c_imx->adapter.dev.parent);out:// 如果启用了运行时电源管理,则在结束时禁用它if (enable_runtime_pm)pm_runtime_disable(i2c_imx->adapter.dev.parent);// 输出调试信息,显示函数退出时的状态dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n", __func__,(result < 0) ? "error" : "success msg",(result < 0) ? result : num);// 返回最终的结果,成功则返回消息数量,失败则返回错误码return (result < 0) ? result : num;
}

CPU 调用 i2c_transfe(i2c device driver) → 内部调用了通过 i2c_imx_xfer(adap->algo->master_xfer) 与 I2C 控制器通信 → 如果是读操作,则调用i2c_adapter中的 i2c_imx_readi2c_imx_read 从 I2C 控制器接收数据并填充到 msgsbuf 中 → CPU 读取 buf 中的数据

在上面讲的i2c_imx_xfer,是不是有开始信号、停止信号、data。这些都是iic传输协议的格式中有提到的,至于addr、w/r,这些就封装在了i2c_msg中,格式:I2C(IIC)协议讲解-CSDN博客


框架例子:

static int xxx_master_xfer(struct i2c_adapter *adapter,struct i2c_msg *msgs, int num)
{for (i = 0; i < num; i++) {struct i2c_msg *msg = msgs[i];{// 1. 发出S信号: 设置寄存器发出S信号CTLREG = S;// 2. 根据Flag发出设备地址和R/W位: 把这8位数据写入某个DATAREG即可发出信号//    判断是否有ACKif (!ACK)return ERROR;else {// 3. read / writeif (read) {STATUS = XXX; // 这决定读到一个数据后是否发出ACK给对方val = DATAREG; // 这会发起I2C读操作} else if(write) {DATAREG = val; // 这会发起I2C写操作val = STATUS;  // 判断是否收到ACKif (!ACK)return ERROR;}                }// 4. 发出P信号CTLREG = P;}}return i;
}

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

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

相关文章

时空数据时序预测模型: HA、VAR、GBRT、GCN、DCRNN、FCCF、ST-MGCN

HA (Historical Average) HA (Historical Average&#xff0c;历史平均模型) 是一种基础的时间序列预测方法&#xff0c;通常用于预测具有周期性或季节性规律的数据。它通过计算历史上同一时间段的平均值来预测未来值&#xff0c;假设数据会遵循某种周期性的变化模式。以下是对…

智能家居的“眼睛”:计算机视觉如何让家更智能

引言 在不远的未来&#xff0c;当我们走进家门&#xff0c;灯光自动亮起&#xff0c;空调已经调至最舒适的温度&#xff0c;甚至音乐也播放着我们最喜欢的歌曲。 这一切&#xff0c;都得益于智能家居系统的发展。而在这个系统中&#xff0c;计算机视觉技术扮演着至关重要的角色…

SpringBoot车辆管理系统:构建与优化

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

群晖通过 Docker 安装 MySQL

1. 打开 Docker 应用&#xff0c;并在注册表搜索 MySQL 2. 下载 MySQL 镜像&#xff0c;并选择版本 3. 在 Docker 文件夹中创建 MySQL&#xff0c;并创建子文件夹 4. 设置权限 5. 选择 MySQL 映像运行&#xff0c;创建容器 6. 配置 MySQL 容器 6.1 使用高权限执行容器 6.2 启…

scrapy 爬虫学习之【中医药材】爬虫

本项目纯学习使用。 1 scrapy 代码 爬取逻辑非常简单&#xff0c;根据url来处理翻页&#xff0c;然后获取到详情页面的链接&#xff0c;再去爬取详情页面的内容即可&#xff0c;最终数据落地到excel中。 经测试&#xff0c;总计获取 11299条中医药材数据。 import pandas as…

idea 2023 配置 web service

前言 能在网上查到的资料,都是比较老的,搞了一上午才配置好了环境 因此记录一下,服务你我他 我的环境: java 1.8,Idea2023.1 配置web service 服务端 直接新建一个java新项目 下载插件 添加框架支持 启动项目 配置web service 客户端 新建项目,下载三个插件的步骤和上面服务…

JMeter之mqtt-jmeter 插件介绍

前言 mqtt-jmeter插件是JMeter中的一个第三方插件&#xff0c;用于支持MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;协议的性能测试。MQTT是一种轻量级的发布/订阅消息传输协议&#xff0c;广泛应用于物联网和传感器网络中。 一、安装插件 mqtt-jmeter项目…

【Hive】6-Hive函数、运算符使用

Hive函数、运算符使用 Hive内置运算符 概述 整体上&#xff0c;Hive支持的运算符可以分为三大类&#xff1a;关系运算、算术运算、逻辑运算。 官方参考文档&#xff1a;https://cwiki.apache.org/confluence/display/Hive/LanguageManualUDF 也可以使用下述方式查看运算符的…

2024年AI 制作PPT新宠儿,3款神器集锦,让你的演示与众不同

咱们今儿聊聊最近超火的AI做PPT的工具。这年头&#xff0c;谁不想省事儿&#xff0c;少熬夜加班&#xff0c;多享受享受生活啊&#xff1f;所以&#xff0c;AI开始帮咱们搞定做PPT这种费时的活儿&#xff0c;我自然得好好研究研究。今天&#xff0c;我就给大家详细说说三款很火…

Linux下的进程解析(level 2)

目录 引言 pid解析 /proc 系统调用 fork &#xff1a;创建子进程 执行流分析 父子进程谁先运行 引言 在当今的信息技术时代&#xff0c;操作系统作为计算机系统的核心组件&#xff0c;承担着资源管理、任务调度等重要职责。Linux作为一种开源、高性能的操作系统&#xf…

第11篇:网络安全协议

目录 引言 11.1 安全套接字层&#xff08;SSL&#xff09;和传输层安全&#xff08;TLS&#xff09;协议 11.1.1 SSL/TLS 的工作原理 11.1.2 SSL/TLS 的应用场景 11.2 虚拟专用网&#xff08;VPN&#xff09;和 IP 安全协议&#xff08;IPSec&#xff09; 11.2.1 VPN 的工…

《深度学习》OpenCV EigenFaces算法 人脸识别

目录 一、EigenFaces算法 1、什么是EigenFaces算法 2、原理 3、实现步骤 1&#xff09;数据预处理 2&#xff09;特征提取 3&#xff09;构建模型 4&#xff09;识别 4、优缺点 1&#xff09;优点 2&#xff09;缺点 二、案例实现 1、完整代码 运行结果&#xff…

9.存储过程安全性博客大纲(9/10)

存储过程安全性博客大纲 引言 在数据库系统中&#xff0c;存储过程是一种预先编写好的SQL代码集合&#xff0c;它被保存在数据库服务器上&#xff0c;可以通过指定的名称来调用执行。存储过程可以包含一系列的控制流语句&#xff0c;如IF条件语句、WHILE循环等&#xff0c;使…

「从零开始的 Vue 3 系列」:第十一章——跨域问题解决方案全解析

前言 本系列将从零开始&#xff0c;系统性地介绍 Vue 3 的常用 API&#xff0c;逐步深入每个核心概念与功能模块。通过详尽的讲解与实战演示&#xff0c;帮助大家掌握 Vue 3 的基础与进阶知识&#xff0c;最终具备独立搭建完整 Vue 3 项目的能力。 第十一章&#xff1a;跨域问…

Win32图片库CxImage在vs2022下的编译和使用

一、编译CxImage库 1、下载CxImage_702库的源码:在下面的链接中下载cximage702_full.7z https://sourceforge.net/projects/cximage/files/7.02/ 2、解压到某一目录&#xff0c;vs2022打开CxImageFull_vc10.sln解决方案文件&#xff0c;提示升级点确定 3、先编译下面的这几…

基于SSM+微信小程序的房屋租赁管理系统(房屋2)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM微信小程序的房屋租赁管理系统实现了有管理员、中介和用户。 1、管理员功能有&#xff0c;个人中心&#xff0c;用户管理&#xff0c;中介管理&#xff0c;房屋信息管理&#xff…

[Linux] 逐层深入理解文件系统 (2)—— 文件重定向

标题&#xff1a;[Linux] 逐层深入理解文件系统 &#xff08;2&#xff09;—— 文件重定向 个人主页水墨不写bug &#xff08;图片来源于网络&#xff09; 目录 一、文件的读取和写入 二、文件重定向的本质 1.手动模拟重定向的过程——把标准输出重定向到redir.txt 2.重定向…

019_基于python+django食品销售数据分析系统2024_4032ydxt

目录 系统展示 开发背景 代码实现 项目案例 获取源码 博主介绍&#xff1a;CodeMentor毕业设计领航者、全网关注者30W群落&#xff0c;InfoQ特邀专栏作家、技术博客领航者、InfoQ新星培育计划导师、Web开发领域杰出贡献者&#xff0c;博客领航之星、开发者头条/腾讯云/AW…

基于SpringBoot的“社区医院管理服务系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“社区医院管理服务系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统首页界面图 用户注册界面图 医生界面…

这4款实用的工具简直就是硬盘数据丢失的救星。

硬盘数据丢失的情况有很多种&#xff0c;像误删除&#xff0c;格式化&#xff0c;病毒攻击&#xff0c;硬件故障等等。如果不是物理上的损坏&#xff0c;丢失的数据还是可以通过一些方法进行恢复的。这里就跟大家分享几款可以进行数据恢复的专业数据件&#xff0c;希望可以帮助…