All of lore.kernel.org
 help / color / mirror / Atom feed
* [parisc-linux] [RFC] Alternative implementation of glibc spinlocks
@ 2005-07-02 13:59 Randolph Chung
       [not found] ` <200507020935.42389.mszick@morethan.org>
  2005-07-04  1:00 ` Carlos O'Donell
  0 siblings, 2 replies; 10+ messages in thread
From: Randolph Chung @ 2005-07-02 13:59 UTC (permalink / raw)
  To: parisc-linux

In order to satisfy the ldcw address alignment requirements of the
PA architecture, currently in glibc we implement locks in an
ingenious but rather difficult to manage way. We define a spinlock as
an array of 4 ints (16-bytes) at during lock time we select the
individual word that has correct alignment, and use that to do locking. 

This works, but experience has shown that it makes glibc implementation
and merging to upstream rather difficult. glibc is full of assumptions
that a spinlock is an "int", and, while it is getting better, there is
still a lot of code that assumes an uninitialized lock has the value "0"

There has been some proposals to redo our locking infrastructure. For
example, see willy's proposal at http://wiki.parisc-linux.org/Locks
This solves the "initialize to 0" problem, but it still makes glibc
implementation difficult because of the scalar lock assumption.

Attached is a compiled but untested code snippet that contains an
alternative proposal for how to handle spinlocks in glibc. Actual locks
are allocated dynamically and lazily. The exposed glibc lock type is
simply a "cookie" to the actual lock. This allows glibc to still
think of a lock as a scalar. Some obvious downsides are:

1) We are moving more compile-time work to run-time; requires memory
   allocation at run-time, potentially slow.
2) The pthread interface allows spinlock locking to fail, and this
   code may fail a locking operation if memory cannot be allocated,
   but most code probably don't handle this case and may deadlock
   or function incorrectly.
3) The code is not 64-bit clean, although that can be fixed if needed
   by making the lock cookie an index.

There are probably other problems. I *think* this code still works with
locks in shared memory, but I may be missing some cases.

Comments?

randolph
-- 
Randolph Chung
Debian GNU/Linux Developer, hppa/ia64 ports
http://www.tausq.org/


#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <assert.h>
#include <errno.h>

typedef int lock_t;

/* Exported interface */
int spin_init(lock_t *lock);
int spin_destroy(lock_t *lock);
int spin_lock(lock_t *lock);
int spin_unlock(lock_t *lock);
int spin_trylock(lock_t *lock);
int spin_is_locked(lock_t *lock);

/* Implementation */

/* The idea of this implementation is that each lock_t points to
 * a dynamically allocated, properly aligned lock.
 *
 * We manage these dynamically aligned locks as pages of locks. Each
 * lock page contains a header that contains a simple freelist of
 * lock objects.
 */

#define PAGESIZE 4096

struct lock_page;

/* An internal lock is a 16-byte structure that is allocated aligned
 * to a 16-byte boundary. The first word of the lock structure is
 * actually used to do ldcw. The other fields can be used for other
 * accounting purposes.
 */
struct internal_lock {
	int lockword;
	struct lock_page *page;
	short next;
	short pad1;
	int pad2;
};

/* Locks are allocated in pages; each page contains this header. */
struct lock_page_hdr {
	struct lock_page *next; /* Chain together allocated pages */
	short freelist; /* First available lock index in this page */
	/* Pad to 16-bytes, not really needed, but just to make
	 * it explicit.
	 */
	unsigned short pad1[3];
	int pad2;
};

#define LOCKS_PER_PAGE ((PAGESIZE - sizeof(struct lock_page_hdr))/sizeof(struct internal_lock))

struct lock_page {
	struct lock_page_hdr hdr;
	struct internal_lock locks[LOCKS_PER_PAGE];
};

/* Internal data */
static struct internal_lock __lock __attribute__((aligned(16))) = { 1, 0, 0, 0, 0 };
static struct lock_page *__lock_pages;

static int
__ldcw(void *a)
{
	int ret;
	__asm__ __volatile__("ldcw 0(%1),%0" : "=r" (ret) : "r" (a));
	return ret;
}

static void
__raw_spin_lock(struct internal_lock *l)
{
	while (__ldcw(&l->lockword))
		while (l->lockword == 0)
			/* spin */ ;
}

static void
__raw_spin_unlock(struct internal_lock *l)
{
	l->lockword = 0;
}

static lock_t
__allocate_lock(void)
{
	struct lock_page *p;
	short target;

	__raw_spin_lock(&__lock);
	for (p = __lock_pages; p != NULL; p = p->hdr.next)
		if (p->hdr.freelist >= 0)
			break;

	if (p == NULL) {
		void *m;
		int i;
		struct internal_lock *l;

		m = mmap(0, PAGESIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0);
		if (m == MAP_FAILED) {
			__raw_spin_unlock(&__lock);
			return ENOMEM;
		}
		p = (struct lock_page *)m;
		p->hdr.freelist = 0;

		/* Initialize lock linked list */
		for (i = 0; i < LOCKS_PER_PAGE; i++) {
			l = &p->locks[i];
			l->lockword = 1;
			l->page = p;
			l->next = i + 1;
		}
		l->next = -1;

		p->hdr.next = __lock_pages;
		__lock_pages = p;
	}

	assert(p && p->hdr.freelist >= 0);

	target = p->hdr.freelist;
	p->hdr.freelist = p->locks[target].next;
	p->locks[target].next = -1;
	
	__raw_spin_unlock(&__lock);

	return (int)&p->locks[target];
}

static int
__free_lock(lock_t lock)
{
	struct lock_page *p;
	struct internal_lock *l;

	__raw_spin_lock(&__lock);
	l = (struct internal_lock *)lock;
	p = l->page;

	l->next = p->hdr.freelist;
	p->hdr.freelist = l - &p->locks[0];
	__raw_spin_unlock(&__lock);

	return 0;
}

static struct internal_lock *
__find_lock(lock_t lock)
{
	return (struct internal_lock *)lock;
}

int spin_init(lock_t *lock)
{
	*lock = __allocate_lock();
	if (*lock == 0)
		return ENOMEM;
	return 0;
}

int spin_destroy(lock_t *lock)
{
	if (*lock)
		return __free_lock(*lock);
	return 0;
}

int spin_lock(lock_t *lock)
{
	struct internal_lock *l;

	/* lazy lock init */
	if (*lock == 0) {
		if (spin_init(lock) != 0)
			return EINVAL;
	}

	l = __find_lock(*lock);
	if (!l)
		return EINVAL;

	__raw_spin_lock(l);
	return 0;
}

int spin_unlock(lock_t *lock)
{
	struct internal_lock *l;

	/* Assume unallocated locks are unlocked */
	if (*lock == 0)
		return 0;

	l = __find_lock(*lock);
	if (!l) 
		return EINVAL;

	__raw_spin_unlock(l);
	return 0;
}

int spin_trylock(lock_t *lock)
{
	struct internal_lock *l;

	/* lazy lock init */
	if (*lock == 0) {
		if (spin_init(lock) != 0)
			return EINVAL;
	}

	l = __find_lock(*lock);
	if (!l)
		return EINVAL;

	return __ldcw(&l->lockword) != 0 ? 0 : EBUSY;
}

int spin_is_locked(lock_t *lock)
{
	struct internal_lock *l;

	/* Assume unallocated locks are unlocked */
	if (*lock == 0)
		return 0;

	l = __find_lock(*lock);
	if (l)
		return l->lockword == 0;

	/* Assume invalid locks are unlocked */
	return 0;
}
_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [parisc-linux] [RFC] Alternative implementation of glibc spinlocks
       [not found] ` <200507020935.42389.mszick@morethan.org>
@ 2005-07-02 15:23   ` Randolph Chung
  0 siblings, 0 replies; 10+ messages in thread
From: Randolph Chung @ 2005-07-02 15:23 UTC (permalink / raw)
  To: Michael S. Zick, parisc-linux

Please reply on list.

> 1) Your free-listing of locks does not appear to be thread safe;
>     I.E: Protected from concurrent changes.
> 
> But then, perhaps my mind is going and I just did not
> read the code closely enough.

They are protected by a spinlock.

> 2) How about an alternative implementation?
> Based on 4,096 byte pages;
> 
> Using the find_[first|last]_[zero|one] asm routines;
> 
> As I recall, they only go to 64 bits currently but only
> require 3 instructions per binary radix (add 6 instructions
> for 256 bits);
> 
> First 16 bytes of the page - your current header;
> Second 16 bytes of the page - bitmap of available locks;

Why do you need a bitmap?

> First bit of the bitmap represents the current header -
> use this to lock the page list (currently called free-list);
> 
> Second bit of the bitmap represents the bitmap -
> use this to lock the bitmap;

how do you use a bit to do locking?

> The remaining 254 bits/lock structures per page are usable.

randolph
_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [parisc-linux] [RFC] Alternative implementation of glibc spinlocks
  2005-07-02 13:59 [parisc-linux] [RFC] Alternative implementation of glibc spinlocks Randolph Chung
       [not found] ` <200507020935.42389.mszick@morethan.org>
@ 2005-07-04  1:00 ` Carlos O'Donell
  2005-07-04  1:59   ` Randolph Chung
  1 sibling, 1 reply; 10+ messages in thread
From: Carlos O'Donell @ 2005-07-04  1:00 UTC (permalink / raw)
  To: Randolph Chung; +Cc: parisc-linux

On Sat, Jul 02, 2005 at 06:59:39AM -0700, Randolph Chung wrote:
> 1) We are moving more compile-time work to run-time; requires memory
>    allocation at run-time, potentially slow.
> 2) The pthread interface allows spinlock locking to fail, and this
>    code may fail a locking operation if memory cannot be allocated,
>    but most code probably don't handle this case and may deadlock
>    or function incorrectly.
> 3) The code is not 64-bit clean, although that can be fixed if needed
>    by making the lock cookie an index.

Randolph,

This is awesome! This is taking the deferred ideas to the extreme! :)
I like the idea, but I don't think it will bake (See my comments about
lock serializing, and shared memory).
 
> There are probably other problems. I *think* this code still works with
> locks in shared memory, but I may be missing some cases.
> 
> Comments?

What about?

a) Lock serializing (essentially lock copying)
b) Lock copying with differing destination alignment.

Our current implementation only supports copying a lock to a destination
that has the same alignment. In reality this is virtually 100% of all the
cases.

Our current implementation also supports serializing the lock to disk
and reading it back again. I've seen some gnome code do weird things
with object state serialization, mind you the lock has a level of
indirection and the addresses are fixed up, in this case though that
sort of thing wouldn't work.

The cookie implementation trivially solves b), but a) is harder.

I believe that compiling and running gnome is the best test of lock
serializing and copying. There is probably a number of libstdc++ test
cases that can test this as well? Dave would know better there.
 
> randolph
> -- 
> Randolph Chung
> Debian GNU/Linux Developer, hppa/ia64 ports
> http://www.tausq.org/
> 
> 
> #include <stdio.h>
> #include <string.h>
> #include <sys/mman.h>
> #include <assert.h>
> #include <errno.h>
> 
> typedef int lock_t;
> 
> /* Exported interface */
> int spin_init(lock_t *lock);
> int spin_destroy(lock_t *lock);
> int spin_lock(lock_t *lock);
> int spin_unlock(lock_t *lock);
> int spin_trylock(lock_t *lock);
> int spin_is_locked(lock_t *lock);
> 
> /* Implementation */
> 
> /* The idea of this implementation is that each lock_t points to
>  * a dynamically allocated, properly aligned lock.
>  *
>  * We manage these dynamically aligned locks as pages of locks. Each
>  * lock page contains a header that contains a simple freelist of
>  * lock objects.
>  */
> 
> #define PAGESIZE 4096
> 
> struct lock_page;
> 
> /* An internal lock is a 16-byte structure that is allocated aligned
>  * to a 16-byte boundary. The first word of the lock structure is
>  * actually used to do ldcw. The other fields can be used for other
>  * accounting purposes.
>  */
> struct internal_lock {
> 	int lockword;
> 	struct lock_page *page;
> 	short next;
> 	short pad1;
> 	int pad2;
> };
> 
> /* Locks are allocated in pages; each page contains this header. */
> struct lock_page_hdr {
> 	struct lock_page *next; /* Chain together allocated pages */
> 	short freelist; /* First available lock index in this page */
> 	/* Pad to 16-bytes, not really needed, but just to make
> 	 * it explicit.
> 	 */
> 	unsigned short pad1[3];
> 	int pad2;
> };
> 
> #define LOCKS_PER_PAGE ((PAGESIZE - sizeof(struct lock_page_hdr))/sizeof(struct internal_lock))
> 
> struct lock_page {
> 	struct lock_page_hdr hdr;
> 	struct internal_lock locks[LOCKS_PER_PAGE];
> };
> 
> /* Internal data */
> static struct internal_lock __lock __attribute__((aligned(16))) = { 1, 0, 0, 0, 0 };
> static struct lock_page *__lock_pages;
> 
> static int
> __ldcw(void *a)
> {
> 	int ret;
> 	__asm__ __volatile__("ldcw 0(%1),%0" : "=r" (ret) : "r" (a));
> 	return ret;
> }
> 
> static void
> __raw_spin_lock(struct internal_lock *l)
> {
> 	while (__ldcw(&l->lockword))
> 		while (l->lockword == 0)
> 			/* spin */ ;
> }
> 
> static void
> __raw_spin_unlock(struct internal_lock *l)
> {
> 	l->lockword = 0;
> }

If __raw_spin_unlock is inlined in the same function with
__raw_spin_lock, we may need to force a memory barrier to keep the
ordering correct?
 
> static lock_t
> __allocate_lock(void)
> {
> 	struct lock_page *p;
> 	short target;
> 
> 	__raw_spin_lock(&__lock);
> 	for (p = __lock_pages; p != NULL; p = p->hdr.next)
> 		if (p->hdr.freelist >= 0)
> 			break;
> 
> 	if (p == NULL) {
> 		void *m;
> 		int i;
> 		struct internal_lock *l;
> 
> 		m = mmap(0, PAGESIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0);

I guess if this replaced the linuxthreads, or nptl implementation we can
assume that mmap is available and we are already relocated.


> 		if (m == MAP_FAILED) {
> 			__raw_spin_unlock(&__lock);
> 			return ENOMEM;
> 		}
> 		p = (struct lock_page *)m;
> 		p->hdr.freelist = 0;
> 
> 		/* Initialize lock linked list */
> 		for (i = 0; i < LOCKS_PER_PAGE; i++) {
> 			l = &p->locks[i];
> 			l->lockword = 1;
> 			l->page = p;
> 			l->next = i + 1;
> 		}
> 		l->next = -1;
> 
> 		p->hdr.next = __lock_pages;
> 		__lock_pages = p;
> 	}
> 
> 	assert(p && p->hdr.freelist >= 0);
> 
> 	target = p->hdr.freelist;
> 	p->hdr.freelist = p->locks[target].next;
> 	p->locks[target].next = -1;
> 	
> 	__raw_spin_unlock(&__lock);
> 
> 	return (int)&p->locks[target];
> }
>
> static int
> __free_lock(lock_t lock)
> {
> 	struct lock_page *p;
> 	struct internal_lock *l;
> 
> 	__raw_spin_lock(&__lock);
> 	l = (struct internal_lock *)lock;
> 	p = l->page;
> 
> 	l->next = p->hdr.freelist;
> 	p->hdr.freelist = l - &p->locks[0];
> 	__raw_spin_unlock(&__lock);
> 
> 	return 0;
> }
> 
> static struct internal_lock *
> __find_lock(lock_t lock)
> {
> 	return (struct internal_lock *)lock;
> }

Why this level of indirection? Have you thought about other indexing
methods other than the address of the lock?
 
> int spin_init(lock_t *lock)
> {
> 	*lock = __allocate_lock();
> 	if (*lock == 0)
> 		return ENOMEM;
> 	return 0;
> }
> 
> int spin_destroy(lock_t *lock)
> {
> 	if (*lock)
> 		return __free_lock(*lock);
> 	return 0;
> }
> 
> int spin_lock(lock_t *lock)
> {
> 	struct internal_lock *l;
> 
> 	/* lazy lock init */
> 	if (*lock == 0) {
> 		if (spin_init(lock) != 0)
> 			return EINVAL;
> 	}
> 
> 	l = __find_lock(*lock);
> 	if (!l)
> 		return EINVAL;
> 
> 	__raw_spin_lock(l);
> 	return 0;
> }
> 
> int spin_unlock(lock_t *lock)
> {
> 	struct internal_lock *l;
> 
> 	/* Assume unallocated locks are unlocked */
> 	if (*lock == 0)
> 		return 0;
> 
> 	l = __find_lock(*lock);
> 	if (!l) 
> 		return EINVAL;
> 
> 	__raw_spin_unlock(l);
> 	return 0;
> }
> 
> int spin_trylock(lock_t *lock)
> {
> 	struct internal_lock *l;
> 
> 	/* lazy lock init */
> 	if (*lock == 0) {
> 		if (spin_init(lock) != 0)
> 			return EINVAL;
> 	}
> 

See my comment below that we need a check for a lock in the initialized
state that has a "locked" state.

We need a check for:

if (*lock == 1) 
  {
    /* Initialize lock held */
  }

> 	l = __find_lock(*lock);
> 	if (!l)
> 		return EINVAL;
> 
> 	return __ldcw(&l->lockword) != 0 ? 0 : EBUSY;
> }
> 
> int spin_is_locked(lock_t *lock)
> {
> 	struct internal_lock *l;
> 
> 	/* Assume unallocated locks are unlocked */

How do you initialize a static lock to a "locked" state code? :)

> 	if (*lock == 0)
> 		return 0;
> 
> 	l = __find_lock(*lock);
> 	if (l)
> 		return l->lockword == 0;
> 
> 	/* Assume invalid locks are unlocked */
> 	return 0;
> }

What happens if you stick this lock in shared memory? Each thread in
each process will see a different lock?

I really think that Dave's implementation, being the simplest works in
99% of the cases, with the only restriction being that a copied lock
must be copied to a destination of the same alignment.

c.

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [parisc-linux] [RFC] Alternative implementation of glibc spinlocks
  2005-07-04  1:00 ` Carlos O'Donell
@ 2005-07-04  1:59   ` Randolph Chung
  2005-07-04  4:14     ` Carlos O'Donell
  0 siblings, 1 reply; 10+ messages in thread
From: Randolph Chung @ 2005-07-04  1:59 UTC (permalink / raw)
  To: Carlos O'Donell; +Cc: parisc-linux

> a) Lock serializing (essentially lock copying)

I still think "lock copying" is a fundamentally flawed idea, but I think
we can actually support this in a sneaky way.

All you need to serialize is the state of the lock, either it's locked
or not.

We could design the cookie to store this information in the least
significant bit. When somebody tries to access a cookie that doesn't
correspond to an existant lock, we can materialize the lock at that point.

A bigger problem as we discussed on IRC is how to handle locks in shared
memory.

I suppose we could use an extension of the above trick. If a lock is
initialized shared (by setting pshared=true in pthread_spin_init), the
cookie will have a bit to indicate this, and the lock memory is
initialized in a shared memory segment with a well known name. If
another process comes along and gets ahold of such a lock, it would then
attach to the shared memory segment and then get access to the same lock
object in memory.

“Any software problem can be solved by adding another layer of
indirection” -SMB

:-)

randolph

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [parisc-linux] [RFC] Alternative implementation of glibc spinlocks
  2005-07-04  1:59   ` Randolph Chung
@ 2005-07-04  4:14     ` Carlos O'Donell
  2005-07-04  5:49       ` Randolph Chung
  0 siblings, 1 reply; 10+ messages in thread
From: Carlos O'Donell @ 2005-07-04  4:14 UTC (permalink / raw)
  To: Randolph Chung; +Cc: parisc-linux

On Mon, Jul 04, 2005 at 09:59:14AM +0800, Randolph Chung wrote:
> > a) Lock serializing (essentially lock copying)
> 
> I still think "lock copying" is a fundamentally flawed idea, but I think
> we can actually support this in a sneaky way.

Copying a lock creates a new lock with the same status as the old lock.
I don't think it's that flawed, it's the same idea as copying a
structure with a lock inside. You don't need an explicit copy operator,
it should just work (tm).

In the case of a cookie it works great.

> All you need to serialize is the state of the lock, either it's locked
> or not.
> 
> We could design the cookie to store this information in the least
> significant bit. When somebody tries to access a cookie that doesn't
> correspond to an existant lock, we can materialize the lock at that point.

This is exactly what I was thinking, but there is an issue:

- A lock that was serliazed may actually coincide with a lock already in
  the system. The code can't tell which is which. Unless we use another
  level of indirection.

It seems to me that it should be perfectly legal for two threads,
working on a datastructure, to be able to serialize that data to disk,
read it back and continue working in a synchronized fashion without the
locks going haywire.

It would seem that at all costs we don't want to allocate the same lock
address twice. This is wasteful of memory, and possibly the wrong thing
to do if we are serializing objects with the intent to free memory.

> A bigger problem as we discussed on IRC is how to handle locks in shared
> memory.
> 
> I suppose we could use an extension of the above trick. If a lock is
> initialized shared (by setting pshared=true in pthread_spin_init), the
> cookie will have a bit to indicate this, and the lock memory is
> initialized in a shared memory segment with a well known name. If
> another process comes along and gets ahold of such a lock, it would then
> attach to the shared memory segment and then get access to the same lock
> object in memory.

I like this idea!

Though I don't like my personal idea of forcing an equal mapping at a
high address. Instead I think a table for indirection would work.

> ?Any software problem can be solved by adding another layer of
> indirection? -SMB

When I feel the weight of a possibly complex implementation getting me
down, I think over a single question:

"What problem are we trying to solve?"

- The core glibc maintainers don't care about odd-ball arches?
- Uninitialized locks are trouble?
- "Locks are int" is not true?

The last two, make it easier to get past the first problem :)

I like the idea of using the last two bits of the lock to indicate
status, taken or not, and shared or not.

c.

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [parisc-linux] [RFC] Alternative implementation of glibc spinlocks
  2005-07-04  4:14     ` Carlos O'Donell
@ 2005-07-04  5:49       ` Randolph Chung
  2005-07-04 14:54         ` Carlos O'Donell
  0 siblings, 1 reply; 10+ messages in thread
From: Randolph Chung @ 2005-07-04  5:49 UTC (permalink / raw)
  To: Carlos O'Donell; +Cc: parisc-linux

> Copying a lock creates a new lock with the same status as the old lock.
> I don't think it's that flawed, it's the same idea as copying a
> structure with a lock inside. You don't need an explicit copy operator,
> it should just work (tm).

If a currently locked lock is copied, is it still locked? If another
thread copies a locked lock, can it now enter a critical section? Does
the copied lock refer to the same lock object or a different one?

> - A lock that was serliazed may actually coincide with a lock already in
>   the system. The code can't tell which is which. Unless we use another
>   level of indirection.

I don't see how you can fix this even with indirection?

> It seems to me that it should be perfectly legal for two threads,
> working on a datastructure, to be able to serialize that data to disk,
> read it back and continue working in a synchronized fashion without the
> locks going haywire.

You can serialize the data, but to serialize a lock seems to be
questionable. I remain unconvinced that real life programs rely on this
behavior, but I'm willing to be shown an example :)

>>I suppose we could use an extension of the above trick. If a lock is
>>initialized shared (by setting pshared=true in pthread_spin_init), the
>>cookie will have a bit to indicate this, and the lock memory is
>>initialized in a shared memory segment with a well known name. If
>>another process comes along and gets ahold of such a lock, it would then
>>attach to the shared memory segment and then get access to the same lock
>>object in memory.
> I like this idea!
> Though I don't like my personal idea of forcing an equal mapping at a
> high address. Instead I think a table for indirection would work.

Yes. Forcing a fixed mapping seems to be fragile.

> When I feel the weight of a possibly complex implementation getting me
> down, I think over a single question:
> 
> "What problem are we trying to solve?"
> 
> - The core glibc maintainers don't care about odd-ball arches?
> - Uninitialized locks are trouble?
> - "Locks are int" is not true?
> 
> The last two, make it easier to get past the first problem :)

Well, ultimately I hope we would decide on whether or not to do this
based on technical merit rather than the politics of glibc maintainence.
Is it more natural to deal with an implementation that uses scalar lock
objects? Can it really work efficiently? Can we ensure compliance with
the appropriate standards?

randolph

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [parisc-linux] [RFC] Alternative implementation of glibc spinlocks
  2005-07-04  5:49       ` Randolph Chung
@ 2005-07-04 14:54         ` Carlos O'Donell
  2005-07-04 15:35           ` John David Anglin
  2005-07-05  1:39           ` Randolph Chung
  0 siblings, 2 replies; 10+ messages in thread
From: Carlos O'Donell @ 2005-07-04 14:54 UTC (permalink / raw)
  To: Randolph Chung; +Cc: parisc-linux

On Mon, Jul 04, 2005 at 01:49:00PM +0800, Randolph Chung wrote:
> > Copying a lock creates a new lock with the same status as the old lock.
> > I don't think it's that flawed, it's the same idea as copying a
> > structure with a lock inside. You don't need an explicit copy operator,
> > it should just work (tm).
> 
> If a currently locked lock is copied, is it still locked? If another
> thread copies a locked lock, can it now enter a critical section? Does
> the copied lock refer to the same lock object or a different one?

If the lock is locked when copied, the copy is locked.

Wether or not the thread can enter the critical section is dependant
upon the programmer. The programmer would have to ensure the lock copy
was atomic by protecting the structure. Then it could point the two
threads at the new copy, and delete the old copy.

A lock is a lock. It doesn't refer to anything unless you the programmer
give it some meaning.

The copy of the lock *can* have a different lock cookie, if that's what
you're asking? It just has to have the same status :)

If you serliaze two locks with the same cookie, the rematerialized locks
IMO *can* be allowed to have different cookies.

> > - A lock that was serliazed may actually coincide with a lock already in
> >   the system. The code can't tell which is which. Unless we use another
> >   level of indirection.
> 
> I don't see how you can fix this even with indirection?

You're right, we can't. After 2^32 locks are initialized the value
wraps. That's a fundamental problem with reusing the lock storage. If
the lock were to be serialized with the object it could uses the object
namespace as a level of indirection. Providing our own level of
indirection with a single data word is difficult. It gets better with
two words... even better with three words... but where will our
performance go?

If we add a level of indirection we insert a lookup requirement. The
lookup needs to determine if it has to materialize a lock, index load
the lock from the table, which might be on a faraway page (requiring
more searching), and our performance is shot.

We could get a full 32-bit address space if we used a space register to
store the locks into a different space? The space could also be shared
with other processes in the same way shared libraries would have been
implemented using space register?

It still doesn't solve our lock rematerializing issue. I think the issue
will never go away if we have to provide our own lock cookie namespace 
management.

> > It seems to me that it should be perfectly legal for two threads,
> > working on a datastructure, to be able to serialize that data to disk,
> > read it back and continue working in a synchronized fashion without the
> > locks going haywire.
> 
> You can serialize the data, but to serialize a lock seems to be
> questionable. I remain unconvinced that real life programs rely on this
> behavior, but I'm willing to be shown an example :)

A threaded lisp interpreter writes a running image to disk, only a
partial write and not the entire address space. It expects to reload
this from disk and start a thread up at the same place. If it has lock
aliasing other threads might collide with the loaded lock cookie.

What about gnome's object managment system? Surely it's threaded at some
level, and it requires that objects have locks for their thread critical
data. Perhaps you want to lock an objects data, and push it across the
network. The remote system only has serialized state information and
would need to rematerialize a lock on a different host :)

I think having the lock data with the lock is the only reasonable
solution.

> >>I suppose we could use an extension of the above trick. If a lock is
> >>initialized shared (by setting pshared=true in pthread_spin_init), the
> >>cookie will have a bit to indicate this, and the lock memory is
> >>initialized in a shared memory segment with a well known name. If
> >>another process comes along and gets ahold of such a lock, it would then
> >>attach to the shared memory segment and then get access to the same lock
> >>object in memory.
> > I like this idea!
> > Though I don't like my personal idea of forcing an equal mapping at a
> > high address. Instead I think a table for indirection would work.
> 
> Yes. Forcing a fixed mapping seems to be fragile.

That's why I liked your idea of shm_open better.

> > When I feel the weight of a possibly complex implementation getting me
> > down, I think over a single question:
> > 
> > "What problem are we trying to solve?"
> > 
> > - The core glibc maintainers don't care about odd-ball arches?
> > - Uninitialized locks are trouble?
> > - "Locks are int" is not true?
> > 
> > The last two, make it easier to get past the first problem :)
> 
> Well, ultimately I hope we would decide on whether or not to do this
> based on technical merit rather than the politics of glibc maintainence.
> Is it more natural to deal with an implementation that uses scalar lock
> objects? Can it really work efficiently? Can we ensure compliance with
> the appropriate standards?

Politics is a part of life, and removing it from the equation means you
are missing important information when making a decision.

I don't know what "more natural" means in this case. It's not a
measurable quantity. A the POSIX pthread interface it's already a
structure, so what difference does it make, the internals are opaque.

A deferred dynamically allocated lock system faces cookie namespace
problems.

Compliance with the standards is important, but second to a robust and
working implementation under minimum defined criteria.

Let me run a thought an experiment, let me construct the most idiotic
implementation of "scalar lock" that comes to mind...

Implementation of Scalar Locks (Take 1)
=======================================

a. glibc has a single aligned master lock.
b. Every thread uses a scalar for a lock.
c. The pthread functions do the following:
	- If the sig_atomic_t lock != 0
          Use LWS CAS to take the lock [1]
          = Done.
	- Block signals.
	- Increment the sig_atomic_t lock.
	- Acquire a master lock.
	- Twiddle a bit in the scalar lock.
	- Unlock a master lock.
	- Decrement the sig_atomic_t lock.
	- Unblock signals.

Cons:
= Signal handling latency goes up when doing lots of locking.
= A kill signal while taking the lock requires a fallback to
  the kernel for protection against concurrent accesses.

Pros:
= Simple scalar locks.
= The master locks can be extended to N master locks
  whose selection is based on a hash of the address
  of the scalar lock, improving SMP performance.
= No lock cookie namespace management.
= Locks can be copied.
= Locks can be serialized.

[1] This is because we took a signal in the critical section
of the locking code, and we need the kernel to arbitrate the lock
access.

Actually, this sorta worked out nice. What do you think?

c.

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [parisc-linux] [RFC] Alternative implementation of glibc spinlocks
  2005-07-04 14:54         ` Carlos O'Donell
@ 2005-07-04 15:35           ` John David Anglin
  2005-07-05  1:39           ` Randolph Chung
  1 sibling, 0 replies; 10+ messages in thread
From: John David Anglin @ 2005-07-04 15:35 UTC (permalink / raw)
  To: Carlos O'Donell; +Cc: parisc-linux

> On Mon, Jul 04, 2005 at 01:49:00PM +0800, Randolph Chung wrote:
> > > Copying a lock creates a new lock with the same status as the old lock.
> > > I don't think it's that flawed, it's the same idea as copying a
> > > structure with a lock inside. You don't need an explicit copy operator,
> > > it should just work (tm).
> > 
> > If a currently locked lock is copied, is it still locked? If another
> > thread copies a locked lock, can it now enter a critical section? Does
> > the copied lock refer to the same lock object or a different one?
> 
> If the lock is locked when copied, the copy is locked.
> 
> Wether or not the thread can enter the critical section is dependant
> upon the programmer. The programmer would have to ensure the lock copy
> was atomic by protecting the structure. Then it could point the two
> threads at the new copy, and delete the old copy.

Seems like a recipe for disaster.  How do you ensure that each thread
actually is using the new copy?  It seems to me that this requires another
layer of locking around each lock.  All operations on the lock would have
to be done inside the outer lock.  This increases the possibility of
deadlock and the overhead in acquiring and releasing a lock.  Then,
you are going to tell me that you want the capability to move the outer
protection lock...

Dave
-- 
J. David Anglin                                  dave.anglin@nrc-cnrc.gc.ca
National Research Council of Canada              (613) 990-0752 (FAX: 952-6602)
_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [parisc-linux] [RFC] Alternative implementation of glibc spinlocks
  2005-07-04 14:54         ` Carlos O'Donell
  2005-07-04 15:35           ` John David Anglin
@ 2005-07-05  1:39           ` Randolph Chung
  2005-07-05  5:14             ` Carlos O'Donell
  1 sibling, 1 reply; 10+ messages in thread
From: Randolph Chung @ 2005-07-05  1:39 UTC (permalink / raw)
  To: Carlos O'Donell; +Cc: parisc-linux

> If the lock is locked when copied, the copy is locked.
> 
> Wether or not the thread can enter the critical section is dependant
> upon the programmer. The programmer would have to ensure the lock copy
> was atomic by protecting the structure. Then it could point the two
> threads at the new copy, and delete the old copy.

I have to agree with Dave - this seems like a recipe for disaster.

> If we add a level of indirection we insert a lookup requirement. The
> lookup needs to determine if it has to materialize a lock, index load
> the lock from the table, which might be on a faraway page (requiring
> more searching), and our performance is shot.

sounds like we are implementing a VM with page tables. We need a TLB! :-)

> It still doesn't solve our lock rematerializing issue. I think the issue
> will never go away if we have to provide our own lock cookie namespace 
> management.

In any case rematerializing a lock seems iffy.....

> A threaded lisp interpreter writes a running image to disk, only a
> partial write and not the entire address space. It expects to reload
> this from disk and start a thread up at the same place. If it has lock
> aliasing other threads might collide with the loaded lock cookie.

I'm not sure it can do this without doing fixups after reading from a
disk image. For example, in many object systems pointers are written to
disks as UUIDs and when the object is read back from disk the UUID needs
to be reconverted to a real pointer. I would think the situation is the
same with locks; to have predictable behavior you have to write
information about the state of the lock and manage it yourself, instead
of relying on the underlying representation of the lock.

> I think having the lock data with the lock is the only reasonable
> solution.

Perhaps, but "lock data" should not be the internal representation of
the lock. My reading of the POSIX/SuSv3 pthread spec suggests that the
interface is designed explicitly to allow locks to be allocated dynamically.

> Politics is a part of life, and removing it from the equation means you
> are missing important information when making a decision.

Yes, but I don't think we should settle on an implementation that is
otherwise inferior only to get around the politics of an open source
project.

> I don't know what "more natural" means in this case. It's not a
> measurable quantity. A the POSIX pthread interface it's already a
> structure, so what difference does it make, the internals are opaque.

s/more natural/easier to implement/ then.

> A deferred dynamically allocated lock system faces cookie namespace
> problems.

I think there are two separate "namespace" problems:
1) Shared memory locks - this is relatively easy to solve
2) Serializable locks - this is difficult

> Compliance with the standards is important, but second to a robust and
> working implementation under minimum defined criteria.

I dunno about "second to"....

> Let me run a thought an experiment, let me construct the most idiotic
> implementation of "scalar lock" that comes to mind...
> 
> Implementation of Scalar Locks (Take 1)
> =======================================
> 
> a. glibc has a single aligned master lock.
> b. Every thread uses a scalar for a lock.
> c. The pthread functions do the following:
> 	- If the sig_atomic_t lock != 0
>           Use LWS CAS to take the lock [1]
>           = Done.
> 	- Block signals.
> 	- Increment the sig_atomic_t lock.
> 	- Acquire a master lock.
> 	- Twiddle a bit in the scalar lock.
> 	- Unlock a master lock.
> 	- Decrement the sig_atomic_t lock.
> 	- Unblock signals.

Your increment/decrement operations will also need to be done with a LWS.

One undesirable property of this approach seems to be that as the lock
becomes more contended, you spend an increasing amount of time spinning
in the kernel (doing your LWS CAS). As you told me last night, this
means the kernel has interrupts off, so processes are not getting
scheduled, and your lock contention becomes worse.

randolph

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [parisc-linux] [RFC] Alternative implementation of glibc spinlocks
  2005-07-05  1:39           ` Randolph Chung
@ 2005-07-05  5:14             ` Carlos O'Donell
  0 siblings, 0 replies; 10+ messages in thread
From: Carlos O'Donell @ 2005-07-05  5:14 UTC (permalink / raw)
  To: Randolph Chung; +Cc: parisc-linux

On Tue, Jul 05, 2005 at 09:39:00AM +0800, Randolph Chung wrote:
> > If the lock is locked when copied, the copy is locked.
> > 
> > Wether or not the thread can enter the critical section is dependant
> > upon the programmer. The programmer would have to ensure the lock copy
> > was atomic by protecting the structure. Then it could point the two
> > threads at the new copy, and delete the old copy.
> 
> I have to agree with Dave - this seems like a recipe for disaster.

Users do weird things, it's not that hard to send a SIGSTOP to a pair of
threads and do this work.
 
> > If we add a level of indirection we insert a lookup requirement. The
> > lookup needs to determine if it has to materialize a lock, index load
> > the lock from the table, which might be on a faraway page (requiring
> > more searching), and our performance is shot.
> 
> sounds like we are implementing a VM with page tables. We need a TLB! :-)

Yes we do, but in that case we have control over serialization to swap
:)

> > It still doesn't solve our lock rematerializing issue. I think the issue
> > will never go away if we have to provide our own lock cookie namespace 
> > management.
> 
> In any case rematerializing a lock seems iffy.....

Agreed.
 
> > A threaded lisp interpreter writes a running image to disk, only a
> > partial write and not the entire address space. It expects to reload
> > this from disk and start a thread up at the same place. If it has lock
> > aliasing other threads might collide with the loaded lock cookie.
> 
> I'm not sure it can do this without doing fixups after reading from a
> disk image. For example, in many object systems pointers are written to
> disks as UUIDs and when the object is read back from disk the UUID needs
> to be reconverted to a real pointer. I would think the situation is the
> same with locks; to have predictable behavior you have to write
> information about the state of the lock and manage it yourself, instead
> of relying on the underlying representation of the lock.

The situation is not the same with locks, the system pointers could get
serialized as UUID's but then we'd have to go in and write hppa specific
code to also serialize the lock to a special UUID?!

> > I think having the lock data with the lock is the only reasonable
> > solution.
> 
> Perhaps, but "lock data" should not be the internal representation of
> the lock. My reading of the POSIX/SuSv3 pthread spec suggests that the
> interface is designed explicitly to allow locks to be allocated dynamically.

I agree with you :)
 
> > Politics is a part of life, and removing it from the equation means you
> > are missing important information when making a decision.
> 
> Yes, but I don't think we should settle on an implementation that is
> otherwise inferior only to get around the politics of an open source
> project.

What if it means the different between forking and maintaining your own
repository? :)

> > I don't know what "more natural" means in this case. It's not a
> > measurable quantity. A the POSIX pthread interface it's already a
> > structure, so what difference does it make, the internals are opaque.
> 
> s/more natural/easier to implement/ then.

Ok.

> > A deferred dynamically allocated lock system faces cookie namespace
> > problems.
> 
> I think there are two separate "namespace" problems:
> 1) Shared memory locks - this is relatively easy to solve
> 2) Serializable locks - this is difficult

I had pondered using a space register to access locks, and create a
4GB space just for locks :)

> > Compliance with the standards is important, but second to a robust and
> > working implementation under minimum defined criteria.
> 
> I dunno about "second to"....

How about "close second to" ? :)

> > Let me run a thought an experiment, let me construct the most idiotic
> > implementation of "scalar lock" that comes to mind...
> > 
> > Implementation of Scalar Locks (Take 1)
> > =======================================
> > 
> > a. glibc has a single aligned master lock.
> > b. Every thread uses a scalar for a lock.
> > c. The pthread functions do the following:
> > 	- If the sig_atomic_t lock != 0
> >           Use LWS CAS to take the lock [1]
> >           = Done.
> > 	- Block signals.
> > 	- Increment the sig_atomic_t lock.
> > 	- Acquire a master lock.
> > 	- Twiddle a bit in the scalar lock.
> > 	- Unlock a master lock.
> > 	- Decrement the sig_atomic_t lock.
> > 	- Unblock signals.
> 
> Your increment/decrement operations will also need to be done with a LWS.

I think I see what you mean. One thread executing LWS CAS on
scalar_lock, and the other twiddling the sclar_lock bit could happen in
the above algorithm. If I'm going to use LWS CAS it might as well be to
handle the lock itself.
 
I'm *trying* to execute the sequence of insns required to twiddle the
scalar_lock atomically using a mutex provided by ldcw on a masterlock[n],
"n" being selected from a hash of the scalar_lock address.

The problem is that if any thread took a signal, it could face deadlock
if it tries to do any other similar pthread locking function. I thought
myself clever and setup a fallback to LWS CAS.

Though it won't work since LWS CAS uses a set of kernel locks. And two
threads might try to twiddle the lock independantly of eachother in this
case (e.g. kernel lock vs. master lock protecting one scalar lock).

Do we think that LWS CAS could be used effectively to implement locks?

All the test code is in the "userspace" repository under test-lws. I
wrote a small set of routines for it aswell. Perhaps you want to review
that for me to make sure I didn't get anything wrong?

> One undesirable property of this approach seems to be that as the lock
> becomes more contended, you spend an increasing amount of time spinning
> in the kernel (doing your LWS CAS). As you told me last night, this
> means the kernel has interrupts off, so processes are not getting
> scheduled, and your lock contention becomes worse.

Almost right, interrupts are still on, we just don't schedule. The tail
call of schedule is in assembly and it checks to see if you came from
the gateway, if you did then it doesn't schedule (or deliver your
signals). Interrupts have to be left on so I can fault the page back in
from disk if it happens to be swapped out.

c.

_______________________________________________
parisc-linux mailing list
parisc-linux@lists.parisc-linux.org
http://lists.parisc-linux.org/mailman/listinfo/parisc-linux

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2005-07-05  5:14 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-07-02 13:59 [parisc-linux] [RFC] Alternative implementation of glibc spinlocks Randolph Chung
     [not found] ` <200507020935.42389.mszick@morethan.org>
2005-07-02 15:23   ` Randolph Chung
2005-07-04  1:00 ` Carlos O'Donell
2005-07-04  1:59   ` Randolph Chung
2005-07-04  4:14     ` Carlos O'Donell
2005-07-04  5:49       ` Randolph Chung
2005-07-04 14:54         ` Carlos O'Donell
2005-07-04 15:35           ` John David Anglin
2005-07-05  1:39           ` Randolph Chung
2005-07-05  5:14             ` Carlos O'Donell

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.