Chinaunix首页 | 论坛 | 博客
  • 博客访问: 941305
  • 博文数量: 173
  • 博客积分: 3436
  • 博客等级: 中校
  • 技术积分: 1886
  • 用 户 组: 普通用户
  • 注册时间: 2009-01-07 09:29
文章分类

全部博文(173)

文章存档

2016年(6)

2015年(10)

2014年(14)

2013年(8)

2012年(36)

2011年(63)

2010年(19)

2009年(17)

分类: BSD

2012-02-06 23:06:50

独占锁的管理主要由三个函数组成:sx_xlock()、sx_try_xlock()和sx_xunlock()。
函数void sx_xlock(struct sx *sx):
mtx_lock(sx->sx_lock);
while (sx->sx_cnt != 0) {
sx->sx_excl_wcnt++;
cv_wait(&sx->sx_excl_cv, sx->sx_lock);
sx->sx_excl_wcnt--;
}
sx->sx_cnt--;
sx->sx_xholder = curthread;
mtx_unlock(sx->sx_lock);
该 函数申请一个独占锁,在互斥体sx_lock的保护下,如果sx_cnt不等于0,则说明该锁正被独占或者共享占有,因此,将独占等待计数器 sx_excl_wcnt加1,调用cv_wait()函数等待条件变量sx_excl_cv满足条件。当条件满足后,独占等待计数器 sx_excl_wcnt减1,sx_cnt计数器减1,说明现在被独占,将sx_xholder成员指向当前申请独占锁的线程地址。
函数int sx_try_xlock(struct sx *sx):
mtx_lock(sx->sx_lock);
if (sx->sx_cnt == 0) {
sx->sx_cnt--;
sx->sx_xholder = curthread;
mtx_unlock(sx->sx_lock);
return (1);
} else {
mtx_unlock(sx->sx_lock);
return (0);
}
该函数试图获得独占锁,如果可以则将sx_cnt计数器为0,则说明该锁是自由的,可以申请独占锁,返回非零值,表示成功;如果不能,则说明该锁正被独占或是共享,直接返回0,表示失败。
函数void sx_xunlock(struct sx *sx):
mtx_lock(sx->sx_lock);
sx->sx_cnt++;
sx->sx_xholder = NULL;
if (sx->sx_shrd_wcnt > 0)
cv_broadcast(&sx->sx_shrd_cv);
else if (sx->sx_excl_wcnt > 0)
cv_signal(&sx->sx_excl_cv);
mtx_unlock(sx->sx_lock);
该 函数释放独占锁,将sx_cnt加1,恢复到自由状态(sx_cnt为0),断开sx_xholder的指针链接。如果有共享锁申请等待队列 (sx_shrd_wcnt大于0),则发一个广播信号,唤醒所有等待在条件变量sx_shrd_cv上的线程;如果没有共享锁申请,则检查是否有其他独 占锁申请(sx_excl_wcnt大于0),如果有,则发一个信号,唤醒这个等待队列的第一个线程。
另外,共享/独占锁支持相互的转换,即共享锁可以转换为独占锁,独占锁也可以转换为共享锁。这是通过函数sx_try_upgrade()和sx_downgrade()实现的。
函数int sx_try_upgrade(struct sx *sx):
mtx_lock(sx->sx_lock);
if (sx->sx_cnt == 1) {
sx->sx_cnt = -1;
sx->sx_xholder = curthread;
mtx_unlock(sx->sx_lock);
return (1);
} else {
mtx_unlock(sx->sx_lock);
return (0);
}
该函数实现试图将共享锁转换成独占锁。在sx_lock互斥体的保护下,如果当前有且仅有一个共享锁,则可以转换,转换成功后,返回1,表示成功;否则返回0,表示失败。
函数void sx_downgrade(struct sx *sx):
mtx_lock(sx->sx_lock);
sx->sx_cnt = 1;
sx->sx_xholder = NULL;
if (sx->sx_shrd_wcnt > 0)
cv_broadcast(&sx->sx_shrd_cv);
mtx_unlock(sx->sx_lock);
该 函数实现将独占锁转换成共享锁。因为在独占的情况下,是不可能有共享的情况存在。因此,直接将sx_cnt变为1,说明现在有一个共享锁,断开独占锁的线 程链接。如果还有其他共享锁申请,由于该锁原来是独占而被阻塞,因此,需要发一个广播信号,通知阻塞线程队列,现在可以共享。
5 信号灯(semaphore)
信 号灯统计机制为访问一组资源(资源池)提供了一种同步机制。不同于互斥体,信号灯并没有持有者的概念,因此,信号灯常用于一个线程需要一个资源,而另一个 线程需要释放该资源的情况。记录资源(释放资源provider,该资源的信号灯加1)总是成功,而等待资源(申请资源consumer,该资源信号灯减 1)只有在信号灯大于0的情况才能成功。信号灯的管理比互斥体复杂许多,因此花费更大,所以其效率不高。
5.1 信号灯数据结构
struct sema {
struct mtx sema_mtx; /* General protection lock. */
struct cv sema_cv; /* Waiters. */
int sema_waiters; /* Number of waiters. */
int sema_value; /* Semaphore value. */
};
成员sema_mtx用于保护对sema结构的操作。成员sema_cv是等待信号灯的条件变量,成员sema_waiters统计等待信号灯的线程数目。成员sema_value记录信号灯的值。
5.2 信号灯的函数
函数void sema_init(struct sema *sema, int value, const char *description):
bzero(sema, sizeof(*sema));
mtx_init(&sema->sema_mtx, description, "sema backing lock",
MTX_DEF | MTX_NOWITNESS | MTX_QUIET);
cv_init(&sema->sema_cv, description);
sema->sema_value = value;
该函数用于初始化一个信号灯。初始化该sema的内部互斥体sema_mtx和条件变量sema_cv。而信号灯计数器sema_value的值则是通过入参value获得。
函数void sema_destroy(struct sema *sema):
mtx_destroy(&sema->sema_mtx);
cv_destroy(&sema->sema_cv);
该函数用于销毁一个信号灯。该函数包含销毁互斥体sema_mtx和条件变量sema_cv。
函数void sema_post(struct sema *sema):
mtx_lock(&sema->sema_mtx);
sema->sema_value++;
if (sema->sema_waiters && sema->sema_value > 0)
cv_signal(&sema->sema_cv);
mtx_unlock(&sema->sema_mtx);
该函数用于增加信号灯计数器sema_value的值,如果目前有其他线程阻塞于该信号灯,并且该信号灯计数器大于0。则发一个信号,唤醒等待该信号灯队列的第一个线程。
函数void sema_wait(struct sema *sema):
mtx_lock(&sema->sema_mtx);
while (sema->sema_value == 0) {
sema->sema_waiters++;
cv_wait(&sema->sema_cv, &sema->sema_mtx);
sema->sema_waiters--;
}
sema->sema_value--;
mtx_unlock(&sema->sema_mtx);
该 函数申请得到一个信号灯。如果该信号灯的计数器sema_value为0,则该信号灯代表的资源不可再申请,则将该线程加入sema_cv条件变量的等待 队列中,同时将该信号灯的等待计数器加1。当得到该信号灯后,将等待计数器sema_waiters减1,同时将该信号灯计数器sema_value减 1,说明该信号灯代表的资源已经被申请了一次。
函数 int sema_timedwait(struct sema *sema, int timo):
mtx_lock(&sema->sema_mtx);
for (timed_out = 0; sema->sema_value == 0 && timed_out == 0Wink {
sema->sema_waiters++;
timed_out = cv_timedwait(&sema->sema_cv, &sema->sema_mtx, timo);
sema->sema_waiters--;
}
if (sema->sema_value > 0) {
sema->sema_value--;
ret = 1;
} else {
ret = 0;
}
mtx_unlock(&sema->sema_mtx);
该函数和上一个函数功能一样,只不过它有等待时间的限定。如果成功获得该信号灯,则返回1。如果不成功,则返回0。
函数int sema_trywait(struct sema *sema):
mtx_lock(&sema->sema_mtx);
if (sema->sema_value > 0) {
sema->sema_value--;
ret = 1;
} else {
ret = 0;
}
mtx_unlock(&sema->sema_mtx);
该函数试图获得一个信号灯,如果不能,立即返回0,能则返回1。
函数int sema_value(struct sema *sema):
mtx_lock(&sema->sema_mtx);
ret = sema->sema_value;
mtx_unlock(&sema->sema_mtx);
该函数用于获得信号灯的计数器值。因为信号灯的值随时有可能变化,因此,得到sema_value的值必须在sema_mtx的保护下。
6 lockmgr锁机制(lock)
lockmgr 锁提供了多重共享锁机制,支持共享锁向独占锁的转变功能。其等待lockmgr锁可用的过程,采用msleep机制(参考 msleep(9)) - 基于事件的进程阻塞机制。因此,lockmgr锁实现所谓的加锁/解锁,是通过lockmgr锁的上行(upgrade)/下行(downgrade)操 作,这是前面提及的锁机制的策略变化。和前面锁机制不同的另一个方面是,其调用者是进程。
6.1 lockmgr锁的数据结构
我们略过DEBUG相关的调试成员。
struct lock {
struct mtx *lk_interlock; /* lock on remaining fields */
u_int lk_flags; /* see below */
int lk_sharecount; /* # of accepted shared locks */
int lk_waitcount; /* # of processes sleeping for lock */
short lk_exclusivecount; /* # of recursive exclusive locks */
short lk_prio; /* priority at which to sleep */
const char *lk_wmesg; /* resource sleeping (for tsleep) */
int lk_timo; /* maximum sleep time (for tsleep) */
pid_t lk_lockholder; /* pid of exclusive lock holder */
struct lock *lk_newlock; /* lock taking over this lock */
}
成员lk_interlock用户保护lock结构内部成员的操作,我们稍后结合lockmgr锁的初始化再讨论。
成员lk_flags在初始化的时候代表了该锁的类型,在通过lockmgr管理该锁时,也可以作为入参表示其期望的操作。
#define LK_TYPE_MASK 0x0000000f /* type of lock sought */
#define LK_SHARED 0x00000001 /* shared lock */
#define LK_EXCLUSIVE 0x00000002 /* exclusive lock */
#define LK_UPGRADE 0x00000003 /* shared-to-exclusive upgrade */
#define LK_EXCLUPGRADE 0x00000004 /* first shared-to-exclusive upgrade */
#define LK_DOWNGRADE 0x00000005 /* exclusive-to-shared downgrade */
#define LK_RELEASE 0x00000006 /* release any type of lock */
#define LK_DRAIN 0x00000007 /* wait for all lock activity to end */
#define LK_EXCLOTHER 0x00000008 /* other process holds lock */
LK_SHARED:一个进程获得一个共享lockmgr锁,如果该进程独占持有该lockmgr锁,则需要下行处理,使其变为共享锁。
LK_EXCLUSIVE: 当lockmgr锁被独占时,是不允许共享申请的。如果现在有共享存在,则需要将其上行处理为独占。通常仅有一个独占状态存在,但是,一个持有独占 lockmgr锁的进程,在其设置了LK_CANRECURSE标志的情况下,可以申请额外的独占锁。
LK_UPGRADE:一个持有共享状态的lockmgr锁可以通过上行处理,将共享态变为独占态。我们需要在意的是,在上行处理的过程中,其它进程也同样有机会得到独占机会。
LK_EXCLUPGRADE:和LK_UPGRADE意义一样,只不过在上行过程中,其它进程不可能获得独占机会。
LK_DOWNGRADE:一个独占持有lockmgr锁的进程,可以通过下行处理,将其转换为共享状态。如果该进程持有递归独占锁,那么所有的独占状态都需要下行处理。
LK_RELEASE:释放一个lockmgr锁实体。
LK_DRAIN:这用于等待该锁所有活动状态结束,并标识为退役,一般用在释放锁之前。
LK_EXCLOTHER:一般用在函数lockstatus()返回里,说明该锁正被其他进程独占。
关于这些标识,我们在后面结合函数更详细的说明。
成员lk_sharecount统计该锁已被接受的共享状态的数目。
成员lk_waitcount是阻塞于该锁的进程数目。
成员lk_exclusivecount说明了递归独占该锁的数目。
成员lk_prio,如果某个进程不能获得该锁,而不得不sleep,那么它应该sleep在什么优先级的队列上,是由该成员指出的。
成员lk_wmesg是sleep的说明性文字。
成员lk_timo说明了该进程最大的sleep时间。
成员lk_lockholder,如果该锁被某个进程独占,该成员说明了该进程ID。
成员lk_newlock,如果该锁被其他锁托管,则该成员指向托管锁的地址。
6.2 lockmgr锁的函数接口
函数void lockinit(struct lock *lkp, int prio, const char *wmesg, int timo, int flags):
初始化一个lockmgr锁。
if (lock_mtx_valid == 0) {
mtx_init(&lock_mtx, "lockmgr", NULL, MTX_DEF);
lock_mtx_valid = 1;
}
这 段代码的目的是初始化全局互斥体lock_mtx。通常,该段代码永远不会调用。因为,在系统启动时,通过宏SYSINIT(lmgrinit, SI_SUB_LOCK, SI_ORDER_FIRST, lockmgr_init, NULL)注册lock子系统时,在lockmgr_init()函数中已经初始化了lock_mtx,并且将lock_mtx_valid设置为1了。
if (mtx_pool_valid) {
mtx_lock(&lock_mtx);
lkp->lk_interlock = mtx_pool_alloc();
mtx_unlock(&lock_mtx);
} else {
lkp->lk_interlock = &lock_mtx;
}
通 过这段代码,我们可以明白lock_mtx的作用,如果系统支持互斥体池,则lock_mtx用于保护从互斥体池中获得互斥体A的过程,由互斥体A来保护 该lockmgr锁的操作;如果系统不支持互斥体,由lock_mtx充当lockmgr锁的内部成员操作的保护机制。
lkp->lk_flags = (flags & LK_EXTFLG_MASK);
lkp->lk_sharecount = 0;
lkp->lk_waitcount = 0;
lkp->lk_exclusivecount = 0;
lkp->lk_prio = prio;
lkp->lk_wmesg = wmesg;
lkp->lk_timo = timo;
lkp->lk_lockholder = LK_NOPROC;
lkp->lk_newlock = NULL;
初始化lock结构的其他成员,lk_flags 、lk_prio、lk_wmesg和lk_timo由入参指定。
函数void lockdestroy(struct lock *lkp):
该函数用于销毁一个lockmgr锁,但是目前没有任何操作。
函数int lockcount(struct lock *lkp):
mtx_lock(lkp->lk_interlock);
count = lkp->lk_exclusivecount + lkp->lk_sharecount;
mtx_unlock(lkp->lk_interlock);
该函数用于统计目前共享和独占持有该锁的数目。
函数int lockstatus(struct lock *lkp, struct thread *td):
mtx_lock(lkp->lk_interlock);
if (lkp->lk_exclusivecount != 0) {
if (td == NULL || lkp->lk_lockholder == td->td_proc->p_pid)
lock_type = LK_EXCLUSIVE;
else
lock_type = LK_EXCLOTHER;
} else if (lkp->lk_sharecount != 0)
lock_type = LK_SHARED;
mtx_unlock(lkp->lk_interlock);
该 函数用于得到目前锁的状态。如果存在递归调用独占状态,则检查入参td是否为空,如果为空,或是不为空,并且该线程的进程ID和 lk_lockholder一样,则说明该锁被当前进程占有;否则该锁被其它进程独占。如果不存在独占,如果共享统计不为0,则说明该锁被共享占有。如果 什么都不是,则说明该锁是自由的,返回0。
函数int lockmgr(struct lock *lkp, u_int flags, struct mtx *interlkp, struct thread *td):
该函数是lockmgr锁里最关键、最复杂的函数,是lock机制管理的核心。
error = 0;
if (td == NULL)
pid = LK_KERNPROC;
else
pid = td->td_proc->p_pid;
mtx_lock(lkp->lk_interlock);
if (flags & LK_INTERLOCK) {
mtx_unlock(interlkp);
}
这段代码根据td的入参,设定pid的值,根据输入的flags是否含有LK_INTERLOCK值,决定是否需要输入的互斥体interlkp的保护。如果指明了LK_INTERLOCK,则说明就用lk_interlock保护就够了。
extflags = (flags | lkp->lk_flags) & LK_EXTFLG_MASK;
该语句用于分离lock的额外标志。这里有必要介绍一下lock的额外标志:
#define LK_NOWAIT 0x00000010 /* do not sleep to await lock */
#define LK_SLEEPFAIL 0x00000020 /* sleep, then return failure */
#define LK_CANRECURSE 0x00000040 /* allow recursive exclusive lock */
#define LK_REENABLE 0x00000080 /* lock is be reenabled after drain */
#define LK_NOPAUSE 0x01000000 /* no spinloop */
#define LK_TIMELOCK 0x02000000 /* use lk_timo, else no timeout */
LK_NOWAIT、 LK_SLEEPFAIL和LK_CANRECURSE通常实在lockinit()时设置的。当一个lockmgr锁已经设置了LK_DRAIN 后,说明该锁准备释放,但是可以通过LK_REENABLE,重新使用该锁。LK_NOPAUSE说明该锁不支持自旋等待。LK_TIMELOCK说明该 锁支持等待时间的设定,具体时间是通过lk_timo获得。
函数lockmgr()完成预处理后,主要是通过一个很大的switch (flags & LK_TYPE_MASK)语句来处理不同的管理要求。下面我们根据入参flags含有的标识来说明:
LK_SHARED:
if (lkp->lk_lockholder != pid) {
lockflags = LK_HAVE_EXCL;
mtx_lock_spin(&sched_lock);
if (td != NULL && !(td->td_flags & TDF_DEADLKTREAT))
lockflags |= LK_WANT_EXCL | LK_WANT_UPGRADE;
mtx_unlock_spin(&sched_lock);
error = acquire(&lkp, extflags, lockflags);
if (error)
break;
sharelock(lkp, 1);
break;
}
sharelock(lkp, 1);
LK_DOWNGRADE:
sharelock(lkp, lkp->lk_exclusivecount);
lkp->lk_exclusivecount = 0;
lkp->lk_flags &= ~LK_HAVE_EXCL;
lkp->lk_lockholder = LK_NOPROC;
if (lkp->lk_waitcount)
wakeup((void *)lkp);
break;
这段代码由两个部分组成,如果是LK_SHARED的标识,则需要执行这正段代码;如果是LK_DOWNGRADE,则只用执行后半段。
我们考虑LK_SHARED的情况,函数sharelock(struct lock *lkp, int incr)就是将LK_SHARE_NONZERO赋给该lkp的lk_flags成员,并将lk_sharecount加incr。
在什么情况下,lkp->lk_lockholder != pid不成立呢?
只 有在该锁lkp正被当前进程独占的情况下,lkp->lk_lockholder才等于pid。这时,根据LK_SHARED的意义,我们不仅需要 调用增加这次共享请求的计数(sharelock函数),而且需要下行处理,故而需要执行LK_DOWNGRADE的功能。将所有独占态改变为共享态,并 试图唤醒所有阻塞于该lkp的所有进程。
当上述判断成立,则有两种情况:A、该锁本就是共享态;B、该锁被其他进程独占。无论是哪种情况,都涉及到lockmgr锁的内部函数acquire(),因此,有必要在这里插入对该函数的分析。
函数static int acquire(struct lock **lkpp, int extflags, int wanted):
if ((extflags & LK_NOWAIT) && (lkp->lk_flags & wanted)) {
return EBUSY;
}
if (((lkp->lk_flags | extflags) & LK_NOPAUSE) == 0) {
error = apause(lkp, wanted);
if (error == 0)
return 0;
}
如果该锁的额外标识了不必sleep来等待获得该锁(LK_NOWAIT),并且该锁lk_flags标志含有想申请的值(wanted),则直接返回EBUSY,申请失败。
如果所有的标志都没有含有LK_NOPAUSE(不支持自旋等待),则调用apause()函数在解锁(lk_interlock)的情况下等待一段时间直到lk_flags不再含有wanted的标志,成功返回0,失败返回1。
我们考虑情况A,在这里就会成功返回。再考虑在lockmgr()函数里,会调用sharelock()完成LK_SHARED的设置。
函数acquire()余下的部分是等待该锁可用(lk_flags不再含有wanted的标志)。所有的等待操作在splhigh的保护下进行(参考 splhigh(9))。下面考虑等待主体部分。
lkp->lk_flags |= LK_WAIT_NONZERO;
lkp->lk_waitcount++;
error = msleep(lkp, lkp->lk_interlock, lkp->lk_prio, lkp->lk_wmesg,
((extflags & LK_TIMELOCK) ? lkp->lk_timo : 0));
添加LK_WAIT_NONZERO标志到lk_flags中,等待计数器lk_waitcount加1,调用msleep等待lkp满足条件。
if (lkp->lk_waitcount == 1) {
lkp->lk_flags &= ~LK_WAIT_NONZERO;
lkp->lk_waitcount = 0;
} else {
lkp->lk_waitcount--;
}
if (error) {
splx(s);
return error;
}
当条件满足后,如果lk_waitcount为1,清除LK_WAIT_NONZERO标志,lk_waitcount计为0。如果lk_waitcount不为1,则只是lk_waitcount减1(有可能别的进程也在等待)。如果出错,则返回出错值。
if (extflags & LK_SLEEPFAIL) {
splx(s);
return ENOLCK;
}
如果设置了LK_SLEEPFAIL,则说明只要sleep了,就算出错,返回ENOLOCK。
if (lkp->lk_newlock != NULL) {
mtx_lock(lkp->lk_newlock->lk_interlock);
mtx_unlock(lkp->lk_interlock);
if (lkp->lk_waitcount == 0)
wakeup((void *)(&lkp->lk_newlock));
*lkpp = lkp = lkp->lk_newlock;
}
如果有其他锁托管了该锁,则做转换。
现在,我们已经了解了acquire()函数的功能,考虑情况B,则根据acquire()的返回,如果没出错,则说明,该锁的独占已经释放,可以共享占有,同样调用sharelock()函数实现共享。我们接下来分析lockmgr()函数switch中的其它情况。
根据LK_EXCLUPGRADE和LK_UPGRADE的区别,就是前者在上行处理时,多了独占的考虑。因此有一段预处理,如下:
LK_EXCLUPGRADE:
if (lkp->lk_flags & LK_WANT_UPGRADE) {
shareunlock(lkp, 1);
error = EBUSY;
break;
}
首 先判断lk_flags是否含有LK_WANT_UPGRADE标识,如果含有说明有其他进程试图上行处理该锁,那么调用shareunlock()函数 释放该锁,说明不能该进程不能LK_EXCLUPGRADE,返回EBUSY。如果该进程可以独占上行处理,余下的处理和一般上行处理一致。
LK_UPGRADE:
if ((lkp->lk_lockholder == pid) || (lkp->lk_sharecount <= 0))
panic("lockmgr: upgrade exclusive lock");
shareunlock(lkp, 1);
如果lk_lockholder等于pid,说明该锁被独占,或者是lk_sharecount小于0,说明该锁没有被共享占有,则说明出错。不出错,说明当前至少有一个共享锁,为了上行处理,先对该锁的共享占有解锁一次。
if ((extflags & LK_NOWAIT) && ((lkp->lk_flags & LK_WANT_UPGRADE) ||
lkp->lk_sharecount > 1)) {
error = EBUSY;
break;
}
如果额外标志包含LK_NOWAIT(不支持sleep等待该锁),并且lk_flags标志包含LK_WANT_UPGRADE(说明有其他进程正在上行处理)或者lk_sharecount大于1(说明不止一个共享状态),那么认为不能上行处理,返回EBUSY。
if ((lkp->lk_flags & LK_WANT_UPGRADE) == 0) {
lkp->lk_flags |= LK_WANT_UPGRADE;
error = acquire(&lkp, extflags, LK_SHARE_NONZERO);
lkp->lk_flags &= ~LK_WANT_UPGRADE;
if (error)
break;
lkp->lk_flags |= LK_HAVE_EXCL;
lkp->lk_lockholder = pid;
if (lkp->lk_exclusivecount != 0)
panic("lockmgr: non-zero exclusive count");
lkp->lk_exclusivecount = 1;
break;
}
如 果lk_falgs不包含LK_WANT_UPGRADE,则目前没有其他进程上行,本进程可以设置LK_WANT_UPGRADE,说明本进程正在上行 处理。然后,调用acquire()函数等待该锁可用,成功等待后,设置LK_HAVE_EXCL(说明获得独占态的锁),设置 lk_lockholder。最后判断lk_exclusivecount是否为0,不为0,则出现异常错误。为0,则完全成功返回。
就LK_EXCLUPGRADE而言,如果能获得,执行到这里,就可以成功。
if ( (lkp->lk_flags & (LK_SHARE_NONZERO|LK_WAIT_NONZERO)) ==
LK_WAIT_NONZERO)
wakeup((void *)lkp);
程序走到这一步,说明别的进程B在上行处理,通过该语句判断说明,本进程是最后一个持有共享态的该锁,由于在前面已经解锁了,因此可以唤醒进程B继续上行处理。
就LK_UPGRADE操作而言,如果执行到这一步,说明别的进程正在上行处理。无论别的进程在做什么,LK_UPGRADE余下的操作和LK_EXCLUSIVE操作并无二样。
LK_EXCLUSIVE:
if (lkp->lk_lockholder == pid && pid != LK_KERNPROC) {
if ((extflags & (LK_NOWAIT | LK_CANRECURSE)) == 0)
panic("lockmgr: locking against myself");
if ((extflags & LK_CANRECURSE) != 0) {
lkp->lk_exclusivecount++;
break;
}
}
如果该锁已经被本进程独占持有,而且该锁支持LK_CANRECURSE,只须将lk_exclusivecount计数器加1。
if ((extflags & LK_NOWAIT) && (lkp->lk_flags & (LK_HAVE_EXCL | LK_WANT_EXCL
| LK_WANT_UPGRADE | LK_SHARE_NONZERO))) {
error = EBUSY;
break;
}
如果该进程不支持等待该锁(LK_NOWAIT),并且该锁已经获得独占态、或正在上行处理、或共享态存在,则返回EBUSY,不能获得。
error = acquire(&lkp, extflags, (LK_HAVE_EXCL | LK_WANT_EXCL));
if (error)
break;
lkp->lk_flags |= LK_WANT_EXCL;
error = acquire(&lkp, extflags, LK_WANT_UPGRADE | LK_SHARE_NONZERO);
lkp->lk_flags &= ~LK_WANT_EXCL;
if (error)
break;
lkp->lk_flags |= LK_HAVE_EXCL;
lkp->lk_lockholder = pid;
if (lkp->lk_exclusivecount != 0)
panic("lockmgr: non-zero exclusive count");
lkp->lk_exclusivecount = 1;
break;
这段代码,第一次调用acquire()函数,等待其他进程独占该锁使用完成;第二次调用acquire()函数是等待共享使用锁的进程完成。当这些处理完后,说明独占获得该锁。
LK_RELEASE:
if (lkp->lk_exclusivecount != 0) {
if (lkp->lk_lockholder != pid && lkp->lk_lockholder != LK_KERNPROC) {
panic("lockmgr: pid %d, not %s %d unlocking", pid, "exclusive lock holder",
lkp->lk_lockholder);
}
if (lkp->lk_exclusivecount == 1) {
lkp->lk_flags &= ~LK_HAVE_EXCL;
lkp->lk_lockholder = LK_NOPROC;
lkp->lk_exclusivecount = 0;
} else {
lkp->lk_exclusivecount--;
}
} else if (lkp->lk_flags & LK_SHARE_NONZERO)
shareunlock(lkp, 1);
if (lkp->lk_flags & LK_WAIT_NONZERO)
wakeup((void *)lkp);
break;
首 先考虑该锁独占计数器lk_exclusivecount不为0(说明该锁被独占使用,不可能还有共享态存在),如果lk_lockholder不等于 pid,而且也不等于LK_KERNPROC,这是lockmgr锁规则不允许的,出错。如果该计数器lk_exclusivecount为1,无论该锁 是否支持递归调用,都是最后一个独占态,应该将该锁状态还原为初始态。如果该计数器lk_exclusivecount不为0,说明该锁被本进程递归调 用,而且还有其他独占态存在,只须将lk_exclusivecount减1就可以了。
考虑该锁被共享使用(LK_SHARE_NONZERO),调用shareunlock()函数处理就可以了。
如果该锁的lk_flags 标志含有LK_WAIT_NONZERO,说明有其他进程正阻塞于该锁,试图唤醒这些进程。处理完成。
LK_DRAIN:
if (lkp->lk_lockholder == pid)
panic("lockmgr: draining against myself");
error = acquiredrain(lkp, extflags);
if (error)
break;
lkp->lk_flags |= LK_DRAINING | LK_HAVE_EXCL;
lkp->lk_lockholder = pid;
lkp->lk_exclusivecount = 1;
break;
如果lk_lockholder等于pid,说明该锁正被本进程独占持有,而本进程不能等待本进程该锁独占活动状态结束,所以出错。
调用acquiredrain()函数等待该锁所有状态的操作完成。如果成功,则善后处理。
如果lockmgr的操作不是以上需求,则出错(switch语句的default处理)。
if ((lkp->lk_flags & LK_WAITDRAIN) && (lkp->lk_flags & (LK_HAVE_EXCL |
LK_WANT_EXCL | LK_WANT_UPGRADE |
LK_SHARE_NONZERO | LK_WAIT_NONZERO)) == 0) {
lkp->lk_flags &= ~LK_WAITDRAIN;
wakeup((void *)&lkp->lk_flags);
}
mtx_unlock(lkp->lk_interlock);
最后这段代码是上述操作成功完成后,最后处理部分。如果lk_flags标志含有LK_WAITDRAIN(说明有其他进程B等待结束锁的活动状态),并且所有的活动状态确实结束了,则唤醒进程B。
至此,lockmgr()函数的讨论结束。
函数void lockmgr_printinfo(lkp):
if (lkp->lk_sharecount)
printf(" lock type %s: SHARED (count %d)", lkp->lk_wmesg, lkp->lk_sharecount);
else if (lkp->lk_flags & LK_HAVE_EXCL)
printf(" lock type %s: EXCL (count %d) by pid %d",
lkp->lk_wmesg, lkp->lk_exclusivecount, lkp->lk_lockholder);
if (lkp->lk_waitcount > 0)
printf(" with %d pending", lkp->lk_waitcount);
该函数是用于打印lockmgr锁的当前信息,十分简单。
7. 关于FreeBSD5.0的锁机制再讨论
A、尽量使用sleep类型(MTX_DEF)的互斥体机制,目前自旋锁(MTX_SPIN)主要用于SMP调度方面。
B、在持有一个互斥体(不是Giant)的时候,不要使用tsleep(),因为在该函数中,除了Giant以外,不会释放任何其他互斥体。
C、当持有一个互斥体A(不包括Giant)的时候,在使用msleep()和cv_wait()函数的时候,应该将A作为入参传入。在这两个函数里,会一开始释放该互斥体A,在函数结束的时候,对互斥体A重新加锁。
D、尽量避免使用递归锁机制
阅读(1059) | 评论(0) | 转发(0) |
0

上一篇:FreeBSD内核锁机制一

下一篇:FreeBSD install

给主人留下些什么吧!~~