EPICS学习:数据库 Locking(锁定)、Scaning(扫描)、Processing(处理)


一、概述

Locking:是为了防止两个不同的任务同时修改相关的数据库记录。
Database scanning:数据库扫描是决定何时处理记录的机制。
Processing:记录处理的基本功能包括获取输入字段的当前值和输出输出字段的当前值。记录处理也会随着记录的复杂而变得越来越复杂。

二、记录链接

记录链接有以下几种类型:

  • INLINK
  • OUTLINK
  • FWDLINK

INLINK和OUTLINK可以是以下之一:

  • 常量(CONSTANT)
  • 数据库链接(DB_LINK)
    连接到同一个IOC中的其他记录的链接
  • 通道访问链接(CA_LINK)
    到另一个IOC中的记录的链接。它是通过一个特殊的IOC客户端任务访问的。也可以强制链接为通道访问链接,即使它引用同一IOC中的记录。
  • 硬件链接

前向链接指向的记录,在包含前向链接的记录被处理完时处理。支持以下几种类型:

  • 常量(CONSTANT)
  • 数据库链接(DB_LINK)
    连接到同一个IOC中的其他记录的链接
  • 通道访问链接(CA_LINK)
    到另一个IOC中的记录的链接,或强制成为通道访问链接的链接。除非链接引用了PROC字段,否则它将被忽略。如果它确实引用了PROC字段,则发出一个值为1的通道访问put。

链接在文件link.h中定义。
下面主要讨论数据库链接。

三、链接操作

在一个链路(不包括硬件链路)上可以预先形成的基本操作如下。

  • dbGetLink:检索到由输入链接引用的字段的值
  • dbPutLink:输出链接引用的字段的值被更改。
  • db_ScanPassive:如果转发链接引用的记录是被动的,则处理它。

一个前向链接只指向一个(通常是被动)记录,这个记录在包含链接的记录处理之后再处理。 对于输入和输出链接,应用程序开发人员可以指定两个附加属性:进程被动和最大化严重性。

3.1 Process Passive

Process Passive属性的值为NPP(非进程被动)或PP(进程被动)。他决定是在从输入链接获取值之前还 是在将值写入输出链接之后处理链接记录。linked记录只有当链接的Process Passive属性为PP目标记录的 SCAN字段为Passive时才会被处理。

还可以指定其它三个选项,CA,CA和CPP。这些选项强制将链接处理为Channel Access Link。

3.2 Maximize Severity

最大严重程度(Maximize Severity)属性有NMS(Non-Maximize Severity),MS(Maximize Severity),MSS(Maximize Status and Severity)或MSI(Maximize Severity if Invalid)。它决 定警报严重性(alarm severity)是否跨链接传播。如果属性是MSI,则只会传播INVALID_ALARM;属性设置为MS或者MSS,会传播比记录当前严重程度更严重的所有报警。对于输入链接,链接引用的记录的警报严重性将传播到包含链接的记录。对于输出链接,包含链接的记录的报警严重性将传播到链接引用 的记录。如果严重性发生变化,则相关报警状态设置为LINK_alarm,除非属性为MSS,此时报警状态将 随严重性一起复制。

确定报警状态和严重程度是否应该更改的方法被称为“(maximize severity)最大严重性”。除了其实际状态和严重性之外,每个记录还有新的严重性和状态。新的状态和严重性,表示NO__ALARM。每当一个软件组件想要修改状态和严重性,它首先检查新的严重性,只有在要设置的严重性大于当前新严重性时才会进行更改。如果确实进行了更改,则会更改新状态和新严重性,而不是当前状态和严重性。当检查数据库监视器时(通常由记录处理例程完成),当前状态和严重性被设置为等于新值,新值重置为零。最终结果是,当前报警状态和严重性反映了最高严重性的未完成报警。如果出现多个相同严重程度的报警,则报警状态反映检测到的第一个报警。

四、数据库锁定

需要锁定以防止由于不同线程的并发访问而损坏记录字段。可以使用dbScanLock对单个记录执行记录锁定,也可以使用dbScanLockMany对记录列表执行记录锁定。在访问任何记录字段之前,必须通过调用dbScanLock或dbScanLockMany来锁定记录。

4.1 单个记录锁定

dbScanLock(struct dbCommon *precord);
dbScanUnlock(struct dbCommon *precord);

可以通过调用dbScanLock锁定单个记录以供访问,稍后通过调用dbScanUnlock解锁单个记录。
一个线程一次只能用dbScanLock锁定一个记录。

4.2 多个记录锁定

typedef struct dbLocker dbLocker;
dbLocker *dbLockerAlloc(struct dbCommon **precs,size_t nrecs,unsigned int flags);
void dbLockerFree(dbLocker *);
void dbScanLockMany(dbLocker*);
void dbScanUnlockMany(dbLocker*);

使用dbScanLockMany可以安全地锁定多个记录。首先,必须从记录指针数组创建dbLocker*。此对象可用于使用dbScanLockMany锁定和解锁该特定记录组。

dbScanLockMany不能递归调用。在调用dbScanLockMany之后,线程必须在再次调用dbScanLockMany之前使用相同的dbLocker*调用dbScanLockMany。

dbScanLock可以递归调用。dbScanLockMany的第一个参数是dbCommon*数组(即指向记录实例的指针),第二个参数是该数组中的元素数。数组可能包含重复的元素。元素可能是无效的(NULL)。

dbScanLockMany(flag)的第三个参数必须为零,因为目前没有定义任何标志。

4.3 递归锁定

递归锁定是线程试图锁定已锁定的记录。

/* This is valid recursive locking */
dbCommon *prec = ...;
dbScanLock(prec);
dbScanLock(prec);
dbScanUnlock(prec);
dbScanUnlock(prec);

But not:
/* This is NOT valid */
dbCommon *prec1 = ..., *prec2 = ...;
assert(prec1!=prec2);
dbScanLock(prec1);
dbScanLock(prec2); /* potential deadlock here! */
dbScanUnlock(prec2);
dbScanUnlock(prec1);

dbScanLock和dbscanlockMany的递归锁定的规则如下:

  • dbScanLockMany不支持递归。一个线程一次只能持有一个组锁定(dbLocker*)。
  • dbScanLock可用于递归地锁定记录。
  • dbScanLock可用于已用dbScanLockMany锁定的记录。
/* This is valid multi-locking */
dbCommon *precs[2] = {prec1, prec2};
dbLocker *L = dbLockerAlloc(precs, 2, 0);
dbScanLockMany(L);
dbScanLock(precs[0]);
dbScanUnlock(precs[0]);
dbScanLock(precs[1]);
dbScanUnlock(precs[1]);
dbScanUnlockMany(L);
dbLockerFree(L);

4.4 什么时候锁定

在IOC处理记录时,它总是被锁定的。因此,设备和记录支持代码决不能从任何支持回调函数中调用dbScanLock或dbScanLockMany。

但是,当异步操作从用户线程或CALLBACK完成时,异步设备支持可以显式地调用dbScanLock。

函数dbPutField和dbGetField隐式调用dbScanLock或dbScanLockMany。函数dbPut和dbGet没有。

通过任何类型的数据库链接连接的所有记录都放在同一个锁集中。R3.14之前的EPICS Base版本允许NPP NMS输入链路跨越两个不同的锁集,但是当字段值的读写操作本质上不是原子的时候,这是不安全的。此功能不再可用于断开锁集。

五、数据库扫描

数据库扫描是指请求处理数据库记录。支持4种扫描类型:

  • Periodic – 定期扫描记录
  • I\O event – I/O中断引起记录扫描
  • Event – 任何任务发出post_event请求都会扫描记录
  • Passive – 调用dbscanpassessive将扫描记录。当且仅当记录是被动的且尚未被处理时,dbScanPassive才会发出记录处理请求。

dbScanPassive请求来自调用以下例程之一的任务:

  • dbScanPassive:只有记录处理例程dbGetLink、dbPutLink和dbPutField调用dbscanpassessive例程。记录处理例程为记录中的每个前向链接调用它。
  • dbPutField:此例程设置目标字段值,然后,如果该字段被标记为pp(TRUE),则调用dbScanPassive。每种记录类型的每个字段都有一个属性pp在记录定义文件中声明为TRUE或FALSE。属性是由记录类型设置的全局属性。使用pp只影响对dbPutField例程的调用。如果dbPutField发现该记录已经处于活动状态(异步记录可能会发生这种情况),并且应该让它进行处理,那么它会安排在当前处理完成后再次对其进行处理。
  • dbGetLink:如果链接包含process passive标志PP,则此例程首先调用dbScanPassive来处理目标记录。无论是否调用dbScanPassive,然后从目标字段获取值。
  • dbPutLink:此例程设置目标字段。然后,如果链接包含process passive标志PP,它将调用dbScanPassive来处理目标记录。只从记录处理例程调用dbPutLink。如果dbPutLink发现由于指向此记录的dbPutField而该记录已处于活动状态,则它会安排在当前处理完成后,稍后再次处理该记录。

所有非记录处理任务(通道访问、序列程序等)都调用dbGetField来获取数据库值。dbGetField只读取值而不要求处理记录。

六、记录处理

对dbProcess的调用将处理记录。每个记录支持模块必须提供一个例程process。此例程执行与记录处理相关的大部分工作。由于记录处理的
细节是特定于记录类型的,因此本主题将在“记录支持”一章中进行更详细的讨论。

七、创建数据库链接的指导

将记录链接在一起的能力是IOC软件的一个非常强大的功能。为了正确地使用链接,重要的是应用程序开发人员了解它们是如何处理的。作为介绍,请考虑以下示例:
在这里插入图片描述
假设A、B和C都是被动记录。这个符号表示A与B有一个前向链接,B与C有一个前向链接。C有一个从A获取值的输入链接。假如由于
某种原因A被处理。以下事件序列发生:

  1. A开始处理。在处理A的过程中,请求处理B
  2. B开始处理。在处理B的过程中,请求处理C
  3. C开始处理。第一步是通过输入链接获取A的值
  4. 这时出现了一个问题。请注意,input链接指定process passive(由InLink后面的PP表示)。但是process passive声明应该在检索值之前处理A。我们是在一个无限循环中吗?答案是否定的。每个记录都包含一个PACT字段(processing active),在记录开始时设置为TRUE,在所有处理完成之前不会设置为FALSE。当处理C时,A仍然具有PACT TRUE,并且不会再次处理
  5. C从A获取值并完成它的处理。控制返回到B
  6. B完成将控制权返回给A
  7. A完成处理

7.1 与数据库相关的规则

7.1.1 处理顺序

处理循序遵循以下规则:

  1. 前向链接(Forward links)按从左到右和从上到下的顺序进行处理。例如,以下记录按FLNK1、FLNK2、FLNK3、FLNK4的顺序进行处理
    在这里插入图片描述

  2. 如果一个记录有多个输入链接(例如计算或选择记录),则通常按自然顺序获取输入值。例如,对于名为INPA、INPB、…、INPL的链接字段,将按A、B、C等顺序读取链接。因此,如果获得的输入结果是正在处理的记录,则处理顺序是有保证的。但是,某些记录类型可能不遵循此规则。

  3. 所有的输入和输出链接在前向链接(Forward links)之前被处理。

7.1.2 锁集

除下一段中列出的条件外,所有直接或间接链接在一起的记录都放在同一个锁集中。当dbScanLock或dbScanLockMany被调用时,整个集合被锁定,而不仅仅是指定的记录。这可以防止两个不同的任务同时修改同一锁集中的记录。

7.1.3 PACT- Process Active

每个记录都包含一个字段PACT。此字段在记录处理开始时设置为TRUE,在记录完全处理之前不会设置为FALSE。为了防止无限的处理循环,每当通过前向链接或带有PP-link选项的数据库链接处理记录时,将保存链接记录的PACT字段并将其设置为TRUE,然后再次恢复。本节开头给出的示例给出了一个示例。在接下来的两部分中,我们将看到PACTYUM LIST还有其他用途。

7.1.4 Process Active: 链接选项

输入和输出链接有一个称为进程被动(process passive)的选项。对于每个这样的链接,应用程序开发人员可以指定process passive TRUE(PP)或process passive FALSE(NPP)。参考下面的例子:
在这里插入图片描述
假设除fanout之外的所有记录都是被动的(passive)。处理fanout记录时,会发生以下事件序列:

  1. Fanout开始处理并请求处理B
  2. B开始处理。它调用dbGetLink从A获取数据
  3. 因为输入链接具有process passive ture,所以向进程A发出请求
  4. 处理A,获取数据值,并将控制权返回给B
  5. B完成处理并将控制权返还给fanout。fanout请求处理C
  6. C开始处理。它调用dbGetLink来从A获取数据
  7. 因为输入链接具有process passive TRUE,所以发送请求处理A
  8. A被处理,获取数据,控制权返还给C
  9. C完成处理返回给fanout
  10. fanout完成

注意,A被处理了两次。这是不必要的。如果C的输入链接被声明为No Process Passive那么A就指挥被处理1次。因此一个更好的解决方案是:
在这里插入图片描述

7.1.5 Process Active:字段属性

所有记录类型字段定义都有一个名为process_passive的属性,该属性在记录定义文件中指定。它不能被IOC应用程序开发人员更改。此属性仅由dbPutField使用。它决定在dbPutField在记录中设置字段后是否处理被动记录。有关各个字段的设置,请参阅记录参考手册中的特定于记录的信息。

7.1.6 Maximize Severity:链接选项

输入和输出链接有一个名为“最大化严重性(maximize severity)”的选项。对于每个这样的链接,应用程序开发人员可以将选项指定为MS(最大化严重性)、NMS(非最大化严重性)、MSS(最大化状态和严重性)或MSI(最大化严重性,如果无效)。

定义数据库输入或输出链接时,应用程序开发人员可以使用此选项指定是否以及如何在带有数据的链接之间传播警报严重性。只有当新的严重性将大于目标记录的当前严重性时,才会传输报警严重性。如果严重性被传播,则报警状态设置为LINK_alarm(除非LINK选项为MSS,此时报警状态也将从源记录中复制)。

八、同步记录指南

同步记录是一种无需等待就可以完全处理的记录。因此,应用程序开发人员在定义一组相关记录时从来不需要考虑延迟的可能性。唯一要考虑的是决定何时处理记录以及以什么顺序处理一组记录。
下面将回顾应用程序程序员可用于决定何时处理记录和强制执行记录处理顺序的方法。

  1. 可以通过I/O事件或事件定期(以几种速率之一)扫描记录
  2. 对于每个周期组和每个事件组,PHAS字段可用于指定处理顺序
  3. 应用程序程序员无法控制不同组中记录的记录处理顺序
  4. 禁用字段(SDIS、DISA和DISV)可用于禁用正在处理的记录。通过让整个记录集的SDIS字段引用同一个输入记录,可以同时启用或禁用整个记录集
  5. 记录(周期性或其他)可以是一组被动记录的根,每当根记录被处理时,这些记录都将被处理。集合由输入、输出和前向链接组成
  6. 每个记录字段的process_passive属性决定当dbPutField定向到该字段时,是否将处理被动记录。应用程序开发人员必须意识到外部源使用此机制触发记录处理的可能性
  7. 输入和输出链接的process_passive选项使应用程序开发人员可以控制如何扫描一组记录
  8. 可以定义常规链接结构。然而,应用程序程序员应该小心,不要在没有仔细分析处理顺序的情况下定义任意结构

九、异步记录指南

前面的讨论不包括异步设备支持。例如GPIB输入记录。当记录被处理时,GPIB请求被启动,处理例程返回。然而,在GPIB请求完成之前,处理并没有真正完成。这是通过异步完成例程来处理的。让我们陈述一下异步记录处理的几个属性。

在所有异步记录的初始处理过程中,将执行以下操作:

  1. PACT字段设置为TURE
  2. 获取所有输入链接的数据
  3. 开始记录处理
  4. 记录处理例程返回

异步完成例程执行以下算法:

  1. 记录处理仍在继续
  2. 检查特定于记录的报警条件
  3. 监视器启动
  4. 前向链接已处理
  5. PACT被设置为FALSE

上述规则的一些属性是:

  1. 异步记录处理不会延迟扫描仪
  2. 从记录处理开始到异步完成例程完成之间的时间,将不再尝试完成记录。这是因为PACT是真实的。例程dbProcess检查PACT,如果为TRUE,则不调用记录处理例程。但是请注意,如果dbProcess连续10次发现该记录处于活动状态,则会引发SCAN_ALARM
  3. 只有当异步完成例程完成记录处理时,才会触发前向和输出链接

有了这些规则下面的工作就很好做了:
在这里插入图片描述
当为记录ASYN调用dbProcess时,将启动处理,但不会调用dbScanPassive。在异步完成例程执行之前,将忽略处理ASYN的任何其他尝试。当调用异步回调时,执行dbScanPassive。

9.1 无限循环

无限的处理循环是可能的。

假设A和B都是异步被动记录,并且请求处理A。则按顺序发生以下事件:
在这里插入图片描述

  1. A开始记录处理并返回留下PACT TRUE
  2. 一段时间后,A的记录完成。在记录完成期间,发出请求处理B。B开始处理,控制权返回到A,A完成其PACT字段为FALSE。
  3. 稍后,B的记录完成。在记录完成期间,请求处理A。A开始处理,控制权返回到B,B完成其PACT字段为FALSE

这样就建立了一个无限的处理循环。由应用程序开发人员来防止这种循环。

9.2 获取旧数据

指向被动异步记录的dbGetLink可以获取旧数据。在这里插入图片描述
如果A是一个被动异步记录,那么记录B的dbGetLink请求将强制为记录A调用dbProcess。dbProcess启动处理,但在操作完成之前立即返回。然后dbGetLink读取字段值,该值仍然是旧的,因为处理将在稍后完成。

9.3 延时

考虑以下列子:
在这里插入图片描述

第二个ASYN记录在第一个记录完成之前不会开始处理,以此类推。这不是真正的问题,只是应用程序开发人员必须意识到异步记录导致的延迟。同样,请注意,扫描器并没有延迟,只是异步记录的下游记录。

十、Cached Puts

dbPutLink和dbPutField后面的规则提供了“缓存”put。这是必要的,因为记录是异步的。两个情况会产生。

第一个结果来自dbPutField,它是来自数据库外部的put,即通道访问put。如果由于记录已开始处理但尚未发生异步完成而将其定向到已具有PACT TRUE的记录,则会向该记录写入一个值,但在再次处理该记录之前,不会对该值执行任何操作。

第二种情况是由于dbPutField指向某个记录而导致dbPutLink发现该记录已处于活动状态。在这种情况下,dbPutLink安排在记录最终完成处理时重新处理该记录。如果记录因为在记录处理链中出现两次而已经处于活动状态,则不会重新处理它,因为记录处理链将构成一个无限循环。

十一、processNotify

当通道访问客户机调用ca_put_callback并请求通知调用者由于该put而处理的所有记录完成时,使用dbProcessNotify。由于异步记录和在记录之间有条件地使用数据库链接,这可能会很复杂,并且由于put而处理的记录集无法预先确定。类型为putProcessRequest的dbProcessNotify的结果与dbPutField相同,但以下情况除外:

  • dbProcessNotify请求被排队而不是缓存。因此,当其他请求被定向到已具有活动dbProcessNotify的记录时,它们将被排
    序。当每一个完成时,它将释放队列中的下一个
  • 如果dbProcessNotify链接到一个不活动但附加了dbProcessNotify的记录,则不会尝
    试处理该记录。

十二、Channel Access Links

一个通道访问链接是:

  • 引用不同IOC中的记录的记录链接
  • 应用程序开发人员强制将其作为通道访问链接的链接

通道访问客户端任务(dbCa)处理通道访问链接的所有I/O。它执行以下操作:

  • 在IOC初始化时,dbCa为每个通道访问链接发出信道访问搜索请求
  • 对于每个输入链接,它使用通道的本地字段类型和元素计数建立一个通道访问监视器。它还监视报警状态。每当调用监视器回调时,新数据都存储在属于dbCa的缓冲区中。当iocCore或记录支持模块从链接请求数据时,缓冲区的内容将转换为请求的类型
  • 对于每个输出链接,当iocCore/record支持在建立通道访问连接之后第一次发出put时,会分配一个缓冲区。它还监视报警状态。每当调用monitor回调时,新数据都存储在属于dbCa的缓冲区中。每次iocCore/record支持发出put时,这个缓冲区的大小足以存储通道的本机字段类型和元素计数,数据被转换并放入缓冲区,并向dbCa请求发出新的ca_put

即使一个链接引用了同一个IOC中的一个记录,也可以强制它像一个频道访问链接一样工作尤其是记录不会被强制放在同一个锁集中。作为一个例子,考虑一个扫描记录,它链接到一组不相关的记录,每个记录都可能导致大量记录被处理。通常不希望将所有这些记录强制放入一
个锁集中。强制将这些链接作为通道访问链路来处理可以解决这个问题。

连接IOC之间的CA链接会产生与消息传递协议、操作系统调用和网络活动相关的额外开销。相比之下,通过直接调用数据库访问函数(如
dbPutField和dbGetField),或者通过直接从数据库监视器订阅事件队列接收回调,可以更有效地执行连接同一IOC中的记录的CA链接。

因为通道访问链接只通过dbPutField、dbGetField和使用数据库监视器订阅事件队列与数据库交互,因此它们与数据库的交互与数据库链接
有着根本的不同,后者紧密集成在执行数据库记录的代码中。因此,由于通道访问不支持传递进程被动标志,因此通道访问链接的语义与数
据库链接不同。让我们分别讨论INLINK、OUTLINK和FWDLINK的通道访问语义。

12.1 INLINK

process passive选项包括:

  • 输入链接总是像NPP一样
  • CA – 强制链接成为通道访问链接
  • CP – 强制链接成为链接访问链接,并在监视器出现时请求时处理包含该链接的记录
  • 强制链接成为通道访问链接,并且还请求在监视器发生时处理包含该链接的记录(如果它是被动的)

Maximize Severity 被关注。

12.2 OUTLINK

process passive选项包括:

  • 不可能兑现PP或NPP选项;put操作立即完成,但目标记录是否会处理取决于目标字段的process passive属性
  • CA – 强制该链接为通道访问链接。

Maximize Severity 不被关注。

12.3 FWLINK

只有当通道访问前向链接引用了记录的PROC字段时,才会使用它。在这种情况下,每次发出前向链路请求时,执行值为1的ca-put。由于这种实现方式,前向链接只能指向被动记录的要求不适用于信道接入前向链路;无论目标记录的SSCAN字段值如何,都将处理目标记录。
可用选项包括:

  • CA – 强制链接成为通道访问链接。

Maximize Severity 不被关注。

十三、记录锁定算法

本节介绍dbScanLock和dbScanLockMany的实现细节。本节中任何关于链接和链接的讨论都只涉及数据库链接(DB_LINK)。不需要锁定其他类型的链接。

锁集用epicMutexId保护一个或多个记录。每个锁集维护其成员记录的列表。

记录和锁集之间的关系构成了锁定算法的基础。每一个记录在它的生命周期中都是某个锁集的成员。记录和锁集之间的关系在lockRecord* 私有结构中建立,该结构是每个记录的LSET字段。每个lockRecord结构都包含一个epicsSpin*,以保持其一致性。

记录通过与DBF_INLINK、DBF_OUTLINK和DBF_FWDLINK字段类型的链接相互关联。这些链接是定向的,从带有链接字段的记录到它所针对的记录字段。这是记录(节点)和链接(边)的有向图。

两个记录之间的数据库链接的存在将它们放在同一个锁集中。这使得涉及多个记录的数据库处理链能够保持一致性。当前未通过任何数据库链接(直接或间接)连接的记录放在不同的锁集中。这样就可以并行扫描不相关的处理链。

当在两个不同锁集中的两个记录之间创建数据库链接时,锁集中的所有记录都将移动到一个锁集中。另一个(现在是空的)锁集是空闲的。这称为合并操作。

每当两个记录之间的数据库链接被破坏时,锁集(图)就可能被分区(一分为二)。当发生这种情况时,将创建一个新的锁集,并用一组连接的记录填充。这称为拆分操作。

记录和锁集之间关联的访问和修改受以下规则控制:

  • 更改关联时,必须锁定lockset mutex和lockRecord spinlock
  • 读取关联时,必须锁定lockset mutex或lockRecord spinlock

自旋锁的一个基本特性是,在任何阻塞操作(包括锁定互斥锁)期间都不能保持它。这定义了锁定的顺序。必须先锁定互斥锁(lockset),然后锁定旋转锁(lockRecord)。

这使事情复杂化,因为锁定操作以记录指针(dbCommon*)开始。必须先锁定旋转锁才能找到记录的当前锁集。但是,必须先解锁旋转锁,然后才能锁定锁具。必须小心,因为当两者都未锁定时,关联可能会发生变化。此外,当两个锁集合并时,其中一个将被释放。

为了安全地处理这个问题,每个锁集都包含一个参考计数器。只有当这个计数器降到零时,锁集才会释放出来。此计数器对每个活动引用有一个“计数”。每个lockRecord都是一个活动引用。此外,dbLocker还可以保存活动引用。

锁定锁集的过程如下:

  • 锁定lockRecord(spinlock)
  • 增加锁集的参考计数器
  • 解锁lockRecord
  • 锁定锁集(mutex)
  • 再次锁定lockRecord
  • 检查一下记录的锁组是否更改过
  • 解锁lockRecord
  • 缩减锁集的参考计数器

在解锁spinlock和锁定互斥锁之间的这段时间内,record和lockset之间的关联可能会发生变化。这可以在互斥锁被锁定后检测到。发生这种情况时,必须用新的锁具重新尝试整个操作。

我们假设数据库链接修改是一个相对罕见的操作。

创建数据库链接时,需要锁定多个锁集。底层的epicsMutex API只支持在一个调用中锁定一个互斥体。在锁定第二个及以后的锁时,必须小心避免死锁。

避免死锁的两种常见策略是使用带有所有权跟踪的try-lock操作,或者建立全局顺序。目前采用第二种策略。通过比较所有锁集互斥锁的内存(指针)地址,将它们放入全局顺序。锁定是按地址递增的顺序进行的。

在创建链接时合并两个锁集的方法是锁定两个锁集,然后将它们的记录列表连接为一个。这样留下了一个空锁集。

当链接断开时,将一个锁集拆分为两个需要查找该锁集是否已被分区。认识到移除两个记录(比如“A”和“B”)之间的一个链接的行为最多会导致两个锁集,这一点很有帮助。认识到移除两个记录(比如“A”和“B”)之间的一个链接的行为最多会导致两个锁集,这一点很有帮助。

要确定锁集是否已分区,只需从两个记录(“a”)中的一个开始,然后递归地遍历记录“a”的其余链接。如果在遍历期间遇到记录’B’,则锁集尚未分区。如果所有连接到“A”的记录都可以在没有找到“B”的情况下被遍历,那么锁集已被分区。所有与“A”相关的记录成为一个锁集,而其余的记录(包括“B”在内)成为第二个。

在IOC启动期间,记录的完整列表将被迭代(由dbLockInitRecords执行),并且根据当时定义的链接创建和填充所需的锁集。

  • 30
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值