RK3568驱动指南|第十三篇 输入子系统-第148章 通用事件处理层open函数分析

本文主要是介绍RK3568驱动指南|第十三篇 输入子系统-第148章 通用事件处理层open函数分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


【公众号】迅为电子

【粉丝群】824412014(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第十三篇 输入子系统_全新升级)_基于RK3568

【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


第148章 通用事件处理层open函数分析

148.1 open函数分析

在上个章节中,我们已经学习了通用事件处理层connect函数,在connect函数中,要创建字符设备,创建字符设备中最重要的操作是实现文件操作集中的函数,如下所示:

比如我们在应用程序中使用open函数打开设备节点,会执行文件操作集中的open函数,当我们在应用程序中使用read函数读设备节点的时候,会执行文件操作集中的read函数。本章节我们重点学习open函数,如下所示:

static int evdev_open(struct inode *inode, struct file *file)
{// 从 inode 的 i_cdev 成员中获取 evdev 结构体的指针struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);// 计算缓冲区大小unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);// 计算分配内存的大小,包括 evdev_client 结构体和输入事件缓冲区unsigned int size = sizeof(struct evdev_client) +bufsize * sizeof(struct input_event);// 定义 evdev_client 结构体指针struct evdev_client *client;int error;// 分配内存用于存储 evdev_client 结构体和输入事件缓冲区client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);if (!client)client = vzalloc(size);if (!client)return -ENOMEM;// 初始化 client 结构体的成员变量client->bufsize = bufsize;spin_lock_init(&client->buffer_lock);client->evdev = evdev;// 将 client 添加到 evdev 的客户端列表中evdev_attach_client(evdev, client);// 打开底层设备error = evdev_open_device(evdev);if (error)goto err_free_client;// 将 client 结构体设置为文件的私有数据file->private_data = client;// 使用 nonseekable_open 函数打开文件,设置文件为不可寻址的nonseekable_open(inode, file);// 返回成功的状态码return 0;err_free_client:// 打开设备失败,需要进行错误处理// 从 evdev 的客户端列表中移除 clientevdev_detach_client(evdev, client);// 释放 client 分配的内存kvfree(client);// 返回错误码return error;
}

上面这段代码是输入设备驱动程序中evdev_open函数,用于处理输入设备的打开操作,以下是对代码的解释。

1 首先,函数使用container_of宏从inode->i_cdev中获取指向struct evdev的指针。这里假设struct evdev结构体包含一个名为cdev的成员变量,该成员变量保存了指向字符设备的指针。

2 函数调用evdev_compute_buffer_size函数来计算输入设备缓冲区的大小,存储在bufsize变量中。该函数根据输入设备的属性计算出缓冲区的大小。

3 接下来,函数计算需要分配的内存大小,即struct evdev_client结构体的大小加上输入事件缓冲区的大小。这个大小存储在size变量中。

4 函数使用kzalloc函数尝试以GFP_KERNEL | __GFP_NOWARN标志分配内存空间,并将返回的指针赋给client变量。如果kzalloc分配失败,则使用vzalloc函数尝试以GFP_KERNEL标志分配内存空间。

5如果最终分配的内存空间为空,则表示内存分配失败,函数返回-ENOMEM。

6如果内存分配成功,函数根据分配的大小设置client结构体的字段,包括缓冲区大小(bufsize)、缓冲区的自旋锁(buffer_lock)、关联的输入设备指针(evdev)。

7 接下来,函数调用evdev_attach_client函数将client结构体与输入设备关联起来,表示该客户端正在访问该输入设备。

8 函数调用evdev_open_device函数打开输入设备,执行一些与设备相关的初始化操作。如果打开设备失败,函数跳转到err_free_client标签处进行错误处理。如果打开设备成功,函数将client指针存储在file结构的private_data字段中,以便在后续的文件操作中可以访问到client结构体。

9最后,函数调用nonseekable_open函数将文件标记为不可寻址,以指示该文件不支持随机访问。

10如果一切顺利,函数返回0,表示输入设备的打开操作成功。

11如果在任何步骤中出现错误,函数通过跳转到err_free_client标签处进行错误处理。在错误处理中,函数调用evdev_detach_client函数将client从输入设备中分离,然后使用kvfree函数释放client占用的内存空间,并返回相应的错误码。

函数调用evdev_open_device函数打开输入设备,我们来详细看看这个函数

在上述代码中,首先使用mutex_lock_interruptible函数获取输入设备的互斥锁,如果无法获取锁,函数将返回相应的错误码。如果成功获取了锁,函数会继续执行后续的操作。

函数首先检查输入设备的exist字段,如果该字段为假(0),表示输入设备不存在,函数将返回-ENODEV,表示设备不存在的错误码。否则,如果输入设备的open字段为0,表示设备之前没有被打开过,函数将进一步执行以下操作。

  1. 调用input_open_device函数打开输入设备,并将返回值存储在retval变量中。
  2. 如果打开设备失败,函数会将evdev->open减一,表示设备的打开计数器递减。

最后,函数释放输入设备的互斥锁,使用mutex_unlock函数,并返回retval,表示打开设备的结果。

148.2 ioctl函数分析

接下来我们继续分析下ioctl函数,如下图所示:

evdev_ioctl函数如下所示:

上述函数中调用evdev_ioctl_handler函数来处理IO控制操作,传递给它file指针,cmd和类型转换后的arg作为参数。函数将arg转换为void __user *类型,以便在用户空间和内核空间之间传递指针。函数将evdev_ioctl_handler的返回值作为自己的返回值,并将其直接返回给调用者。

evdev_ioctl_handler函数如下所示:

static long evdev_ioctl_handler(struct file *file, unsigned int cmd,void __user *p, int compat_mode)
{// 从文件结构获取指向evdev_client的指针struct evdev_client *client = file->private_data;// 从evdev_client获取指向evdev的指针struct evdev *evdev = client->evdev;int retval;// 尝试获取evdev的互斥锁,如果无法获取则返回相应的错误码retval = mutex_lock_interruptible(&evdev->mutex);if (retval)return retval;// 检查设备是否存在或者客户端是否已被撤销if (!evdev->exist || client->revoked) {// 如果设备不存在或客户端已被撤销,则返回设备不存在的错误码retval = -ENODEV;goto out;}// 调用evdev_do_ioctl函数来执行实际的IO控制操作,并将返回值存储在retval变量中retval = evdev_do_ioctl(file, cmd, p, compat_mode);out:// 解锁evdev的互斥锁mutex_unlock(&evdev->mutex);// 返回IO控制操作的结果return retval;
}

上面的代码是evdev_ioctl_handler的函数,用于处理输入设备的IO控制操作,以下是对代码的解释。

1 函数接收一个指向struct file结构的指针file,一个无符号整数cmd,一个指向void __user类型的指针p,以及一个整数compat_mode作为参数。

2 函数从file结构的private_data字段获取指向struct evdev_client的指针client,并从中获取指向struct evdev的指针evdev。这些结构体用于表示输入设备和客户端信息。

3函数尝试获取输入设备的互斥锁,使用mutex_lock_interruptible函数。如果无法获取锁,函数将返回相应的错误码。如果成功获取了锁,函数会继续执行后续的操作。

4 函数首先检查输入设备的exist字段和客户端的revoked字段,如果设备不存在或者客户端已被撤销,则将返回-ENODEV,表示设备不存在的错误码。否则,函数调用evdev_do_ioctl函数来执行实际的IO控制操作,传递给它file指针,cmd,p和compat_mode作为参数,并将返回值存储在retval变量中。

5 函数通过标签out跳转到mutex_unlock处,在解锁输入设备的互斥锁之前最终返回retval的值。

6 最后,函数解锁输入设备的互斥锁,使用mutex_unlock函数,并返回retval,表示IO控制操作的结果。

evdev_do_ioctl函数来执行实际的IO控制操作,我们详细来看看这个函数,如下所示:

static long evdev_do_ioctl(struct file *file, unsigned int cmd,void __user *p, int compat_mode)
{struct evdev_client *client = file->private_data;struct evdev *evdev = client->evdev;struct input_dev *dev = evdev->handle.dev;struct input_absinfo abs;struct input_mask mask;struct ff_effect effect;int __user *ip = (int __user *)p;unsigned int i, t, u, v;unsigned int size;int error;/* First we check for fixed-length commands */switch (cmd) {case EVIOCGVERSION:return put_user(EV_VERSION, ip);case EVIOCGID:if (copy_to_user(p, &dev->id, sizeof(struct input_id)))return -EFAULT;return 0;case EVIOCGREP:if (!test_bit(EV_REP, dev->evbit))return -ENOSYS;if (put_user(dev->rep[REP_DELAY], ip))return -EFAULT;if (put_user(dev->rep[REP_PERIOD], ip + 1))return -EFAULT;return 0;case EVIOCSREP:if (!test_bit(EV_REP, dev->evbit))return -ENOSYS;if (get_user(u, ip))return -EFAULT;if (get_user(v, ip + 1))return -EFAULT;input_inject_event(&evdev->handle, EV_REP, REP_DELAY, u);input_inject_event(&evdev->handle, EV_REP, REP_PERIOD, v);return 0;case EVIOCRMFF:return input_ff_erase(dev, (int)(unsigned long) p, file);case EVIOCGEFFECTS:i = test_bit(EV_FF, dev->evbit) ?dev->ff->max_effects : 0;if (put_user(i, ip))return -EFAULT;return 0;case EVIOCGRAB:if (p)return evdev_grab(evdev, client);elsereturn evdev_ungrab(evdev, client);case EVIOCREVOKE:if (p)return -EINVAL;elsereturn evdev_revoke(evdev, client, file);case EVIOCGMASK: {void __user *codes_ptr;if (copy_from_user(&mask, p, sizeof(mask)))return -EFAULT;codes_ptr = (void __user *)(unsigned long)mask.codes_ptr;return evdev_get_mask(client,mask.type, codes_ptr, mask.codes_size,compat_mode);}case EVIOCSMASK: {const void __user *codes_ptr;if (copy_from_user(&mask, p, sizeof(mask)))return -EFAULT;codes_ptr = (const void __user *)(unsigned long)mask.codes_ptr;return evdev_set_mask(client,mask.type, codes_ptr, mask.codes_size,compat_mode);}case EVIOCSCLOCKID:if (copy_from_user(&i, p, sizeof(unsigned int)))return -EFAULT;return evdev_set_clk_type(client, i);case EVIOCGKEYCODE:return evdev_handle_get_keycode(dev, p);case EVIOCSKEYCODE:return evdev_handle_set_keycode(dev, p);case EVIOCGKEYCODE_V2:return evdev_handle_get_keycode_v2(dev, p);case EVIOCSKEYCODE_V2:return evdev_handle_set_keycode_v2(dev, p);}size = _IOC_SIZE(cmd);/* Now check variable-length commands */
#define EVIOC_MASK_SIZE(nr)	((nr) & ~(_IOC_SIZEMASK << _IOC_SIZESHIFT))switch (EVIOC_MASK_SIZE(cmd)) {case EVIOCGPROP(0):return bits_to_user(dev->propbit, INPUT_PROP_MAX,size, p, compat_mode);case EVIOCGMTSLOTS(0):return evdev_handle_mt_request(dev, size, ip);case EVIOCGKEY(0):return evdev_handle_get_val(client, dev, EV_KEY, dev->key,KEY_MAX, size, p, compat_mode);case EVIOCGLED(0):return evdev_handle_get_val(client, dev, EV_LED, dev->led,LED_MAX, size, p, compat_mode);case EVIOCGSND(0):return evdev_handle_get_val(client, dev, EV_SND, dev->snd,SND_MAX, size, p, compat_mode);case EVIOCGSW(0):return evdev_handle_get_val(client, dev, EV_SW, dev->sw,SW_MAX, size, p, compat_mode);case EVIOCGNAME(0):return str_to_user(dev->name, size, p);case EVIOCGPHYS(0):return str_to_user(dev->phys, size, p);case EVIOCGUNIQ(0):return str_to_user(dev->uniq, size, p);case EVIOC_MASK_SIZE(EVIOCSFF):if (input_ff_effect_from_user(p, size, &effect))return -EFAULT;error = input_ff_upload(dev, &effect, file);if (error)return error;if (put_user(effect.id, &(((struct ff_effect __user *)p)->id)))return -EFAULT;return 0;}/* Multi-number variable-length handlers */if (_IOC_TYPE(cmd) != 'E')return -EINVAL;if (_IOC_DIR(cmd) == _IOC_READ) {if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0, 0)))return handle_eviocgbit(dev,_IOC_NR(cmd) & EV_MAX, size,p, compat_mode);if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) {if (!dev->absinfo)return -EINVAL;t = _IOC_NR(cmd) & ABS_MAX;abs = dev->absinfo[t];if (copy_to_user(p, &abs, min_t(size_t,size, sizeof(struct input_absinfo))))return -EFAULT;return 0;}}if (_IOC_DIR(cmd) == _IOC_WRITE) {if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCSABS(0))) {if (!dev->absinfo)return -EINVAL;t = _IOC_NR(cmd) & ABS_MAX;if (copy_from_user(&abs, p, min_t(size_t,size, sizeof(struct input_absinfo))))return -EFAULT;if (size < sizeof(struct input_absinfo))abs.resolution = 0;/* We can't change number of reserved MT slots */if (t == ABS_MT_SLOT)return -EINVAL;/** Take event lock to ensure that we are not* changing device parameters in the middle* of event.*/spin_lock_irq(&dev->event_lock);dev->absinfo[t] = abs;spin_unlock_irq(&dev->event_lock);return 0;}}return -EINVAL;
}

上述代码中的这些命令解释如下:

EVIOCGVERSION: 获取去掉版本号
EVIOCGID:获取输入设备的ID信息
EVIOCSREP:获取按键重复设置
EVIOCGKEYCODE: 获取按键码
EVIOCGKEYCODE_V2: 获取按键映射表
EVIOCSKEYCODE:设置按键值
EVIOCSKEYCODE_V2:设置按键映射表
EVIOCGNAME(len):获取设备名称
EVIOCGPHYS(len):获取物理位置
EVIOCGUNIQ(len):获取唯一标识符
EVIOCGPROP(len):获取设备属性
EVIOCGMTSLOTS(len):获取多点触控信息
EVIOCGKEY(len):获取全局按键状态
EVIOCGLED(len):获取所有LED状态
EVIOCGSND(len):获取所有声音状态
EVIOCGSW(len):获取所有开关状态
EVIOCGBIT(ev,len):获取事件位图
EVIOCGABS(abs):获取绝对值/范围
EVIOCSABS(abs):设置绝对值/范围
EVIOCSFF:发送力反馈效果到力反馈设备
EVIOCRMFF:删除力反馈效果
EVIOCGEFFECTS:报告同时可播放的效果数量
EVIOCGRAB:占用/释放输入设备
EVIOCREVOKE:撤销设备访问权限
EVIOCGMASK:检索当前事件掩码
EVIOCSMASK:设置事件掩码
EVIOCSCLOCKID:设置用于时间戳的时钟标识

总结来说,evdev_do_ioctl函数是通用事件处理层中用于处理输入设备ioctl命令的函数。它根据传入的参数,找到对应的输入设备对象,并执行相应的操作。这使得开发者可以通过ioctl命令与输入设备进行交互,设置参数、查询状态或执行其他特定的操作,以满足特定的应用需求。

148.3 poll函数分析

接下来我们继续分析下poll函数,如下所示:

evdev_poll函数如下所示:

static __poll_t evdev_poll(struct file *file, poll_table *wait)
{// 获取文件私有数据中的evdev_client结构体指针struct evdev_client *client = file->private_data;// 获取evdev_client结构体中的evdev指针struct evdev *evdev = client->evdev;__poll_t mask;// 将当前进程加入到等待队列中,等待evdev->wait的唤醒事件poll_wait(file, &evdev->wait, wait);// 检查evdev->exist和client->revoked的值if (evdev->exist && !client->revoked)// 如果evdev存在且client未被撤销,设置mask为EPOLLOUT | EPOLLWRNORMmask = EPOLLOUT | EPOLLWRNORM;else// 否则,设置mask为EPOLLHUP | EPOLLERRmask = EPOLLHUP | EPOLLERR;// 检查client中的packet_head和tail的值if (client->packet_head != client->tail)// 如果packet_head和tail不相等,设置mask为mask | EPOLLIN | EPOLLRDNORMmask |= EPOLLIN | EPOLLRDNORM;// 返回最终的mask值return mask;
}

上述代码是用于处理evdev设备文件的poll操作。它通过检查相应的事件状态来确定是否需要等待,读取或写入。函数首先将当前进程添加到等待队列中,以等待evdev->wait的唤醒事件。然后根据evdev->exist和client->revoked的值设置mask的值。如果evdev存在且client未被撤销,则将mask设置为EPOLLOUT | EPOLLWRNORM,表示可写入。否则,将mask设置为EPOLLHUP | EPOLLERR,表示发生错误或连接已挂断。接下来,函数检查client中的packet_head和tail的值,如果它们不相等,则将mask设置为mask | EPOLLIN | EPOLLRDNORM,表示可读取。最后,函数返回最终的mask值,表示等待、读取和写入的事件状态。

148.4 fasync函数分析

接下来我们继续分析下fasync函数,如下所示:、

evdev_fasync函数如下所示:

static int evdev_fasync(int fd, struct file *file, int on)
{// 获取文件私有数据中的evdev_client结构体指针struct evdev_client *client = file->private_data;// 调用fasync_helper函数来处理进程的异步通知// 该函数会根据on的值,将进程添加到或从异步通知的列表中// 并将通知相关的数据存储在client->fasync中return fasync_helper(fd, file, on, &client->fasync);
}

上述函数用于处理evdev设备文件的异步通知注册和注销。它接收文件描述符fd,文件指针file和一个整数on作为参数。函数首先从文件的私有数据中获取evdev_client结构体指针。然后调用fasync_helper函数,该函数负责处理进程的异步通知。根据on的值,fasync_helper函数将进程添加到异步通知的列表中或从列表中移除,并将通知相关的数据存储在client->fasync中。最后函数返回fasync_helper的返回值,表示异步通知的注册和注销是否成功。

148.5 llseek函数

接下来我们继续分析下llseek函数

no_llseek函数如下所示:

在函数开始时,首先将-ESPIPE作为返回值直接返回。这个函数的作用是阻止对设备文件执行llseek操作,也就是不允许通过改变文件位置指针来随机访问设备文件。这可能是因为设备特性或驱动程序的限制所导致的。通过禁止定位操作,可确保对设备文件的访问方式按照预期方式进行,避免潜在的问题和错误。

148.6 release函数分析

接下来我们继续分析release函数,如下所示:

evdev_release函数如下所示:

static int evdev_release(struct inode *inode, struct file *file)
{// 获取文件私有数据中的evdev_client结构体指针struct evdev_client *client = file->private_data;// 获取evdev_client结构体中的evdev指针struct evdev *evdev = client->evdev;unsigned int i;// 获取evdev的互斥锁,确保对evdev的操作是原子的mutex_lock(&evdev->mutex);// 检查evdev->exist和client->revoked的值if (evdev->exist && !client->revoked)// 如果evdev存在且client未被撤销,调用input_flush_device函数刷新设备的输入缓冲区input_flush_device(&evdev->handle, file);// 释放evdev的抢占状态,将客户端从抢占列表中移除evdev_ungrab(evdev, client);// 解锁evdev的互斥锁mutex_unlock(&evdev->mutex);// 从evdev中分离并释放客户端evdev_detach_client(evdev, client);// 释放客户端的事件掩码内存for (i = 0; i < EV_CNT; ++i)bitmap_free(client->evmasks[i]);// 释放客户端的内存kvfree(client);// 关闭evdev设备evdev_close_device(evdev);// 返回0表示成功释放资源return 0;
}

该函数用于释放evdev设备文件相关的资源。它接收一个inode结构体指针和一个file结构体指针作为参数。函数首先从文件的私有数据中获取evdev_client结构体指针和evdev指针。然后获取evdev的互斥锁,确保对evdev的操作是原子的。

接下来,函数检查evdev->exist和client->revoked的值。如果evdev存在且client未被撤销,则调用input_flush_device函数刷新设备的输入缓冲区,确保所有未处理的输入事件被处理。

然后,函数释放evdev的抢占状态,通过调用evdev_ungrab函数将客户端从抢占列表中移除。接着,解锁evdev的互斥锁。

接下来,函数调用evdev_detach_client函数从evdev中分离并释放客户端。

然后,函数使用循环遍历释放客户端的事件掩码内存,通过调用bitmap_free函数进行释放。

接着,函数使用kvfree函数释放客户端的内存。

最后,函数调用evdev_close_device函数关闭evdev设备。

最终,函数返回0表示成功释放资源。

整体来说,这个函数的作用是用于释放设备文件的资源和与之关联的内存空间,以便其他客户端能够继续访问设备并避免占用过多的系统资源。该函数在设备文件关闭时自动调用,通常不需要用户手动调用。

这篇关于RK3568驱动指南|第十三篇 输入子系统-第148章 通用事件处理层open函数分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/845443

相关文章

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

Python进行JSON和Excel文件转换处理指南

《Python进行JSON和Excel文件转换处理指南》在数据交换与系统集成中,JSON与Excel是两种极为常见的数据格式,本文将介绍如何使用Python实现将JSON转换为格式化的Excel文件,... 目录将 jsON 导入为格式化 Excel将 Excel 导出为结构化 JSON处理嵌套 JSON:

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We

python使用try函数详解

《python使用try函数详解》Pythontry语句用于异常处理,支持捕获特定/多种异常、else/final子句确保资源释放,结合with语句自动清理,可自定义异常及嵌套结构,灵活应对错误场景... 目录try 函数的基本语法捕获特定异常捕获多个异常使用 else 子句使用 finally 子句捕获所

Python极速搭建局域网文件共享服务器完整指南

《Python极速搭建局域网文件共享服务器完整指南》在办公室或家庭局域网中快速共享文件时,许多人会选择第三方工具或云存储服务,但这些方案往往存在隐私泄露风险或需要复杂配置,下面我们就来看看如何使用Py... 目录一、android基础版:HTTP文件共享的魔法命令1. 一行代码启动HTTP服务器2. 关键参

深度解析Nginx日志分析与499状态码问题解决

《深度解析Nginx日志分析与499状态码问题解决》在Web服务器运维和性能优化过程中,Nginx日志是排查问题的重要依据,本文将围绕Nginx日志分析、499状态码的成因、排查方法及解决方案展开讨论... 目录前言1. Nginx日志基础1.1 Nginx日志存放位置1.2 Nginx日志格式2. 499

Python对接支付宝支付之使用AliPay实现的详细操作指南

《Python对接支付宝支付之使用AliPay实现的详细操作指南》支付宝没有提供PythonSDK,但是强大的github就有提供python-alipay-sdk,封装里很多复杂操作,使用这个我们就... 目录一、引言二、准备工作2.1 支付宝开放平台入驻与应用创建2.2 密钥生成与配置2.3 安装ali

Redis MCP 安装与配置指南

《RedisMCP安装与配置指南》本文将详细介绍如何安装和配置RedisMCP,包括快速启动、源码安装、Docker安装、以及相关的配置参数和环境变量设置,感兴趣的朋友一起看看吧... 目录一、Redis MCP 简介二、安www.chinasem.cn装 Redis MCP 服务2.1 快速启动(推荐)2.

OpenCV在Java中的完整集成指南分享

《OpenCV在Java中的完整集成指南分享》本文详解了在Java中集成OpenCV的方法,涵盖jar包导入、dll配置、JNI路径设置及跨平台兼容性处理,提供了图像处理、特征检测、实时视频分析等应用... 目录1. OpenCV简介与应用领域1.1 OpenCV的诞生与发展1.2 OpenCV的应用领域2

MyBatis-Plus 自动赋值实体字段最佳实践指南

《MyBatis-Plus自动赋值实体字段最佳实践指南》MyBatis-Plus通过@TableField注解与填充策略,实现时间戳、用户信息、逻辑删除等字段的自动填充,减少手动赋值,提升开发效率与... 目录1. MyBATis-Plus 自动赋值概述1.1 适用场景1.2 自动填充的原理1.3 填充策略