Linux tracepoint 简介

news2025/1/15 14:05:40

文章目录

  • 前言
  • 一、跟踪点的目的
  • 二、跟踪点的使用
    • 2.1 简介
    • 2.2 DECLARE_TRACE
  • 三、TRACE_EVENT
  • 参考资料

前言

本文提供了如何在内核中插入跟踪点并将 probe functions 连接到它们的示例,并提供了一些 probe functions 的示例。可以在不创建自定义内核模块的情况下使用跟踪点来使用事件跟踪基础结构注册探测函数。

一、跟踪点的目的

tracepoint是内核预先定义的静态探测点,是一种静态插桩方法,可以用于挂载钩子(hook)函数来做trace,放置在代码中的跟踪点提供了一个钩子(hook)来调用您可以在运行时提供的函数(探针)。跟踪点可以是“on”(一个 probe连接到跟踪点)或“off”(没有 probe 附加到跟踪点上)。当跟踪点“关闭”时,除了添加一个微小的时间惩罚(检查分支的条件)和空间惩罚(在插入指令的函数末尾为函数调用添加几个字节,并在单独的部分中添加一个数据结构)之外,它没有任何其它影响。当跟踪点为“on”时,每次在调用方的执行上下文中执行跟踪点时都会调用您提供的函数。当提供的函数结束其执行时,它返回到调用者(从 tracepoint site 继续)。

可以在代码中的重要位置放置跟踪点。它们是轻量级钩子,可以传递任意数量的参数,原型在放置在头文件中的跟踪点声明中描述。

当系统执行到 tracepoint 点时,会执行 tracepoint 上的我们注册的 probe 函数(可以注册多个 probe 函数),类似于printk,输出当前 tracepoint 上下文的环境信息,只是不是输出到终端,而是 ring buffer,这样我们在用户层通过debugfs(tracefs)接口就可以读取 ring buffer 中的信息,了解到内核中当前运行的状态。

tracepoints可以用于跟踪和性能计数。跟踪点表示内核中使用的一些重要事件,构建系统内正在发生的事情的“全貌”。跟踪点是内核源代码中的一个标记,当启用它时,可以用来在标记所在的点处挂钩到正在运行的内核。它们可由许多工具用于内核调试和性能问题诊断。

二、跟踪点的使用

2.1 简介

跟踪点需要两个元素:

头文件中存放跟踪点语句(为了使用跟踪点,应该包括linux/tracepoint.h。)。
In include/trace/events/subsys.h:

#undef TRACE_SYSTEM
#define TRACE_SYSTEM subsys

#if !defined(_TRACE_SUBSYS_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_SUBSYS_H

#include <linux/tracepoint.h>

DECLARE_TRACE(subsys_eventname,
        TP_PROTO(int firstarg, struct task_struct *p),
        TP_ARGS(firstarg, p));

#endif /* _TRACE_SUBSYS_H */

/* This part must be outside protection */
#include <trace/define_trace.h>

c文件中跟踪点的声明。
In subsys/file.c (其中必须添加跟踪语句):

#include <trace/events/subsys.h>

#define CREATE_TRACE_POINTS
DEFINE_TRACE(subsys_eventname);

void somefct(void)
{
        ...
        trace_subsys_eventname(arg, task);
        ...
}

(1)
subsys_eventname是事件的唯一标识符:
subsys是子系统的名称。
eventname是要跟踪的事件的名称。
会在 tracing/event/目录下产生: tracing/event/subsys/subsys_eventname/ 这样一个跟踪点目录。

(2)
TP_PROTO(int firstarg,struct task_struct*p)是此跟踪点调用的函数的原型。
TP_ARGS(firstarg,p)是与原型中相同的参数名称。

(3)
如果在多个源文件中使用 the header ,#define CREATE_TRACE_POINTS应该只出现在一个源文件中。

通过register_trace_subsys_eventname()为特定跟踪点提供 probe(要调用的函数),可以将函数(probe)连接到跟踪点。通过unregister_trace_subsys_eventname()删除probe;它将移除probe。必须在模块退出函数结束之前调用tracepoint_synchronize_unregister(),以确保没有使用 probe 的调用者。这一点,以及在探测调用周围禁用抢占的事实,确保探测移除和模块卸载是安全的。

跟踪点机制支持插入同一跟踪点的多个实例,但必须在所有内核上对给定的跟踪点名称进行单个定义,以确保不会发生类型冲突。使用原型对跟踪点进行名称修改,以确保类型正确。编译器在注册站点验证探针类型的正确性。跟踪点可以放在内联函数、内联静态函数、展开循环以及常规函数中。

这里建议使用命名方案“subsys_event”作为限制冲突的约定。跟踪点名称对内核是全局的:无论是在核心内核映像中还是在模块中,它们都被认为是相同的。

如果必须在内核模块中使用跟踪点,则可以使用EXPORT_TRACEPOINT_SYMBOL_GPL()或EXPORT_TRACEPOINT_SYMBOL()导出定义的跟踪点。

#define EXPORT_TRACEPOINT_SYMBOL_GPL(name)				\
	EXPORT_SYMBOL_GPL(__tracepoint_##name)
#define EXPORT_TRACEPOINT_SYMBOL(name)					\
	EXPORT_SYMBOL(__tracepoint_##name)

如果您需要为跟踪点参数做一些工作,并且该工作仅用于跟踪点,则可以使用以下语句将该工作封装在 If 语句中:

if (trace_foo_bar_enabled()) {
        int i;
        int tot = 0;

        for (i = 0; i < count; i++)
                tot += calculate_nuggets();

        trace_foo_bar(tot);
}

所有trace_tracepoint()调用都定义了一个匹配的trace_tracepoint_enabled()函数,如果启用了跟踪点,则返回true,否则返回false。trace_tracepoint()应该始终位于if(trace_trackpoint_enabled())的块内,以防止启用的跟踪点和看到的检查之间的竞争。

使用trace_tracepoint_enabled()的优点是它使用跟踪点的static_key来允许使用跳转标签实现if语句,并避免条件分支。

如果需要从头文件中调用跟踪点,建议不要直接调用跟踪点或使用trace_tracepoint_enabled()函数调用,因为如果头文件中包含的头文件设置了CREATE_TRACE_POINTS ,则头文件中的跟踪点可能会产生副作用,此外,trace_tracepoint()在内联函数中并没有那么小,如果被其他内联函数使用,可能会导致内核膨胀。取而代之的是,包括tracepoint-defs.h并使用tracepoint_Enable()。

In a C file:

void do_trace_foo_bar_wrapper(args)
{
        trace_foo_bar(args);
}

In the header file:

DECLARE_TRACEPOINT(foo_bar);

static inline void some_inline_function()
{
        [..]
        if (tracepoint_enabled(foo_bar))
                do_trace_foo_bar_wrapper(args);
        [..]
}

2.2 DECLARE_TRACE

(1)
DECLARE_TRACE()传递“proto”作为跟踪点原型,传递“void*__data,proto”为回调原型。

#define DECLARE_TRACE(name, proto, args)				\
		__DECLARE_TRACE(name, PARAMS(proto), PARAMS(args), 1,	\
				PARAMS(void *__data, proto),		\
				PARAMS(__data, args))

(2)

/*
 * Make sure the alignment of the structure in the __tracepoints section will
 * not add unwanted padding between the beginning of the section and the
 * structure. Force alignment to the same alignment as the section start.
 */
#define __DECLARE_TRACE(name, proto, args, cond, data_proto, data_args) \
	extern struct tracepoint __tracepoint_##name;			\
	static inline void trace_##name(proto)				\
	{								\
		if (static_key_false(&__tracepoint_##name.key))		\
			__DO_TRACE(&__tracepoint_##name,		\
				TP_PROTO(data_proto),			\
				TP_ARGS(data_args),			\
				TP_CONDITION(cond),,);			\
	}								\
	__DECLARE_TRACE_RCU(name, PARAMS(proto), PARAMS(args),		\
		PARAMS(cond), PARAMS(data_proto), PARAMS(data_args))	\
		
	//通过register_trace_##name()为跟踪点提供 probe
	static inline int						\
	register_trace_##name(void (*probe)(data_proto), void *data)	\
	{								\
		return tracepoint_probe_register(#name, (void *)probe,	\
						 data);			\
	}								\
	
	//通过unregister_trace_##name()删除probe,移除probe
	static inline int						\
	unregister_trace_##name(void (*probe)(data_proto), void *data)	\
	{								\
		return tracepoint_probe_unregister(#name, (void *)probe, \
						   data);		\
	}								\
	static inline void						\
	check_trace_callback_type_##name(void (*cb)(data_proto))	\
	{								\
	}	
// linux-3.10/kernel/tracepoint.c

/**
 * tracepoint_probe_register -  Connect a probe to a tracepoint
 * @name: tracepoint name
 * @probe: probe handler
 *
 * Returns 0 if ok, error value on error.
 * The probe address must at least be aligned on the architecture pointer size.
 */
int tracepoint_probe_register(const char *name, void *probe, void *data)
{
	struct tracepoint_func *old;

	mutex_lock(&tracepoints_mutex);
	old = tracepoint_add_probe(name, probe, data);
	if (IS_ERR(old)) {
		mutex_unlock(&tracepoints_mutex);
		return PTR_ERR(old);
	}
	tracepoint_update_probes();		/* may update entry */
	mutex_unlock(&tracepoints_mutex);
	release_probes(old);
	return 0;
}
EXPORT_SYMBOL_GPL(tracepoint_probe_register);

/**
 * tracepoint_probe_unregister -  Disconnect a probe from a tracepoint
 * @name: tracepoint name
 * @probe: probe function pointer
 *
 * We do not need to call a synchronize_sched to make sure the probes have
 * finished running before doing a module unload, because the module unload
 * itself uses stop_machine(), which insures that every preempt disabled section
 * have finished.
 */
int tracepoint_probe_unregister(const char *name, void *probe, void *data)
{
	struct tracepoint_func *old;

	mutex_lock(&tracepoints_mutex);
	old = tracepoint_remove_probe(name, probe, data);
	if (IS_ERR(old)) {
		mutex_unlock(&tracepoints_mutex);
		return PTR_ERR(old);
	}
	tracepoint_update_probes();		/* may update entry */
	mutex_unlock(&tracepoints_mutex);
	release_probes(old);
	return 0;
}
EXPORT_SYMBOL_GPL(tracepoint_probe_unregister);
struct tracepoint_func {
	void *func;
	void *data;
};

struct tracepoint {
	const char *name;		/* Tracepoint name */
	struct static_key key;  
	void (*regfunc)(void);
	void (*unregfunc)(void);
	struct tracepoint_func __rcu *funcs;
};

每种Tracepoint有一个name、一个key开关、一系列桩函数。

key(static_key来允许使用跳转标签实现if语句,并避免条件分支):tracepoint是否使能的开关trace_tracepoint_enabled(),如果回调函数数组为空则key为disable,如果回调函数数组中有函数指针则key为enable。

regfunc:注册回调函数时用到的hook函数(注意:不是用来注册回调函数的,注册回调函数是register_trace_##name)。

unregfunc:注销回调函数时的的hook函数(注意:不是用来注销回调函数的,注销回调函数是unregister_trace_##name)。

/*
 * Sets the probe callback corresponding to one tracepoint.
 */
static void set_tracepoint(struct tracepoint_entry **entry,
	struct tracepoint *elem, int active)
{
	WARN_ON(strcmp((*entry)->name, elem->name) != 0);

	if (elem->regfunc && !static_key_enabled(&elem->key) && active)
		elem->regfunc();
	else if (elem->unregfunc && static_key_enabled(&elem->key) && !active)
		elem->unregfunc();

	/*
	 * rcu_assign_pointer has a smp_wmb() which makes sure that the new
	 * probe callbacks array is consistent before setting a pointer to it.
	 * This array is referenced by __DO_TRACE from
	 * include/linux/tracepoints.h. A matching smp_read_barrier_depends()
	 * is used.
	 */
	rcu_assign_pointer(elem->funcs, (*entry)->funcs);
	if (active && !static_key_enabled(&elem->key))
		static_key_slow_inc(&elem->key);
	else if (!active && static_key_enabled(&elem->key))
		static_key_slow_dec(&elem->key);
}

/*
 * Disable a tracepoint and its probe callback.
 * Note: only waiting an RCU period after setting elem->call to the empty
 * function insures that the original callback is not used anymore. This insured
 * by preempt_disable around the call site.
 */
static void disable_tracepoint(struct tracepoint *elem)
{
	if (elem->unregfunc && static_key_enabled(&elem->key))
		elem->unregfunc();

	if (static_key_enabled(&elem->key))
		static_key_slow_dec(&elem->key);
	rcu_assign_pointer(elem->funcs, NULL);
}

funcs:回调函数数组,tracepoint的作用就是在桩函数被命中时,逐个调用回调函数数组中的函数。
在这里插入图片描述

(3)

#ifdef CONFIG_TRACEPOINTS

/*
 * it_func[0] is never NULL because there is at least one element in the array
 * when the array itself is non NULL.
 *
 * Note, the proto and args passed in includes "__data" as the first parameter.
 * The reason for this is to handle the "void" prototype. If a tracepoint
 * has a "void" prototype, then it is invalid to declare a function
 * as "(void *, void)". The DECLARE_TRACE_NOARGS() will pass in just
 * "void *data", where as the DECLARE_TRACE() will pass in "void *data, proto".
 */
#define __DO_TRACE(tp, proto, args, cond, prercu, postrcu)		\
	do {								\
		struct tracepoint_func *it_func_ptr;			\
		void *it_func;						\
		void *__data;						\
									\
		if (!(cond))						\
			return;						\
		prercu;							\
		rcu_read_lock_sched_notrace();				\
		it_func_ptr = rcu_dereference_sched((tp)->funcs);	\
		if (it_func_ptr) {					\
			do {						\
				it_func = (it_func_ptr)->func;		\
				__data = (it_func_ptr)->data;		\
				((void(*)(proto))(it_func))(args);	\
			} while ((++it_func_ptr)->func);		\
		}							\
		rcu_read_unlock_sched_notrace();			\
		postrcu;						\
	} while (0)

一个 while 循环 ,依次执行回调函数数组 struct tracepoint_func __rcu *funcs 的 回调函数 func ;即依次执行 tracepoint 上的注册的回调函数。

三、TRACE_EVENT

我在这三篇文章中描述了 TRACE_EVENT :
Linux 调试之 TRACE_EVENT(一)
Linux 调试之 TRACE_EVENT(二)
Linux 调试之 TRACE_EVENT(三)

(1)
注意这里 TRACE_EVENT 宏展开是 DECLARE_TRACE 宏:

// linux-3.10/include/linux/tracepoint.h

#define TRACE_EVENT(name, proto, args, struct, assign, print)	\
	DECLARE_TRACE(name, PARAMS(proto), PARAMS(args))

(2)
注意这里 TRACE_EVENT 宏展开是 DEFINE_TRACE 宏:

// linux-3.10/include/linux/tracepoint.h

/*
 * We have no guarantee that gcc and the linker won't up-align the tracepoint
 * structures, so we create an array of pointers that will be used for iteration
 * on the tracepoints.
 */
#define DEFINE_TRACE_FN(name, reg, unreg)				 \
	static const char __tpstrtab_##name[]				 \
	__attribute__((section("__tracepoints_strings"))) = #name;	 \
	struct tracepoint __tracepoint_##name				 \
	__attribute__((section("__tracepoints"))) =			 \
		{ __tpstrtab_##name, STATIC_KEY_INIT_FALSE, reg, unreg, NULL };\
	static struct tracepoint * const __tracepoint_ptr_##name __used	 \
	__attribute__((section("__tracepoints_ptrs"))) =		 \
		&__tracepoint_##name;

#define DEFINE_TRACE(name)						\
	DEFINE_TRACE_FN(name, NULL, NULL);

// linux-3.10/include/trace/define_trace.h

#undef TRACE_EVENT
#define TRACE_EVENT(name, proto, args, tstruct, assign, print)	\
	DEFINE_TRACE(name)
[root@localhost ]# readelf -S xfs.ko
There are 48 section headers, starting at offset 0x181bf0:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  ......
  [17] __tracepoints_ptr PROGBITS         0000000000000000  000f7f98
       0000000000000bc0  0000000000000000   A       0     0     8
  [18] .rela__tracepoint RELA             0000000000000000  000f8b58
       0000000000002340  0000000000000018   I      44    17     8
  [19] __tracepoints_str PROGBITS         0000000000000000  000faea0
  ......
  [37] __tracepoints     PROGBITS         0000000000000000  00137520
       0000000000005df8  0000000000000000  WA       0     0     32
  [38] .rela__tracepoint RELA             0000000000000000  0013d318
       0000000000002340  0000000000000018   I      44    37     8
  ......
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

DECLARE_TRACE 宏 和 DEFINE_TRACE 宏 不一样。

从 TRACE_EVENT 的宏定义可以看到,TRACE_EVENT使用的是 tracepoint 机制。需要静态的定义桩函数,并且在插桩位置显式的调用。

宏TRACE_EVENT提供了定义跟踪点的另一种方法。trace event可以使用 tracepoint 机制,内核其它机制也可以使用 tracepoint ,只是kernel的绝大部分tracepoint都是trace event在使用,trace event也必须向tracepoint注册自己的回调函数,这些回调函数的作用就是在函数被命中时往ring buffer中写入trace信息。内核提前帮我们实现了钩子(hook)函数并挂到tracepoint上,当使能一个 trace event 时,它会输出内容到ftrace ringbuffer中,这样就可以获取到内核运行信息了。

参考资料

Linux 3.10.0

https://static.lwn.net/kerneldoc/trace/tracepoints.html
https://pwl999.blog.csdn.net/article/details/80514271
https://blog.csdn.net/jasonactions/article/details/123470620
https://blog.csdn.net/Rong_Toa/article/details/116602224
https://zhuanlan.zhihu.com/p/547477490

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

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

相关文章

高可用 Canal集群( 秒懂 + 史上最全)

文章很长&#xff0c;而且持续更新&#xff0c;建议收藏起来&#xff0c;慢慢读&#xff01;疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 &#xff1a; 免费赠送 :《尼恩Java面试宝典》 持续更新 史上最全 面试必备 2000页 面试必备 大厂必备 涨薪必备 免费赠送 经典…

工控安全-Modbus协议

文章目录一、什么是Modbus协议二、Modbus通信过程三、Modbus存储区四、Modbus协议类型4.1 Modbus RTU协议4.1.1 Modbus报文帧结构4.1.2 主机对从机读数据操作4.1.3 主机对从机写数据操作4.1.4 10功能码数据解析4.1.5 总结4.2 Modbus ACSII协议4.3 Modbus-TCP4.4 Modbus-PLUS一、…

SecXOps 关键技术 模型更新

模型更新 定义内涵 本节的模型更新是指在模型训练完成并正式上线后&#xff0c;由运维人员采集并提供新的数据对 原有模型进行再训练、更新参数的过程。 技术背景 随着时间的推移&#xff0c;由于周期性事件、突变等状况的发生&#xff0c;当下的数据集和之前用于训练 模型…

Anaconda开发环境

Anaconda开发环境 Anacanda是一个基于数据分析和机器学习的集成环境&#xff08;给我们集成好了数据分析和机器学习对应的各种环境和模块&#xff09; jupyter&#xff1a;就是Anaconda这个集成环境提供的一个基于浏览器可视化的编码工具。 注意事项&#xff1a;在环境搭建的…

如何在 iOS、MacOS 上使用 ChatGPT 和适用于 iPhone 的最佳 ChatGPT 应用程序

目录 什么是聊天 GPT&#xff1f;如何在我的 iPhone 上使用 ChatGPT&#xff1f;适用于 iPhone 的最佳 ChatGPT 应用程序 在过去的几周里&#xff0c;出现了许多 ChatGPT 应用程序和网站。如果您想了解什么是 ChatGPT、如何在 iOS 上使用它以及适用于 iOS 的最佳 ChatGPT 应用…

Dockerfile介绍及常用保留指令

从本文开始,咱们将介绍docker的另外一个技术点:dockerfile.我们来看看DockerFile相关的知识点,我们将怎么学习? 1:DockerFile是什么? 2:DockerFile构建过程解析 3:常用的保留字指令 4:案例及小总结。如下图: dockerFile是什么? DockerFile是用来构建Docker镜像…

RV1126笔记五:人脸识别方案<三>

若该文为原创文章,转载请注明原文出处。 一、介绍 通过图片形式,解析图片数据,检测图片上有没有存在人脸,提取人脸特征,把特征数据保存到数据库里。 二、流程图 使用的是Rock-X AI组件库。 三、数据库的使用 在前面,交叉编译了Sqlite3,把编译的库和可执行文件拷贝…

C++:类和对象:多态

1&#xff1a;多态的基本概念 多态就是指多种状态&#xff0c;它是 C面向对象三大特性之一。 多态分为两类 1&#xff1a;静态多态&#xff1a;函数重载和运算符重载。 2&#xff1a;动态多态&#xff1a;派生类和虚函数实现运行时多态。 静态多态和动态多态区别&#xff1a; 1…

set(关联性容器)

众所周知,不会set的人就不会c(你自己编的吧!),到底什么是set呢?我们今天就来了解一下set的奥秘. set是啥 set 作为一个容器也是用来存储同一数据类型的数据类型&#xff0c;并且能从一个数据集合中取出数据&#xff0c;在set 中每个元素的值都唯一&#xff0c;而且系统能根据…

Nacos学习笔记 (5)Nacos整合SpringBoot流程

前提&#xff0c;先下载Nacos并启动 Nacos Server。 1. Nacos 融合 Spring Boot 为注册配置中心 实现&#xff1a; 通过 Nacos Server 和 nacos-config-spring-boot-starter 实现配置的动态变更&#xff1b;通过 Nacos Server 和 nacos-discovery-spring-boot-starter 实现服…

16、Mysql高级之锁问题

16、Mysql高级之锁问题 文章目录16、Mysql高级之锁问题1、锁概述2、锁分类3、Mysql 锁4、MyISAM 表锁4.1 如何加表锁5.2.2 读锁案例4.3、写锁案例4.4、结论4.5、查看锁的争用情况5、InnoDB 行锁5.1、行锁介绍5.2、背景知识5.3、InnoDB 的行锁模式5.4、案例准备工作5.5、行锁基本…

机器学习模型-BUPA liver disorders-探索饮酒与肝炎关系(论文,科研,医疗信息化诊断系统用)

肝炎是由细菌、病毒、寄生虫、酒精、药物、化学物质、自身免疫等多种致病因素引起的肝脏炎症的统称。儿童及成年人均可患病&#xff0c;病毒感染导致的病毒性肝炎较为常见。 过渡饮酒是肝炎重要因素 过渡饮酒会引发下述血检指标异常&#xff0c;暗示肝炎发生。 酒精相关性肝病…

使用Nordic的nrf52840实现蓝牙DFU过程

需要用到的软件&#xff1a; 交叉编译环境&#xff1a;gcc-arm-none-eabi MinGW&#xff1a;下载 Python&#xff1a;下载 micro-ecc源码&#xff1a;下载 nRFUtil&#xff1a;下载或者直接使用python的pip来安装 手机app&#xff1a;nRF Toolbox或者nRF Connect 安装 gcc-…

【爬虫】JS调试解决反爬问题系列3—sign破解

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…

LabVIEW如何减少下一代测试系统中的硬件过时 1

LabVIEW如何减少下一代测试系统中的硬件过时 1 许多测试系统的问题是&#xff0c;整个系统运行的时间必须超过单个系统组件的支持时间。有时被测试的设备有几十年的有效使用寿命&#xff0c;而许多测试仪器已经过时&#xff0c;在5年或更短的时间后就不再支持了。其他时候&…

技能梳理32@电源防反接电路+光耦隔离电路+串口磁耦隔离电路

电源防反接电路 CN2是个防反接插座&#xff1b; F1是个自恢复保险丝&#xff1b; MB6S是个整流桥电路&#xff0c;主要是这个模块实现的防反接效果&#xff1b; SPX3819是个电平转换模块&#xff0c;封装挺小的&#xff1b;LDO-EN是用来控制SPX3819是否使能的&#xff1b; …

等保2.0参与医院网络安全管理的重要性

随着现代医院 IT 技术架构的演变、新兴技术的引入&#xff0c;来自医院内外部的各种安全风险不断出现&#xff0c;对医院网络安全提出了更多挑战&#xff0c;医院网络安全在技术层面和管理层面都亟待完善。为此&#xff0c;借鉴相关法律法规、行业标准等&#xff0c;提出提升现…

Web前端开发神器WebStorm v2022.3发布——支持新的CSS功能

WebStorm是一款JavaScript 开发工具。被广大中国JS开发者誉为"Web前端开发神器""最强大的HTML5编辑器""最智能的JavaSscript IDE"等。与IntelliJ IDEA同源&#xff0c;继承了IntelliJ IDEA强大的JS部分的功能。 WebStorm v2022.3官方正式版下载…

百度工程师教你玩转设计模式(装饰器模式)

作者 | 北极星小组 想要写好代码&#xff0c;设计模式&#xff08;Design Pattern&#xff09;是必不可少的基本功&#xff0c;设计模式是对面向对象设计&#xff08;Object Oriented Design&#xff09;中反复出现的一类问题的一种解决方案&#xff0c;本篇介绍装饰器模式&am…

(第一章)OpGL超级宝典学习:配置和超级宝典相同的工作环境

目录前言配套资源配置解压文件夹复制资源HOWTOBUILD什么是CMake什么是GLFW安装CMake开始构建build glfw生成debug和release的lib库build sample推送结语前言 最近发现学习好像到了一定的瓶颈&#xff0c;马上要到2023年了&#xff0c;想要在新的一年开始后对自己有一定的突破&a…