Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Operating System Implementation: Synchronization and Thread-Safe I/O

Tech May 14 1

In multi-threaded environments, concurrent access to video memory and ports can cause GP exceptions due to incorrect calculation of write offsets. To address this issue, we implement semaphore and synchronization mechanisms.

When a thread waits for a semaphore, it voluntarily blocks until another thread unblocks it. Only a thread can block itself; one thread cannot force another to block.

void OS_ThreadBlock(enum THREAD_STATE newState) {
    uint8_t              interruptFlag;
    struct THREAD_CTRL   *currentThread;

    KASSERT(newState == THREAD_STATE_BLOCKED || newState == THREAD_STATE_WAITING || newState == THREAD_STATE_HANGING);
    interruptFlag = DisableInterrupts();
    currentThread = GetCurrentThread();
    currentThread->state = newState;
    OS_Schedule();
    RestoreInterrupts(interruptFlag);
}

void OS_ThreadUnblock(struct THREAD_CTRL *thread) {
    uint8_t interruptFlag;

    KASSERT(thread->state == THREAD_STATE_BLOCKED || thread->state == THREAD_STATE_HANGING ||
            thread->state == THREAD_STATE_WAITING);
    
    interruptFlag = DisableInterrupts();
    if (thread->state != THREAD_STATE_READY) {
        if (ListContains(&readyQueue, &thread->queueNode))
            KERNEL_PANIC("OS_ThreadUnblock: blocked thread in Ready Queue");
        ListInsertHead(&readyQueue, &thread->queueNode);
        thread->state = THREAD_STATE_READY;
    }
    RestoreInterrupts(interruptFlag);
}

To implement mutual exclusion, we can use binary semaphores:

struct BINARY_SEMAPHORE {
    BOOL           isAvailable;
    struct LIST    waitingThreads;
};

void OS_InitBinarySemaphore(struct BINARY_SEMAPHORE *semaphore, BOOL initialValue);

void OS_AcquireSemaphore(struct BINARY_SEMAPHORE *semaphore);

void OS_ReleaseSemaphore(struct BINARY_SEMAPHORE *semaphore);

  • BINARY_SEMAPHORE structure: Represents a binary semaphore
    • isAvailable: Boolean value (0 or 1) indicating if the semaphore is available
    • waitingThreads: Queue of threads blocked waiting for the semaphore
  • Initialization (OS_InitBinarySemaphore): All binary semaphores must be initialized with this function
  • Acquire (OS_AcquireSemaphore): Adds current thread to waiting queue and blocks the thread
  • Release (OS_ReleaseSemaphore): Removes a thread from the waiting queue and wakes it up
  • Strict mutual exclusion: The value can only switch between 0 and 1

The MUTEX_LOCK structure is a simple wrapper around a binary semaphore that implements a recursive mutex:

struct MUTEX_LOCK {
    struct THREAD_CTRL    *owner;
    struct BINARY_SEMAPHORE semaphore;
    uint32_t              recursionCount;
};

void OS_InitMutex(struct MUTEX_LOCK *mutex);

void OS_AcquireMutex(struct MUTEX_LOCK *mutex);

void OS_ReleaseMutex(struct MUTEX_LOCK *mutex);

The recursionCount field allows the same thread to acquire the lock multiple times without causing deadlock. Other threads must wait until the lock is complete released.

The console device implementation uses locks to prevent GP exceptions in multi-threaded environments:

#ifndef __OS_CONSOLE_H
#define __OS_CONSOLE_H 1

void Console_Initialize(void);

void Console_Lock(void);

void Console_Unlock(void);

void Console_PrintString(const char *str);

void Console_PrintLine(const char *str);

void Console_PrintChar(char ch);

void Console_PrintAddress(void *addr);

void Console_PrintInt(int val, int base);

#endif

Keyboard Input Handling

The following discussion is based on traditional PS/2 keyboards. Most modern keyboards connect via USB and follow the HID protocol. However, if a computer supports PS/2 compatible ports, the following code remains applicable.

Three sets of keyboard scan codes have existed historically. Regardless of which set the keyboard uses, the i8042 compatible chip converts them to Scan Code Set 1, which is then processed by the operating system's keyboard interrupt handler.

When a user presses any key, a make code is sent. If the key is held down, the make code is repeated. When the key is released, a break code is sent. For Scan Code Set 1:

  1. Most keys have one-byte scan codes, where make code + 0x80 = break code
  2. Some extended keys use two bytes: make code is 0xe0, 0x?? and break code is 0xe0, 0x?? + 0x80
  3. Special keys might use four bytes (which we won't handle)
  4. The highest bit (8th bit) is always 0 for make codes and 1 for break codes
  5. Most operating systems don't process break codes directly

For 8086-compatible systems, keyboard interrupt signals come from the master 8259A PIC's IR1 interface (interrupt number 0x21):

; 8259A Interrupt Vector Table
VECTOR 0x20,KEYBOARD_HANDLER
VECTOR 0x21,KEYBOARD_HANDLER
VECTOR 0x22,KEYBOARD_HANDLER
VECTOR 0x23,KEYBOARD_HANDLER
VECTOR 0x24,KEYBOARD_HANDLER
VECTOR 0x25,KEYBOARD_HANDLER
VECTOR 0x26,KEYBOARD_HANDLER
VECTOR 0x27,KEYBOARD_HANDLER
VECTOR 0x28,KEYBOARD_HANDLER
VECTOR 0x29,KEYBOARD_HANDLER
VECTOR 0x2a,KEYBOARD_HANDLER
VECTOR 0x2b,KEYBOARD_HANDLER
VECTOR 0x2c,KEYBOARD_HANDLER
VECTOR 0x2d,KEYBOARD_HANDLER
VECTOR 0x2e,KEYBOARD_HANDLER
VECTOR 0x2f,KEYBOARD_HANDLER

The keyboard interrupt handler reads the scan code from port 0x60:

int scanCode = inb(KEYBOARD_DATA_PORT);


<p>The port transfers one byte at a time, so pressing certain special keys (extended keys) may trigger two interrupts (and four when released, including breaks).</p>

<p>We don't handle four-byte keys or break codes. The implementation processes scan codes as follows:</p>

  • Read the keyboard buffer port (0x60) to get the raw scan code
  • If the scan code is 0xe0, mark the next scan code as an extended key and set an extension flag
  • If the extension flag is set, mark the scan code with 0xe000 in the high byte and reset the flag
  • Determine if it's a press (Make Code) or release (Break Code) using the highest bit (0x80)
  • For release events, update the global state of modifier keys (Shift/Ctrl/Alt) to FALSE
  • Determine the correct character based on Shift and CapsLock states

Circular Buffer Implementation

The circular buffer implementation in iocbuf.c provides a thread-safe, fixed-size (128-byte) circular buffer that supports producer-consumer patterns.

A circular buffer is a fixed-size contiguous memory area accessed as a circular queue. When data is written, the head pointer moves forward; when data is read, the tail pointer moves forward.

The circular buffer ensures thread safety through:

  • Lock mechanism: Ensures atomic operations on buffer state, preventing data inconsistency
  • Blocking and waking:
    • When a producer finds the buffer full, it blocks itself, waiting for consumers to read data
    • After consumers read data, they check if any producers are waiting and wake them up
    • Conversely, when consumers find the buffer empty, they block themselves, waiting for producers to write data

For the keyboard device, the keyboard interrupt handler acts as the producer, while future implementations like Shell and user programs will act as consumers:

// keyboard.h
extern struct CIRCULAR_BUFFER keyboardBuffer;

// keyboard.c
    char character = keyMap[scanCode][shiftState];
    if (character) {
        if (!CB_IsFull(&keyboardBuffer)) {
            // The keyboard interrupt handler acts as a producer,
            // placing characters into the buffer for consumption by other threads
            CB_PutChar(&keyboardBuffer, character);
        }
        return;
    }

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.