【linux kernel】 一文总结linux内核中的kobject、kset和ktype

文章目录

        • 一、kobject、kset、ktype
          • (1-1)kobject
          • (1-2)ktype
          • (1-3)kset
        • 二、kobject操作API
          • (2-1)kobject_init()
          • (2-2)kobject_add()
          • (2-3)kobject_rename()
          • (2-4)kobject_name()
          • (2-5)kobject_get()
          • (2-6)kobject_put()
          • (2-7)kobject_del()
          • (2-8)kobject_move()
          • (2-9)kobject_uevent
          • (2-10)kobject_uevent_env
        • 三、引用计数
        • 四、创建简单的kobject
        • 五、移除kobject
        • 六、kset提供的功能

一、kobject、kset、ktype

kobject是内核中表示对象的基本结构,kset是用于组织和管理 kobject 的集合,而 ktype 是用于定义 kobject 的类型。通过使用这三个概念,可以实现内核对象的管理、组织和操作,kobjectksetktype是内核中核心基础结构,绚丽多彩的内核世界在这些基础结构上逐一构建。

(1-1)kobject

kobjectstruct kobject类型的对象。kobjects有一个名称name和一个引用计数。kobject还有一个父指针(允许对象被安排到层次结构中),一个特定的类型ktype,一个特定的对象集合kset,以及一个是否在sysfs虚拟文件系统中的状态表示。struct kobject是linux内核中的数据结构,用于表示内核对象(Kernel Object)。它是Linux设备模型的核心之一,用于表示内核中的各种实体,如设备、驱动程序、总线、类别等。

struct kobject提供了一种统一的方式来管理内核中的对象。它允许内核开发人员创建具有层次结构的对象树,通过父子关系链表关系连接不同的对象。通过与sysfs文件系统的集成,可以为每个内核对象创建相应的sysfs文件,使用户空间能够查看和修改对象的属性。

struct kobject还提供了引用计数的机制,确保在对象不再被使用时能够正确释放相关资源。引用计数允许多个实体引用同一个内核对象,并在所有引用都被释放时自动销毁对象。kobject通常被嵌入到其他结构中。

🔺注意:任何结构中只能嵌入一个kobject

struct kobject定义如下:

struct kobject {const char		*name;struct list_head	entry;struct kobject		*parent;struct kset		*kset;struct kobj_type	*ktype;struct 	*sd;struct kref		kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASEstruct delayed_work	release;
#endifunsigned int state_initialized:1;unsigned int state_in_sysfs:1;unsigned int state_add_uevent_sent:1;unsigned int state_remove_uevent_sent:1;unsigned int uevent_suppress:1;
};
  • name:内核对象的名称
  • entry:用于将内核对象添加到父对象的子对象列表中。
  • parent:指向父对象的指针,用于构建对象之间的层次关系。
  • kset:指向所属的kset(kobject集合)的指针,用于进行对象管理。
  • ktype:指向对象类型的指针,描述了对象的行为和属性。
  • sd:指向sysfs目录实体(sysfs_dirent)的指针,用于与sysfs文件系统进行交互。
  • kref:用于实现引用计数,确保在对象不再需要时能够正确释放资源。
(1-2)ktype

ktype是嵌入在kobject中的对象类型,用于描述struct kobject的类型和行为。每个kobject结构都需要对应的ktype,因为ktype用于控制创建和销毁kobjec时发生的事情。

struct kobj_type {void (*release)(struct kobject *kobj);const struct sysfs_ops *sysfs_ops;struct attribute **default_attrs;const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);const void *(*namespace)(struct kobject *kobj);
};
  • release:指向释放对象资源的函数指针。当对象的引用计数变为零时,将调用此函数来释放对象相关的资源。
  • sysfs_ops:指向struct sysfs_ops的指针,用于定义sysfs文件系统操作的回调函数。通过这些回调函数,可以自定义sysfs文件的读取、写入、属性创建等操作。
  • default_attrs:指向struct attribute指针数组的指针,表示对象的默认属性。这些属性将在对象的sysfs目录中显示,并允许用户空间访问和修改。
  • child_ns_type:指向函数的指针,用于获取对象的子命名空间类型。如果对象具有子对象,并且这些子对象在命名空间上有不同的要求,可以通过此函数提供相应的命名空间类型操作。
  • namespace:指向函数的指针,用于获取对象的命名空间标识。命名空间标识是一个不透明的指针,用于标识对象所属的命名空间。

struct kobj_type用于描述struct kobject的类型和行为,为每个struct kobject实例提供相关的操作和属性定义。通过自定义struct kobj_type,可以定制不同类型的内核对象,并指定相应的release函数、属性、命名空间等。

在使用struct kobject时,通常会创建一个自定义的struct kobj_type实例,并将其与struct kobject关联。这样可以为每个对象提供独立的类型和行为,并在必要时通过回调函数和属性操作与用户空间进行交互。

每个kobject都必须有一个release()方法,并且该kobject必须持久化存在(保持一致的状态),直到release()被调用。

(1-3)kset

kset是一组kobject。这些kobject可以是相同的ktype,也可以属于不同的ktype。kset是kobject集合的基本容器类型。kset中也包含了一个自己的kobject,但是可以忽略实现细节,因为kset核心代码会自动处理这个kobject。kset如下定义:

struct kset {//这个kset的所有kobject的列表struct list_head list; //用于遍历kobject的锁spinlock_t list_lock;//放在kset中的kobjectstruct kobject kobj;//kset的uevent操作集const struct kset_uevent_ops *uevent_ops; 
};

从根文件系统角度来看,当看到一个包含其他目录的sysfs目录时,通常每个目录都对应同一个kset中的一个kobject。

总而言之,struct kset用于表示内核对象的集合,它是一种特殊的struct kobject,用于将相关的内核对象组织成逻辑上的集合。通过struct kset,可以将一组具有相似特性或属性的内核对象归类并进行管理。

struct kset对象通常作为父对象,包含一组子对象,这些子对象可以是不同类型的内核对象,但它们在逻辑上具有某种相关性。例如,设备驱动程序可以创建一个struct kset对象,作为设备驱动程序的集合,每个具体的设备驱动程序都是其中的一个子对象。

struct kset提供了一些常用的操作和功能,如添加和删除子对象、遍历子对象等。通过对struct kset对象进行操作,可以很方便地管理和访问集合中的内核对象。

二、kobject操作API

kobject的接口位于/include/linux/kobject.h中,本小节总结常用的kobject操作API。

(2-1)kobject_init()
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
  • kobj:指向要初始化的内核对象的指针。
  • ktype:指向描述该内核对象类型的kobj_type结构体的指针。

ktype是创建kobject所必需的,因为每个kobject都必须有一个相关联的kobj_type。调用kobject_init()后,要向sysfs注册kobject,必须调用函数kobject_add()

(2-2)kobject_add()
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);
  • kobj:指向要添加的内核对象的指针。
  • parent:指向父级内核对象的指针。新创建的内核对象将成为父级对象的子对象。
  • fmt:一个格式化字符串,用于指定要添加的内核对象的名称。

kobject_add()将正确地设置kobject的父对象和kobject的名称。如果kobject要与特定的kset相关联,则必须在调用kobject_add()之前分配kobj->kset。如果kset与kobject相关联,那么kobject的父对象可以在调用kobject_add()时设置为NULL,然后kobject的父对象将是kset本身。

由于kobject的名称是在添加到内核时设置的,因此不应该直接操作kobject的名称,如果必须修改kobject的名称,则调用kobject_rename():

一旦通过kobject_add()注册了kobject,就不能直接使用kfree()释放它,唯一安全的方法是使用kobject_put()完成。最好总是在kobject_init()之后使用kobject_put(),以避免出现错误。

(2-3)kobject_rename()
int kobject_rename(struct kobject *kobj, const char *new_name);

注意:kobject_rename()不执行任何锁操作,也不知道什么名称是有效的,因此调用者必须提供自己的健全检查和序列化。

(2-4)kobject_name()
const char *kobject_name(const struct kobject * kobj);

有一个helper函数可以同时初始化和将kobject添加到内核中,它的名字叫kobject_init_and_add():

int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,struct kobject *parent, const char *fmt, ...);
  • kobj:指向要初始化和添加的内核对象的指针。
  • ktype:指向描述该内核对象类型的kobj_type结构体的指针。
  • parent:指向父级内核对象的指针。新创建的内核对象将成为父级对象的子对象。
  • fmt:一个格式化字符串,用于指定新创建的内核对象的名称。此字符串可以包含格式化占位符,后面跟着对应的参数,类似于printf()函数的用法。

参数与上面描述的kobject_init()和kobject_add()函数相同。

(2-5)kobject_get()

kobject_get()函数用于增加给定 kobject 的引用计数:

struct kobject *kobject_get(struct kobject *kobj);
  • kobj:要增加引用计数的 kobject 的指针。

当某段代码需要继续使用 kobject 时,可以通过调用 kobject_get() 来增加其引用计数,从而防止其在其他地方被释放。通常,每个 kobject_get() 都应该有对应的 kobject_put() 来减少引用计数,以确保在不再需要时能够正确地释放 kobject。

(2-6)kobject_put()

kobject_put() 函数用于减少给定kobject的引用计数,并在引用计数减为零时释放相应的内存空间:

void kobject_put(struct kobject *kobj);
  • kobj:要减少引用计数的 kobject 的指针。

使用kobject_put()函数可以减少kobject的引用计数,当引用计数减为零时,表示没有代码再需要该kobject,并且可以安全地释放其占用的内存。通常每个 kobject_get()都应该有对应的 kobject_put() 来减少引用计数,以确保在不再需要时能够正确地释放 kobject。

(2-7)kobject_del()

kobject_del() 函数用于从内核对象层次结构中注销给定的 kobject:

void kobject_del(struct kobject *kobj);
  • kobj:要注销的 kobject 的指针。

调用kobject_del()函数将从内核对象层次结构中移除指定的kobject,这将使得 kobject 不再可见,但不会释放与之相关的内存,且其引用计数仍保持不变。通常,这个函数用于在不允许睡眠的情况下销毁对象,以确保在后续能够安全地完成对象的清理工作。要完全清理与 kobject 相关的内存,还需要在适当的时候调用 kobject_put()

(2-8)kobject_move()

kobject_move() 函数用于将一个 kobject 移动到另一个父对象下:

int kobject_move(struct kobject *kobj, struct kobject *new_parent)
  • kobj:要移动的 kobject 的指针。

  • new_parent:新的父对象的指针。

调用kobject_move()函数会将指定的 kobject 移动到另一个父对象下,这可以用于重新组织内核对象层次结构中的对象。移动后,kobject 将成为新父对象的子对象,并从原父对象的子对象列表中删除。

(2-9)kobject_uevent
int kobject_uevent(struct kobject *kobj, enum kobject_action action);

kobject_uevent()函数用于向用户空间发送一个与指定kobject对象关联的uevent事件。

  • kobj:指向要发送uevent事件的kobject对象的指针。
  • action:指定要发送的uevent事件的动作类型,通常是一个枚举类型。

成功时返回0,失败时返回负值。

使用kobject_uevent()函数,内核可以在发生特定事件时向用户空间发送通知,用户空间的程序可以监听这些事件并执行相应的操作。例如,在设备插入或移除时,内核可以调用kobject_uevent()函数发送相应的uevent事件,以通知用户空间发生了相关的变化。

请注意,kobject_uevent()函数只是将uevent事件添加到内核中的uevent队列中,实际的uevent处理是由对应的守护进程来完成的,它会从内核的uevent队列中获取事件并执行相应的操作。

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		//动作类型的最大值,用于边界检查。
};

这里给出了枚举类型kobject_action的定义,它包含了一组常见的kobject动作类型:

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

这些动作类型用于指示kobject_uevent()函数要发送的uevent事件的类型。当内核调用kobject_uevent()函数时,它会指定一个动作类型,告知用户空间发生了哪种类型的事件。用户空间的程序可以根据收到的uevent事件类型来执行相应的操作。

(2-10)kobject_uevent_env
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,char *envp_ext[])

kobject_uevent_env()函数与kobject_uevent()函数类似,用于向用户空间发送一个与指定kobject对象关联的uevent事件,但kobject_uevent_env允许在发送uevent事件时传递额外的环境变量。kobject_uevent()本质上也是调用kobject_uevent_env()实现,只是envp_ext位NULL。

  • kobj:指向要发送uevent事件的kobject对象的指针。
  • action:指定要发送的uevent事件的动作类型,通常是一个枚举类型。
  • envp_ext[]:一个字符串数组,包含要传递给用户空间的额外环境变量。数组中的每个元素都是一个以key=value格式表示的字符串,用于指定一个环境变量的键值对。

使用kobject_uevent_env()函数,内核可以在发生特定事件时向用户空间发送通知,并传递额外的环境变量信息。用户空间的程序可以监听这些事件并执行相应的操作,并根据收到的环境变量信息来进行进一步处理。

三、引用计数

kobject的关键功能之一是充当嵌入它的对象的引用计数器,只要对对象的引用存在,该对象(和支持它的代码)也必须继续存在。用于操作kobject引用计数的底层函数有:

struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject *kobj);

成功调用kobject_get()将增加kobject的引用计数器并返回指向该kobject的指针。当一个引用被释放时,对kobject_put()的调用将减少引用计数,并可能释放该对象。

注意,kobject_init()将引用计数设置为1,因此设置kobject的代码最终需要执行kobject_put()来释放该引用。

因为kobject是动态的,所以它们不能被静态地声明或在堆栈上声明,而是总是动态地分配。内核的未来版本将包含对静态创建的kobject的运行时检查,并将警告开发人员这种不当使用。

四、创建简单的kobject

有时候,我没仅仅想在在sysfs层次结构中创建一个简单的目录,而不必与复杂的kset、show和store函数以及其他细节纠缠在一起。则可以使用下列API创建这样的一个条目:

struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
  • name:指定新创建的内核对象的名称。
  • parent:指向父级内核对象的指针。新创建的内核对象将成为父级对象的子对象。

这个函数将创建一个kobject,并将它放在sysfs中指定的父kobject下面的位置。要创建与这个kobject相关的简单属性,需使用:

int sysfs_create_file(struct kobject *kobj, const struct attribute *attr);

或者

int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp);

注意:这里使用的两种属性类型,以及使用kobject_create_and_add()创建的kobject,都可以是kobj_attribute类型,因此不需要创建特殊的自定义属性。

五、移除kobject

如果一个kobject成功注册到kobject核心后,当代码完成对其的使用时,必须对其进行清理,这时候需要调用kobject_put()。通过这样做,kobject核心将自动清理该kobject分配的所有内存。如果已为对象发送了KOBJ_ADD uevent,则将发送相应的KOBJ_REMOVE uevent,并且其他 sysfs 管理都将得到合适的处理。

如果我没需要对kobject进行两阶段删除(例如,当我们需要销毁对象时不能睡眠),那么可以调用kobject_del()将从sysfs注销kobject,这使得 kobject “不可见”,但它没有被清理,这时候对象的引用计数仍然相同,在后续调用kobject_put()来完成与kobject相关联的内存清理操作。

kobject_del() 可用于断开对父对象的引用,如果构造了循环引用。在某些情况下,父对象引用子对象是有效的,必须使用显式调用kobject_del()来中断循环引用,以便调用释放函数。

六、kset提供的功能

一个kset提供以下三个功能:

(1)kset就像一个装东西的袋子,里面装着一组物体。内核可以使用kset来跟踪“所有块设备”或“所有PCI设备驱动程序”。

(2)kset也是sysfs中的一个子目录,其中可以显示与kset相关联的kobject,每个kset都包含一个kobject,它可以被设置为其他kobject的父对象;sysfs层次结构的顶级目录就是这样构造的。

(3)kset可以支持kobject的"热插拔",并影响uevent事件如何报告给用户空间。

在面向对象的术语中,“kset”是顶级容器类;kset包含它们自己的kobject,但是kobject是由kset代码管理的,不应该由用户直接操作。

kset将其子节点保存在标准的内核链表中,kobject通过kset字段指向包含它们的kset。在几乎所有的情况下,属于kset的kobject都在它们的父对象中包含kset(或者严格地说是内嵌的kobject)。

由于kset中包含一个kobject,所以始终应该动态创建kset,而不是静态或在堆栈上声明它。

创建一个新的kset,代码如下:

struct kset *kset_create_and_add(const char *name,const struct kset_uevent_ops *uevent_ops,struct kobject *parent_kobj);

当使用完kset之后,需要调用:

void kset_unregister(struct kset *k);

去销毁它。该函数将从sysfs中删除kset并减少其引用计数,当引用计数减为零时,kset将被释放。因为这时候对kset的其他引用可能仍然存在,所以释放操作可能发生在kset_unregister()返回之后。

如果kset希望控制与它关联的kobject的uevent的ops,它可以使用结构体kset_uevent_ops来处理,该结构定义如下:

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);
};
  • filter:用于过滤要发送uevent的struct kobject对象。如果返回值为非零,则表示该对象的uevent将被发送;如果返回值为零,则表示该对象的uevent将被忽略。
  • name:用于获取要发送uevent的struct kobject对象的名称,返回的字符串将作为uevent中的"NAME"字段。
  • uevent:用于发送uevent的回调函数。它将会在struct kobject对象的uevent触发时被调用,并接收struct kobject对象、struct kobj_uevent_env环境变量作为参数。

通过定义struct kset_uevent_ops 结构体,并将其赋值给struct kset对象的uevent_ops成员,可以自定义struct kset对象的uevent操作行为。当相关的内核对象发生与uevent相关的事件时,内核将调用struct kset_uevent_ops 中定义的函数来处理和发送uevent。

使用struct kset_uevent_ops,可以灵活地定义struct kset对象的uevent行为,例如过滤要发送的uevent对象、指定uevent中的名称字段、处理和发送uevent等。这能够在内核对象发生变化时,根据需要触发自定义的操作和通知。

参考链接:

https://www.kernel.org/doc/html/latest/core-api/kobject.html

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

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

相关文章

【命名空间详解】c++入门

目录 命名空间的定义 1.命名空间的正常定义 2.命名空间还可以嵌套 3. 命名空间可以合并 命名空间的使用 1.加命名空间名称及作用域限定符 2.使用using将命名空间中某个成员引入 3.使用using namespace 命名空间名称 引入 输入,输出 输出 命名空间的定义 …

linux命令ar使用说明

ar 建立或修改备存文件,或是从备存文件中抽取文件 补充说明 ar命令 是一个建立或修改备存文件,或是从备存文件中抽取文件的工具,ar可让您集合许多文件,成为单一的备存文件。在备存文件中,所有成员文件皆保有原来的属…

Java技术学习|Git

学习材料声明 尚硅谷Git入门到精通全套教程(涵盖GitHub\Gitee码云\GitLab) GIt Git 是一个免费的、开源的分布式版本控制系统,可以快速高效地处理从小型到大型的各种项目。Git 易于学习,占地面积小,性能极快。 它具有…

ARM_day8:基于iic总线的通信

一、IIC总线的基本概念: iic总线是一种带应答的同步的、串行、半双工的通信方式,支持一个主机对应多个从机。它有一根SCL(时钟线)和一根SDA(数据线)组成,由于只有一根数据线,所以它是…

英伟达大跳水!一夜暴跌10%,市值蒸发2000亿

相信大家已经在各大社交平台上看到了,英伟达一夜蒸发了2000亿美元! GPT-3.5研究测试: https://hujiaoai.cn GPT-4研究测试: https://higpt4.cn Claude-3研究测试(全面吊打GPT-4): https://hic…

大语言模型隐私防泄漏:差分隐私、参数高效化

大语言模型隐私防泄漏:差分隐私、参数高效化 写在最前面题目6:大语言模型隐私防泄漏Differentially Private Fine-tuning of Language Models其他初步和之前的基线微调模型1微调模型2通过低秩自适应进行微调( 实例化元框架1) 在隐…

Vue2 —— 学习(九)

目录 一、全局事件总线 (一)全局总线介绍 关系图 对图中的中间商 x 的要求 1.所有组件都能看到 2.有 $on $off $emit (二)案例 发送方 student 接收方 二、消息订阅和发布 (一)介绍 &#xff08…

虚拟机中的打印机,无法打印内容,打印的是白纸或英文和数字,打印不了中文

原因:打印机驱动设置不正确 解决方案: 打开打印机属性 -> 高级 -> 新驱动程序 下一页 -> Windows 更新 耐心等待,时间较长。 选择和打印机型号匹配的驱动,我选择的是: 虽然虚拟机和主机使用的驱动不…

跨境电商指南:防关联浏览器和云主机有什么区别?

跨境电商的卖家分为独立站卖家和平台卖家。前者会自己开设独立站点,比如通过 shopify;后者则是入驻亚马逊或 Tiktok 等平台,开设商铺。其中平台卖家为了扩大收益,往往不止开一个店铺,或者有店铺代运营的供应商&#xf…

皇后之战:揭秘N皇后问题的多维解法与智慧【python 力扣52题】

作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。 会一些的技术:数据分析、算法、SQL、大数据相关、python 欢迎加入社区:码上找工作 作者专栏每日更新: LeetCode解锁1000题: 打怪升级之旅 python数据分析…

Go之map详解

map的结构 map实现的两个关键数据结构 hmap 定义了map的结构bmap 定义了hmap.buckets中每个bucket的结构 // A header for a Go map. type hmap struct {count int // 元素的个数flags uint8 // 状态标记,标记map当前状态,是否正在写入B …

css层叠性,继承性,优先级

前言 本文概要:讲述css的三大特性,层叠,继承和优先级。 层叠性 描述:我们试想以下这种情况:我们定义了同一个选择器,但是定义的属性不同。属性有相同的也有不同的,那么最后我们这个页面会听谁的…

CSS display属性

目录 概述: 设置display示例: none: block: inline: inline-block : 概述: 在CSS中我们可以使用display属性来控制元素的布局,我们可以通过display来设置元素的类型。 在不设置…

封装个js分页插件

// 分页插件类 class PaginationPlugin {constructor(fetchDataURL, options {}) {this.fetchDataURL fetchDataURL;this.options {containerId: options.containerId || paginationContainer,dataSizeAttr: options.dataSizeAttr || toatalsize, // 修改为实际API返回的数据…

vue里面事件修饰符.prevent使用案例

什么是.prevent事件修饰符? 在Vue中,事件修饰符是指在事件处理函数后面添加的特殊标记,用于修改事件的行为。.prevent事件修饰符是其中之一,它的作用是阻止事件的默认行为。通常情况下,当用户触发某些事件时&#xff0…

ppt技巧:​如何将两个PPT幻灯片文件合并成一个?

第一种方式:复制粘贴幻灯片 1. 打开第一个PPT幻灯片文件,确保你已经熟悉该文件的内容和布局。 2. 打开第二个PPT幻灯片文件,浏览其中的所有幻灯片,选择你想要合并到第一个文件中的幻灯片。 3. 使用快捷键CtrlC(Wind…

虚拟ip地址怎么弄到手机上

在当下的社会,手机已经变得至关重要,它融入了我们的日常生活,无论是上网冲浪、社交互动,还是工作学习,都离不开它。但有时候,由于某些限制,我们可能无法充分享受网络带来的便利。这时&#xff0…

Nginx part2.1

目录 搭建目录网页 为网页设置用户登录 做一个文件目录网页,并进行登陆 示范 搭建目录网页 启动nginx: systemctl start nginx 开机自启动nginx: systemctl enable nginx 启动完服务后,查看自己的nginx的状态:sys…

【JavaWeb】Day47.Mybatis基础操作——删除

Mybatis基础操作 需求 准备数据库表 emp 创建一个新的springboot工程,选择引入对应的起步依赖(mybatis、mysql驱动、lombok) application.properties中引入数据库连接信息 创建对应的实体类 Emp(实体类属性采用驼峰命名&#xf…

【C++提高】常用容器

常用容器 引言:迭代器的使用一、vector容器1. vector基本概念2. vector的迭代器3. vector构造函数4. vector赋值操作5. vector容量和大小6. vector插入和删除7. vector数据存取8. vector互换容器9. vector预留空间 二、deque容器1. deque容器的基本概念2. deque容器…