Questions about mutex implementation in kern/kern_mutex.c

John Baldwin jhb at freebsd.org
Fri Sep 17 21:11:23 UTC 2010


On Friday, September 17, 2010 1:42:44 pm Andrey Simonenko wrote:
> On Thu, Sep 16, 2010 at 02:16:05PM -0400, John Baldwin wrote:
> > On Thursday, September 16, 2010 1:33:07 pm Andrey Simonenko wrote:
> > > The mtx_owned(9) macro uses this property, mtx_owned() does not use anything
> > > special to compare the value of m->mtx_lock (volatile) with current thread
> > > pointer, all other functions that update m->mtx_lock of unowned mutex use
> > > compare-and-set instruction.  Also I cannot find anything special in
> > > generated Assembler code for volatile variables (except for ia64 where
> > > acquire loads and release stores are used).
> > 
> > No, mtx_owned() is just not harmed by the races it loses.  You can certainly 
> > read a stale value of mtx_lock in mtx_owned() if some other thread owns the 
> > lock or has just released the lock.  However, we don't care, because in both 
> > of those cases, mtx_owned() returns false.  What does matter is that 
> > mtx_owned() can only return true if we currently hold the mutex.  This works 
> > because 1) the same thread cannot call mtx_unlock() and mtx_owned() at the 
> > same time, and 2) even CPUs that hold writes in store buffers will snoop their 
> > store buffer for local reads on that CPU.  That is, a given CPU will never 
> > read a stale value of a memory word that is "older" than a write it has 
> > performed to that word.
> 
> Looks like I understand the logic why mtx_owned() works correctly when
> mtx_lock is present in CPU cache or is absent in CPU cache.  The mtx_lock
> value definitely can say whether lock is held by the current thread, but
> it cannot say whether it is unowned or is owned by another thread.
> 
> Let me ask another one question about memory barriers and thread migration.
> 
> Let a thread locked a mutex, modified shared data protected by this mutex
> and was migrated from CPU1 to CPU2 (mutex is still locked).  In this scenario
> just migrated thread will not see stale data for a mutex itself (the
> m->mtx_lock value) and for shared data on CPU2 because when it was migrated
> from CPU1 there was at least one unlock call for some another mutex that had
> release semantics and appropriate memory barrier instruction was run
> implicitly or explicitly.  As a result this "rel" memory barrier made all
> modifications from CPU1 visible on another CPUs.  When CPU2 switched to just
> migrated thread there was at least on lock call for some another mutex with
> acquire semantics, so "rel/acq" memory barriers pair works here together.
> (Also I consider case when CPU2 did not work with that mutex, but worked
> with its memory before.  Some thread on CPU2 could allocate some memory,
> worked with it and freed it.  Later the same part of memory was allocated
> by a thread on CPU1 for mutex).
> 
> Is the above written description correct?

Yes.

> > > There are some places in the kernel where a variable is updated in
> > > something like "do { v = value; } while (!atomic_cmpset_int(&value, ...));"
> > > and that variable is not "volatile", but the compiler generates correct
> > > Assembler code.  So "volatile" is not a requirement for all cases.
> > 
> > Hmm, I suspect that many of those places actually do use volatile.  The 
> > various lock cookies (mtx_lock, etc.) are declared volatile in the structure.  
> > Otherwise the compiler would be free to conclude that 'v = value;' is a loop 
> > invariant and move it out of the loop which would break.  Given that, the 
> > construct you referred to does in fact require 'value' to be volatile.
> 
> I checked Assembler code for these functions:
> 
> kern/subr_msgbuf.c:msgbuf_addchar()
> vm/vm_map.c:vmspace_free()

They may happen to accidentally work because atomic_cmpset() clobbers all of
memory, but these should be marked volatile.

Index: vm/vm_map.c
===================================================================
--- vm/vm_map.c	(revision 212801)
+++ vm/vm_map.c	(working copy)
@@ -343,10 +343,7 @@
 	if (vm->vm_refcnt == 0)
 		panic("vmspace_free: attempt to free already freed vmspace");
 
-	do
-		refcnt = vm->vm_refcnt;
-	while (!atomic_cmpset_int(&vm->vm_refcnt, refcnt, refcnt - 1));
-	if (refcnt == 1)
+	if (atomic_fetchadd_int(&vm->vm_refcnt, -1) == 1)
 		vmspace_dofree(vm);
 }
 
Index: vm/vm_map.h
===================================================================
--- vm/vm_map.h	(revision 212801)
+++ vm/vm_map.h	(working copy)
@@ -237,7 +237,7 @@
 	caddr_t vm_taddr;	/* (c) user virtual address of text */
 	caddr_t vm_daddr;	/* (c) user virtual address of data */
 	caddr_t vm_maxsaddr;	/* user VA at max stack growth */
-	int	vm_refcnt;	/* number of references */
+	volatile int vm_refcnt;	/* number of references */
 	/*
 	 * Keep the PMAP last, so that CPU-specific variations of that
 	 * structure on a single architecture don't result in offset
Index: sys/msgbuf.h
===================================================================
--- sys/msgbuf.h	(revision 212801)
+++ sys/msgbuf.h	(working copy)
@@ -38,7 +38,7 @@
 #define	MSG_MAGIC	0x063062
 	u_int	msg_magic;
 	u_int	msg_size;		/* size of buffer area */
-	u_int	msg_wseq;		/* write sequence number */
+	volatile u_int msg_wseq;	/* write sequence number */
 	u_int	msg_rseq;		/* read sequence number */
 	u_int	msg_cksum;		/* checksum of contents */
 	u_int	msg_seqmod;		/* range for sequence numbers */

-- 
John Baldwin


More information about the freebsd-hackers mailing list