/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2015 Intel Corporation. * Copyright 2012 Hasan Alayli */ /** * @file lthread_api.h * * @warning * @b EXPERIMENTAL: this API may change without prior notice * * This file contains the public API for the L-thread subsystem * * The L_thread subsystem provides a simple cooperative scheduler to * enable arbitrary functions to run as cooperative threads within a * single P-thread. * * The subsystem provides a P-thread like API that is intended to assist in * reuse of legacy code written for POSIX p_threads. * * The L-thread subsystem relies on cooperative multitasking, as such * an L-thread must possess frequent rescheduling points. Often these * rescheduling points are provided transparently when the application * invokes an L-thread API. * * In some applications it is possible that the program may enter a loop the * exit condition for which depends on the action of another thread or a * response from hardware. In such a case it is necessary to yield the thread * periodically in the loop body, to allow other threads an opportunity to * run. This can be done by inserting a call to lthread_yield() or * lthread_sleep(n) in the body of the loop. * * If the application makes expensive / blocking system calls or does other * work that would take an inordinate amount of time to complete, this will * stall the cooperative scheduler resulting in very poor performance. * * In such cases an L-thread can be migrated temporarily to another scheduler * running in a different P-thread on another core. When the expensive or * blocking operation is completed it can be migrated back to the original * scheduler. In this way other threads can continue to run on the original * scheduler and will be completely unaffected by the blocking behaviour. * To migrate an L-thread to another scheduler the API lthread_set_affinity() * is provided. * * If L-threads that share data are running on the same core it is possible * to design programs where mutual exclusion mechanisms to protect shared data * can be avoided. This is due to the fact that the cooperative threads cannot * preempt each other. * * There are two cases where mutual exclusion mechanisms are necessary. * * a) Where the L-threads sharing data are running on different cores. * b) Where code must yield while updating data shared with another thread. * * The L-thread subsystem provides a set of mutex APIs to help with such * scenarios, however excessive reliance on on these will impact performance * and is best avoided if possible. * * L-threads can synchronise using a fast condition variable implementation * that supports signal and broadcast. An L-thread running on any core can * wait on a condition. * * L-threads can have L-thread local storage with an API modelled on either the * P-thread get/set specific API or using PER_LTHREAD macros modelled on the * RTE_PER_LCORE macros. Alternatively a simple user data pointer may be set * and retrieved from a thread. */ #ifndef LTHREAD_H #define LTHREAD_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include struct lthread; struct lthread_cond; struct lthread_mutex; struct lthread_condattr; struct lthread_mutexattr; typedef void *(*lthread_func_t) (void *); /* * Define the size of stack for an lthread * Then this is the size that will be allocated on lthread creation * This is a fixed size and will not grow. */ #define LTHREAD_MAX_STACK_SIZE (1024*64) /** * Define the maximum number of TLS keys that can be created * */ #define LTHREAD_MAX_KEYS 1024 /** * Define the maximum number of attempts to destroy an lthread's * TLS data on thread exit */ #define LTHREAD_DESTRUCTOR_ITERATIONS 4 /** * Define the maximum number of lcores that will support lthreads */ #define LTHREAD_MAX_LCORES RTE_MAX_LCORE /** * How many lthread objects to pre-allocate as the system grows * applies to lthreads + stacks, TLS, mutexs, cond vars. * * @see _lthread_alloc() * @see _cond_alloc() * @see _mutex_alloc() * */ #define LTHREAD_PREALLOC 100 /** * Set the number of schedulers in the system. * * This function may optionally be called before starting schedulers. * * If the number of schedulers is not set, or set to 0 then each scheduler * will begin scheduling lthreads immediately it is started. * If the number of schedulers is set to greater than 0, then each scheduler * will wait until all schedulers have started before beginning to schedule * lthreads. * * If an application wishes to have threads migrate between cores using * lthread_set_affinity(), or join threads running on other cores using * lthread_join(), then it is prudent to set the number of schedulers to ensure * that all schedulers are initialised beforehand. * * @param num * the number of schedulers in the system * @return * the number of schedulers in the system */ int lthread_num_schedulers_set(int num); /** * Return the number of schedulers currently running * @return * the number of schedulers in the system */ int lthread_active_schedulers(void); /** * Shutdown the specified scheduler * * This function tells the specified scheduler to * exit if/when there is no more work to do. * * Note that although the scheduler will stop * resources are not freed. * * @param lcore * The lcore of the scheduler to shutdown * * @return * none */ void lthread_scheduler_shutdown(unsigned lcore); /** * Shutdown all schedulers * * This function tells all schedulers including the current scheduler to * exit if/when there is no more work to do. * * Note that although the schedulers will stop * resources are not freed. * * @return * none */ void lthread_scheduler_shutdown_all(void); /** * Run the lthread scheduler * * Runs the lthread scheduler. * This function returns only if/when all lthreads have exited. * This function must be the main loop of an EAL thread. * * @return * none */ void lthread_run(void); /** * Create an lthread * * Creates an lthread and places it in the ready queue on a particular * lcore. * * If no scheduler exists yet on the current lcore then one is created. * * @param new_lt * Pointer to an lthread pointer that will be initialized * @param lcore * the lcore the thread should be started on or the current lcore * -1 the current lcore * 0 - LTHREAD_MAX_LCORES any other lcore * @param lthread_func * Pointer to the function the for the thread to run * @param arg * Pointer to args that will be passed to the thread * * @return * 0 success * EAGAIN no resources available * EINVAL NULL thread or function pointer, or lcore_id out of range */ int lthread_create(struct lthread **new_lt, int lcore, lthread_func_t func, void *arg); /** * Cancel an lthread * * Cancels an lthread and causes it to be terminated * If the lthread is detached it will be freed immediately * otherwise its resources will not be released until it is joined. * * @param new_lt * Pointer to an lthread that will be cancelled * * @return * 0 success * EINVAL thread was NULL */ int lthread_cancel(struct lthread *lt); /** * Join an lthread * * Joins the current thread with the specified lthread, and waits for that * thread to exit. * Passes an optional pointer to collect returned data. * * @param lt * Pointer to the lthread to be joined * @param ptr * Pointer to pointer to collect returned data * 0 * @return * 0 success * EINVAL lthread could not be joined. */ int lthread_join(struct lthread *lt, void **ptr); /** * Detach an lthread * * Detaches the current thread * On exit a detached lthread will be freed immediately and will not wait * to be joined. The default state for a thread is not detached. * * @return * none */ void lthread_detach(void); /** * Exit an lthread * * Terminate the current thread, optionally return data. * The data may be collected by lthread_join() * * After calling this function the lthread will be suspended until it is * joined. After it is joined then its resources will be freed. * * @param ptr * Pointer to pointer to data to be returned * * @return * none */ void lthread_exit(void *val); /** * Cause the current lthread to sleep for n nanoseconds * * The current thread will be suspended until the specified time has elapsed * or has been exceeded. * * Execution will switch to the next lthread that is ready to run * * @param nsecs * Number of nanoseconds to sleep * * @return * none */ void lthread_sleep(uint64_t nsecs); /** * Cause the current lthread to sleep for n cpu clock ticks * * The current thread will be suspended until the specified time has elapsed * or has been exceeded. * * Execution will switch to the next lthread that is ready to run * * @param clks * Number of clock ticks to sleep * * @return * none */ void lthread_sleep_clks(uint64_t clks); /** * Yield the current lthread * * The current thread will yield and execution will switch to the * next lthread that is ready to run * * @return * none */ void lthread_yield(void); /** * Migrate the current thread to another scheduler * * This function migrates the current thread to another scheduler. * Execution will switch to the next lthread that is ready to run on the * current scheduler. The current thread will be resumed on the new scheduler. * * @param lcore * The lcore to migrate to * * @return * 0 success we are now running on the specified core * EINVAL the destination lcore was not valid */ int lthread_set_affinity(unsigned lcore); /** * Return the current lthread * * Returns the current lthread * * @return * pointer to the current lthread */ struct lthread *lthread_current(void); /** * Associate user data with an lthread * * This function sets a user data pointer in the current lthread * The pointer can be retrieved with lthread_get_data() * It is the users responsibility to allocate and free any data referenced * by the user pointer. * * @param data * pointer to user data * * @return * none */ void lthread_set_data(void *data); /** * Get user data for the current lthread * * This function returns a user data pointer for the current lthread * The pointer must first be set with lthread_set_data() * It is the users responsibility to allocate and free any data referenced * by the user pointer. * * @return * pointer to user data */ void *lthread_get_data(void); struct lthread_key; typedef void (*tls_destructor_func) (void *); /** * Create a key for lthread TLS * * This function is modelled on pthread_key_create * It creates a thread-specific data key visible to all lthreads on the * current scheduler. * * Key values may be used to locate thread-specific data. * The same key value may be used by different threads, the values bound * to the key by lthread_setspecific() are maintained on a per-thread * basis and persist for the life of the calling thread. * * An optional destructor function may be associated with each key value. * At thread exit, if a key value has a non-NULL destructor pointer, and the * thread has a non-NULL value associated with the key, the function pointed * to is called with the current associated value as its sole argument. * * @param key * Pointer to the key to be created * @param destructor * Pointer to destructor function * * @return * 0 success * EINVAL the key ptr was NULL * EAGAIN no resources available */ int lthread_key_create(unsigned int *key, tls_destructor_func destructor); /** * Delete key for lthread TLS * * This function is modelled on pthread_key_delete(). * It deletes a thread-specific data key previously returned by * lthread_key_create(). * The thread-specific data values associated with the key need not be NULL * at the time that lthread_key_delete is called. * It is the responsibility of the application to free any application * storage or perform any cleanup actions for data structures related to the * deleted key. This cleanup can be done either before or after * lthread_key_delete is called. * * @param key * The key to be deleted * * @return * 0 Success * EINVAL the key was invalid */ int lthread_key_delete(unsigned int key); /** * Get lthread TLS * * This function is modelled on pthread_get_specific(). * It returns the value currently bound to the specified key on behalf of the * calling thread. Calling lthread_getspecific() with a key value not * obtained from lthread_key_create() or after key has been deleted with * lthread_key_delete() will result in undefined behaviour. * lthread_getspecific() may be called from a thread-specific data destructor * function. * * @param key * The key for which data is requested * * @return * Pointer to the thread specific data associated with that key * or NULL if no data has been set. */ void *lthread_getspecific(unsigned int key); /** * Set lthread TLS * * This function is modelled on pthread_set_specific() * It associates a thread-specific value with a key obtained via a previous * call to lthread_key_create(). * Different threads may bind different values to the same key. These values * are typically pointers to dynamically allocated memory that have been * reserved by the calling thread. Calling lthread_setspecific with a key * value not obtained from lthread_key_create or after the key has been * deleted with lthread_key_delete will result in undefined behaviour. * * @param key * The key for which data is to be set * @param key * Pointer to the user data * * @return * 0 success * EINVAL the key was invalid */ int lthread_setspecific(unsigned int key, const void *value); /** * The macros below provide an alternative mechanism to access lthread local * storage. * * The macros can be used to declare define and access per lthread local * storage in a similar way to the RTE_PER_LCORE macros which control storage * local to an lcore. * * Memory for per lthread variables declared in this way is allocated when the * lthread is created and a pointer to this memory is stored in the lthread. * The per lthread variables are accessed via the pointer + the offset of the * particular variable. * * The total size of per lthread storage, and the variable offsets are found by * defining the variables in a unique global memory section, the start and end * of which is known. This global memory section is used only in the * computation of the addresses of the lthread variables, and is never actually * used to store any data. * * Due to the fact that variables declared this way may be scattered across * many files, the start and end of the section and variable offsets are only * known after linking, thus the computation of section size and variable * addresses is performed at run time. * * These macros are primarily provided to aid porting of code that makes use * of the existing RTE_PER_LCORE macros. In principle it would be more efficient * to gather all lthread local variables into a single structure and * set/retrieve a pointer to that struct using the alternative * lthread_data_set/get APIs. * * These macros are mutually exclusive with the lthread_data_set/get APIs. * If you define storage using these macros then the lthread_data_set/get APIs * will not perform as expected, the lthread_data_set API does nothing, and the * lthread_data_get API returns the start of global section. * */ /* start and end of per lthread section */ extern char __start_per_lt; extern char __stop_per_lt; #define RTE_DEFINE_PER_LTHREAD(type, name) \ __typeof__(type)__attribute((section("per_lt"))) per_lt_##name /** * Macro to declare an extern per lthread variable "var" of type "type" */ #define RTE_DECLARE_PER_LTHREAD(type, name) \ extern __typeof__(type)__attribute((section("per_lt"))) per_lt_##name /** * Read/write the per-lcore variable value */ #define RTE_PER_LTHREAD(name) ((typeof(per_lt_##name) *)\ ((char *)lthread_get_data() +\ ((char *) &per_lt_##name - &__start_per_lt))) /** * Initialize a mutex * * This function provides a mutual exclusion device, the need for which * can normally be avoided in a cooperative multitasking environment. * It is provided to aid porting of legacy code originally written for * preemptive multitasking environments such as pthreads. * * A mutex may be unlocked (not owned by any thread), or locked (owned by * one thread). * * A mutex can never be owned by more than one thread simultaneously. * A thread attempting to lock a mutex that is already locked by another * thread is suspended until the owning thread unlocks the mutex. * * lthread_mutex_init() initializes the mutex object pointed to by mutex * Optional mutex attributes specified in mutexattr, are reserved for future * use and are currently ignored. * * If a thread calls lthread_mutex_lock() on the mutex, then if the mutex * is currently unlocked, it becomes locked and owned by the calling * thread, and lthread_mutex_lock returns immediately. If the mutex is * already locked by another thread, lthread_mutex_lock suspends the calling * thread until the mutex is unlocked. * * lthread_mutex_trylock behaves identically to rte_thread_mutex_lock, except * that it does not block the calling thread if the mutex is already locked * by another thread. * * lthread_mutex_unlock() unlocks the specified mutex. The mutex is assumed * to be locked and owned by the calling thread. * * lthread_mutex_destroy() destroys a mutex object, freeing its resources. * The mutex must be unlocked with nothing blocked on it before calling * lthread_mutex_destroy. * * @param name * Optional pointer to string describing the mutex * @param mutex * Pointer to pointer to the mutex to be initialized * @param attribute * Pointer to attribute - unused reserved * * @return * 0 success * EINVAL mutex was not a valid pointer * EAGAIN insufficient resources */ int lthread_mutex_init(char *name, struct lthread_mutex **mutex, const struct lthread_mutexattr *attr); /** * Destroy a mutex * * This function destroys the specified mutex freeing its resources. * The mutex must be unlocked before calling lthread_mutex_destroy. * * @see lthread_mutex_init() * * @param mutex * Pointer to pointer to the mutex to be initialized * * @return * 0 success * EINVAL mutex was not an initialized mutex * EBUSY mutex was still in use */ int lthread_mutex_destroy(struct lthread_mutex *mutex); /** * Lock a mutex * * This function attempts to lock a mutex. * If a thread calls lthread_mutex_lock() on the mutex, then if the mutex * is currently unlocked, it becomes locked and owned by the calling * thread, and lthread_mutex_lock returns immediately. If the mutex is * already locked by another thread, lthread_mutex_lock suspends the calling * thread until the mutex is unlocked. * * @see lthread_mutex_init() * * @param mutex * Pointer to pointer to the mutex to be initialized * * @return * 0 success * EINVAL mutex was not an initialized mutex * EDEADLOCK the mutex was already owned by the calling thread */ int lthread_mutex_lock(struct lthread_mutex *mutex); /** * Try to lock a mutex * * This function attempts to lock a mutex. * lthread_mutex_trylock behaves identically to rte_thread_mutex_lock, except * that it does not block the calling thread if the mutex is already locked * by another thread. * * * @see lthread_mutex_init() * * @param mutex * Pointer to pointer to the mutex to be initialized * * @return * 0 success * EINVAL mutex was not an initialized mutex * EBUSY the mutex was already locked by another thread */ int lthread_mutex_trylock(struct lthread_mutex *mutex); /** * Unlock a mutex * * This function attempts to unlock the specified mutex. The mutex is assumed * to be locked and owned by the calling thread. * * The oldest of any threads blocked on the mutex is made ready and may * compete with any other running thread to gain the mutex, it fails it will * be blocked again. * * @param mutex * Pointer to pointer to the mutex to be initialized * * @return * 0 mutex was unlocked * EINVAL mutex was not an initialized mutex * EPERM the mutex was not owned by the calling thread */ int lthread_mutex_unlock(struct lthread_mutex *mutex); /** * Initialize a condition variable * * This function initializes a condition variable. * * Condition variables can be used to communicate changes in the state of data * shared between threads. * * @see lthread_cond_wait() * * @param name * Pointer to optional string describing the condition variable * @param c * Pointer to pointer to the condition variable to be initialized * @param attr * Pointer to optional attribute reserved for future use, currently ignored * * @return * 0 success * EINVAL cond was not a valid pointer * EAGAIN insufficient resources */ int lthread_cond_init(char *name, struct lthread_cond **c, const struct lthread_condattr *attr); /** * Destroy a condition variable * * This function destroys a condition variable that was created with * lthread_cond_init() and releases its resources. * * @param cond * Pointer to pointer to the condition variable to be destroyed * * @return * 0 Success * EBUSY condition variable was still in use * EINVAL was not an initialised condition variable */ int lthread_cond_destroy(struct lthread_cond *cond); /** * Wait on a condition variable * * The function blocks the current thread waiting on the condition variable * specified by cond. The waiting thread unblocks only after another thread * calls lthread_cond_signal, or lthread_cond_broadcast, specifying the * same condition variable. * * @param cond * Pointer to pointer to the condition variable to be waited on * * @param reserved * reserved for future use * * @return * 0 The condition was signalled ( Success ) * EINVAL was not a an initialised condition variable */ int lthread_cond_wait(struct lthread_cond *c, uint64_t reserved); /** * Signal a condition variable * * The function unblocks one thread waiting for the condition variable cond. * If no threads are waiting on cond, the rte_lthread_cond_signal() function * has no effect. * * @param cond * Pointer to pointer to the condition variable to be signalled * * @return * 0 The condition was signalled ( Success ) * EINVAL was not a an initialised condition variable */ int lthread_cond_signal(struct lthread_cond *c); /** * Broadcast a condition variable * * The function unblocks all threads waiting for the condition variable cond. * If no threads are waiting on cond, the rte_lathed_cond_broadcast() * function has no effect. * * @param cond * Pointer to pointer to the condition variable to be signalled * * @return * 0 The condition was signalled ( Success ) * EINVAL was not a an initialised condition variable */ int lthread_cond_broadcast(struct lthread_cond *c); #ifdef __cplusplus } #endif #endif /* LTHREAD_H */