MP(多CPU)同步原语代码示例
----引自《现代体系结构上的UNIX系统》
为了便于对示例的展开,我们首先假定一个场景:
内核中把检测到的错误信息记录到一个磁盘文件中的情形。出错信息通过内存中的一个队列来传递给日志进程(logging process)的。
当出现一个错误时,就在队列中加入一项,并且通过调用函数log_error 通知日志进程。出错日志进程接着把队列中的项写到磁盘上。
这就使得碰到错误的进程不必等候I/O完成或者或得为了向文件执行I/O而可能需要的任何锁,并且避免了任何可能的上锁次序问题。
代码1, 采用事件计数的出错日志通知机制
日志进程
log_error(error) |
{ | for(next=1; ; next++) {
lock(&err_queue); | await(&err_event, next);
把出错信息加入到队列 | lock(&err_queue);
unlock(&err_queue); | 从队列中删除项
advance(&err_event); | unlock(&err_queue);
} | write error to disk
| }
队列本身由一个自旋锁来保护。在本例中,事件计数只用于同步的目的,并不提供互斥。
在试用事件计数的时候,advance操作会永久性地改变事件计数的状态。advance和await操作的相对时序没有关系。
代码2, 采用同步变量的出错日志通知机制
日志进程
log_error(error) |
{ | for(;;){
lock(&err_queue); | lock(&err_queue);
把出错信息加入到队列 | if (queue_empty){
SV_SIGNAL(&err_syncvar, 0); | SV_WAIT(&err_syncvar, PRI, &err_queue);
unlock(&err_queue); | lock(&err_queue);
} | }
| 从队列中删除项
| unlock(&err_queue);
| 把错误写入磁盘
| }
因为同步变量自身没有保留状态,所以当日志进程测试队列的状态并决定是等待一项还是从队列中删除一项的时候,必须占有自旋锁。类似地,log_error在
发送信号时也必须占有自旋锁。注,SV_WAIT将释放自旋锁,并且阻塞日志进程,SV_SIGNAL到后从阻塞处继续执行。
代码3, 采用管程的出错日志通知机制
日志进程
log_error(error) | for(;;){
{ | mon_enter(&err_mon);
mon_enter(&err_mon); | if (queue empty)
把出错信息加入到队列 | mon_wait(&err_mon, NEWENTRY);
|
mon_signal(&err_mon, NEWENTRY); | 从队列中删除项
mon_exit(&err_mon); | mon_exit(&err_mon);
} | 把错误写入磁盘
| }
代码4, 采用信号量的出错日志通知机制
日志进程
log_error(error) | for(;;){
{ | P(&err_sema);
lock(&err_queue); | lock(&err_queue);
把出错信息加入到队列 | 从队列中删除项
unlock(err_queue); | unlock(&err_queue);
V(&err_sema); | 把错误写入磁盘
} | }