1

好的,所以我试图在 Promela 中模拟一个CLH-RW 锁

锁的工作方式很简单,真的:

队列由 . 组成,tail读取者和写入者都将一个包含单个节点的节点排入队列,bool succ_must_wait他们通过创建一个新节点并使用tail.

尾部因此成为节点的前任,pred

然后他们旋转等待pred.succ_must_wait直到它是false

读者首先增加一个读者计数器ncritR,然后将自己的标志设置为false,允许多个读者同时在临界区。释放一个读锁仅仅意味着ncritR再次递减。

作家等到ncritR达到零,然后进入临界区。false在释放锁之前,它们不会将其标志设置为。

不过,我有点努力在 promela 中建模。

我当前的尝试(见下文)尝试使用数组,其中每个节点基本上由许多数组条目组成。

这失败了,因为让我们说A自己入队,然后B自己入队。然后队列将如下所示:

S <- A <- B

S哨兵节点在哪里。

现在的问题是,当A运行到完整性并重新入队时,队列看起来像

S <- A <- B <- A'

在实际执行中,这绝对没问题,因为AA'是不同的节点对象。并且由于A.succ_must_wait将已设置为首次释放falseA的锁,B最终会取得进展,因此A'最终也会取得进展。

但是,在下面的基于数组的 promela 模型中发生的情况是AA'占据相同的数组位置,导致错过已释放锁B的事实,从而在(错误地)等待而不是等待(正确)对于。ABA'AA'B

对此的一个可能的“解决方案”可能是A等到B确认发布。但这并不适用于锁的工作方式。

另一个“解决方案”是等待一个 CHANGE in pred.succ_must_wait,其中一个 release 会增加succ_must_wait,而不是重置它到0.

但是我打算对锁的一个版本进行建模,其中pred可能会发生变化(即,一个节点可能被允许忽略它的一些前辈),而且我并不完全相信像增加版本这样的东西不会引起问题有了这个变化。

那么在 promela 中对像这样的隐式队列进行建模的“最智能”方法是什么?

/* CLH-RW Lock */
/*pid: 0 = init, 1-2 = reader, 3-4 =  writer*/

ltl liveness{ 
    ([]<> reader[1]@progress_reader)
    && ([]<> reader[2]@progress_reader)
    && ([]<> writer[3]@progress_writer)
    && ([]<> writer[4]@progress_writer)
 }

bool initialised = 0;

byte ncritR;
byte ncritW;
byte tail;
bool succ_must_wait[5]
byte pred[5]

init{
    assert(_pid == 0);
    ncritR = 0;
    ncritW = 0;

    /*sentinel node*/
    tail =0;
    pred[0] = 0;
    succ_must_wait[0] = 0;
    initialised = 1;
}

active [2] proctype reader()
{
    assert(_pid >= 1);
    (initialised == 1)
    do
    :: else ->
        succ_must_wait[_pid] = 1;
        atomic {
            pred[_pid] = tail;
            tail = _pid;
        }

        (succ_must_wait[pred[_pid]] == 0)

        ncritR++;
        succ_must_wait[_pid] = 0;
        atomic {
            /*freeing previous node for garbage collection*/
            pred[_pid] = 0;
        }

        /*CRITICAL SECTION*/
progress_reader:
        assert(ncritR >= 1);
        assert(ncritW == 0);

        ncritR--;

        atomic {
            /*necessary to model the fact that the next access creates a new queue node*/
            if
            :: tail == _pid -> tail = 0;
            :: else ->
            fi
        }
    od
}

active [2] proctype writer()
{
    assert(_pid >= 1);
    (initialised == 1)
    do
    :: else -> 
        succ_must_wait[_pid] = 1;

        atomic {
            pred[_pid] = tail;
            tail = _pid;
        }

        (succ_must_wait[pred[_pid]] == 0)
        (ncritR == 0)
        atomic {
            /*freeing previous node for garbage collection*/
            pred[_pid] = 0;
        }
        ncritW++;

        /* CRITICAL SECTION */
progress_writer:
        assert(ncritR == 0);
        assert(ncritW == 1);

        ncritW--;
        succ_must_wait[_pid] = 0;

        atomic {
            /*necessary to model the fact that the next access creates a new queue node*/
            if
            :: tail == _pid -> tail = 0;
            :: else -> 
            fi
        }
    od
}
4

1 回答 1

1

首先,有几点注意事项:

  • 您不需要将变量初始化为0,因为:

    所有变量的默认初始值为零。

    请参阅文档

  • 您不需要在atomic {}语句中包含单个指令,因为任何基本语句都是原子执行的。为了提高验证过程的效率,应尽可能使用d_step {}在这里,您可以找到有关该主题的相关stackoverflow Q/A 。

  • init {}_pid == 0当以下两个条件之一成立时,保证有:

    • 没有active proctype声明
    • init {}active proctype在源代码中出现的任何其他内容之前声明

    活动进程(init {}包括)在源代码中按出现顺序生成。run ...所有其他进程都按照相应语句的出现顺序产生。


我在您的模型上发现了以下问题:

  • 该指令pred[_pid] = 0是无用的,因为该内存位置仅在分配后读取pred[_pid] = tail

  • 当您释放节点的后继者时,您设置succ_must_wait[_pid]0only 并且不会使后继者正在等待的节点实例无效。这是您在问题中确定但无法解决的问题。我建议的解决方案是添加以下代码:

    pid j;
    for (j: 1..4) {
        if
            :: pred[j] == _pid -> pred[j] = 0;
            :: else -> skip;
        fi
    }
    

    这应该包含在一个atomic {}块中。

  • 当您发现刚刚离开临界区的节点也是队列中的节点时,您正确设置tail回。您还正确地将此操作包含在一个块中。但是,可能会发生这样的情况——当你即将进入这个块时——另一个进程——仍然在某个空闲状态等待——决定执行初始原子块并复制当前值——这对应于到刚刚过期的节点——到他自己的内存位置。如果现在刚刚退出临界区的节点再次尝试加入它,将自己的值设置为to ,您将获得另一个循环等待的实例0lastatomic {}atomic {}tail pred[_pid]succ_must_wait[_pid]1进程之间。正确的做法是将此部分与发布后继的代码合并。


以下内联函数可用于释放给定节点的后继节点:

inline release_succ(i)
{
    d_step {
        pid j;
        for (j: 1..4) {
            if
                :: pred[j] == i ->
                    pred[j] = 0;
                :: else ->
                    skip;
            fi
        }
        succ_must_wait[i] = 0;
        if
            :: tail == _pid -> tail = 0;
            :: else -> skip;
        fi
    }
}

完整的模型如下:

byte ncritR;
byte ncritW;
byte tail;
bool succ_must_wait[5];
byte pred[5];

init
{
    skip
}

inline release_succ(i)
{
    d_step {
        pid j;
        for (j: 1..4) {
            if
                :: pred[j] == i ->
                    pred[j] = 0;
                :: else ->
                    skip;
            fi
        }
        succ_must_wait[i] = 0;
        if
            :: tail == _pid -> tail = 0;
            :: else -> skip;
        fi
    }
}

active [2] proctype reader()
{
loop:
    succ_must_wait[_pid] = 1;
    d_step {
        pred[_pid] = tail;
        tail = _pid;
    }

trying:
    (succ_must_wait[pred[_pid]] == 0)

    ncritR++;
    release_succ(_pid);

    // critical section    
progress_reader:
    assert(ncritR > 0);
    assert(ncritW == 0);

    ncritR--;

    goto loop;
}

active [2] proctype writer()
{
loop:
    succ_must_wait[_pid] = 1;

    d_step {
        pred[_pid] = tail;
        tail = _pid;
    }

trying:
    (succ_must_wait[pred[_pid]] == 0) && (ncritR == 0)

    ncritW++;

    // critical section
progress_writer:
    assert(ncritR == 0);
    assert(ncritW == 1);

    ncritW--;

    release_succ(_pid);

    goto loop;
}

我在模型中添加了以下属性:

  • p0:等于等于的编写无限频繁地经历其进度状态,前提是它有机会无限频繁地执行某些指令:_pid4

    ltl p0 {
       ([]<> (_last == 4)) ->
       ([]<> writer[4]@progress_writer)
    };
    

    这个属性应该是true

  • p1:临界区的读者永远不会超过一个:

    ltl p1 {
        ([] (ncritR <= 1))
    };
    

    显然,我们希望此属性false位于符合您的规范的模型中。

  • p2:在关键部分永远不会超过一个作家:

    ltl p2 {
        ([] (ncritW <= 1))
    };
    

    这个属性应该是true

  • p3:没有任何节点同时是其他两个节点的前身,除非这样的节点是 node 0

    ltl p3 {
        [] (
            (((pred[1] != 0) && (pred[2] != 0)) -> (pred[1] != pred[2])) &&
            (((pred[1] != 0) && (pred[3] != 0)) -> (pred[1] != pred[3])) &&
            (((pred[1] != 0) && (pred[4] != 0)) -> (pred[1] != pred[4])) &&
            (((pred[2] != 0) && (pred[3] != 0)) -> (pred[2] != pred[3])) &&
            (((pred[2] != 0) && (pred[4] != 0)) -> (pred[2] != pred[4])) &&
            (((pred[3] != 0) && (pred[4] != 0)) -> (pred[3] != pred[4]))
        )
    };
    

    这个属性应该是true

  • p4 : 总是真的,每当writer尝试_pid访问4关键部分时,它最终会到达那里:

    ltl p4 {
        [] (writer[4]@trying -> <> writer[4]@progress_writer)
    };
    

    这个属性应该是true

验证结果符合我们的预期:

~$ spin -search -ltl p0 -a clhrw_lock.pml 
...
Full statespace search for:
    never claim             + (p0)
    assertion violations    + (if within scope of claim)
    acceptance   cycles     + (fairness disabled)
    invalid end states      - (disabled by never claim)

State-vector 68 byte, depth reached 3305, errors: 0
...

~$ spin -search -ltl p1 -a clhrw_lock.pml 
...
Full statespace search for:
    never claim             + (p1)
    assertion violations    + (if within scope of claim)
    acceptance   cycles     + (fairness disabled)
    invalid end states      - (disabled by never claim)

State-vector 68 byte, depth reached 1692, errors: 1
...

~$ spin -search -ltl p2 -a clhrw_lock.pml
...
Full statespace search for:
    never claim             + (p2)
    assertion violations    + (if within scope of claim)
    acceptance   cycles     + (fairness disabled)
    invalid end states      - (disabled by never claim)

State-vector 68 byte, depth reached 3115, errors: 0
...

~$ spin -search -ltl p3 -a clhrw_lock.pml 
...
Full statespace search for:
    never claim             + (p3)
    assertion violations    + (if within scope of claim)
    acceptance   cycles     + (fairness disabled)
    invalid end states      - (disabled by never claim)

State-vector 68 byte, depth reached 3115, errors: 0
...

~$ spin -search -ltl p4 -a clhrw_lock.pml 
...
Full statespace search for:
    never claim             + (p4)
    assertion violations    + (if within scope of claim)
    acceptance   cycles     + (fairness disabled)
    invalid end states      - (disabled by never claim)

State-vector 68 byte, depth reached 3115, errors: 0
...
于 2017-11-09T00:42:47.020 回答