Exercise 1

Semaphores

Let’s see the use of semaphores in action. In this exercise, we will initialize a semaphore that will have the count-property reflect the number of instances of a particular resource in the system. There will be one consumer thread that will want to consume the resource and a producer thread that will produce it. Every time the consumer thread tries to get access, we decrement and print the value of the count, and every time the producer thread relinquishes access to the instance, we increment and print the value of the count.

Exercise steps

1. In the GitHub repository for this course, open the base code for this exercise, found in lesson8/fund_less8_exer1 of whichever version directory you are using (v2.x.x or v1.6.0-v1.9.1).

2. Set the priority of the producer and consumer thread. The priority of the consumer thread is set higher to be able to demonstrate its ability to request access to an unavailable resource.

#define PRODUCER_PRIORITY        5 
#define CONSUMER_PRIORITY        4

3. Initialize the number of instances of the limited resource to be 10. It is not important to know what the resource is in this exercise (it can be anything from printers, fax, peripherals, clock), just assume that we have 10 instances of that resource.

volatile uint32_t available_instance_count = 10;

4. Create the producer thread that just releases the resource without any checks and sleeps for a random (0-9ms) amount of time. When it wakes up from this sleep, it repeats this step in a loop indefinitely.

void producer(void)
{
	printk("Producer thread started\n");
	while (1) {
		release_access();
		// Assume the resource instance access is released at this point
		k_msleep( sys_rand32_get() % 10);
	}
}

5. Create the consumer thread that gets access to the resource without any checks and assumes to get access before it goes to sleep for a random (0-9ms) amount of time. When it wakes up, it repeats this step in a loop indefinitely.

void consumer(void)
{
	printk("Consumer thread started\n");
	while (1) {
		get_access();
		// Assume the resource instance access is released at this point
		k_msleep( sys_rand32_get() % 10);
	}
}

6.1 In the function get_access(), which emulates getting the resource, decrement the available resource.

	available_instance_count--;
	printk("Resource taken and available_instance_count = %d\n",                  available_instance_count);

6.2 In the function release_access(), which emulates releasing the resource, increment the available resource. Then, in both functions, print the available_instance_count.

	available_instance_count++;
	printk("Resource given and available_instance_count = %d\n",                  available_instance_count);

If the count value is more than 10 or less than 0, then that is a race condition as the actual resources in the real application cannot go beyond max or below 0. However, no checks are performed to determine if the resource is available or not.

7. Build the application and flash it on your development kit. Using a serial terminal, such as the integrated nRF Terminal or PuTTY you should see the below output:

As you can see, the available instance counts printed are negative numbers. This should not happen with a physical resource. Since we are not atomically checking for the validity of this instance count before accessing/relinquishing access to the instance, we end up in a scenario where the producer and consumer threads lose synchronization in terms of accessing the limited resources.

But this is easy to fix with semaphores and the application does not need the burden to atomically check/validate the internal counts of the available resources.

9. Add a semaphore by first defining the semaphore using K_SEM_DEFINE() like below, just after the consumer and producer priorities are defined.

K_SEM_DEFINE(instance_monitor_sem, 10, 10);

10.1 Take the semaphore using k_sem_take() in get_access() right before accessing the resource.

k_sem_take(&instance_monitor_sem, K_FOREVER);

10.2 Then release the semaphore using k_sem_give() in release_access() after finishing accessing the resource.

k_sem_give(&instance_monitor_sem); 

That is it. You have now defined and used a semaphore. You have also forced the consumer thread to wait indefinitely until the resource is available. Since the consumer thread has a higher priority than the producer thread, the consumer is trying to take the resource at a faster rate than the rate at which the producer can give. This means the consumer should wait for longer times.

11. Build the application and flash it on your development kit. Using a serial terminal such as the integrated nRF Terminal or PuTTY you should see the below output:

You can see that the consumer starts accessing all of the 10 resources very quickly but is forced to wait when the resource count becomes 0. The consumer thread is then blocked until the producer thread gives the semaphore and the resource becomes available, unblocking the consumer thread.

The solution for this exercise can be found in the GitHub repository, lesson7/fund_less8_exer1_solution of whichever version directory you are using (v2.x.x or v1.6.0-v1.9.1).

Register an account
Already have an account? Log in
(All fields are required unless specified optional)

Forgot your password?
Enter your email address, and we will send a link to reset your password.