海山数据库(He3DB)源码解读:T_AlterRoleStmt原理浅析

Windows 10 下 DockerToolbox 安装全攻略:从下载到配置阿里云镜像加速 本文详细介绍了在Windows 10系统上安装和配置DockerToolbox的完整流程。从下载安装包、执行初步安装,到关键步骤——修改虚拟机存储路径以及配置阿里云镜像加速器,旨在帮助开发者,尤其是初次接触Docker的新手,快速搭建高效的本地容器开发环境,并解决国内镜像下载缓慢的痛点。 阅读详情

一、概述

   AlterRole 在 He3DB 中用来修改已有角色的命令,可以更改角色的权限、密码、有效期限等。

二、AlterRole 命令的执行流程

  1. PostgresMain
  2. exec_simple_query →执行简单的 SQL 查询;
  3. StartTransactionCommand → 开始事务;
  4. pg_parse_query →解析为内部的抽象语法树(AST);
  5. PortalRun
  6. standard_ProcessUtility →权限检查和准备;
  7. AlterRole→处理创建角色的具体逻辑;
  8. CommandCounterIncrement→增量更新当前的命令计数器;
  9. CommitTransactionCommand→ 提交当前事务;
  10. finish_xact_command→ 在事务结束时,执行必要的清理和关闭操作;
    在这里插入图片描述
图1 AlterRole 命令的执行流程图

三、核心结构体介绍

 (一)  AlterRoleStmt 是一个用于定义 He3DB 中 ALTER ROLE 命令语法的数据结构。它是一个结构体,包含了执行 ALTER ROLE 操作所需的所有信息。

typedef struct AlterRoleStmt
{
	NodeTag		type;
	RoleSpec   *role;			/* role */
	List	   *options;		/* List of DefElem nodes */
	int			action;			/* +1 = add members, -1 = drop members */
} AlterRoleStmt;

NodeTag type: 这是一个枚举类型,用于标识该结构体是哪种节点类型。NodeTag 通常用于区分不同的 SQL 语句类型。

RoleSpec *role: 这是一个指向 RoleSpec 结构体的指针,表示要修改的角色。RoleSpec 结构体可能包含角色的名称或其他标识信息。

List *options: 这是一个指向 List 结构的指针,包含了在 ALTER ROLE 语句中指定的各种选项。List 结构通常用于存储一组 DefElem 节点,每个 DefElem 节点代表一个选项(例如,设置密码、设置有效期等)。

int action: 这是一个整数,表示要对角色执行的操作类型。通常,+1 表示向角色中添加成员(例如,将用户添加到角色中),-1 表示从角色中移除成员(例如,将用户从角色中移除)。

四、核心代码解析

   在 He3DB 的源代码中, AlterRole(@src\backend\commands\user.c) 用于修改数据库角色属性的命令。你可以使用这一命令来更改角色的密码、权限、登录状态等。

/*
 * ALTER ROLE
 *
 * Note: the rolemembers option accepted here is intended to support the
 * backwards-compatible ALTER GROUP syntax.  Although it will work to say
 * "ALTER ROLE role ROLE rolenames", we don't document it.
 */
Oid
AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
{
	Datum new_record[Natts_pg_authid] = {0};
	bool new_record_nulls[Natts_pg_authid] = {0};
	bool new_record_repl[Natts_pg_authid] = {0};

这三行代码定义了三个数组,用于存储修改后角色的属性值(new_record),存储属性值是否为 NULL 的标志(new_record_nulls),以及标识是否准备替换现有值的标志(new_record_repl)。

	Relation	pg_authid_rel;
	TupleDesc	pg_authid_dsc;
	HeapTuple	tuple,
				new_tuple;
	Form_pg_authid authform;

这些变量用于数据库操作:pg_authid_rel 为 pg_authid 表的关系,pg_authid_dsc 为表描述,tuple 用于存储当前角色的元组,new_tuple 用于存储修改后的元组,authform 是角色的表单结构。

	ListCell   *option = NULL;
	char	   *rolename = NULL;
	char	   *password = NULL;	/* user password */
	int			connlimit = -1; /* maximum connections allowed */
	char	   *validUntil = NULL;	/* time the login is valid until */
	Datum		validUntil_datum;	/* same, as timestamptz Datum */
	bool validUntil_null = false;

这些变量用于存储角色选项和属性,如角色名、用户密码、最大连接数、有效时间等。

	DefElem    *dpassword = NULL;
	DefElem    *dissuper = NULL;
	DefElem    *dinherit = NULL;
	DefElem    *dcreaterole = NULL;
	DefElem    *dcreatedb = NULL;
	DefElem    *dcanlogin = NULL;
	DefElem    *disreplication = NULL;
	DefElem    *dconnlimit = NULL;
	DefElem    *drolemembers = NULL;
	DefElem    *dvalidUntil = NULL;
	DefElem    *dbypassRLS = NULL;

这些指针变量用于存储从 stmt 中提取的各个选项(如密码、超级用户权限、继承权限等)。

	Oid roleid = 0;

用于存储角色的 OID。

	check_rolespec_name(stmt->role,
						_("Cannot alter reserved roles."));

检查指定角色名是否为保留角色,若是则报错。

	/* Extract options from the statement node tree */
	foreach(option, stmt->options)

开始遍历 ALTER ROLE 语句中的选项,提取每一个选项(defel)。

	{
		DefElem    *defel = (DefElem *) lfirst(option);

		if (strcmp(defel->defname, "password") == 0) //检查当前选项是否为 “password”。
		{
			if (dpassword) //如果之前已存在 dpassword 选项,则报错;否则将其保存到 dpassword 变量中。
				errorConflictingDefElem(defel, pstate);
			dpassword = defel;
		}
		else if (strcmp(defel->defname, "superuser") == 0) //检查当前选项是否为 “superuser”,并处理与上述类似的逻辑。
		{
			if (dissuper)
				errorConflictingDefElem(defel, pstate);
			dissuper = defel;
		}
		else if (strcmp(defel->defname, "inherit") == 0)
		{
			if (dinherit)
				errorConflictingDefElem(defel, pstate);
			dinherit = defel;
		}
		else if (strcmp(defel->defname, "createrole") == 0)
		{
			if (dcreaterole)
				errorConflictingDefElem(defel, pstate);
			dcreaterole = defel;
		}
		else if (strcmp(defel->defname, "createdb") == 0)
		{
			if (dcreatedb)
				errorConflictingDefElem(defel, pstate);
			dcreatedb = defel;
		}
		else if (strcmp(defel->defname, "canlogin") == 0)
		{
			if (dcanlogin)
				errorConflictingDefElem(defel, pstate);
			dcanlogin = defel;
		}
		else if (strcmp(defel->defname, "isreplication") == 0)
		{
			if (disreplication)
				errorConflictingDefElem(defel, pstate);
			disreplication = defel;
		}
		else if (strcmp(defel->defname, "connectionlimit") == 0)
		{
			if (dconnlimit)
				errorConflictingDefElem(defel, pstate);
			dconnlimit = defel;
		}
		else if (strcmp(defel->defname, "rolemembers") == 0 &&
				 stmt->action != 0)
		{
			if (drolemembers)
				errorConflictingDefElem(defel, pstate);
			drolemembers = defel;
		}
		else if (strcmp(defel->defname, "validUntil") == 0)
		{
			if (dvalidUntil)
				errorConflictingDefElem(defel, pstate);
			dvalidUntil = defel;
		}
		else if (strcmp(defel->defname, "bypassrls") == 0)
		{
			if (dbypassRLS)
				errorConflictingDefElem(defel, pstate);
			dbypassRLS = defel;
		}
		else   //如果遇到未识别的选项,则记录错误。
			elog(ERROR, "option \"%s\" not recognized",
				 defel->defname);
	}

	if (dpassword && dpassword->arg) //如果用户确实指定了密码,则将其提取出来。
		password = strVal(dpassword->arg);
	if (dconnlimit) //如果用户指定了最大连接数,则将其转换为整数并验证其合法性。
	{
		connlimit = intVal(dconnlimit->arg);
		if (connlimit < -1)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("invalid connection limit: %d", connlimit)));
	}
	if (dvalidUntil) //同样处理有效时间选项。
		validUntil = strVal(dvalidUntil->arg);

	/*
	 * Scan the pg_authid relation to be certain the user exists.
	 */
	pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock); //打开 pg_authid 表并获取排他锁,以确保安全读取。
	pg_authid_dsc = RelationGetDescr(pg_authid_rel); //获取表结构描述。

	tuple = get_rolespec_tuple(stmt->role); //根据角色名获取角色的元组,并用结构体来访问角色的字段。
	authform = (Form_pg_authid) GETSTRUCT(tuple);
	rolename = pstrdup(NameStr(authform->rolname));
	roleid = authform->oid;  //保存角色名和角色的 OID 值。
	if (authform->rolsuper || dissuper)  //检查是否尝试修改超级用户角色或其属性,若没有超级用户权限则报错。
	{
		if (!superuser())
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("must be superuser to alter superuser roles or change superuser attribute")));
	}
	else if (authform->rolreplication || disreplication)  //检查是否尝试修改具备复制权限的角色,同样检查是否具有相应权限。
	{
		if (!superuser())
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("must be superuser to alter replication roles or change replication attribute")));
	}
	else if (dbypassRLS) //检查是否尝试修改跳过行级安全性属性。
	{
		if (!superuser())
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("must be superuser to change bypassrls attribute")));
	}
	else if (!have_createrole_privilege())  //如果没有创建角色的权限,则检查其余选项的修改权限,如果没有权限则报错。
	{
		/* check the rest */
		if (dinherit || dcreaterole || dcreatedb || dcanlogin || dconnlimit ||
			drolemembers || dvalidUntil || !dpassword || roleid != GetUserId())
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("permission denied")));
	}

	
	if (dvalidUntil)  //如果指定了有效期,则调用函数将其转换为数据格式;否则从现有元组中获取有效时间属性。
	{
		validUntil_datum = DirectFunctionCall3(timestamptz_in,
											   CStringGetDatum(validUntil),
											   ObjectIdGetDatum(InvalidOid),
											   Int32GetDatum(-1));
		validUntil_null = false;
	}
	else
	{

		validUntil_datum = SysCacheGetAttr(AUTHNAME, tuple,
										   Anum_pg_authid_rolvaliduntil,
										   &validUntil_null);
	}


	if (check_password_hook && password)//如果存在密码检查钩子且用户提供了密码,则调用钩子进行密码检查。
		(*check_password_hook) (rolename,
								password,
								get_password_type(password),
								validUntil_datum,
								validUntil_null);

然后

	MemSet(new_record, 0, sizeof(new_record));
	MemSet(new_record_nulls, false, sizeof(new_record_nulls));
	MemSet(new_record_repl, false, sizeof(new_record_repl));

清空 new_record,new_record_nulls 和 new_record_repl 数组,以准备填充新的角色属性。

	if (dissuper)
	{
		new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(boolVal(dissuper->arg));
		new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
	}

如果 dissuper(解除超级用户权限的标志)存在,将 new_record 中对应超级用户权限的位置更新为布尔值,表示此角色是否仍然是超级用户,并标记为需要替换(new_record_repl)。

	if (dinherit)
	{
		new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(boolVal(dinherit->arg));
		new_record_repl[Anum_pg_authid_rolinherit - 1] = true;
	}

对于 dinherit(角色是否可以继承权限),同样更新 new_record 并标记为需要替换。

	if (dcreaterole)
	{
		new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(boolVal(dcreaterole->arg));
		new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true;
	}

如果 dcreaterole(角色是否可以创建角色)存在,则更新相应字段并标记。

	if (dcreatedb)
	{
		new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(boolVal(dcreatedb->arg));
		new_record_repl[Anum_pg_authid_rolcreatedb - 1] = true;
	}

如果 dcreatedb(角色是否可以创建数据库)设置,更新对应字段。

	if (dcanlogin)
	{
		new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(boolVal(dcanlogin->arg));
		new_record_repl[Anum_pg_authid_rolcanlogin - 1] = true;
	}

如果 dcanlogin(角色是否可以登录)存在,进行类似的处理。

	if (disreplication)
	{
		new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(boolVal(disreplication->arg));
		new_record_repl[Anum_pg_authid_rolreplication - 1] = true;
	}

对于 disreplication(角色是否可以进行复制操作),同样进行更新。

	if (dconnlimit)
	{
		new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
		new_record_repl[Anum_pg_authid_rolconnlimit - 1] = true;
	}

如果有连接限制的设置 dconnlimit,将新的连接限制值(一种整型)更新到新记录。

	/* password */
	if (password)
	{
		char *shadow_pass = NULL;
		const char *logdetail = NULL;

		/* Like in CREATE USER, don't allow an empty password. */
		if (password[0] == '\0' ||
			plain_crypt_verify(rolename, password, "", &logdetail) == STATUS_OK)
		{
			ereport(NOTICE,
					(errmsg("empty string is not a valid password, clearing password")));
			new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
		}

如果存在 password,定义两个用于处理密码的变量。检查密码是否为空,或者明文密码是否验证通过。如果是,输出警告并将密码字段标记为 NULL。

		else
		{
			/* Encrypt the password to the requested format. */
			shadow_pass = encrypt_password(Password_encryption, rolename,
										   password);
			new_record[Anum_pg_authid_rolpassword - 1] =
				CStringGetTextDatum(shadow_pass);
		}
		new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
	}

如果密码有效,则加密密码并存储到新记录中。

	/* unset password */
	if (dpassword && dpassword->arg == NULL)
	{
		new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
		new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
	}

如果 dpassword 存在并且其参数为 NULL,标记密码为 NULL,并替换密码字段。

	/* valid until */
	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
	new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;

对角色的有效期进行更新。

	if (dbypassRLS)
	{
		new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(boolVal(dbypassRLS->arg));
		new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true;
	}

	new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
								  new_record_nulls, new_record_repl);
	CatalogTupleUpdate(pg_authid_rel, &tuple->t_self, new_tuple);

	InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);

	ReleaseSysCache(tuple);
	heap_freetuple(new_tuple);

如果 dbypassRLS 表示角色可以绕过行级安全性,那么更新其对应字段。创建一个新的元组(记录),更新数据库中角色的信息。将新元组更新写入到 pg_authid_rel 表中。如果需要,调用对象后置修改钩子。释放元组的系统缓存和占用的内存。

	/*
	 * Advance command counter so we can see new record; else tests in
	 * AddRoleMems may fail.
	 */
	if (drolemembers)
	{
		List	   *rolemembers = (List *) drolemembers->arg;

		CommandCounterIncrement();

		if (stmt->action == +1) /* add members to role */
			AddRoleMems(rolename, roleid,
						rolemembers, roleSpecsToIds(rolemembers),
						GetUserId(), false);
		else if (stmt->action == -1)	/* drop members from role */
			DelRoleMems(rolename, roleid,
						rolemembers, roleSpecsToIds(rolemembers),
						false);
	}

如果要更改角色成员,首先进行了指令计数的递增,然后根据操作是添加还是删除成员,分别调用 AddRoleMems 或 DelRoleMems 方法来更新角色的成员。

	/*
	 * Close pg_authid, but keep lock till commit.
	 */
	table_close(pg_authid_rel, NoLock);

	return roleid;
}

关闭在 pg_authid 表中的操作,但保持锁直到提交。返回角色的 ID,完成角色更新操作。

别再为FPGA程序裸奔发愁了!手把手教你用Quartus和AES256给Altera芯片上把‘锁’ 本文详细介绍了如何为Altera/Intel FPGA芯片(如Cyclone系列)构建可靠的AES256加密体系,涵盖密钥类型选择、Quartus实操到JTAG链路验证的全流程。通过硬件级AES256引擎实现配置流解密,确保FPGA程序的安全保护,适用于工业控制器等高安全需求场景。 阅读详情

相关推荐

Python | KS检验以及其余非参数检验的实现

Python | KS检验以及其余非参数检验的实现1 什么是KS检验2 KS检验分类?3 KS检验的Python实现3.1 检验指定的数列是否服从正态分布3.2 检验指定的两个数列是否服从相同分布4 其余的非参数检验4.1 Wilcoxon符号秩检验(t检验的非参数版本)4.2 Kruskal-Wallis H检验(方差分析的非参数版本)4.3 Mann-Whitney秩检验5 参考 1 什么是KS检验 定义:检验一个分布f(x)与理论分布g(x)【比如正态分布】是否一致,或两个观测值分布是否有显著差异的检

RUC_Lee的博客 1万+

在Codex 让AI模型 拥有长期智能记忆

本文介绍Claude-Mem V15新功能——智能记忆召回,解决AI遗忘、重复提示、执行不一致等问题。通过本地部署claude-mem,实现自动记录决策(如数据库选型)、跨会话智能召回,支持记忆写入、检索、遗忘全流程闭环。相比传统记忆方案,其基于Hooks自动捕获工具调用,结构化存储,可追溯、可共享、可清理,显著提升工程效率与协作一致性,是长期项目中理想的“工作黑匣子”与“工程脑”。

米莱虾 262

数学建模传染病模型_【数学建模】之传染病模型

点击【拇指笔记】,关注我的公众号。SE模型缺点:没有考虑接触到的人中还有一部分病人,所以所有人并不会全部被感染。建立模型:已感染人数(病人)每个病人每天有效接触(足以使人致病)人数为根据(1)(2)可以建立模型:等式两边同时除以\Delta t由导数定义有同时,取\Delta t = 1天由(1)(5)两式可得最终的模型:Logistic模型缺点:区分已感染者(病人)和未感染者(健康人)...

weixin_39590453的博客 2227

Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)

Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)

要努力去发光,而不是被照亮~ 115

LVGL项目内存告急?试试把中文字库放到SD卡里(基于LvglFontTool V0.5)

本文详细介绍了在LVGL嵌入式项目中如何通过将中文字库存储到SD卡来优化内存使用。针对Flash资源有限的MCU,如STM32F103,提供了基于LvglFontTool V0.5的外部字库生成与动态加载方案,适用于多字号、多语言及OTA场景,显著减少固件体积并提升显示灵活性。

weixin_33726318的博客 456

浏览器缓存导致代码不更新?一招解决部署后用户仍看到旧页面的问题

方案效果适用场景文件名哈希永久解决前端工程化项目HTTP缓存头控制精准控制需服务器权限Meta标签部分生效临时补救方案最佳实践:入口HTML禁用缓存,静态资源使用哈希文件名 + 长期缓存,达到「秒级更新」+「高性能」的平衡!

江拥羡橙的博客 810

Spring Boot 数据源与 MyBatis(-Plus) 自定义配置详解

本文详解 Spring Boot 中单数据源环境下 HikariCP 与 MyBatis-Plus 的自定义配置。通过手动声明 SqlSessionFactory,实现下划线转驼峰、日志输出、一级缓存策略等精细化控制。结合 HikariCP 核心参数调优(如最大连接数、连接生命周期),确保高性能与稳定性。适用于仅需访问单一数据库的场景,避免多数据源复杂性,兼顾性能与可维护性。

BADAO_LIUMANG_QIZHI的博客 193

手把手教你学pcie--内存管理和保护机制(一):MPU与其他内存管理和保护机制详解

MPU(Memory Protection Unit)是一种专门用于 ​。,这些机制通常协同工作,形成多层防御体系,兼顾安全性、性能和灵活性。

MHD0815的博客 278

Docker Compose:搭建本地多服务开发环境

从一个 Java API、PostgreSQL 和 Redis 开始,配置服务名、端口、环境变量、健康检查、数据卷和依赖关系。理解 Compose 文件如何被解析、项目如何隔离,以及哪些数据不能依赖“重建容器”保留,能够把个人开发环境整理成团队可复用的启动入口。

m0_62221377的博客 425

硬方案——几种实用的恒流源电路设计方案,敬请收藏(运放、三极管在手,让你玩出多种花样)

0 引言 我们在信号测量、供电等应用场景时常会涉及到使用恒流源,以前觉得恒流源挺神奇的,研究了下实现方案,其实也没有多么高深(这里讨论的仅限于用在信号测量中的小电流恒流源设计),这里给出几个实用的恒流源电路设计方案。 1 恒流源电路设计 (1)使用三极管搭建的恒流源 (2)使用运放搭建的恒流源 (3)使用恒流二极管搭建的恒流源 pt100恒流源测温电路图_电子技术_电工之家 ...

数字世界 万物互联 9524

2、欧洲期权的赫斯顿模型

本文详细介绍了赫斯顿随机波动率模型在欧洲期权定价中的应用。模型通过引入随机方差过程,克服了布莱克-斯科尔斯模型中波动率恒定的局限性,更贴近市场实际。文章阐述了模型的动态结构、风险中性转换、期权定价公式及其半封闭解的计算方法,并提供了基于VBA的数值实现方案。通过梯形法则与高斯积分等数值方法对比,分析了不同积分策略的精度与效率。同时验证了当波动率为零时,赫斯顿模型退化为布莱克-斯科尔斯模型的一致性。最后讨论了模型的优势、挑战及实际应用建议,强调了数值积分稳定性和参数估计的重要性。

tequila的博客 348

45-存储过程与函数

匿名块跑完就没了,想下次再用只能复制粘贴。给块起个名字存进数据库,就成了存储过程与函数:一次编译、处处调用、权限收口,还能让 Java 侧少拼 SQL。这篇讲封装、调用、限制与排错的完整套路。CREATE OR REPLACE 覆盖式创建,事务由调用方控制;参数三模式 IN 只进、OUT 只出、IN OUT 双向,OUT 参数调用时要备好变量接;调用三姿势 EXEC、CALL、匿名块,命名传参=>最清晰;函数必有 RETURN,想在 SQL 里调用就不能碰 DML 和事务控制;

yuhou25的博客 152

Git下载与安装

一、Git下载 二、Git安装

欢迎订阅我的“JAVA全栈开发笔记(全)”专栏,价格优惠,仅需19.9元。 6万+

AI全栈实战 | 3.9-01 爬虫入门:requests 加 BeautifulSoup 三分钟跑通,但 90% 的人栽在这 4 个“礼貌“问题上

⚠️:本文内容仅用于。请遵守目标站点的服务条款与相关法律法规,不得对目标服务造成负担,不得采集、传播个人隐私数据。技术是中性的,边界在你手里。爬虫的入门技术(requests 发请求 + BeautifulSoup 解析)三分钟就能学会。真正区分"会用爬虫"和"会做爬虫的人"的,是这篇我们跑通"请求→解析→存储"的最小闭环,然后重点把"合规与礼貌"这根弦给你绷上。

源码获取 | 运行部署 | 定制二开修改,如果回复不及时请耐心等待,请见谅。 389

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

上一篇: 海山数据库(He3DB)源码解读:SubBegin-Rollback
下一篇: 海山数据库(He3DB)源码解读:T_CreateRoleStmt原理浅析
DAWN1221
博客等级 码龄7年 893粉丝 95原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值