Archive for June, 2007

Type safe hooks in C

I sometimes envy the C++ guys for having simple APIs for the signal-slot mechanism. While the signal and slots were designed by the people doing GUI toolkits — with the main credit going to the Qt project — they can be useful as a general inter-module communication mechanism in any software project.

Signals provide loose module coupling, which is crucial for good design. All you need to declare are the arguments of the signal, and then any module is free to generate them and any other module is free to receive them. If the module sending signals is freed, no problem, the receiver will simply not receive any more signals. If the receiver module is freed, again no problem, the signals will be simply ignored. It’s that simple.

Another property of the signals is that they are type safe, meaning that if the type of the arguments of a signal changes and you fail to update all receivers, you will get compile time errors instead of weird hard-to-find run-time bugs. The type unsafeness for callbacks is too often ignored by C programmers, and it generates a good share of bugs.

The reason I said I envy C++ programmers is that the mechanism can’t be elegantly implemented in C, because C lacks generics and function overloading. But fear not, if we know what we aim for, we can get pretty close to anything in C. Here is how I do it: a hook is a list of pointers to callback functions. The receivers register their callbacks by adding them to the list. When the hook is called, i.e. the signal is generated, it will call all the callbacks from its list. Pretty simple so far, but how do you provide type safeness? you ask. With wrappers and macros.

Lets suppose the hook structure looks something like:

1
2
3
4
5
6
7
8
9
typedef struct hook_cb {
	void (*f)(void);
        struct hook *next;
} hook_cb_t;
 
typedef struct hook {
        struct hook *hooks;
        pthread_rwlock_t *lock;
} hook_t;

It’s basically the list of callbacks that I was talking about, protected by a read-write lock. We then need methods for registering and unregistering the callbacks. Here are the prototypes, you have the pleasure of doing the single-linked list insertion and removal on your own (or cheat with whatever library).

1
2
3
4
5
6
7
8
9
/**
 * Adds f to the hook->hooks_cb list.
 */
int hook_register_cb(hook_t *hook, void(*f)(void)); 
 
/**
 * Removes f from the hook->hooks_cb.
 */
int hook_unregister_cb(hook_t *hook, void(*f)(void));

So far everything is type unsafe. The type of the callback was just randomly chosen to look like a function. But the macro comes into play:

1
2
3
4
5
6
7
#define hook_call(hook, type, ... ) do { \
        hook_cb_t __cb; \
        pthread_rwlock_rdlock(&(hook)->lock); \
        for (__cb = (hook)->hooks_cb; __cb; __cb = __cb->next) \
                ((type*)__cb->f)(__VA_ARGS__); \
        pthread_rwlock_unlock(&(hook)->lock); \
} while(0)

This hook_call macro receives as arguments a pointer to the hook structure, the type of the callback, and a variable number of arguments. It iterates through the callback list, casts the function pointers to the provided type and calls them with the macro arguments using the variadic macro for that. If the arguments don’t match the given function type, the compiler will report an error, which is all we ever wanted, actually.

For example, lets suppose we have a hook with two integer arguments. The callback type is:

1
typedef void(my_callback_t)(int, int, float);

Then, the hook can be called with something like:

1
hooks_call(hook, my_callback_t, 3, 4);

As the careful reader will notice, this only solves half of the problem. If the receivers register callbacks of the wrong type, it will be called anyway and cause troubles. This is why when declaring hooks, I also create wrappers for the register routines. Extending the above example:

1
2
3
4
5
6
7
inline static int my_hook_register( hook_t *hook, my_callback_t *f ) {
        return hook_register(hook, (void*)f) ;
}
 
inline static int my_hook_unregister( hook_t *hook, my_callback_t *f ) {
        return hooks_unregister( hook, (void*)f );
}

These 7 lines are the actual cost of type safeness. A fair price if you ask me. Especially since you can have callbacks with any number of arguments having any type. No need for casts in the callback, no need for packing structures, no need for documenting the type of the arguments. Things get simpler because the glue is centralized in the hook declaration.

Finally, a wrapper for the calling macro might also come handy to simplify the signal generators.

1
2
3
inline static void my_hook_call(hook_t *hook, int a, int b) {
        hook_call(hook, my_callback_t, a, b );
}

Useful? Found an error? Something not clear?
Leave a comment.

Comments

Atomic Operations

“Where did atomic.h go?!?”

..was my surprised reaction when I compiled one of my applications in Debian Etch for the first time. It compiled with no problems on Sarge and on Gentoo, but couldn’t find the atomic.h header file on Etch. A bit confused, I asked my friend, and he didn’t seem to know at the first queries, so after I figured it out, I wrote this post.

First, to understand why was atomic.h removed, you should know the following:

  • The /usr/include/asm/atomic.h , as it is found on Debian Sarge is a _kernel_ header, somehow cleaned up to compile well in user-space, but still a kernel header.
  • Including kernel headers in user-space is generally bad idea, unless you are using the kernel API (e.g. for an ioctl).
  • The atomic.h header, in particular, was not meant to be included in user-space. For example, if on a SMP machine you don’t compile with CONFIG_SMP, the operations will loose their atomicity. Even worse, on some architecture the atomic.h is completely broken in user-space because it’s working by disabling interrupts. Here is a LKML thread on the subject.
  • It’s Linux specific, other Unix-es might not have an equivalent.

Despite these things, many applications (e.g. mysql) used the atomic.h because of the lack of alternatives. There is no equivalent in glibc. Some framework libraries, like GLib or apr have their own implementation for atomic operations but linking against them just for that doesn’t always make sense. Simulating them with pthread spin locks is not much of an option either, as much of the performance is wasted. Finally, maintaining assembly versions in each application is out of the question.

The good news is that now there is a good and portable solution: gcc atomic builtins. Since they are provided by the compiler, who is our specialist in generating machine code, they are sure to be correct on all supported architectures and operating systems. In fact, it makes so much sense to me to have the atomic operations as a language extension that I’m surprised we had to wait until version 4.1 of gcc to see them implemented. The downsides are that (1) some old processor will not use them efficiently and (2) the API is a little cumbersome.

To get you going, here is an in-place replacement for the atomic.h header:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#ifndef _ATOMIC_H
#define _ATOMIC_H
 
/**
 * Atomic type.
 */
 
typedef struct {
volatile int counter;
} atomic_t;
 
#define ATOMIC_INIT(i)  { (i) }
 
/**
 * Read atomic variable
 * @param v pointer of type atomic_t
 *
 * Atomically reads the value of @v.
 */
#define atomic_read(v) ((v)->counter)
 
/**
 * Set atomic variable
 * @param v pointer of type atomic_t
 * @param i required value
 */
#define atomic_set(v,i) (((v)->counter) = (i))
 
/**
 * Add to the atomic variable
 * @param i integer value to add
 * @param v pointer of type atomic_t
 */
static inline void atomic_add( int i, atomic_t *v ) 
{
         (void)__sync_add_and_fetch(&v->counter, i);
}
 
/**
 * Subtract the atomic variable
 * @param i integer value to subtract
 * @param v pointer of type atomic_t
 *
 * Atomically subtracts @i from @v.
 */
static inline void atomic_sub( int i, atomic_t *v ) 
{
        (void)__sync_sub_and_fetch(&v->counter, i);
}
 
/**
 * Subtract value from variable and test result
 * @param i integer value to subtract
 * @param v pointer of type atomic_t
 *
 * Atomically subtracts @i from @v and returns
 * true if the result is zero, or false for all
 * other cases.
 */
static inline int atomic_sub_and_test( int i, atomic_t *v ) 
{
        return !(__sync_sub_and_fetch(&v->counter, i));
}
 
/**
 * Increment atomic variable
 * @param v pointer of type atomic_t
 *
 * Atomically increments @v by 1.
 */
static inline void atomic_inc( atomic_t *v ) 
{
       (void)__sync_fetch_and_add(&v->counter, 1);
}
 
/**
 * @brief decrement atomic variable
 * @param v: pointer of type atomic_t
 *
 * Atomically decrements @v by 1.  Note that the guaranteed
 * useful range of an atomic_t is only 24 bits.
 */
static inline void atomic_dec( atomic_t *v ) 
{
       (void)__sync_fetch_and_sub(&v->counter, 1);
}
 
/**
 * @brief Decrement and test
 * @param v pointer of type atomic_t
 *
 * Atomically decrements @v by 1 and
 * returns true if the result is 0, or false for all other
 * cases.
 */
static inline int atomic_dec_and_test( atomic_t *v ) 
{
       return !(__sync_sub_and_fetch(&v->counter, 1));
}
 
/**
 * @brief Increment and test
 * @param v pointer of type atomic_t
 *
 * Atomically increments @v by 1
 * and returns true if the result is zero, or false for all
 * other cases.
 */
static inline int atomic_inc_and_test( atomic_t *v ) 
{
      return !(__sync_add_and_fetch(&v->counter, 1));
}
 
/**
 * @brief add and test if negative
 * @param v pointer of type atomic_t
 * @param i integer value to add
 *
 * Atomically adds @i to @v and returns true
 * if the result is negative, or false when
 * result is greater than or equal to zero.
 */
static inline int atomic_add_negative( int i, atomic_t *v ) 
{
       return (__sync_add_and_fetch(&v->counter, i) < 0);
}
 
#endif

Pretty straight forward isn’t it? It could be even more powerful and simpler if you don’t need precise compatibility with atomic.h. For example, atomic_add could easily return the result values:

1
2
3
4
static inline int atomic_add( int i, atomic_t *v ) 
{
         return __sync_add_and_fetch(&v->counter, i);
}

As a second example, consider a compare and swap operation, frequently used in lock-free algorithms. Once again, it’s trivially:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * @brief compare and swap
 * @param v pointer of type atomic_t
 *
 * If the current value of @b v is @b oldval,
 * then write @b newval into @b v. Returns #TRUE if
 * the comparison is successful and @b newval was
 * written.
 */
static inline int atomic_cas( atomic_t *v, int oldval, int newval ) 
{
        return __sync_bool_compare_and_swap(&v->counter, oldval, newval);
}

Found this useful? Leave a comment.

Comments (9)

Howdy World!

Since this is my first post on my brand new site, I think it would be polite to present myself and tell you what I will write about in these pages. My name is Tudor Golubenco (hence the domain name. Original, eh?) and I’m a young software engineer. You might know the type: having a modern life, advocating Linux, reading Slashdot (they can do just fine without me linking to them), having fun with things like The Daily WTF or xkcd, and you can fill in the rest for your own.

I’m interested in system programming, parallel computing, security and VoIP, which brings me to what I will blog about: system programming, parallel computing, security and VoIP. I also plan to do reviews to the open source software I use, and that I think others should use, too. In very sunny days I might even post about some not-software related subjects, who knows? I’m a Romanian guy leaving in Berlin, so I could post about my efforts to learn German (or about my efforts to *start* learning), about the political situation in my country, or about whatever cool little town I’ve visited in Germany.

To finish my “hello, world” post before promising too much, I hope to be a pleasant host and to have the time for creating some interesting content.

Comments (2)