第一题
输入指令:
可以看到两个线程按照顺序在执行,接下来输入指令:cat flag.s查看该程序代码:
.var flag
.var count
.main
.top
.acquire
mov flag, %ax # get flag
test $0, %ax # if we get 0 back: lock is free!
jne .acquire # if not, try again
mov $1, flag # store 1 into flag
# critical section
mov count, %ax # get the value at the address
add $1, %ax # increment it
mov %ax, count # store it back
# release lock
mov $0, flag # clear the flag now
# see if we're still looping
sub $1, %bx
test $0, %bx
jgt .top
halt
通过分析,我们看到这个程序通过变量flag实现了一个自旋锁,类似的C代码为:
void lock(int flag)
{
while(flag==1);
flag=1;
}
void unlock(int flag)
{
flag=0;
}
for(int i=1;i>0;i--)
{
lock(flag);
count++;
unlock(int flag);
}
第二题
输入指令:./x86.py -p flag.s -R ax,bx -M flag,count 时,我们可以得到如下的结果:
通过书本上的知识,我们知道不合理的中断主要影响的是count的值,我们知道进程0和进程1都运行了一次count++,所有最后count的值应该是2,下面输入指令:./x86.py -p flag.s -R ax,bx -M flag,count -c时,可以得到如下的结果:
我们发现结果是正确的,flag.s按照预期的工作,我们预测的结果是正确的。
第三题
由于将bx的值修改为2,根据第一题的分析,我们知道了进程0和进程1的循环次数都会因为寄存器bx值的修改而发生变化,同时bx并不是进程0和进程1所共享的变量,所以进程0和进程1都会循环两次,所以count最后的值应该为4,类似的C程序代码为:
void lock(int flag)
{
while(flag==1);
flag=1;
}
void unlock(int flag)
{
flag=0;
}
for(int i=2;i>0;i--)
{
lock(flag);
count++;
unlock(flag);
}
输入指令:./x86.py -p flag.s -R ax,bx -M flag,count -a bx=2,bx=2 -c时我们可以看到结果为:
flag count ax bx Thread 0 Thread 1
0 0 0 2
0 0 0 2 1000 mov flag, %ax
0 0 0 2 1001 test $0, %ax
0 0 0 2 1002 jne .acquire
1 0 0 2 1003 mov $1, flag
1 0 0 2 1004 mov count, %ax
1 0 1 2 1005 add $1, %ax
1 1 1 2 1006 mov %ax, count
0 1 1 2 1007 mov $0, flag
0 1 1 1 1008 sub $1, %bx
0 1 1 1 1009 test $0, %bx
0 1 1 1 1010 jgt .top
0 1 0 1 1000 mov flag, %ax
0 1 0 1 1001 test $0, %ax
0 1 0 1 1002 jne .acquire
1 1 0 1 1003 mov $1, flag
1 1 1 1 1004 mov count, %ax
1 1 2 1 1005 add $1, %ax
1 2 2 1 1006 mov %ax, count
0 2 2 1 1007 mov $0, flag
0 2 2 0 1008 sub $1, %bx
0 2 2 0 1009 test $0, %bx
0 2 2 0 1010 jgt .top
0 2 2 0 1011 halt
0 2 0 2 ----- Halt;Switch ----- ----- Halt;Switch -----
0 2 0 2 1000 mov flag, %ax
0 2 0 2 1001 test $0, %ax
0 2 0 2 1002 jne .acquire
1 2 0 2 1003 mov $1, flag
1 2 2 2 1004 mov count, %ax
1 2 3 2 1005 add $1, %ax
1 3 3 2 1006 mov %ax, count
0 3 3 2 1007 mov $0, flag
0 3 3 1 1008 sub $1, %bx
0 3 3 1 1009 test $0, %bx
0 3 3 1 1010 jgt .top
0 3 0 1 1000 mov flag, %ax
0 3 0 1 1001 test $0, %ax
0 3 0 1 1002 jne .acquire
1 3 0 1 1003 mov $1, flag
1 3 3 1 1004 mov count, %ax
1 3 4 1 1005 add $1, %ax
1 4 4 1 1006 mov %ax, count
0 4 4 1 1007 mov $0, flag
0 4 4 0 1008 sub $1, %bx
0 4 4 0 1009 test $0, %bx
0 4 4 0 1010 jgt .top
0 4 4 0 1011 halt
发现,与我们的分析是相符合的,我们知道flag.s的程序的实现是成功的。
第四题
刚开始,看到这题的时候,会想到可能是中断引起的临界区的竞争从而造成的错误,后面发现我们已经实现了锁。所以,我们这里判断的可能是一个程序的性能,如果中断在进程0开始执行的地方(即进程0获得了锁),进程1开始运行,会一直自旋,占用了CPU但是不会产生作用,所以可以认为在进程0刚开始运行的时候中断,然后进程1开始自旋占用CPU是一种不好的结果。这里,我们举个例子,当时钟中断频率为5的时候的结果为
flag count ax bx Thread 0 Thread 1
0 0 0 2
0 0 0 2 1000 mov flag, %ax
0 0 0 2 1001 test $0, %ax
0 0 0 2 1002 jne .acquire
1 0 0 2 1003 mov $1, flag
1 0 0 2 1004 mov count, %ax
1 0 0 2 ------ Interrupt ------ ------ Interrupt ------
1 0 1 2 1000 mov flag, %ax
1 0 1 2 1001 test $0, %ax
1 0 1 2 1002 jne .acquire
1 0 1 2 1000 mov flag, %ax
1 0 1 2 1001 test $0, %ax
1 0 0 2 ------ Interrupt ------ ------ Interrupt ------
1 0 1 2 1005 add $1, %ax
1 1 1 2 1006 mov %ax, count
0 1 1 2 1007 mov $0, flag
0 1 1 1 1008 sub $1, %bx
0 1 1 1 1009 test $0, %bx
0 1 1 2 ------ Interrupt ------ ------ Interrupt ------
0 1 1 2 1002 jne .acquire
0 1 0 2 1000 mov flag, %ax
0 1 0 2 1001 test $0, %ax
0 1 0 2 1002 jne .acquire
1 1 0 2 1003 mov $1, flag
1 1 1 1 ------ Interrupt ------ ------ Interrupt ------
1 1 1 1 1010 jgt .top
1 1 1 1 1000 mov flag, %ax
1 1 1 1 1001 test $0, %ax
1 1 1 1 1002 jne .acquire
1 1 1 1 1000 mov flag, %ax
1 1 0 2 ------ Interrupt ------ ------ Interrupt ------
1 1 1 2 1004 mov count, %ax
1 1 2 2 1005 add $1, %ax
1 2 2 2 1006 mov %ax, count
0 2 2 2 1007 mov $0, flag
0 2 2 1 1008 sub $1, %bx
0 2 1 1 ------ Interrupt ------ ------ Interrupt ------
0 2 1 1 1001 test $0, %ax
0 2 1 1 1002 jne .acquire
0 2 0 1 1000 mov flag, %ax
0 2 0 1 1001 test $0, %ax
0 2 0 1 1002 jne .acquire
0 2 2 1 ------ Interrupt ------ ------ Interrupt ------
0 2 2 1 1009 test $0, %bx
0 2 2 1 1010 jgt .top
0 2 0 1 1000 mov flag, %ax
0 2 0 1 1001 test $0, %ax
0 2 0 1 1002 jne .acquire
0 2 0 1 ------ Interrupt ------ ------ Interrupt ------
1 2 0 1 1003 mov $1, flag
1 2 2 1 1004 mov count, %ax
1 2 3 1 1005 add $1, %ax
1 3 3 1 1006 mov %ax, count
0 3 3 1 1007 mov $0, flag
0 3 0 1 ------ Interrupt ------ ------ Interrupt ------
1 3 0 1 1003 mov $1, flag
1 3 3 1 1004 mov count, %ax
1 3 4 1 1005 add $1, %ax
1 4 4 1 1006 mov %ax, count
0 4 4 1 1007 mov $0, flag
0 4 3 1 ------ Interrupt ------ ------ Interrupt ------
0 4 3 0 1008 sub $1, %bx
0 4 3 0 1009 test $0, %bx
0 4 3 0 1010 jgt .top
0 4 3 0 1011 halt
0 4 4 1 ----- Halt;Switch ----- ----- Halt;Switch -----
0 4 4 0 1008 sub $1, %bx
0 4 4 0 ------ Interrupt ------ ------ Interrupt ------
0 4 4 0 1009 test $0, %bx
0 4 4 0 1010 jgt .top
0 4 4 0 1011 halt
上述结果,就是一种不好的结果。那么好的结果是什么呢?
应该就是,进程0刚刚释放锁的时候,中断这个时候来了,正好开始运行进程1,这样的时钟中断频率就应该会导致产生良好的结果。所以当中断频率过快或中断发生在线程开锁之前时,将会导致不好的结果,相对的,中断频率刚好发生在一个线程开锁之后将会使CPU得到充分的利用,可以认为是一个好的结果。
第五题
使用指令查看程序test-and-set.c的代码:cat test-and-set.s,查看的结果如下:
xchg指令是一个交换指令,将锁设置为1,同时将锁旧的值保存下来,进行判断如果等于0就继续往下执行,否则停在这里自旋。类似的C程序代码如下:
int swap(int new,int mutex)
{
int old=mutex;
mutex=new;
return old;
}
之后程序写法与第一题中分析一致:
void lock(int mutex)//获取锁
{
while(swap(1,mutex)==1);
}
void unlock(int mutex)//释放锁
{
mutex=0;
}
for(int i=n;i>0;i--)
{
lock(mutex);
count++;
unlock(mutex);
}
第六题
首先,由于设置锁的原因,所以代码肯定可以按照预期工作,但是可能出现第四题中的情况。就是出现在进程0刚获得锁的时候,进程1开始运行就会一直停在那里自旋,有时会导致CPU使用率不高。量化的标准,我觉得可以看CPU中执行自旋的部分占运行整个程序所需要的代码的百分比。
输入指令:./x86.py -p test-and-set.s -i 7 -R ax,bx -M mutex,count -a bx=2 -c查看预测的结果是否正确:
mutex count ax bx Thread 0 Thread 1
0 0 0 2
0 0 1 2 1000 mov $1, %ax
1 0 0 2 1001 xchg %ax, mutex
1 0 0 2 1002 test $0, %ax
1 0 0 2 1003 jne .acquire
1 0 0 2 1004 mov count, %ax
1 0 1 2 1005 add $1, %ax
1 1 1 2 1006 mov %ax, count
1 1 0 2 ------ Interrupt ------ ------ Interrupt ------
1 1 1 2 1000 mov $1, %ax
1 1 1 2 1001 xchg %ax, mutex
1 1 1 2 1002 test $0, %ax
1 1 1 2 1003 jne .acquire
1 1 1 2 1000 mov $1, %ax
1 1 1 2 1001 xchg %ax, mutex
1 1 1 2 1002 test $0, %ax
1 1 1 2 ------ Interrupt ------ ------ Interrupt ------
0 1 1 2 1007 mov $0, mutex
0 1 1 1 1008 sub $1, %bx
0 1 1 1 1009 test $0, %bx
0 1 1 1 1010 jgt .top
0 1 1 1 1000 mov $1, %ax
1 1 0 1 1001 xchg %ax, mutex
1 1 0 1 1002 test $0, %ax
1 1 1 2 ------ Interrupt ------ ------ Interrupt ------
1 1 1 2 1003 jne .acquire
1 1 1 2 1000 mov $1, %ax
1 1 1 2 1001 xchg %ax, mutex
1 1 1 2 1002 test $0, %ax
1 1 1 2 1003 jne .acquire
1 1 1 2 1000 mov $1, %ax
1 1 1 2 1001 xchg %ax, mutex
1 1 0 1 ------ Interrupt ------ ------ Interrupt ------
1 1 0 1 1003 jne .acquire
1 1 1 1 1004 mov count, %ax
1 1 2 1 1005 add $1, %ax
1 2 2 1 1006 mov %ax, count
0 2 2 1 1007 mov $0, mutex
0 2 2 0 1008 sub $1, %bx
0 2 2 0 1009 test $0, %bx
0 2 1 2 ------ Interrupt ------ ------ Interrupt ------
0 2 1 2 1002 test $0, %ax
0 2 1 2 1003 jne .acquire
0 2 1 2 1000 mov $1, %ax
1 2 0 2 1001 xchg %ax, mutex
1 2 0 2 1002 test $0, %ax
1 2 0 2 1003 jne .acquire
1 2 2 2 1004 mov count, %ax
1 2 2 0 ------ Interrupt ------ ------ Interrupt ------
1 2 2 0 1010 jgt .top
1 2 2 0 1011 halt
1 2 2 2 ----- Halt;Switch ----- ----- Halt;Switch -----
1 2 3 2 1005 add $1, %ax
1 3 3 2 1006 mov %ax, count
0 3 3 2 1007 mov $0, mutex
0 3 3 1 1008 sub $1, %bx
0 3 3 1 1009 test $0, %bx
0 3 3 1 ------ Interrupt ------ ------ Interrupt ------
0 3 3 1 1010 jgt .top
0 3 1 1 1000 mov $1, %ax
1 3 0 1 1001 xchg %ax, mutex
1 3 0 1 1002 test $0, %ax
1 3 0 1 1003 jne .acquire
1 3 3 1 1004 mov count, %ax
1 3 4 1 1005 add $1, %ax
1 3 4 1 ------ Interrupt ------ ------ Interrupt ------
1 4 4 1 1006 mov %ax, count
0 4 4 1 1007 mov $0, mutex
0 4 4 0 1008 sub $1, %bx
0 4 4 0 1009 test $0, %bx
0 4 4 0 1010 jgt .top
0 4 4 0 1011 halt
可以看到,我们设置的时钟中断频率就会导致CPU的使用率不高,这样会导致CPU执行很多次自旋等待的操作。当每次中断发生在当前线程开锁之前时,将会导致中断后执行的线程一直进行自旋而无法执行相应的操作,导致CPU利用率低,要使CPU利用率得到提高,需要在当前线程释放锁之后再执行中断。
第七题
输入指令:./x86.py -p test-and-set.s -i 10 -R ax,bx -M mutex,count -a bx=5 -P 000011111111 -c模拟在第一个线程中获取锁之后在尝试第二个线程中获取锁的过程:
mutex count ax bx Thread 0 Thread 1
0 0 0 5
0 0 1 5 1000 mov $1, %ax
1 0 0 5 1001 xchg %ax, mutex
1 0 0 5 1002 test $0, %ax
1 0 0 5 1003 jne .acquire
1 0 0 5 ------ Interrupt ------ ------ Interrupt ------
1 0 1 5 1000 mov $1, %ax
1 0 1 5 1001 xchg %ax, mutex
1 0 1 5 1002 test $0, %ax
1 0 1 5 1003 jne .acquire
1 0 1 5 1000 mov $1, %ax
1 0 1 5 1001 xchg %ax, mutex
1 0 1 5 1002 test $0, %ax
1 0 1 5 1003 jne .acquire
1 0 0 5 ------ Interrupt ------ ------ Interrupt ------
1 0 0 5 1004 mov count, %ax
1 0 1 5 1005 add $1, %ax
1 1 1 5 1006 mov %ax, count
0 1 1 5 1007 mov $0, mutex
可以看到,当第一个线程获取锁之后,随后在另一个线程中尝试获取锁是无法成功,也无法进入临界区执行代码,只有在当前拥有锁的线程释放锁之后,另一个线程才能成功获取锁,由此说明这个锁具有使未获得锁的线程进入临界区的功能,正确的事情发生了。
还需要测试的是当拥有锁的线程释放锁之后,另一个线程再获取锁是否能够成功
下面的程序运行结果就可以验证这个结论是否正确:
1 0 0 5 ------ Interrupt ------ ------ Interrupt ------
1 0 0 5 1004 mov count, %ax
1 0 1 5 1005 add $1, %ax
1 1 1 5 1006 mov %ax, count
0 1 1 5 1007 mov $0, mutex
0 1 1 5 ------ Interrupt ------ ------ Interrupt ------
0 1 1 5 1000 mov $1, %ax
1 1 0 5 1001 xchg %ax, mutex
1 1 0 5 1002 test $0, %ax
1 1 0 5 1003 jne .acquire
1 1 1 5 1004 mov count, %ax
1 1 2 5 1005 add $1, %ax
1 2 2 5 1006 mov %ax, count
0 2 2 5 1007 mov $0, mutex
0 2 1 5 ------ Interrupt ------ ------ Interrupt ------
0 2 1 4 1008 sub $1, %bx
0 2 1 4 1009 test $0, %bx
0 2 1 4 1010 jgt .top
0 2 1 4 1000 mov $1, %ax
0 2 2 5 ------ Interrupt ------ ------ Interrupt ------
0 2 2 4 1008 sub $1, %bx
0 2 2 4 1009 test $0, %bx
0 2 2 4 1010 jgt .top
0 2 1 4 1000 mov $1, %ax
1 2 0 4 1001 xchg %ax, mutex
1 2 0 4 1002 test $0, %ax
1 2 0 4 1003 jne .acquire
1 2 2 4 1004 mov count, %ax
这说明这个锁在释放之后,其他线程可以获取锁,锁的功能正常。
综上,我们可以知道这个锁的设计是成功的,不仅可以满足在一个进程拥有锁的时候,另一个进程无法进入,同时在一个进程释放锁的时候,另一个进程可以拥有锁,完成了锁的互斥性。