jeffr_tech ([info]jeffr_tech) wrote,
@ 2007-08-13 21:52:00
Previous Entry  Add to memories!  Tell a Friend  Next Entry
Using atomics in struct file.
I'm completely obsessive compulsive about code sometimes. Haven't been able to sleep while thinking about file and descriptor locking. I ended up completely rewriting the unix domain socket garbage collector. It now no longer scans the entire list of descriptors in the system. This enabled me to remove the list of all descriptors and the lock that protected it. Saving a few pointers in every struct file.

In addition to this, I removed all explicit locking of struct file. The reference count and file flags, settable via fcntl(F_SETFL), were protected by a mutex. This was also used for some cases in the garbage collection code that I removed. The reference count is now handled completely with atomics, as are the flags. The flags were somewhat tricky because in some cases you wanted to do:

flags |= SOMEFLAG;
flags &= ~OTHERFLAG;

It's not legal to do this with two separate atomics. The reason is somewhat complex but also pretty interesting. These flags are only set by user-space and inspected by the kernel. In the presence of multiple threads the kernel can not protect against races involving multiple threads setting flags simultaneously. However, one must win. The results can't be a mix of both. So for compound statements I do things like this:

do {
        old = new = fp->f_flags;
        new |= SOMEFLAG:
        new &= ~OTHERFLAG;
}while (atomic_cmpset_int(&fp->f_flags, old, new) == 0);


This loop uses an atomic compare and exchange to update the flags. If f_flags changes after we fetch it into old the loop will restart and we'll try again. In practice restarting the loop is likely to be incredibly rare. For cases which only require updating one bit you can use something like the following in bsd:

atomic_set_int(&fp->f_flag, SOMEFLAG);

Pretty handy. For the atomic reference count we do "if (atomic_fetchadd_int(&fp->f_count, -1) == 1) free the fp". Atomic fetchadd adds -1 to the integer pointed at and returns the old value. If we're closing the last reference we know it's safe to free the structure because it's not possible for another reference to be gained.



(8 comments) - (Post a new comment)

Possible loop in do {} while?
(Anonymous)
2007-08-14 12:36 pm UTC (link)
Jeff,

Since this flags can be set in user-space, is that possible the user create one program to set this forever and cause loop in do {} while?

Regards
mnag@

(Reply to this) (Thread)

Re: Possible loop in do {} while?
[info]jeffr_tech
2007-08-14 06:20 pm UTC (link)
Well I wasn't terribly clear about that. It's settable via syscalls like fcntl and ioctl. Since it's only a few instructions for the cmpxchg and the total syscall time takes a few thousand it'd be impossible for userspace to cause an endless loop.

(Reply to this) (Parent)(Thread)

Re: Possible loop in do {} while?
(Anonymous)
2007-08-15 02:41 am UTC (link)
What if two threads enter the system call at about the same micro/nanosecond? This would in theory cause the threads to eat the CPU until one of the threads would get preempted.

I guess your approach is similar to the Linux kernel's seqlock, which uses a loop like this on the read side.

(Reply to this) (Parent)(Thread)

Re: Possible loop in do {} while?
[info]jeffr_tech
2007-08-15 03:52 am UTC (link)
Well no, because one has to win for the other to loop. Otherwise the data wouldn't have changed.

(Reply to this) (Parent)(Thread)

Re: Possible loop in do {} while?
(Anonymous)
2007-08-16 08:10 am UTC (link)
I meant on an SMP system, where both threads are running on a different CPU. Then the looping would only stop when something is received that causes one of the threads to temporarily stop (interrupt, preempted, etc).

(Reply to this) (Parent)(Thread)

Re: Possible loop in do {} while?
[info]jeffr_tech
2007-08-16 08:16 am UTC (link)
Well cmpset only fails if the data has changed since you loaded it. So one thread would win and not loop, the second thread would loop once and then it would set the location. Even if they both execute the loop at the exact same instant one of them will succeed and one will fail once.

(Reply to this) (Parent)

Re: Possible loop in do {} while?
(Anonymous)
2007-08-16 09:56 am UTC (link)
Linux kernel's seqlock does not use a loop like this. It has no write
operations at all in it, let alone an atomic RMW.

However it is not appropriate to use in a write-side critical section,
such as Jeff's code, where there would be a problem of partially
initialized results leaking out.

(Reply to this) (Parent)(Thread)

Re: Possible loop in do {} while?
[info]jeffr_tech
2007-08-16 10:12 am UTC (link)
We have something very similar to seqlock as well, although it's not generally used as the applications are quite limited.

(Reply to this) (Parent)


(8 comments) - (Post a new comment)

Create an Account
Forgot your login or password?
Login w/ OpenID
English • Español • Deutsch • Русский…