Skip to content

Commit

Permalink
samples: coop_synchronization: new cooperative thread switching sampl…
Browse files Browse the repository at this point in the history
…e app

A simple application that demonstrates the most basic sanity of the kernel.
Two threads (A and B) take turns printing a greeting message to the console
They use yields and semaphores to control the whose turn it is to print.
It also enforces polled uart mode to avoid serial IRQ fires triggering
unpredictable context switch. The rate is uncontrolled. This demonstrates
kernel communication only. It's a good example to test a new arch for
testing cooperative context switching without sys timer first. It's also
useful to test cooperative-only architectures which may not have any timing
or clock facilities.
I.e. this will still work when `CONFIG_SYS_CLOCK_EXISTS`=n

Signed-off-by: Alp Sayin <[email protected]>
  • Loading branch information
alpsayin committed Jun 28, 2023
1 parent bbec614 commit 0f97850
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 0 deletions.
10 changes: 10 additions & 0 deletions samples/coop_synchronization/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright 2023 Advanced Micro Devices, Inc. (AMD)
# Copyright 2023 Alp Sayin
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(coop_synchronization)

target_sources(app PRIVATE src/main.c)
72 changes: 72 additions & 0 deletions samples/coop_synchronization/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
.. _coop_synchronization_sample:

Cooperative Synchronization Sample
##################################

Overview
********

A simple application that demonstrates even more basic sanity of the kernel.
Two threads (A and B) take turns printing a greeting message to the console.
They use yields and semaphores to control the whose turn it is to print.
The rate is only controlled via busy waits. This demonstrates kernel communication only.
This is a good example to test new platforms that may not have a working clock.
I.e. this will still work when :kconfig:option:`CONFIG_SYS_CLOCK_EXISTS` =n
It's also a very simplistic demo allowing users to follow the execution trace in
a no-timer (i.e. nonexistent, disabled, deferred) scenario and experience predictability.
To that end, sample should ideally be built with no interrupt-driven-UART to avoid spurious
IRQ fires which may trigger unpredictable context switch. However, some platforms do not
support polling api for UART. For those, only the regular variant is built.

Building and Running
********************

This project outputs to the console. It can be built and executed
on QEMU as follows:

.. zephyr-app-commands::
:zephyr-app: samples/coop_synchronization
:host-os: unix
:board: qemu_x86
:goals: run
:compact:

Sample Output
=============

.. code-block:: console
thread_a: level 1 irq_lock key = 534
thread_a: level 2 irq_lock key = 22
thread_a: Hello World from cpu 0 on qemu_x86! counter = 1
thread_b: level 1 irq_lock key = 534
thread_b: level 2 irq_lock key = 22
thread_b: Hello World from cpu 0 on qemu_x86! counter = -1
thread_a: level 1 irq_lock key = 534
thread_a: level 2 irq_lock key = 22
thread_a: Hello World from cpu 0 on qemu_x86! counter = 2
thread_b: level 1 irq_lock key = 534
thread_b: level 2 irq_lock key = 22
thread_b: Hello World from cpu 0 on qemu_x86! counter = -2
thread_a: level 1 irq_lock key = 534
thread_a: level 2 irq_lock key = 22
thread_a: Hello World from cpu 0 on qemu_x86! counter = 3
thread_b: level 1 irq_lock key = 534
thread_b: level 2 irq_lock key = 22
thread_b: Hello World from cpu 0 on qemu_x86! counter = -3
thread_a: level 1 irq_lock key = 534
thread_a: level 2 irq_lock key = 22
thread_a: Hello World from cpu 0 on qemu_x86! counter = 4
thread_b: level 1 irq_lock key = 534
thread_b: level 2 irq_lock key = 22
thread_b: Hello World from cpu 0 on qemu_x86! counter = -4
thread_a: level 1 irq_lock key = 534
thread_a: level 2 irq_lock key = 22
thread_a: Hello World from cpu 0 on qemu_x86! counter = 5
thread_b: level 1 irq_lock key = 534
thread_b: level 2 irq_lock key = 22
thread_b: Hello World from cpu 0 on qemu_x86! counter = -5
<repeats endlessly>
Exit QEMU by pressing :kbd:`CTRL+A` :kbd:`x`.
4 changes: 4 additions & 0 deletions samples/coop_synchronization/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CONFIG_STDOUT_CONSOLE=y
# enable to use thread names
CONFIG_THREAD_NAME=y
CONFIG_SCHED_CPU_MASK=y
32 changes: 32 additions & 0 deletions samples/coop_synchronization/sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
sample:
description: A simple application that demonstrates even more basic sanity of the kernel
compared to samples/synchronization. The main variant is the polled_uart which shouldn't
go through any interrupt driven context switch in a no-systick scenario.
name: Coop Synchronization Sample
tests:
sample.kernel.coop_synchronization.polled_uart:
tags: synchronization
extra_configs:
- CONFIG_UART_INTERRUPT_DRIVEN=n
harness: console
harness_config:
type: multi_line
regex:
- "thread_a: level 1 spinlock key = (\\d+)"
- "thread_a: level 2 spinlock key = (\\d+)"
- "thread_a: Hello World from cpu (\\d+) on (\\w+)! counter = (\\d+)"
- "thread_b: level 1 spinlock key = (\\d+)"
- "thread_b: level 2 spinlock key = (\\d+)"
- "thread_b: Hello World from cpu (\\d+) on (\\w+)! counter = -(\\d+)"
sample.kernel.coop_synchronization.interrupt_driven_uart:
tags: synchronization
harness: console
harness_config:
type: multi_line
regex:
- "thread_a: level 1 spinlock key = (\\d+)"
- "thread_a: level 2 spinlock key = (\\d+)"
- "thread_a: Hello World from cpu (\\d+) on (\\w+)! counter = (\\d+)"
- "thread_b: level 1 spinlock key = (\\d+)"
- "thread_b: level 2 spinlock key = (\\d+)"
- "thread_b: Hello World from cpu (\\d+) on (\\w+)! counter = -(\\d+)"
139 changes: 139 additions & 0 deletions samples/coop_synchronization/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright (c) 2012-2014 Wind River Systems, Inc.
* Copyright (c) 2023 Advanced Micro Devices, Inc. (AMD)
* Copyright (c) 2023 Alp Sayin <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

/*
* The coop synchronization demo has two threads that utilize semaphores and k_yield
* to take turns printing a greeting message with a somewhat uncontrolled rate.
* The rate is controlled via a busy waiting loop. The advantage of this demo is that
* it'll work even if there are no timing utilities provided by the architecture.
*/

/* size of stack area used by each thread */
#define STACKSIZE 1024

/* Some locks to take and release to make things interesting */
static struct k_spinlock lock1, lock2;

/*
* @param my_name thread identification string
* @param my_sem thread's own semaphore
* @param other_sem other thread's semaphore
*/
void helloLoop(const char *my_name, struct k_sem *my_sem,
struct k_sem *other_sem, int incrementer)
{
const char *tname;
uint8_t cpu = 0;
struct k_thread *current_thread;
int task_local_counter = 0;

while (1) {
/* take my semaphore */
k_sem_take(my_sem, K_FOREVER);

k_spinlock_key_t key1 = k_spin_lock(&lock1);
{
current_thread = k_current_get();
tname = k_thread_name_get(current_thread);
#if defined(CONFIG_ARC) || defined(CONFIG_ARM) || defined(CONFIG_X86_64) \
|| defined(CONFIG_RISCV) || defined(CONFIG_XTENSA)
cpu = arch_curr_cpu()->id;
#endif
printk("%s: level 1 spinlock key = %d\n", tname, key1.key);
k_spinlock_key_t key2 = k_spin_lock(&lock2);
{
k_yield();
printk("%s: level 2 spinlock key = %d\n", tname, key2.key);
}
k_spin_unlock(&lock2, key2);
}
k_spin_unlock(&lock1, key1);
/* say "hello" */
if (tname == NULL) {
printk("%s: Hello World from cpu %d on %s! counter = %d\n", my_name, cpu,
CONFIG_BOARD, task_local_counter += incrementer);
} else {
printk("%s: Hello World from cpu %d on %s! counter = %d\n", tname, cpu,
CONFIG_BOARD, task_local_counter += incrementer);
}

/* We wait a while here, then let other thread have a turn.
* We use a busy wait here because arch provided k_busy_wait()
* implementation is still likely to depend on the system timer,
* for which the assumption is to be non-existent/disabled/broken.
* On another note: native posix doesn't play nice with busy waits
* as it requires a different API (Z_SPIN_DELAY) for the threads to
* be preemtable inside the busy wait. But the point of this sample
* is not to be preempted by timers (nor UART if possible).
*/
for (int i = 0; i < 100000000; i++) {
arch_nop();
}

k_sem_give(other_sem);
k_yield();
}
}

/* define semaphores */

K_SEM_DEFINE(threadA_sem, 1, 1); /* starts off "available" */
K_SEM_DEFINE(threadB_sem, 0, 1); /* starts off "not available" */

/* threadB is a dynamic thread that is spawned by threadA */

void threadB(void *arg1, void *arg2, void *arg3)
{
int incrementer = (intptr_t)arg1;

ARG_UNUSED(arg2);
ARG_UNUSED(arg3);

/* invoke routine to ping-pong hello messages with threadA */
helloLoop(__func__, &threadB_sem, &threadA_sem, incrementer);
}

K_THREAD_STACK_DEFINE(threadA_stack_area, STACKSIZE);
static struct k_thread threadA_data;

K_THREAD_STACK_DEFINE(threadB_stack_area, STACKSIZE);
static struct k_thread threadB_data;

/* threadA is a static thread that is spawned automatically */

void threadA(void *arg1, void *arg2, void *arg3)
{
int incrementer = (intptr_t)arg1;

ARG_UNUSED(arg2);
ARG_UNUSED(arg3);

/* invoke routine to ping-pong hello messages with threadB */
helloLoop(__func__, &threadA_sem, &threadB_sem, incrementer);
}

void main(void)
{
k_thread_create(&threadA_data, threadA_stack_area,
K_THREAD_STACK_SIZEOF(threadA_stack_area),
threadA, (void *)1, NULL, NULL,
0, 0, K_FOREVER);
k_thread_name_set(&threadA_data, "thread_a");

k_thread_create(&threadB_data, threadB_stack_area,
K_THREAD_STACK_SIZEOF(threadB_stack_area),
threadB, (void *)-1, NULL, NULL,
0, 0, K_FOREVER);
k_thread_name_set(&threadB_data, "thread_b");

k_thread_start(&threadA_data);
k_thread_start(&threadB_data);
}
1 change: 1 addition & 0 deletions samples/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Samples and Demos
modules/*
compression/*
fuel_gauge/*
coop_synchronization/*

.. comment
To add a new sample document, please use the template available under
Expand Down

0 comments on commit 0f97850

Please sign in to comment.