Skip to main content
PSL1GHT provides comprehensive threading APIs for creating and managing PPU (PowerPC Processing Unit) threads on the PlayStation 3, along with synchronization primitives for thread-safe programming.

Overview

Thread functionality is provided through:
  • sys/thread.h - Thread creation and management syscalls
  • lv2/thread.h - Thread utility functions
  • sys/mutex.h - Mutual exclusion locks
  • sys/cond.h - Condition variables

Thread Management

Thread Types

The PS3 uses 64-bit thread identifiers:
typedef u64 sys_ppu_thread_t;

Creating Threads

s32 sysThreadCreate(sys_ppu_thread_t *threadid,
                    void (*entry)(void *),
                    void *arg,
                    s32 priority,
                    u64 stacksize,
                    u64 flags,
                    char *threadname);
threadid
sys_ppu_thread_t*
required
Pointer to storage for the new thread ID
entry
void (*)(void*)
required
Thread entry point function
arg
void*
Argument passed to the thread function
priority
s32
required
Thread priority (0 = highest, higher values = lower priority). Normal priority is 1000.
stacksize
u64
required
Stack size in bytes (minimum 0x1000 / 4KB recommended)
flags
u64
required
Thread creation flags:
  • THREAD_JOINABLE (1) - Thread can be joined
  • THREAD_INTERRUPT (2) - Thread triggered by interrupt
threadname
char*
Thread name for debugging (max 8 characters)
return
s32
Returns 0 on success, non-zero error code on failure

Thread Lifecycle

void sysThreadExit(u64 ret_val);

Joining Threads

LV2_SYSCALL sysThreadJoin(sys_ppu_thread_t threadid, u64 *retval);
Only threads created with the THREAD_JOINABLE flag can be joined. Attempting to join a non-joinable thread will fail.

Detaching Threads

LV2_SYSCALL sysThreadDetach(sys_ppu_thread_t threadid);

Thread Control

Priority Management

LV2_SYSCALL sysThreadSetPriority(sys_ppu_thread_t threadid, s32 prio);
LV2_SYSCALL sysThreadGetPriority(sys_ppu_thread_t threadid, s32 *prio);
Thread priority ranges:
  • 0 = Highest priority (typically reserved for system)
  • 1000 = Normal priority for applications
  • Higher values = Lower priority

Thread Yielding

LV2_SYSCALL sysThreadYield();

Stack Information

typedef struct _sys_ppu_thread_stack_t {
    void *addr;  // Pointer to stack buffer
    u32 size;    // Stack size in bytes
} sys_ppu_thread_stack_t;

Synchronization Primitives

Mutexes

Mutexes provide mutual exclusion for protecting shared resources.
typedef s32 sys_mutex_t;

typedef struct sys_mutex_attr {
    u32 attr_protocol;   // Scheduling policy
    u32 attr_recursive;  // Recursive setting
    u32 attr_pshared;    // Sharing policy
    u32 attr_adaptive;   // Adaptive setting
    u64 key;             // Mutex key
    s32 flags;           // Mutex flags
    u32 _pad;
    char name[8];        // Mutex name
} sys_mutex_attr_t;

Mutex Attributes

ConstantDescription
SYS_MUTEX_PROTOCOL_FIFOFirst-in-first-out scheduling
SYS_MUTEX_PROTOCOL_PRIOPriority-based scheduling
SYS_MUTEX_PROTOCOL_PRIO_INHERITPriority inheritance
ConstantDescription
SYS_MUTEX_ATTR_RECURSIVESame thread can lock multiple times
SYS_MUTEX_ATTR_NOT_RECURSIVELock fails if already held
ConstantDescription
SYS_MUTEX_ATTR_ADAPTIVEAdaptive spinning before blocking
SYS_MUTEX_ATTR_NOT_ADAPTIVEBlock immediately

Mutex Operations

LV2_SYSCALL sysMutexCreate(sys_mutex_t *mutex, 
                           const sys_mutex_attr_t *attr);
LV2_SYSCALL sysMutexDestroy(sys_mutex_t mutex);
LV2_SYSCALL sysMutexLock(sys_mutex_t mutex, u64 timeout_usec);
LV2_SYSCALL sysMutexTryLock(sys_mutex_t mutex);
LV2_SYSCALL sysMutexUnlock(sys_mutex_t mutex);

Condition Variables

Condition variables enable threads to wait for specific conditions.
typedef u32 sys_cond_t;

typedef struct sys_cond_attr {
    u32 attr_pshared;  // Sharing attribute
    s32 flags;         // Flags
    u64 key;           // Key
    char name[8];      // Name
} sys_cond_attr_t;
sysCondWait() atomically unlocks the mutex and waits. When signaled, it re-acquires the mutex before returning.

Complete Thread Example

Here’s the complete threadtest sample from PSL1GHT:
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <malloc.h>
#include <ppu-types.h>
#include <io/pad.h>
#include <sys/thread.h>

static void thread_start(void *arg)
{
    s32 running = 0;
    sys_ppu_thread_t id;
    sys_ppu_thread_stack_t stackinfo;

    // Get thread ID
    sysThreadGetId(&id);
    
    // Get stack information
    sysThreadGetStackInformation(&stackinfo);

    printf("stack\naddr: %p, size: %d\n", 
           stackinfo.addr, stackinfo.size);
    
    while(running < 5) {
        printf("Thread: %08llX\n", (unsigned long long int)id);

        // Yield to other threads
        sysThreadYield();
        sleep(2);
        running++;
    }

    sysThreadExit(0);
}

int main(int argc, char *argv[])
{
    u64 retval;
    s32 ret;
    sys_ppu_thread_t id;
    u64 prio = 1500;
    size_t stacksize = 0x1000;
    char *threadname = "myThread";
    void *threadarg = (void*)0x1337;

    // Create thread
    ret = sysThreadCreate(&id, thread_start, threadarg, 
                          prio, stacksize, 
                          THREAD_JOINABLE, threadname);
    printf("sysThreadCreate: %d\n", ret);

    // Wait for thread to complete
    ret = sysThreadJoin(id, &retval);
    printf("sysThreadJoin: %d - %llX\n", 
           ret, (unsigned long long int)retval);

    printf("Exiting thread test\n");
    return 0;
}

Producer-Consumer Pattern

#include <sys/thread.h>
#include <sys/mutex.h>
#include <sys/cond.h>

#define BUFFER_SIZE 10

struct {
    int buffer[BUFFER_SIZE];
    int count;
    int in;
    int out;
    sys_mutex_t mutex;
    sys_cond_t not_empty;
    sys_cond_t not_full;
} queue;

void init_queue() {
    sys_mutex_attr_t mattr;
    sys_cond_attr_t cattr;
    
    queue.count = 0;
    queue.in = 0;
    queue.out = 0;
    
    sysMutexAttrInitialize(mattr);
    sysMutexCreate(&queue.mutex, &mattr);
    
    sysCondAttrInitialize(cattr);
    sysCondCreate(&queue.not_empty, queue.mutex, &cattr);
    sysCondCreate(&queue.not_full, queue.mutex, &cattr);
}

void producer(void *arg) {
    int item = 0;
    
    while (1) {
        sysMutexLock(queue.mutex, 0);
        
        // Wait while buffer is full
        while (queue.count == BUFFER_SIZE) {
            sysCondWait(queue.not_full, 0);
        }
        
        // Produce item
        queue.buffer[queue.in] = item++;
        queue.in = (queue.in + 1) % BUFFER_SIZE;
        queue.count++;
        
        printf("Produced: %d\n", item - 1);
        
        // Signal consumers
        sysCondSignal(queue.not_empty);
        sysMutexUnlock(queue.mutex);
        
        sysThreadYield();
    }
}

void consumer(void *arg) {
    int item;
    
    while (1) {
        sysMutexLock(queue.mutex, 0);
        
        // Wait while buffer is empty
        while (queue.count == 0) {
            sysCondWait(queue.not_empty, 0);
        }
        
        // Consume item
        item = queue.buffer[queue.out];
        queue.out = (queue.out + 1) % BUFFER_SIZE;
        queue.count--;
        
        printf("Consumed: %d\n", item);
        
        // Signal producers
        sysCondSignal(queue.not_full);
        sysMutexUnlock(queue.mutex);
        
        sysThreadYield();
    }
}

Best Practices

Always Check Returns

Verify thread creation and synchronization operations succeed.

Proper Cleanup

Destroy mutexes and condition variables when done.

Avoid Deadlocks

Always acquire locks in the same order across threads.

Use Timeouts

Consider using timeouts to prevent indefinite blocking.
Common Pitfalls:
  • Forgetting to unlock mutexes
  • Deadlocks from circular lock dependencies
  • Race conditions from missing synchronization
  • Stack overflow from insufficient stack size

See Also

Build docs developers (and LLMs) love