【linux kernel】一文总结linux的uevent机制

文章目录

    • 一、kobject_uevent简介
    • 二、重要数据结构
      • 1、struct kobj_uevent_env
      • 2、struct kset_uevent_ops
    • 三、kobject_uevent_env()详细剖析
      • 1、add_uevent_var()
      • 2、kobject_uevent_net_broadcast()
      • 3、call_usermodehelper_setup()
      • 4、call_usermodehelper_exec()
    • 四、uevent_helper机制
    • 五、mdev

相关文件:

  • /linux/net/core/net_namespace.c
  • /lib/kobject_uevent.c
  • /include/linux/kobject.h
  • /net/netlink/af_netlink.c
  • /include/linux/umh.h
  • /kernel/umh.c

一、kobject_uevent简介

在linux内核中,uevent机制是一种内核和用户空间通信的机制,用于通知用户空间应用程序各种硬件更改或其他事件,比如插入或移除硬件设备(如USB驱动器或网络接口)。uevent表示“用户空间事件”,当硬件事件发生时,内核会生成一个 uevent,并通过 netlink 套接字将其发送到用户空间。用户空间应用程序(例如 udev、mdev),可以监听这些事件并采取相应的操作,例如加载适当的驱动程序或执行其他配置任务。

kobject_uevent()函数可通过发送一个uevent通知用户空间:

int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{return kobject_uevent_env(kobj, action, NULL);
}

从上述代码可知,kobject_uevent()调用kobject_uevent_env()实现核心操作,但是kobject_uevent()没有传输环境变量,故而设置kobject_uevent_env()的第三个参数为NULL

enum kobject_action用于描述内核对象的操作,内核中定义了以下几种操作:

enum kobject_action {KOBJ_ADD,		//对象添加。表示向系统中添加了一个新的kobject对象。KOBJ_REMOVE,	//对象移除。表示从系统中移除了一个kobject对象。KOBJ_CHANGE,	//对象修改。表示kobject对象的属性或状态发生了变化。KOBJ_MOVE,		//对象移动。表示kobject对象被移动到了另一个位置。KOBJ_ONLINE,	//对象上线。表示kobject对象已经准备好在线工作。KOBJ_OFFLINE,	//对象离线。表示kobject对象已经离线,不再处于工作状态。KOBJ_MAX		//动作类型的最大值,用于边界检查。
};

在用户空间可使用udevadm monitor查看uevent事件:

udevadm monitor --udev

二、重要数据结构

1、struct kobj_uevent_env

struct kobj_uevent_env结构的目的是在内核中传递事件相关的参数、环境变量和数据。通过使用这个结构体,内核可以轻松地传递事件相关的信息和数据给相关的处理程序或模块。

struct kobj_uevent_env {char *argv[3];      //用于存储传递给事件的参数。通常情况下,用于表示事件的命令行参数。char *envp[UEVENT_NUM_ENVP];  //这是一个包含 UEVENT_NUM_ENVP 个指针的数组,用于存储传递给事件的环境变量。在Linux内核中,环境变量通常以键值对的形式传递。int envp_idx;      //用于跟踪环境变量数组中的当前索引。它指示下一个环境变量应该存储在数组的哪个位置。char buf[UEVENT_BUFFER_SIZE];  //用于存储事件的文本数据。在内核中,事件通常以文本形式表示。int buflen;  //用于跟踪事件数据缓冲区中的当前有效数据长度。它指示缓冲区中包含的事件数据量。
};

2、struct kset_uevent_ops

struct kset_uevent_ops结构体定义了 kset 的事件操作接口,使得用户可以通过提供相应的函数指针来自定义 kset 中 kobject 的事件处理行为:

struct kset_uevent_ops {int (* const filter)(struct kset *kset, struct kobject *kobj);const char *(* const name)(struct kset *kset, struct kobject *kobj);int (* const uevent)(struct kset *kset, struct kobject *kobj,struct kobj_uevent_env *env);
};
  • int (_ const filter)(struct kset _kset, struct kobject *kobj);:这是一个指向函数的指针,该函数用于过滤 kset 中的 kobject(内核对象)。它接受两个参数,分别是指向 kset 和 kobject 的指针,返回一个整数值,通常表示过滤操作的结果。
  • const char ( const name)(struct kset _kset, struct kobject _kobj);:这是一个指向函数的指针,该函数用于获取 kobject 的名称。它接受两个参数,分别是指向 kset 和 kobject 的指针,返回一个指向字符常量的指针,通常是 kobject 的名称字符串。
  • int (_ const uevent)(struct kset _kset, struct kobject _kobj, struct kobj_uevent_env _env);:这是一个指向函数的指针,该函数用于生成 kobject 的事件。它接受三个参数,分别是指向 kset、kobject 和 kobj_uevent_env 结构体的指针。该函数负责将 kobject 的事件信息填充到给定的环境中,并返回一个整数值,通常表示操作的成功或失败。

三、kobject_uevent_env()详细剖析

kobject_uevent_env()用于在给定的 kobject 上触发一个事件,并且传递一个额外的环境变量数组:

int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[])
  • struct kobject *kobj:表示要触发事件的内核对象。
  • enum kobject_action action:表示要执行的动作,通常是一个枚举类型,定义了可能的动作列表。这可能包括添加、删除或修改对象等操作。
  • char *envp_ext[]:是一个字符指针数组,用于传递额外的环境变量给事件处理程序。这些环境变量以 key=value 的形式表示,其中 key 是环境变量的名称,value 是其对应的值。

该函数将会触发一个事件,并将指定的动作和环境变量信息传递给与该对象相关联的事件处理程序。事件处理程序通常会根据传入的动作和环境变量来执行相应的操作,例如更新内核状态、通知用户空间进程等。kobject_uevent_env()实现如下(具体执行步骤见注释):

int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,char *envp_ext[])
{struct kobj_uevent_env *env;const char *action_string = kobject_actions[action];const char *devpath = NULL;const char *subsystem;struct kobject *top_kobj;struct kset *kset;const struct kset_uevent_ops *uevent_ops;int i = 0;int retval = 0;/** Mark "remove" event done regardless of result, for some subsystems* do not want to re-trigger "remove" event via automatic cleanup.*//如果动作是 KOBJ_REMOVE,则将对象的 state_remove_uevent_sent 标志设置为1,表示“remove”事件已发送。if (action == KOBJ_REMOVE)kobj->state_remove_uevent_sent = 1;//打印调试信息,包括对象的名称、指针和函数名称。pr_debug("kobject: '%s' (%p): %s\n",kobject_name(kobj), kobj, __func__);//找该对象所属的 kset。top_kobj = kobj;while (!top_kobj->kset && top_kobj->parent)top_kobj = top_kobj->parent;//如果对象不属于任何 kset,则返回错误。if (!top_kobj->kset) {pr_debug("kobject: '%s' (%p): %s: attempted to send uevent ""without kset!\n", kobject_name(kobj), kobj,__func__);return -EINVAL;}kset = top_kobj->kset;uevent_ops = kset->uevent_ops;//检查对象的 uevent_suppress 标志是否设置,如果设置了则跳过事件发送。if (kobj->uevent_suppress) {pr_debug("kobject: '%s' (%p): %s: uevent_suppress ""caused the event to drop!\n",kobject_name(kobj), kobj, __func__);return 0;}//如果 uevent_ops 和 uevent_ops->filter 存在且 filter 函数返回0,则跳过事件发送。if (uevent_ops && uevent_ops->filter)if (!uevent_ops->filter(kset, kobj)) {pr_debug("kobject: '%s' (%p): %s: filter function ""caused the event to drop!\n",kobject_name(kobj), kobj, __func__);return 0;}//获取事件的子系统名称。if (uevent_ops && uevent_ops->name)subsystem = uevent_ops->name(kset, kobj);elsesubsystem = kobject_name(&kset->kobj);if (!subsystem) {pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the ""event to drop!\n", kobject_name(kobj), kobj,__func__);return 0;}//分配并初始化一个 kobj_uevent_env 结构体env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);if (!env)return -ENOMEM;//获取对象的路径。devpath = kobject_get_path(kobj, GFP_KERNEL);if (!devpath) {retval = -ENOENT;goto exit;}//添加默认的环境变量,包括动作(ACTION)、设备路径(DEVPATH)和子系统(SUBSYSTEM)。retval = add_uevent_var(env, "ACTION=%s", action_string);if (retval)goto exit;retval = add_uevent_var(env, "DEVPATH=%s", devpath);if (retval)goto exit;retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);if (retval)goto exit;//如果有传入的额外环境变量,则将它们添加到事件环境中if (envp_ext) {for (i = 0; envp_ext[i]; i++) {retval = add_uevent_var(env, "%s", envp_ext[i]);if (retval)goto exit;}}//调用 uevent_ops->uevent 函数,如果存在,以允许 kset 特定的操作添加额外的环境变量。if (uevent_ops && uevent_ops->uevent) {retval = uevent_ops->uevent(kset, kobj, env);if (retval) {pr_debug("kobject: '%s' (%p): %s: uevent() returned ""%d\n", kobject_name(kobj), kobj,__func__, retval);goto exit;}}//根据动作执行额外的操作。如果是 KOBJ_ADD,则标记对象已发送 state_add_uevent_sent 为1。switch (action) {case KOBJ_ADD:/** Mark "add" event so we can make sure we deliver "remove"* event to userspace during automatic cleanup. If* the object did send an "add" event, "remove" will* automatically generated by the core, if not already done* by the caller.*/kobj->state_add_uevent_sent = 1;break;case KOBJ_UNBIND:zap_modalias_env(env);break;default:break;}mutex_lock(&uevent_sock_mutex);/* we will send an event, so request a new sequence number *///获取一个新的序列号并将其添加到事件环境中retval = add_uevent_var(env, "SEQNUM=%llu", ++uevent_seqnum);if (retval) {mutex_unlock(&uevent_sock_mutex);goto exit;}//通过网络广播发送事件。retval = kobject_uevent_net_broadcast(kobj, env, action_string,devpath);mutex_unlock(&uevent_sock_mutex);//如果配置了CONFIG_UEVENT_HELPER,则调用 uevent_helper 来执行额外的操作。
#ifdef CONFIG_UEVENT_HELPER/* call uevent_helper, usually only enabled during early boot */if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {struct subprocess_info *info;retval = add_uevent_var(env, "HOME=/");if (retval)goto exit;retval = add_uevent_var(env,"PATH=/sbin:/bin:/usr/sbin:/usr/bin");if (retval)goto exit;retval = init_uevent_argv(env, subsystem);if (retval)goto exit;retval = -ENOMEM;info = call_usermodehelper_setup(env->argv[0], env->argv,env->envp, GFP_KERNEL,NULL, cleanup_uevent_env, env);if (info) {retval = call_usermodehelper_exec(info, UMH_NO_WAIT);env = NULL;	/* freed by cleanup_uevent_env */}}
#endifexit:kfree(devpath);kfree(env);return retval;
}

总而言之,kobject_uevent_env()调用add_uevent_var()构建默认的信息(包括:ACTION、DEVPATH、SUBSYSTEM和自定义数据envp_ext),然后调用kobject_uevent_net_broadcast()发送uevent。

1、add_uevent_var()

add_uevent_var()是一个用于在内核中构建uevent环境的辅助函数,它允许向 uevent 环境中添加一个键值对,表示内核对象事件中的一个属性或信息。该函数实现如下:

int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...)
{va_list args;int len;if (env->envp_idx >= ARRAY_SIZE(env->envp)) {WARN(1, KERN_ERR "add_uevent_var: too many keys\n");return -ENOMEM;}va_start(args, format);len = vsnprintf(&env->buf[env->buflen],sizeof(env->buf) - env->buflen,format, args);va_end(args);if (len >= (sizeof(env->buf) - env->buflen)) {WARN(1, KERN_ERR "add_uevent_var: buffer size too small\n");return -ENOMEM;}env->envp[env->envp_idx++] = &env->buf[env->buflen];env->buflen += len + 1;return 0;
}

add_uevent_var()函数的作用是根据指定的格式将键值对添加到 uevent 环境中,该函数使用vsnprintf()函数将格式化字符串和参数组合成一个字符串,并将该字符串添加到 kobj_uevent_env 结构体的缓冲区中,如果成功添加键值对,则返回0;如果缓冲区空间不足导致添加失败,则返回-ENOSPC。

使用add_uevent_var()函数可以方便地构建uevent环境,向用户空间发送有关内核对象事件的信息。通常情况下,该函数用于在kobject_uevent_env()函数中构建uevent环境,以向用户空间发送内核对象事件。

2、kobject_uevent_net_broadcast()

`kobject_uevent_net_broadcast()用于在网络命名空间上广播内核对象事件:

int kobject_uevent_net_broadcast(struct kobject *kobj,struct kobj_uevent_env *env,const char *action_string,const char *devpath)
  • (1)首先,它检查是否启用了网络支持 (CONFIG_NET),因为这个功能是基于网络命名空间的。
  • (2)它获取与给定 kobject 相关联的命名空间类型操作 (ops),以确定是否存在网络命名空间。
  • (3)如果 ops 存在,且命名空间类型是网络命名空间 (KOBJ_NS_TYPE_NET),则获取该命名空间。
  • (4)如果存在网络命名空间,它调用 uevent_net_broadcast_tagged 函数,将事件广播到指定的网络命名空间。
  • (5)如果没有找到网络命名空间,或者网络命名空间不支持事件广播,它调用 uevent_net_broadcast_untagged 函数,将事件广播到所有未标记的网络命名空间。
  • (6)最后,它返回广播函数的返回值,指示事件广播的成功或失败。
    总的来说,这个函数负责根据对象的网络命名空间属性,将内核对象事件广播到适当的网络命名空间中。uevent_net_broadcast_tagged()uevent_net_broadcast_untagged()本质上都调用netlink_broadcast()实现核心功能。

3、call_usermodehelper_setup()

call_usermodehelper_setup 函数是 Linux 内核中的一个函数,用于设置和准备调用用户空间辅助程序(usermode helper)。这个函数并不直接执行用户空间辅助程序,而是为其设置参数和环境,并返回一个 subprocess_info 结构体,用于后续的执行。

这个函数通常在内核中的一些子系统中使用,例如 kobject_uevent_net_broadcast 中的调用。函数原型如下:

struct subprocess_info *call_usermodehelper_setup(char *path, char **argv,char **envp, gfp_t gfp_mask,int (*init)(struct subprocess_info *info,struct cred *new),void (*cleanup)(struct subprocess_info *info),void *data);
  • path:用户空间辅助程序的路径。
  • argv:参数数组,用于传递给用户空间辅助程序的命令行参数。
  • envp:环境变量数组,用于传递给用户空间辅助程序的环境变量。
  • gfp_mask:内存分配标志。
  • init:一个可选的初始化函数,用于在用户空间辅助程序执行前进行一些初始化操作。
  • cleanup:一个可选的清理函数,用于在用户空间辅助程序执行完毕后进行清理操作。
  • data:可选的附加数据,可以在初始化和清理函数中使用。

这个函数的作用是为用户空间辅助程序设置参数和环境,以及提供初始化和清理函数。返回的 subprocess_info 结构体包含了执行用户空间辅助程序所需的所有信息,包括路径、参数、环境等。一旦设置完成,用户空间辅助程序就可以通过call_usermodehelper_exec 函数执行。

4、call_usermodehelper_exec()

call_usermodehelper_exec 函数是 Linux 内核中与执行用户空间辅助程序(usermode helper)相关的一个函数。它负责实际执行用户空间辅助程序,并监控其执行状态。通常情况下,它会在 call_usermodehelper_setup 函数之后被调用。

下面是call_usermodehelper_exec 函数原型:

int call_usermodehelper_exec(struct subprocess_info *sub_info, enum umh_wait wait);
  • sub_info:指向 subprocess_info 结构体的指针,该结构体包含了执行用户空间辅助程序所需的所有信息,包括路径、参数、环境等。
  • wait:指定是否等待用户空间辅助程序执行完成的标志,可以是 UMH_WAIT_EXEC(等待执行完成)或 UMH_NO_WAIT(不等待执行完成)。

call_usermodehelper_exec 函数的作用是执行用户空间辅助程序,并等待其执行完成(如果需要)。在执行期间,它会监控用户空间辅助程序的执行状态,并在适当的时候返回执行结果。

这个函数通常在 Linux 内核中的一些子系统中使用,例如 kobject_uevent_net_broadcast 中的调用。通过调用用户空间辅助程序,内核可以执行一些需要借助用户空间程序完成的任务,例如配置、初始化等。

四、uevent_helper机制

在 Linux 内核中,uevent_helper 是一个用户空间辅助程序,用于处理内核对象事件(uevent)。它通常在内核启动期间使用,负责处理设备管理相关的事件,例如设备的插入、拔出、状态变化等。下面是一些使用uevent_helper的典型实践:

  1. 设备热插拔管理: 当一个设备被插入或拔出时,内核会生成相应的 uevent,并调用 uevent_helper 来处理这些事件。uevent_helper 可以根据事件类型执行一些特定的操作,例如加载适当的设备驱动、更新设备管理信息等。
  2. 自动配置和初始化: 在系统启动期间,内核可能需要执行一些自动配置和初始化任务,例如挂载文件系统、加载网络配置、启动服务等。uevent_helper 可以被用于执行这些任务,并根据事件类型执行相应的初始化操作。
  3. 系统监控和管理: uevent_helper 还可以被用于系统监控和管理任务,例如记录事件日志、生成警报、执行故障排除操作等。
  4. 用户空间通知: uevent_helper 还可以与用户空间的其他程序进行通信,例如向用户空间的监控程序发送通知或触发相应的操作。

具体使用 uevent_helper 的方式取决于系统的需求和设计。通常情况下,它会被配置为一个可执行文件或脚本,并在系统启动时由内核调用。在使用uevent_helper时,需要确保其具有足够的权限来执行所需的操作,并确保其安全性和稳定性。

五、mdev

mdev是Linux系统中用于设备管理的工具,具有以下特征:

  • mdev 是一个更轻量级的设备管理器,通常用于嵌入式系统和一些轻量级 Linux 发行版中。
  • 由 BusyBox 提供,并用于在启动时自动创建设备节点。
  • mdev 不支持复杂的规则配置,而是基于简单的设备名匹配规则来创建设备节点。

所以综上所述,mdev 更适用于嵌入式系统或者对资源有限的系统,因为它更加轻量级,但功能也更加有限。

参考busybox源码,mdev实现如下:

int mdev_main(int argc UNUSED_PARAM, char **argv)
{enum {MDEV_OPT_SCAN       = 1 << 0,MDEV_OPT_SYSLOG     = 1 << 1,MDEV_OPT_DAEMON     = 1 << 2,MDEV_OPT_FOREGROUND = 1 << 3,};int opt;RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE);INIT_G();/* We can be called as hotplug helper *//* Kernel cannot provide suitable stdio fds for us, do it ourself */bb_sanitize_stdio();/* Force the configuration file settings exactly */umask(0);xchdir("/dev");opt = getopt32(argv, "^""sS" IF_FEATURE_MDEV_DAEMON("df") "v""\0""vv",&G.verbose);#if ENABLE_FEATURE_MDEV_CONFG.filename = "/etc/mdev.conf";if (opt & (MDEV_OPT_SCAN|MDEV_OPT_DAEMON)) {/* Same as xrealloc_vector(NULL, 4, 0): */G.rule_vec = xzalloc((1 << 4) * sizeof(*G.rule_vec));}
#endifif (opt & MDEV_OPT_SYSLOG) {openlog(applet_name, LOG_PID, LOG_DAEMON);logmode |= LOGMODE_SYSLOG;}#if ENABLE_FEATURE_MDEV_DAEMONif (opt & MDEV_OPT_DAEMON) {/* Daemon mode listening on uevent netlink socket. Fork away* after initial scan so that caller can be sure everything* is up-to-date when mdev process returns.*/int fd = daemon_init(temp);if (!(opt & MDEV_OPT_FOREGROUND)) {/* there is no point in logging to /dev/null */logmode &= ~LOGMODE_STDIO;bb_daemonize_or_rexec(0, argv);}daemon_loop(temp, fd);}
#endifif (opt & MDEV_OPT_SCAN) {/** Scan: mdev -s*///初始化扫描initial_scan(temp);} else {//处理actionprocess_action(temp, getpid());dbg1("%s exiting", curtime());}if (ENABLE_FEATURE_CLEAN_UP)RELEASE_CONFIG_BUFFER(temp);return EXIT_SUCCESS;
}

上述代码中,具体实现步骤如下:

  • 使用位掩码枚举定义了几个选项(MDEV_OPT_SCANMDEV_OPT_SYSLOGMDEV_OPT_DAEMONMDEV_OPT_FOREGROUND)。
  • 初始化一些配置变量并设置环境(INIT_G()bb_sanitize_stdio()umask(0)xchdir("/dev"))。
  • 解析了命令行参数,根据参数的不同执行不同的操作,比如扫描设备、启用系统日志、以守护进程模式运行等。
  • 根据程序运行的不同情况,执行相应的操作,比如进行初始扫描、处理动作等。

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

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

相关文章

猫头虎 AI 前沿科技探索之路(持续更新):ChatGPT/GPT-4 科研应用、论文写作、数据分析与 AI 绘图及文生视频实战全攻略

猫头虎 AI 前沿科技探索之路(持续更新)&#xff1a;ChatGPT/GPT-4 科研应用、论文写作、数据分析与 AI 绘图及文生视频实战全攻略 背景介绍 随着人工智能技术的飞速发展&#xff0c;AI 的应用已经渗透到各个领域&#xff0c;从商业决策到医疗健康&#xff0c;再到日常生活中的…

猫头虎 分享已解决Error || Vanishing/Exploding Gradients: NaN values in gradients

猫头虎 分享已解决Error || Vanishing/Exploding Gradients: NaN values in gradients &#x1f42f; 摘要 &#x1f4c4; 大家好&#xff0c;我是猫头虎&#xff0c;一名专注于人工智能领域的博主。在AI开发中&#xff0c;我们经常会遇到各种各样的错误&#xff0c;其中Vani…

心理学|社会心理学——社会心理学单科作业(中科院)

一、单选题(第1-80小题,每题0.25分,共计40分。) 1、根据霍兰德的观点,社会心理学的形成期是在( )。 分值0.5分 A、社会哲学阶段 B、哲学思辨阶段 C、经验描述阶段 D、实证分析阶段 正确答案: C、经验描述阶段 2、社会行为公式B=f(P,E)中,B指( )。 分…

React+TS 从零开始教程(3):useState

源码链接&#xff1a;下载 在开始今天的内容之前呢&#xff0c;我们需要先看一个上一节遗留的问题&#xff0c;就是给属性设置默认值。 我们不难发现&#xff0c;这个defaultProps已经被废弃了&#xff0c;说明官方并不推荐这样做。其实&#xff0c;这个写法是之前类组件的时候…

国产编程—— 仓颉

应用 仓颉编程语言是一款由华为主导设计和实现的面向全场景智能的编程语言&#xff0c;主要应用于以下领域&#xff1a; 中文字符编码和文本数据处理&#xff1a;仓颉编程语言充分利用汉字的结构特点来设计编码&#xff0c;为开发者提供了一种高效的方式来编码、存储和处理中…

Kafka基础教程

Kafka基础教程 资料来源&#xff1a;Apache Kafka - Introduction (tutorialspoint.com) Apache Kafka起源于LinkedIn&#xff0c;后来在2011年成为一个开源Apache项目&#xff0c;然后在2012年成为一流的Apache项目。Kafka是用Scala和Java编写的。Apache Kafka是基于发布-订…

【Python/Pytorch 】-- K-means聚类算法

文章目录 文章目录 00 写在前面01 基于Python版本的K-means代码02 X-means方法03 最小二乘法简单理解04 贝叶斯信息准则 00 写在前面 时间演变聚类算法&#xff1a;将时间演变聚类算法用在去噪上&#xff0c;基本思想是&#xff0c;具有相似信号演化的体素具有相似的模型参数…

推荐一款AI修图工具,支持AI去水印,AI重绘,AI抠图...

不知道大家有没有这样的一个痛点&#xff0c;发现了一张不错的“素材”&#xff0c; 但是有水印&#xff0c;因此不能采用&#xff0c;但找来找去&#xff0c;还是觉得初见的那个素材不错&#xff0c;怎么办&#xff1f; 自己先办法呗。 二师兄发现了一款功能强大的AI修图工具…

使用Jetpack Compose为Android App创建自定义页面指示器

使用Jetpack Compose为Android App创建自定义页面指示器 在现代移动应用中&#xff0c;页面指示器在提供视觉导航提示方面发挥着重要作用&#xff0c;帮助用户理解其在应用内容中的当前位置。页面指示器特别适用于顺序展示内容的场景&#xff0c;如图片轮播、图像库、幻灯片放…

【Linux】Socket阻塞和非阻塞、同步与异步

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;Linux系列专栏&#xff1a;Linux基础 &#x1f525; 给大家…

java项目生成的api文档

生成Java项目API文档的方法 在进行Java项目开发时&#xff0c;编写良好的API文档对于团队合作和后续维护非常重要。生成API文档不仅可以提高团队协作效率&#xff0c;还可以方便其他开发人员了解项目结构和接口调用方式。在本文中&#xff0c;我们将介绍如何通过Java工具生成A…

WPF与Winform,你的选择是?

概述 在桌面应用的发展历程中&#xff0c;Winform和WPF作为微软推出的两大框架&#xff0c;各自承载着不同的设计理念和技术特色。Winform以其稳定、成熟的技术基础&#xff0c;长期占据着企业级应用开发的重要地位。而WPF&#xff0c;作为后来者&#xff0c;以其现代化的UI设计…

【ajax核心02】底层原理-Promise对象

目录 一&#xff1a;promise对象是什么 二&#xff1a;语法&#xff08;Promise使用步骤&#xff09; 三&#xff1a;Promise-三种状态 一&#xff1a;promise对象是什么 Promise 对象代表异步操作最终的完成&#xff08;或失败&#xff09;以及其结果值。 即Promise对象是…

CentOS7系统的软件源更改为阿里云源-3

要将您的CentOS系统的软件源更改为阿里云源&#xff0c;您可以按照以下步骤操作&#xff1a; 打开终端。 首先&#xff0c;备份原始的 CentOS-Base.repo 文件&#xff0c;以防出现问题时可以恢复&#xff1a; sudo mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/Cen…

SAP系统中的总账会计(知识点总结)

总账会计 财务报表的组织结构 Company Code: 公司代码&#xff0c;an independent accounting entity&#xff0c; 一个独立的会计实体, 存储的数据为 T001。针对同一个公司代码会有Parallel Financial Reporting&#xff08;平行帐&#xff09;的要求&#xff0c;为了实现平…

基于springboot的图书管理系统源码数据库

当下&#xff0c;正处于信息化的时代&#xff0c;许多行业顺应时代的变化&#xff0c;结合使用计算机技术向数字化、信息化建设迈进。传统的图书管理信息管理模式&#xff0c;采用人工登记的方式保存相关数据&#xff0c;这种以人力为主的管理模式已然落后。本人结合使用主流的…

两个模型划分的瓦片数不同,可以把同一物体的两个模型(各有优缺点)就行融合修模吗?

答&#xff1a;网格大师可以针对模型瓦块名称&#xff0c;原点&#xff0c;大小不统一的数据进行融合 网格大师是一款能够解决实景三维模型空间参考、原点、瓦块大小不统一&#xff0c;重叠区域处理问题的工具“百宝箱”&#xff0c;集格式转换、坐标转换、轻量化、瓦片重划分…

uniapp接入微信小程序原生代码配置方案(优化版)

uniapp项目需要把微信小程序原生语法的功能代码嵌套过来&#xff0c;无需把原生代码转换为uniapp&#xff0c;可以配置拷贝的方式集成过来 1、拷贝代码包到src目录 2、vue.config.js中配置原生代码包直接拷贝到编译目录中 3、pages.json中配置分包目录&#xff0c;原生入口…

java基于ssm+jsp 医院远程诊断系统

1前台首页功能模块 医院远程诊断系统&#xff0c;在系统首页可以查看首页、医生信息、论坛信息、我的、跳转到后台、客服等内容&#xff0c;如图1所示。 图1前台首页功能界面图 用户登录&#xff0c;在用户登录页面可以填写用户名、密码、等信息进行用户登录&#xff0c;如图2…

【Linux基础】-- 日志系统syslog与logger的使用方法整理

系统日志配置与使用指南 一、syslog 的使用 syslog 是一种标准用于记录程序运行日志信息的协议。以下是一些基本使用方法&#xff1a; 检查 syslogd 进程 确保 syslogd 进程正在运行。可以通过以下命令检查&#xff1a; ps | grep syslogd如果没有看到 syslogd 进程在运行…