0020 About Task local storage ----------------------------- Maarten Pennings 7 September 1999 fixed typo on sept 21 1999. init explained on sept 25 2000. Introduction ------------ Sometimes there is a need for task local storage, that is, a "global" variable that is local to a task. An example of this is a last error function. Typically, we would have a variable to record the last error, static int LastError; a function to initialize it void Init( void ) { LastError= 0; } a function to get and reset it, int intf_GetLastError( void ) { int last= LastError; LastError= 0; return last; } and finally, there are some commands that may raise the error void intf_DoSomething() { ... if( ... ) { LastError= 17; } ... } The problem is that when a client calls intf_DoSomething(); e= intf_GetLastError() there might be a different task calling DoSomething "in between". The returned last error is not guaranteed to be ours, it could be the last error of the other task. The solution is to store the error per task. Client solution --------------- The general idea is to have an array, with an error per task. Use the TskCurrent() to index the array. The problem with this solution is that a client needs to know how many tasks there are (statically? dynamically?) to allocate the array, and it needs to map a TskCurrent() to an index. For CMX the latter is easy because CMX has consecutive task handles. Server solution --------------- It is also possible to let the RTK provide task local storage "Talos". This requires the following functions to be added to IRealTimeKernel: Talos TlsCreate( int size ); void * TlsAccess( Talos tls ); The function TlsCreate adds a memory block of 'size' bytes to every task. It returns a task independent handle or "pointer" to that block. To address the block of the current task, use the second function TlsAccess, passing it the task independent handle. It returns a task dependent pointer. The last error code would now have the following setup. There is a global variable storing the Talos handle static Talos tls; a function to initialize it void Init( void ) { tls= rtk_TlsCreate( sizeof(int) ) // *(int*)rtk_TlsAccess( tls )= 0; (see note) } a function to get and reset the error, int intf_GetLastError( void ) { int *p= rtk_TlsAccess( tls ); int last=*p; *p= 0; return last; } and finally, there are some commands that may raise the error void intf_DoSomething() { ... if( ... ) { *(int*)rtk_TlsAccess( tls )= 17; } ... } Note: The initialization is a problem now. The line that is commented out only sets "our" int to zero, not the ones of the other tasks. The proposal is that TlsCreate guarantees that the storage is zero. This is not a generic solution but a pragmatic one. A generic solution would be having a function void TlsCreate( int size, Function init ); Function 'init' is called for every task that exists (and for every newly created task when it is created). Macros ------ To make life easier the following definitions could be used on the client side. typedef struct { Type1 var1; Type2 var2; ... } TalosStruct static Talos tls; #define VAR1 ( *(TalosStruct*)rtk_TlsAccess(tls)->var1 ) #define VAR2 ( *(TalosStruct*)rtk_TlsAccess(tls)->var2 ) #define CREATETALOS ( tls = rtk_TlsCreate(sizeof(TalosStruct)) ) We would then write typedef struct { int LastError } TalosStruct static Talos tls; #define LASTERROR ( *(TalosStruct*)rtk_TlsAccess(tls)->LastError ) #define CREATETALOS ( tls = rtk_TlsCreate(sizeof(TalosStruct)) ) void Init( void ) { CREATETALOS; // For each task we rely on the fact that LASTERROR==0 } int intf_GetLastError( void ) { int last=LASTERROR; LASTERROR= 0; return last; } void intf_DoSomething() { ... if( ... ) { LASTERROR= 17; } ... } Implementation -------------- Note that CMX maintains an array of task control blocks 'cmx_tcb'. The rtk wrapper we wrote on top of CMX has its own array of task control blocks (Note: since the current implementation only stores the task function of a task, the tcb of the wrapper is called 'TaskFunctions'. For clarity, we assume an array of 'Tcb' structs in the wrapper.). The task control block 'Tcb' gets an extra field 'Store' used as the task local storage for that task. It is an array of bytes, whose size is determined by a new diversity parameter div_MaxTalos. static struct { ...; Nat8 Store[div_MaxTalos] } Tcb[div_MaxTasks]; There is a global variable StoreIndex. It points to the first used bytes in the 'Store' arrays of all tasks. Note that the Store array is filled downwards. int StoreIndex= div_MaxTalos; The public datatype 'Talos' is nothing more than an index into the Store arrays. typedef int Talos; The create function makes sure there is still enough room in the Store. Since this function manipulates a shared variable (StoreIndex) we need to disable task switching. Talos TlsCreate( int size ) { int tls; ASSERT( size>0 ); rtk_TskBeginCriticalSection(); tls= StoreIndex-= size; rtk_TskEndCriticalSection(); ASSERT( tls >= 0 ); return tls; } By subtracting the pointer to the current CMX tcb 'activetcb' and the pointer to the first CMX tcb 'cmx_tcb', we obtain an index [0..div_MaxTasks). We use this index to get to the current task's local storage 'Store'. The passed 'tls' parameter points to the requested reserved bytes. void * TlsAccess( Talos tls ); { return &Tcb[activetcb-cmx_tcb].Store[tls] } Application note ---------------- In this application note, we show one way to speed up I2C. It is not intended to replace the current speed-up implementation. The problem with the I2C driver is that it needs an event on which a transaction waits while the interrupt is processing until it sends an event. However, events are local to a task, so it is not possible to create a global one. The I2C driver used to create and delete an event on the current task upon every transaction. We could also use task local storage. We set up the task local storage definitions for one event. typedef struct { Event evt } TalosStruct static Talos tls; #define EVENT ( *(TalosStruct*)rtk_TlsAccess(tls)->evt ) #define CREATETALOS ( tls = rtk_TlsCreate(sizeof(TalosStruct)) ) The init_Init routine initializes the entire I2C driver component. Amongst others, it creates the task local storage. void init_Init( void ) { ... CREATETALOS; ... } We need a moment in time to create the event. We suggest to add a function on the I2C control interface. This init function should be called by any task before it makes its first i2c transaction via interface i2c. void i2cc_Init( void ) { ASSERT( EVENT==0 ); EVENT= rtk_EvtCreate( rtk_TskCurrent() ) } Then, during an I2C transaction, we can wait on the event of the current task. I2cStatus i2c_Write(int address, void *msg, int len ) { ... rtk_EvtReceive( EVENT ); ... }