Lock Mechanisms from Microcontrollers to Linux

In concurrent programming development, lock mechanisms are core technologies to ensure data consistency and thread safety. Whether in resource-constrained microcontroller environments or feature-rich Linux systems, lock mechanisms play a crucial role.

Lock Mechanisms

What is a Lock Mechanism?

A lock mechanism is a synchronization primitive used to control access to shared resources by multiple execution units. It ensures that only one execution unit can access the protected resource at any given time, thus avoiding data races and inconsistent states.

Attempt to acquire lock

Is YsY

Is


Thread A
Is the lock available?
Lock acquired successfully
Wait or block
Access shared resource
Release lock
Retry or give up
Other threads can acquire the lock

Classification of Locks

Locks can be classified by granularity, behavior, and scope:
Coarse-grained locks
Fine-grained locks
Blocking locks
Non-blocking locks
Intra-process locks
Inter-process locks
Global locks
Table-level locks
Row-level locks
Field-level locks
Mutex locks
Read-write locks
Spin locks
CAS operations
Thread locks
Memory locks
File locks
Semaphores

Lock Mechanisms in Microcontrollers

Characteristics and Constraints

The microcontroller environment has the following characteristics:

  • Resource Constraints: Limited memory and CPU resources
  • Real-time Requirements: Need to ensure response time
  • Single-core Architecture: Usually only one CPU core
  • Interrupt-driven: Needs to handle hardware interrupts

Common Lock Mechanisms

1. Interrupt Lock

The most basic lock mechanism, ensuring atomicity by disabling interrupts:

// Interrupt lock example
typedef struct {
    uint32_t saved_irq_state;
} irq_lock_t;

void irq_lock_acquire(irq_lock_t *lock) {
    lock->saved_irq_state = __get_PRIMASK();
    __disable_irq();
}

void irq_lock_release(irq_lock_t *lock) {
    if (!lock->saved_irq_state) {
        __enable_irq();
    }
}

2. Atomic Operation Lock

Utilizes CPU atomic instructions to implement lightweight locks:

// Atomic operation lock example
typedef volatile uint32_t atomic_lock_t;

#define ATOMIC_LOCK_INIT 0
#define ATOMIC_LOCK_LOCKED 1

static inline int atomic_lock_try_acquire(atomic_lock_t *lock) {
    return __sync_bool_compare_and_swap(lock, ATOMIC_LOCK_INIT, ATOMIC_LOCK_LOCKED);
}

static inline void atomic_lock_release(atomic_lock_t *lock) {
    __sync_lock_test_and_set(lock, ATOMIC_LOCK_INIT);
}

3. FreeRTOS Mutex Lock

Standard mutex lock in RTOS environments:

#include "FreeRTOS.h"
#include "semphr.h"

// FreeRTOS mutex lock example
SemaphoreHandle_t xMutex;

void init_mutex(void) {
    xMutex = xSemaphoreCreateMutex();
}

void critical_section(void) {
    if (xSemaphoreTake(xMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
        // Critical section code
        xSemaphoreGive(xMutex);
    }
}

Flowchart of Lock Mechanism in Microcontrollers


Is Is

No No


Interrupt occurs
Is there an interrupt lock?
Delay interrupt handling
Handle interrupt immediately
Wait for lock release
Process delayed interrupt
Interrupt handling completed
Resume execution

Lock Mechanisms in Linux

Characteristics and Advantages

The Linux environment has the following characteristics:

  • Multi-core Architecture: Supports true parallel execution
  • Rich System Calls: Provides various synchronization primitives
  • Process Isolation: Requires inter-process communication mechanisms
  • Memory Management: Virtual memory and page management

Common Lock Mechanisms

1. POSIX Thread Lock

Mutex based on the pthread library:

#include <pthread.h>

// POSIX mutex lock example
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *thread_function(void *arg) {
    pthread_mutex_lock(&mutex);
    // Critical section code
    pthread_mutex_unlock(&mutex);
    return NULL;
}

2. Spin Lock

Suitable for locks held for a short time:

#include <pthread.h>

// Spin lock implementation
typedef struct {
    volatile int locked;
} spinlock_t;

void spin_lock(spinlock_t *lock) {
    while (__sync_lock_test_and_set(&lock->locked, 1)) {
        // Spin wait
        __asm__ volatile("pause");
    }
}

void spin_unlock(spinlock_t *lock) {
    __sync_lock_release(&lock->locked);
}

3. File Lock

Used for inter-process synchronization:

#include <sys/file.h>
#include <fcntl.h>

// File lock example
int acquire_file_lock(const char *filename) {
    int fd = open(filename, O_RDWR | O_CREAT, 0644);
    if (fd == -1) return -1;
    
    if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
        close(fd);
        return -1;
    }
    return fd;
}

void release_file_lock(int fd) {
    flock(fd, LOCK_UN);
    close(fd);
}

4. Semaphore

Used for resource counting and synchronization:

#include <semaphore.h>

// Semaphore example
sem_t semaphore;

void init_semaphore(void) {
    sem_init(&semaphore, 0, 1); // Binary semaphore initialized to 1
}

void producer(void) {
    // Produce resource
    sem_post(&semaphore); // Increase semaphore
}

void consumer(void) {
    sem_wait(&semaphore); // Wait for resource
    // Consume resource
}

Architecture Diagram of Linux Lock Mechanisms

Hardware Layer
Kernel Space
User Space



Application
pthread Library
System Call Interface
File System
Process Scheduler
Memory Management
Device Driver
CPU Core
Memory
Interrupt Controller

Commonalities and Differences Analysis

Commonalities

Common goals of lock mechanisms:
Core concepts:
Usage patterns:
Protect shared resources
Avoid data races
Ensure data consistency
Critical section
Atomic operations
Synchronization primitives
Acquire lock
Access resource
Release lock

Differences Comparison

Feature Microcontroller Environment Linux Environment
Resource Constraints Strictly limited Relatively relaxed
Concurrency Model Interrupt-driven Multithreaded/Multiprocess
Lock Granularity Coarse-grained Fine-grained
Implementation Complexity Simple and direct Complex and diverse
Performance Overhead Minimized Acceptable
Debugging Difficulty Difficult Relatively easy

Practical Case Analysis

Shared Counter Protection

Problem Description

Multiple threads/tasks need to safely access and modify a shared counter.

Microcontroller Implementation

// Counter protection in microcontroller environment
typedef struct {
    volatile uint32_t counter;
    irq_lock_t lock;
} safe_counter_t;

void counter_increment(safe_counter_t *counter) {
    irq_lock_acquire(&counter->lock);
    counter->counter++;
    irq_lock_release(&counter->lock);
}

uint32_t counter_get_value(safe_counter_t *counter) {
    uint32_t value;
    irq_lock_acquire(&counter->lock);
    value = counter->counter;
    irq_lock_release(&counter->lock);
    return value;
}

Linux Implementation

// Counter protection in Linux environment
typedef struct {
    volatile uint32_t counter;
    pthread_mutex_t mutex;
} safe_counter_t;

void counter_init(safe_counter_t *counter) {
    counter->counter = 0;
    pthread_mutex_init(&counter->mutex, NULL);
}

void counter_increment(safe_counter_t *counter) {
    pthread_mutex_lock(&counter->mutex);
    counter->counter++;
    pthread_mutex_unlock(&counter->mutex);
}

uint32_t counter_get_value(safe_counter_t *counter) {
    uint32_t value;
    pthread_mutex_lock(&counter->mutex);
    value = counter->counter;
    pthread_mutex_unlock(&counter->mutex);
    return value;
}

Producer-Consumer Pattern

Microcontroller Implementation (FreeRTOS)

#include "FreeRTOS.h"
#include "queue.h"

#define QUEUE_SIZE 10
QueueHandle_t xQueue;

void producer_task(void *pvParameters) {
    int data = 0;
    while (1) {
        if (xQueueSend(xQueue, &data, pdMS_TO_TICKS(100)) == pdTRUE) {
            data++;
        }
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void consumer_task(void *pvParameters) {
    int received_data;
    while (1) {
        if (xQueueReceive(xQueue, &received_data, pdMS_TO_TICKS(1000)) == pdTRUE) {
            // Process data
            printf("Received: %d\n", received_data);
        }
    }
}

Linux Implementation

#include <pthread.h>
#include <semaphore.h>

#define BUFFER_SIZE 10

typedef struct {
    int buffer[BUFFER_SIZE];
    int in, out;
    sem_t empty, full;
    pthread_mutex_t mutex;
} circular_buffer_t;

void producer(circular_buffer_t *cb, int item) {
    sem_wait(&cb->empty);
    pthread_mutex_lock(&cb->mutex);
    
    cb->buffer[cb->in] = item;
    cb->in = (cb->in + 1) % BUFFER_SIZE;
    
    pthread_mutex_unlock(&cb->mutex);
    sem_post(&cb->full);
}

int consumer(circular_buffer_t *cb) {
    int item;
    
    sem_wait(&cb->full);
    pthread_mutex_lock(&cb->mutex);
    
    item = cb->buffer[cb->out];
    cb->out = (cb->out + 1) % BUFFER_SIZE;
    
    pthread_mutex_unlock(&cb->mutex);
    sem_post(&cb->empty);
    
    return item;
}

Common Pitfalls Handling

Priority Inversion

Shared resource
Low priority task
Medium priority task
High priority task
Shared resource
Low priority task
Medium priority task
High priority task
Medium priority task runs
High priority task is blocked by medium priority task
Low priority task cannot release lock
Attempt to acquire lock (blocked)
Preempt CPU

Solution: Priority Inheritance

// Mutex with priority inheritance
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
pthread_mutex_init(&mutex, &attr);

ABA Problem

// ABA problem example
typedef struct node {
    int value;
    struct node *next;
} node_t;

// Incorrect CAS operation
node_t *head = NULL;

void push(int value) {
    node_t *new_node = malloc(sizeof(node_t));
    new_node->value = value;
    
    do {
        new_node->next = head;
    } while (!__sync_bool_compare_and_swap(&head, new_node->next, new_node));
}

// Solution: Use version number or flag
typedef struct node {
    int value;
    struct node *next;
    uint32_t version;  // Version number
} node_t;

Lock Contention


Wait for lock

Wait for lock

Wait for lock

Wait for lock


Thread A
Lock
Thread B
Thread C
Thread D
Lock holder
Release lock
Wake up waiting threads
Thread scheduling

Solution: Lock Separation

// Lock separation example
typedef struct {
    pthread_mutex_t read_mutex;
    pthread_mutex_t write_mutex;
    int reader_count;
    pthread_mutex_t count_mutex;
} rw_lock_t;

void read_lock(rw_lock_t *rw) {
    pthread_mutex_lock(&rw->count_mutex);
    rw->reader_count++;
    if (rw->reader_count == 1) {
        pthread_mutex_lock(&rw->write_mutex);
    }
    pthread_mutex_unlock(&rw->count_mutex);
}

void write_lock(rw_lock_t *rw) {
    pthread_mutex_lock(&rw->write_mutex);
}

Lock Mechanisms from Microcontrollers to Linux

How to reduce bugs in coding, testing, and debugging phases?

Lock Mechanisms from Microcontrollers to Linux

What are the differences between domestic and foreign programmers?

Lock Mechanisms from Microcontrollers to Linux

The impact of stack growth direction on embedded development

Leave a Comment