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
Thread entry point function
Argument passed to the thread function
Thread priority (0 = highest, higher values = lower priority). Normal priority is 1000.
Stack size in bytes (minimum 0x1000 / 4KB recommended)
Thread creation flags:
THREAD_JOINABLE (1) - Thread can be joined
THREAD_INTERRUPT (2) - Thread triggered by interrupt
Thread name for debugging (max 8 characters)
Returns 0 on success, non-zero error code on failure
Thread Lifecycle
Thread Exit
Get Thread ID
Example
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
Priority Functions
Example
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 Structure
Get Stack Info
Example
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.
Mutex Types
Mutex Initialization
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
Protocol (Scheduling Policy)
Constant Description SYS_MUTEX_PROTOCOL_FIFOFirst-in-first-out scheduling SYS_MUTEX_PROTOCOL_PRIOPriority-based scheduling SYS_MUTEX_PROTOCOL_PRIO_INHERITPriority inheritance
Constant Description SYS_MUTEX_ATTR_RECURSIVESame thread can lock multiple times SYS_MUTEX_ATTR_NOT_RECURSIVELock fails if already held
Constant Description 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.
Condition Types
Condition Functions
Example
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:
samples/sys/threadtest/source/main.c
#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 \n addr: %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 = 0x 1000 ;
char * threadname = "myThread" ;
void * threadarg = ( void * ) 0x 1337 ;
// 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
Producer-Consumer Example
#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