// To compile: gcc -m32 -o P_V_counter P_V_counter.c #include #include #include #define NUM_THREADS 2 typedef struct SEMAPHORE { volatile long lock; volatile long sem; } semaphore; int cnt = 0; semaphore s = { .lock = 0, .sem = 1}; // This is an implentation of TestAndSet for gcc/linux/x86. // The caller defines int lock. Calling TestAndSet(&lock) sets lock // to 1 and returns the old value of lock. So, if lock is zero, then // TestAndSet(&lock) returns zero and sets lock to one. This means // the lock has been succesfully acquired. On the other hand, if the lock // had already been set to one by another process or thread, then 1 // would be returned. This would indicate to the caller that the lock is // already being held by another process or thread. The caller keeps retrying // TestAndSet(&lock) until it returns 0. // This code is gcc/linux/intel x86 specific. long TestAndSet (volatile long * lock) { long retval; /* Atomically exchange eax with lock. The atomicity of xchg is what guarantees that at most one process or thread can be holding the lock at any point in time. */ __asm__ __volatile__ ( "movl $0x1, %0 \n" "xchg %0, (%1) \n" : "=&r"(retval) : "r"(lock) : "memory" ); return retval; } void P(semaphore *p) { while(1){ while(TestAndSet(&p->lock)) sched_yield(); // locked in mutual exclusion if (p->sem > 0) { p->sem--; p->lock = 0; //lock is released break; // entering crit. sec. } p->lock = 0; //lock is released sched_yield(); //relinquish CPU } } void V(semaphore *p) { while(TestAndSet(&p->lock)) sched_yield(); // locked in mutual exclusion p->sem++; p->lock = 0; //lock is released } void* worker(void *ptr) { int i; for (i = 0; i < 50000; i++) { P(&s); cnt++; V(&s); } pthread_exit(NULL); } int main(void) { pthread_t threads[NUM_THREADS]; int i, res; for (i = 0; i < NUM_THREADS; i++) { res = pthread_create(&threads[i], NULL, worker, NULL); } for (i = 0; i < NUM_THREADS; i++) { res = pthread_join(threads[i], NULL); } /* Print result */ printf("Final value: %d\n", cnt); }