MySQL 深潜 - 一文详解 MySQL Data Dictionary
简介: 在 MySQL 8.0 之前,Server 层和存储引擎(比如 InnoDB)会各自保留一份元数据(schema name, table definition 等),不仅在信息存储上有着重复冗余,而且可能存在两者之间存储的元数据不同步的现象。不同存储引擎之间(比如 InnoDB 和 MyISAM)有着不同的元数据存储形式和位置(.FRM, .PAR, .OPT, .TRN and .TRG files),造成了元数据无法统一管理。此外,将元数据存放在不支持事务的表和文件中,使得 DDL 变更不会是原子的,crash recovery 也会成为一个问题。
作者 | 泊歌
来源 | 阿里技术公众号
一 背景
在 MySQL 8.0 之前,Server 层和存储引擎(比如 InnoDB)会各自保留一份元数据(schema name, table definition 等),不仅在信息存储上有着重复冗余,而且可能存在两者之间存储的元数据不同步的现象。不同存储引擎之间(比如 InnoDB 和 MyISAM)有着不同的元数据存储形式和位置(.FRM, .PAR, .OPT, .TRN and .TRG files),造成了元数据无法统一管理。此外,将元数据存放在不支持事务的表和文件中,使得 DDL 变更不会是原子的,crash recovery 也会成为一个问题。
为了解决上述问题,MySQL 在 8.0 中引入了 data dictionary 来进行 Server 层和不同引擎间统一的元数据管理,这些元数据都存储在 InnoDB 引擎的表中,自然的支持原子性,且 Server 层和引擎层共享一份元数据,不再存在不同步的问题。
二 整体架构
data dictionary 提供了统一的 client API 供 Server 层和引擎层使用,包含对元数据访问的 acquire() / drop() / store() / update() 基本操作。底层实现了对 InnoDB 引擎存放的数据字典表的读写操作,包含开表(open table)、构造主键、主键查找等过程。client 和底层存储之间通过两级缓存来加速对元数据对象的内存访问,两级缓存都是基于 hash map 实现的,一层缓存是 local 的,由每个 client(每个线程对应一个 client)独享;二级缓存是 share 的,为所有线程共享的全局缓存。下面我将对 data dictionary 的数据结构和实现架构做重点介绍,也会分享一个支持原子的 DDL 在 data dictionary 层面的实现过程。
三 metadata 在内存和引擎层面的表示
data dictionary (简称DD)中的数据结构是完全按照多态、接口/实现的形式来组织的,接口通过纯虚类来实现(比如表示一个表的 Table),其实现类(Table_impl)为接口类的名字加 _impl 后缀。下面以 Table_impl 为例介绍一个表的元数据对象在 DD cache 中的表示。
1 Table_impl
Table_impl 类中包含一个表相关的元数据属性定义,比如下列最基本引擎类型、comment、分区类型、分区表达式等。
class Table_impl : public Abstract_table_impl, virtual public Table {
// Fields.
Object_id m_se_private_id;
String_type m_engine;
String_type m_comment;
// - Partitioning related fields.
enum_partition_type m_partition_type;
String_type m_partition_expression;
String_type m_partition_expression_utf8;
enum_default_partitioning m_default_partitioning;
// References to tightly-coupled objects.
Index_collection m_indexes;
Foreign_key_collection m_foreign_keys;
Foreign_key_parent_collection m_foreign_key_parents;
Partition_collection m_partitions;
Partition_leaf_vector m_leaf_partitions;
Trigger_collection m_triggers;
Check_constraint_collection m_check_constraints;
};
Table_impl 也是代码实现中 client 最常访问的内存结构,开发者想要增加新的属性,直接在这个类中添加和初始化即可,但是仅仅如此不会自动将该属性持久化到存储引擎中。除了上述简单属性之外,还包括与一个表相关的复杂属性,比如列信息、索引信息、分区信息等,这些复杂属性都是存在其他的 DD 表中,在内存 cache 中也都会集成到 Table_impl 对象里。
从 Abstract_table_impl 继承来的 Collection m_columns 就表示表的所有列集合,集合中的每一个对象 Column_impl 表示该列的元信息,包括数值类型、是否为 NULL、是否自增、默认值等。同时也包含指向 Abstract_table_impl 的指针,将该列与其对应的表联系起来。
class Column_impl : public Entity_object_impl, public Column {
// Fields.
enum_column_types m_type;
bool m_is_nullable;
bool m_is_zerofill;
bool m_is_unsigned;
bool m_is_auto_increment;
bool m_is_virtual;
bool m_default_value_null;
String_type m_default_value;
// References to tightly-coupled objects.
Abstract_table_impl *m_table;
};
此外 Table_impl 中也包含所有分区的元信息集合 Collection m_partitions,存放每个分区的 id、引擎、选项、范围值、父子分区等。
class Partition_impl : public Entity_object_impl, public Partition {
// Fields.
Object_id m_parent_partition_id;
uint m_number;
Object_id m_se_private_id;
String_type m_description_utf8;
String_type m_engine;
String_type m_comment;
Properties_impl m_options;
Properties_impl m_se_private_data;
// References to tightly-coupled objects.
Table_impl *m_table;
const Partition *m_parent;
Partition_values m_values;
Partition_indexes m_indexes;
Table::Partition_collection m_sub_partitions;
};
因此获取到一个表的 Table_impl,我们就可以获取到与这个表相关联的所有元信息。
2 Table_impl 是如何持久化存储和访问的
DD cache 中的元信息都是在 DD tables 中读取和存储的,每个表存放一类元信息的基本属性字段,比如 tables、columns、indexes等,他们之间通过主外键关联连接起来,组成 Table_impl 的全部元信息。DD tables 存放在 mysql 的表空间中,在 release 版本对用户隐藏,只能通过 INFORMATION SCHEMA 的部分视图查看;在 debug 版本可通过设置 SET debug='+d,skip_dd_table_access_check' 直接访问查看。比如:
root@localhost:test 8.0.18-debug> SHOW CREATE TABLE mysql.tables\G
*************************< strong> 1. row < /strong>*************************
Table: tables
Create Table: CREATE TABLE `tables` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`schema_id` bigint(20) unsigned NOT NULL,
`name` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`type` enum('BASE TABLE','VIEW','SYSTEM VIEW') COLLATE utf8_bin NOT NULL,
`engine` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`mysql_version_id` int(10) unsigned NOT NULL,
`row_format` enum('Fixed','Dynamic','Compressed','Redundant','Compact','Paged') COLLATE utf8_bin DEFAULT NULL,
`collation_id` bigint(20) unsigned DEFAULT NULL,
`comment` varchar(2048) COLLATE utf8_bin NOT NULL,
`hidden` enum('Visible','System','SE','DDL') COLLATE utf8_bin NOT NULL,
`options` mediumtext COLLATE utf8_bin,
`se_private_data` mediumtext COLLATE utf8_bin,
`se_private_id` bigint(20) unsigned DEFAULT NULL,
`tablespace_id` bigint(20) unsigned DEFAULT NULL,
`partition_type` enum('HASH','KEY_51','KEY_55','LINEAR_HASH','LINEAR_KEY_51','LINEAR_KEY_55','RANGE','LIST','RANGE_COLUMNS','LIST_COLUMNS','AUTO','AUTO_LINEAR') COLLATE utf8_bin DEFAULT NULL,
`partition_expression` varchar(2048) COLLATE utf8_bin DEFAULT NULL,
`partition_expression_utf8` varchar(2048) COLLATE utf8_bin DEFAULT NULL,
`default_partitioning` enum('NO','YES','NUMBER') COLLATE utf8_bin DEFAULT NULL,
`subpartition_type` enum('HASH','KEY_51','KEY_55','LINEAR_HASH','LINEAR_KEY_51','LINEAR_KEY_55') COLLATE utf8_bin DEFAULT NULL,
`subpartition_expression` varchar(2048) COLLATE utf8_bin DEFAULT NULL,
`subpartition_expression_utf8` varchar(2048) COLLATE utf8_bin DEFAULT NULL,
`default_subpartitioning` enum('NO','YES','NUMBER') COLLATE utf8_bin DEFAULT NULL,
`created` timestamp NOT NULL,
`last_altered` timestamp NOT NULL,
`view_definition` longblob,
`view_definition_utf8` longtext COLLATE utf8_bin,
`view_check_option` enum('NONE','LOCAL','CASCADED') COLLATE utf8_bin DEFAULT NULL,
`view_is_updatable` enum('NO','YES') COLLATE utf8_bin DEFAULT NULL,
`view_algorithm` enum('UNDEFINED','TEMPTABLE','MERGE') COLLATE utf8_bin DEFAULT NULL,
`view_security_type` enum('DEFAULT','INVOKER','DEFINER') COLLATE utf8_bin DEFAULT NULL,
`view_definer` varchar(288) COLLATE utf8_bin DEFAULT NULL,
`view_client_collation_id` bigint(20) unsigned DEFAULT NULL,
`view_connection_collation_id` bigint(20) unsigned DEFAULT NULL,
`view_column_names` longtext COLLATE utf8_bin,
`last_checked_for_upgrade_version_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `schema_id` (`schema_id`,`name`),
UNIQUE KEY `engine` (`engine`,`se_private_id`),
KEY `engine_2` (`engine`),
KEY `collation_id` (`collation_id`),
KEY `tablespace_id` (`tablespace_id`),
KEY `type` (`type`),
KEY `view_client_collation_id` (`view_client_collation_id`),
KEY `view_connection_collation_id` (`view_connection_collation_id`),
CONSTRAINT `tables_ibfk_1` FOREIGN KEY (`schema_id`) REFERENCES `schemata` (`id`),
CONSTRAINT `tables_ibfk_2` FOREIGN KEY (`collation_id`) REFERENCES `collations` (`id`),
CONSTRAINT `tables_ibfk_3` FOREIGN KEY (`tablespace_id`) REFERENCES `tablespaces` (`id`),
CONSTRAINT `tables_ibfk_4` FOREIGN KEY (`view_client_collation_id`) REFERENCES `collations` (`id`),
CONSTRAINT `tables_ibfk_5` FOREIGN KEY (`view_connection_collation_id`) REFERENCES `collations` (`id`)
) /*!50100 TABLESPACE `mysql` */ ENGINE=InnoDB AUTO_INCREMENT=549 DEFAULT CHARSET=utf8 COLLATE=utf8_bin STATS_PERSISTENT=0 ROW_FORMAT=DYNAMIC
1 row in set (0.00 sec)
通过以上 mysql.tables 的表定义可以获得存储引擎中实际存储的元信息字段。DD tables 包括 tables、schemata、columns、column_type_elements、indexes、index_column_usage、foreign_keys、foreign_key_column_usage、table_partitions、table_partition_values、index_partitions、triggers、check_constraints、view_table_usage、view_routine_usage 等。
Storage_adapter 是访问持久存储引擎的处理类,包括 get() / drop() / store() 等接口。当初次获取一个表的元信息时,会调用 Storage_adapter::get() 接口,处理过程如下:
Storage_adapter::get()
// 根据访问对象类型,将依赖的 DD tables 加入到 open table list 中
|--Open_dictionary_tables_ctx::register_tables< T>()
|--Table_impl::register_tables()
|--Open_dictionary_tables_ctx::open_tables() // 调用 Server 层接口打开所有表
|--Raw_table::find_record() // 直接调用 handler 接口根据传入的 key(比如表名)查找记录
|--handler::ha_index_read_idx_map() // index read
// 从读取到的 record 中解析出对应属性,调用 field[field_no]->val_xx() 函数
|--Table_impl::restore_attributes()
// 通过调用 restore_children() 函数从与该对象关联的其他 DD 表中根据主外键读取完整的元数据定义
|--Table_impl::restore_children()
|--返回完整的 DD cache 对象
上述在获取列和属性的对应关系时,根据的是 Tables 对象的枚举类型下标,按顺序包含了该类型 DD 表中的所有列,与上述表定义是一一对应的。因此如果我们需要新增 DD 表中存储的列时,也需要往下面枚举类型定义中加入对应的列,并且在 Table_impl::restore_attributes() / Table_impl::store_attributes() 函数中添加对新增列的读取和存储操作。
class Tables : public Entity_object_table_impl {
enum enum_fields {
FIELD_ID,
FIELD_SCHEMA_ID,
FIELD_NAME,
FIELD_TYPE,
FIELD_ENGINE,
FIELD_MYSQL_VERSION_ID,
FIELD_ROW_FORMAT,
FIELD_COLLATION_ID,
FIELD_COMMENT,
FIELD_HIDDEN,
FIELD_OPTIONS,
FIELD_SE_PRIVATE_DATA,
FIELD_SE_PRIVATE_ID,
FIELD_TABLESPACE_ID,
FIELD_PARTITION_TYPE,
FIELD_PARTITION_EXPRESSION,
FIELD_PARTITION_EXPRESSION_UTF8,
FIELD_DEFAULT_PARTITIONING,
FIELD_SUBPARTITION_TYPE,
FIELD_SUBPARTITION_EXPRESSION,
FIELD_SUBPARTITION_EXPRESSION_UTF8,
FIELD_DEFAULT_SUBPARTITIONING,
FIELD_CREATED,
FIELD_LAST_ALTERED,
FIELD_VIEW_DEFINITION,
FIELD_VIEW_DEFINITION_UTF8,
FIELD_VIEW_CHECK_OPTION,
FIELD_VIEW_IS_UPDATABLE,
FIELD_VIEW_ALGORITHM,
FIELD_VIEW_SECURITY_TYPE,
FIELD_VIEW_DEFINER,
FIELD_VIEW_CLIENT_COLLATION_ID,
FIELD_VIEW_CONNECTION_COLLATION_ID,
FIELD_VIEW_COLUMN_NAMES,
FIELD_LAST_CHECKED_FOR_UPGRADE_VERSION_ID,
NUMBER_OF_FIELDS // Always keep this entry at the end of the enum
};
};
四 多级缓存
为了避免每次对元数据对象的访问都需要去持久存储中读取多个表的数据,使生成的元数据内存对象能够复用,data dictionary 实现了两级缓存的架构,第一级是 client local 独享的,核心数据结构为 Local_multi_map,用于加速在当前线程中对于相同对象的重复访问,同时在当前线程涉及对 DD 对象的修改(DDL)时管理 committed、uncommitted、dropped 几种状态的对象。第二级就是比较常见的多线程共享的缓存,核心数据结构为 Shared_multi_map,包含着所有线程都可以访问到其中的对象,所以会做并发控制的处理。
两级缓存的底层实现很统一,都是基于 hash map 的,目前的实现是 std::map。Local_multi_map 和 Shared_multi_map都是派生于 Multi_map_base。
Multi_map_base 对象实现了丰富的 m_map() 模板函数,可以很方便的根据 key 的类型不同选择到对应的 hash map。
Shared_multi_map 与 Local_multi_map 的不同在于,Shared_multi_map 还引入了一组 latch 与 condition variable 用于并发访问中的线程同步与 cache miss 的处理。同时对 Cache_element 对象做了内存管理和复用的相关能力。
1 局部缓存
一级缓存位于每个 Dictionary_client (每个 client 与线程 THD 一一对应)内部,由不同状态(committed、uncommitted、dropped)的 Object_registry 组成。每个 Object_registry 由不同元数据类型的 Local_multi_map 组成,用于管理不同类型的对象(比如表、schema、字符集、统计数据、Event 等)缓存。
其中 committed 状态的 registry 就是我们访问数据库中已经存在的对象时,将其 DD cache object 存放在局部缓存中的位置。uncommitted 和 dropped 状态的存在,主要用于当前连接执行的是一条 DDL 语句,在执行过程中会将要 drop 的旧表对应的 DD object 存放在 dropped 的 registry 中,将还未提交的新表定义对应的 DD object 存放在 uncommitted 的 registry 中,用于执行状态的区分。
2 共享缓存
共享缓存是 Server 全局唯一的,使用单例 Shared_dictionary_cache 来实现。与上述局部缓存中 Object_registry 相似,Shared_dictionary_cache 也需要包含针对各种类型对象的缓存。与 Multi_map_base 实现根据 key 类型自动选取对应 hash map 的模版函数相似,Object_registry 和 Shared_dictionary_cache 也都实现了根据访问对象的类型选择对应缓存的 m_map() 函数,能够很大程度上简化函数调用。
3 缓存获取过程
用户通过 client 调用元数据对象获取函数,传入元数据的 name 字符串,然后构建出对应的 name key,通过 key 去缓存中获取元数据对象。获取的整体过程就是一级局部缓存 -> 二级共享缓存 -> 存储引擎。
// Get a dictionary object.
template < typename K, typename T>
bool Dictionary_client::acquire(const K &key, const T **object,
bool *local_committed,
bool *local_uncommitted) {
// Lookup in registry of uncommitted objects
T *uncommitted_object = nullptr;
bool dropped = false;
acquire_uncommitted(key, &uncommitted_object, &dropped);
...
// Lookup in the registry of committed objects.
Cache_element< T> *element = NULL;
m_registry_committed.get(key, &element);
...
// Get the object from the shared cache.
if (Shared_dictionary_cache::instance()->get(m_thd, key, &element)) {
DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
m_thd->is_error());
return true;
}
}
在一级局部缓存中获取时,会优先去 uncommitted 和 dropped 的 registry 获取,因为这两者是最新的修改,同时判断获取对象是否已经被 dropped。之后再会去 committed 的 registry 获取,如果获取到就直接返回,反之则去二级共享缓存中尝试获取。
Cache miss
共享缓存的获取过程在 Shared_multi_map::get() 中实现。就是加锁后直接的 hash map 查找,如果存在则给引用计数递增后返回;如果不存在,就会进入到 cache miss 的处理过程,调用上面介绍的存储引擎的接口 Storage_adapter::get() 从 DD tables 中读取,创建出来后依次加入共享缓存和局部缓存 committed registry 中。
当第一个 client 获取到完整的 DD cache object,加入到共享缓存之后,移除 m_missed 集合中对应的 key,并通过广播的方式通知之前等待的线程重新在共享缓存中获取。
五 Auto_releaser
Auto_releaser 是一个 RAII 类,基本上在使用 client 访问 DD cache 前都会做一个封装,保证在整个 Auto_releaser 对象存在的作用域内,所获取到的 DD cache 对象都会在局部缓存中存在不释放。Auto_releaser 包含需要 release 的对象 registry,通过 auto_release() 函数收集着当前 client 从共享缓存中获取到的 DD cache 对象,在超出其作用域进行析构时自动 release 对象,从局部缓存 committed 的 registry 中移除对象,并且在共享缓存中的引用计数递减。
在嵌套函数调用过程中,可能在每一层都会有自己的 Auto_releaser,他们之间通过一个简单的链表指针连接起来。在函数返回时将本层需要 release 的对象 release 掉,需要返回给上层使用的 DD cache 对象交给上层的 Auto_releaser 来负责。通过 transfer_release() 可以在不同层次的 Auto_releaser 对象间转移需要 release 的对象,可以灵活的指定不再需要 DD cache 对象的层次。
六 应用举例:inplace DDL 过程中对 DD 的操作
在 MySQL inplace DDL 执行过程中,会获取当前表定义的 DD cache 对象,然后根据实际的 DDL 操作内容构造出新对应的 DD 对象。然后依次调用 client 的接口完成对当前表定义的删除和新表定义的存储。
{
if (thd->dd_client()->drop(table_def)) goto cleanup2;
table_def = nullptr;
DEBUG_SYNC_C("alter_table_after_dd_client_drop");
// Reset check constraint's mode.
reset_check_constraints_alter_mode(altered_table_def);
if ((db_type->flags & HTON_SUPPORTS_ATOMIC_DDL)) {
/*
For engines supporting atomic DDL we have delayed storing new
table definition in the data-dictionary so far in order to avoid
conflicts between old and new definitions on foreign key names.
Since the old table definition is gone we can safely store new
definition now.
*/
if (thd->dd_client()->store(altered_table_def)) goto cleanup2;
}
}
...
/*
If the SE failed to commit the transaction, we must rollback the
modified dictionary objects to make sure the DD cache, the DD
tables and the state in the SE stay in sync.
*/
if (res)
thd->dd_client()->rollback_modified_objects();
else
thd->dd_client()->commit_modified_objects();
在 drop() 过程中,会将当前表定义的 DD cache 对象对应的数据从存储引擎中删除,然后从共享缓存中移除(这要求当前对象的引用计数仅为1,即只有当前线程使用),之后加入到 dropped 局部缓存中。
在 store() 过程中,会将新的表定义写入存储引擎,并且将对应的 DD cache 对象加入 uncommitted 缓存中。
在事务提交或者回滚后,client 将局部缓存中的 dropped 和 uncommitted registry 清除。由于 InnoDB 引擎支持事务,持久存储层面的数据会通过存储引擎的接口提交或回滚,不需要 client 额外操作。
在这个过程中,由于 MDL(metadata lock) 的存在,不会有其他的线程尝试访问正在变更对象的 DD object,所以可以安全的对 Shared_dictionary_cache 进行操作。当 DDL 操作结束(提交或回滚),释放 EXCLUSIVE 锁之后,新的线程就可以重新从存储引擎上加载新的表定义。
七 总结
MySQL data dictionary 解决了背景所述旧架构中的诸多问题,使元数据的访问更加安全,存储和管理成本更低。架构实现非常的精巧,通过大量的模版类实现使得代码能够最大程度上被复用。多层缓存的实现也能显著提升访问效率。通过 client 简洁的接口,让 Server 层和存储层能在任何地方方便的访问元数据。
本文为阿里云原创内容,未经允许不得转载。
MySQL 深潜 - 一文详解 MySQL Data Dictionary的更多相关文章
- 一文详解MySQL如何同时自增自减多个字段
本文将带大家聊一下如何同时自增自减多个字段 开始之前,先分享一套MySQL教程,小白入门或者学习巩固都可以看 MySQL基础入门-mysql教程-数据库实战(MySQL基础+MySQL高级+MySQL ...
- 一文详解MySQL的锁机制
一.表级锁.行级锁.页级锁 数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则. MySQL数据库由于其自身架构的特点,存在多种数据存储引擎, ...
- 高级程序员必知必会,一文详解MySQL主从同步原理,推荐收藏
1. MySQL主从同步实现方式 MySQL主从同步是基于Bin Log实现的,而Bin Log记录的是原始SQL语句. Bin Log共有三种日志格式,可以binlog_format配置参数指定. ...
- MySQL Cluster 配置文件(config.ini)详解
MySQL Cluster 配置文件(config.ini)详解 ################################################################### ...
- MySQL数据类型 int(M) 表示什么意思?详解mysql int类型的长度值问题
MySQL 数据类型中的 integer types 有点奇怪.你可能会见到诸如:int(3).int(4).int(8) 之类的 int 数据类型.刚接触 MySQL 的时候,我还以为 int(3) ...
- Mysql常用show命令,show variables like xxx 详解,mysql运行时参数
MySQL中有很多的基本命令,show命令也是其中之一,在很多使用者中对show命令的使用还容易产生混淆,本文汇集了show命令的众多用法. 详细: http://dev.mysql.com/doc/ ...
- MySQL数据类型以及基本使用详解
MySQL数据类型以及基本使用详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.MySQL服务器的主要组件 我们知道MySQL的主要组件主要是由服务端(mysqld)和客户端 ...
- [深入学习Web安全](5)详解MySQL注射
[深入学习Web安全](5)详解MySQL注射 0x00 目录 0x00 目录 0x01 MySQL注射的简单介绍 0x02 对于information_schema库的研究 0x03 注射第一步—— ...
- mysql中event的用法详解
一.基本概念mysql5.1版本开始引进event概念.event既“时间触发器”,与triggers的事件触发不同,event类似与linux crontab计划任务,用于时间触发.通过单独或调用存 ...
- MYSQL常用内置函数详解说明
函数中可以将字段名当作变量来用,变量的值就是该列对应的所有值:在整理98在线字典数据时(http://zidian.98zw.com/),有这要一个需求,想从多音字duoyinzi字段值提取第一个拼音 ...
随机推荐
- Redis安装(Linux CentOS)
1. 环境介绍 主机系统:CentOS Redis版本:7.0.10 2. 安装过程 检查 GCC 版本 gcc -v redis 6.0 以上需要 gcc 5.3,升级 gcc.如果安装的redis ...
- FreeRTOS教程7 事件组
1.准备材料 正点原子stm32f407探索者开发板V2.4 STM32CubeMX软件(Version 6.10.0) Keil µVision5 IDE(MDK-Arm) 野火DAP仿真器 XCO ...
- 恶意软件开发(四)通过查找进程名进行DLL注入
通过进程名查找PID 当我们编写注入器的时候,肯定是希望直接通过进程名进行注入,而不是像上一篇笔记一样通过手动输入PID进行查找. 通过进程名查找PID的步骤如下: (1)创建系统中所有进程的快照 ( ...
- 记录--a标签跳转新地址无法访问,但手动输入新地址可以访问
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 问题描述 最近遇到一个有意思的问题,项目中有一个地方,点击需要跳转到一个新的域名地址 笔者使用a标签做跳转,跳是跳过去了,可是跳过去以后, ...
- 记录--uni-app在不同平台下拨打电话
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 场景 在App中拨打电话是一个比较常见的应用场景,但是我们通过搜索文章,发现,大部分的博文都是uni-app官网的copy, copy u ...
- Nancy如何接收POST过来的Json数据
当Nancy版本为2.0.0.0时 string postData = Request.Body.AsString; 当Nancy版本为1.4.5.0时 自己写一个扩展方法,代码如下 /// < ...
- 鸿蒙HarmonyOS实战-ArkUI组件(Button)
一.Button Button(按钮)是一种常见的用户界面控件,通常用于触发操作或提交数据.Button 拥有文本标签和一个可点击的区域,用户点击该区域即可触发相应的操作或事件. Button 的主要 ...
- #带权并查集#HDU 3038 How Many Answers Are Wrong
题目 有未知的\(n\)个数,有\(m\)组询问,形如区间和等于给定值, 问有多少条错误的询问,一旦错误忽略此条询问 \(n\leq 2*10^5,m\leq 4*10^4\) 分析 用带权并查集,记 ...
- #搜索#CF525D Arthur and Walls
题目 给出一个\(n*m\)的矩阵,里面有""和"."两种符号,要求把最少的""变成".", 使得".&quo ...
- OpenHarmony系统能力SystemCapability使用指南
一.概述 1.系统能力与 API SysCap,全称SystemCapability,即系统能力,指操作系统中每一个相对独立的特性,如蓝牙,WIFI,NFC,摄像头等,都是系统能力之一.每个系统能力对 ...