Don't think! Just do it!

종합 IT 기술 정체성 카오스 블로그! 이... 이곳은 어디지?

임베디드 소프트웨어/Zephyr

[nRF52840 + Zephyr] #12. Synchronization

방피터 2023. 6. 3. 16:15

Synchronization에 대해서는 Semaphore와 Mutex, 이 두 개만 알면 끝!

😆😆😆😆

Semaphore부터 보자구.

//런타임에서는 이렇게
struct k_sem my_sem;
k_sem_init(&my_sem, 0, 1);

//컴파일타임에서는 이렇게
K_SEM_DEFINE(my_sem, 0, 1);//가용 세마포어 1, 초기 가용 세마포어 0

//or
K_SEM_DEFINE(my_sem, 1, 1);//가용 세마포어 1, 초기 가용 세마포어 1

👆 위처럼 선언하고

👇 아래처럼 사용하지.

//Semaphore take
k_sem_take(&my_sem, K_MSEC(50))

//Semaphore give
k_sem_give(&my_sem);

약간 설명하자면

가용한 세마포어가 있으면

k_sem_take로 세마포어를 차지하고 사용이 끝나면 

k_sem_give로 돌려주는 거지.

 

실습은 조금 있다가 하고 mutex로 넘어가면

👇👇

//런타임에서 이렇게
struct k_mutex my_mutex;
k_mutex_init(&my_mutex);

//컴파일타임에서는 이렇게
K_MUTEX_DEFINE(my_mutex);

//사용은 이렇게
//mutex lock
k_mutex_lock(&my_mutex, K_FOREVER);

//mutex unlock
k_mutex_unlock(&my_mutex);

사용법은 세마포어와 거의 비슷하지.

뮤택스는 가용 공간이 1개인 세마포어와 동작이 동일해.

어때?

 

이런걸 쓸까 싶어? ㅋㅋ

응!

응응응응응응응응응!

Thread나 Task를 사용하는 RTOS에서는 필수적인 개념이야.

왜냐하면 여러 Thread에서 동일한 자원에 접근이 가능할 수 있기 때문이야.

또 억지로 예제를 만들어 볼게 ㅋㅋ

실습을 해봐야 좋겠지?

BLE advertisement 하는 부분에서 한번 살펴볼게.

//prj.conf
CONFIG_HEAP_MEM_POOL_SIZE=1024
CONFIG_BT=y
CONFIG_BT_HCI=y

CONFIG_LOG=y
CONFIG_LOG_BACKEND_UART=y

우선 ble 사용 설정을 하고

결과가 잘 보이게 logging system도 넣어두고

시작!

#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>

LOG_MODULE_REGISTER(main);

#define BT_UUID_REMOTE_SERVICE_VAL BT_UUID_128_ENCODE(0xc99d5788,0xf6dc,0x11ed,0xb67e,0x0242ac120002)
//c99d5788-f6dc-11ed-b67e-0242ac120002
#define BT_UUID_REMOTE_SERVICE BT_UUID_DECLARE_128(BT_UUID_REMOTE_SERVICE_VAL)

static const struct bt_data ad[] = {
	BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
	BT_DATA(BT_DATA_NAME_COMPLETE, "PETERCIRCUITSOFT", sizeof("PETERCIRCUITSOFT") - 1),
};

static const struct bt_data sd[] = {
	BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_REMOTE_SERVICE_VAL),
};

bt_ready_cb_t bt_ready_cb(int err)
{
	if (err) {
		LOG_ERR("Bluetooth init failed (err %d)\n", err);
		return;
	}
	LOG_INF("Bluetooth enabled\n");
}

void main(void)
{
	int err;
	LOG_INF("Hello World! %s\n", CONFIG_BOARD);
	bt_enable(bt_ready_cb);
	err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad),sd, ARRAY_SIZE(sd));
	if(err){
		LOG_ERR("Advertising failed to start (err %d)\n", err);
		return;
	}
}

이렇게 하면 결과가 👇👇👇

Advertising failed

 

advertising failed!!!!

로그를 잘 살펴보면 bluetooth가 enable되기도 전에 advertising이 시작되고 있어.

그래서 오류가 발생하는 거지.

이럴 때 semaphore를 사용하는 거야.

#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>

LOG_MODULE_REGISTER(main);

#define BT_UUID_REMOTE_SERVICE_VAL BT_UUID_128_ENCODE(0xc99d5788,0xf6dc,0x11ed,0xb67e,0x0242ac120002)
//c99d5788-f6dc-11ed-b67e-0242ac120002
#define BT_UUID_REMOTE_SERVICE BT_UUID_DECLARE_128(BT_UUID_REMOTE_SERVICE_VAL)

static const struct bt_data ad[] = {
	BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
	BT_DATA(BT_DATA_NAME_COMPLETE, "PETERCIRCUITSOFT", sizeof("PETERCIRCUITSOFT") - 1),
};

static const struct bt_data sd[] = {
	BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_REMOTE_SERVICE_VAL),
};

//여기 추가!!!
K_SEM_DEFINE(sem, 0, 1);

bt_ready_cb_t bt_ready_cb(int err)
{
	if (err) {
		LOG_ERR("Bluetooth init failed (err %d)\n", err);
		return;
	}
	LOG_INF("Bluetooth enabled\n");
    //여기 추가!!!
	k_sem_give(&sem);
}

void main(void)
{
	int err;
	LOG_INF("Hello World! %s\n", CONFIG_BOARD);
	bt_enable(bt_ready_cb);
    //여기 추가!!!
	k_sem_take(&sem, K_FOREVER);
	err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad),sd, ARRAY_SIZE(sd));
	if(err){
		LOG_ERR("Advertising failed to start (err %d)\n", err);
		return;
	}
}

우선 세마포어 선언하고!

bt_enable을 실행하고 k_sem_take로 세마포어에 공간이 생기기를 기다리지.

bt_enable이 끝나고 bt_ready_cb가 수행되면 그 때 k_sem_give가 실행되서 세마포어에 공간을 주게 됨.

그러면 k_sem_take에서 대기하고 있던 스레드가 무사히?

세마포어 공간을 차지하고 넘어가서 Ble advertisement를 수행할 수 있게되지!

정상적으로 동작한다.

mutex도 동작 방식이 세마포어와 거의 비슷한데

같은 스레드 안에서는 lock을 여러번 할 수 있어서 위 예제처럼 사용은 안돼.

하나의 자원(예를 들면 SPI, UART)에 여러 쓰레드에서 접근하는 걸 상정하고 있기 때문이야.

또 억지로 예제를 만들어보면

👇👇👇

#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(main);

K_MUTEX_DEFINE(my_mutex);

void my_thread_entry_point(void *p1, void *p2, void *p3);

K_THREAD_DEFINE(my_thread, 1024, my_thread_entry_point, NULL, NULL, NULL, 7, 0, 0);

void my_thread_entry_point(void *p1, void *p2, void *p3)
{
	while(1){
		k_mutex_lock(&my_mutex, K_FOREVER);
		printk("my_thread is running!\n");
		k_msleep(100);
		printk("my_thread is running!\n");
		k_msleep(100);
		printk("my_thread is running!\n");
		k_msleep(100);
		k_mutex_unlock(&my_mutex);
	}
}

void main(void)
{
	for(;;){
		k_mutex_lock(&my_mutex, K_FOREVER);
		printk("main thread is running!\n");
		k_msleep(100);
		printk("main thread is running!\n");
		k_msleep(100);
		printk("main thread is running!\n");
		k_msleep(100);
		k_mutex_unlock(&my_mutex);
	}
}

이렇게 mutex를 설정하면

아래와 같이 한 쓰레드에서 printk를 전부 하고 난 후에

다른 쓰레드에서 printk를 수행하지.

그런데 만약 mutex를 없애면 어떻게 될까?

전부 주석처리해놓고 테스트 해바바 ㅋ

아래와 같이 쓰레드가 쉬는 틈을 다른 쓰레드가 치고 들어가서

자기 걸 수행해버리고 말지.

이건 단순하게 printk니까 큰 문제가 없지만

SPI나 I2C처럼 여러 디바이스가 버스로 묶여있는 하드웨어에서는

치명적인 오류가 발생할 수 있어.

 

그래서!

세마포어와 뮤텍스는 필수!

 

다음 시간부터는 본격적 nRF로 BLE를 구동시켜 볼거야!

안녕!

👋👋👋👋

반응형