eCos Programming


Thread creation


Name

cyg_thread_create -- Create a new thread

#include <cyg/kernel/kapi.h>




void cyg_thread_create
(cyg_addrword_t sched_info, cyg_thread_entry_t* entry, cyg_addrword_t entry_data, char* name, void* stack_base, cyg_ucount32 stack_size, cyg_handle_t* handle, cyg_thread* thread);


Description

The cyg_thread_create function allows application code and eCos packages to create new threads. In many applications this only happens during system initialization and all required data is allocated statically. However additional threads can be created at any time, if necessary. A newly created thread is always in suspended state and will not start running until it has been resumed via a call to cyg_thread_resume. Also, if threads are created during system initialization then they will not start running until the eCos scheduler has been started.
The name argument is used primarily for debugging purposes, making it easier to keep track of which cyg_thread structure is associated with which application-level thread. The kernel configuration optionCYGVAR_KERNEL_THREADS_NAME controls whether or not this name is actually used.
On creation each thread is assigned a unique handle, and this will be stored in the location pointed at by the handle argument. Subsequent operations on this thread including the required cyg_thread_resume should use this handle to identify the thread.
The kernel requires a small amount of space for each thread, in the form of a cyg_thread data structure, to hold information such as the current state of that thread. To avoid any need for dynamic memory allocation within the kernel this space has to be provided by higher-level code, typically in the form of a static variable. The thread argument provides this space.

Thread Entry Point

The entry point for a thread takes the form:
void
thread_entry_function(cyg_addrword_t data)
{
    …
}
      
The second argument to cyg_thread_create is a pointer to such a function. The third argument entry_data is used to pass additional data to the function. Typically this takes the form of a pointer to some static data, or a small integer, or 0 if the thread does not require any additional data.
If the thread entry function ever returns then this is equivalent to the thread calling cyg_thread_exit. Even though the thread will no longer run again, it remains registered with the scheduler. If the application needs to re-use the cyg_thread data structure then a call to cyg_thread_delete is required first.

Thread Priorities

The sched_info argument provides additional information to the scheduler. The exact details depend on the scheduler being used. For the bitmap and mlqueue schedulers it is a small integer, typically in the range 0 to 31, with 0 being the highest priority. The lowest priority is normally used only by the system's idle thread. The exact number of priorities is controlled by the kernel configuration option CYGNUM_KERNEL_SCHED_PRIORITIES.
It is the responsibility of the application developer to be aware of the various threads in the system, including those created by eCos packages, and to ensure that all threads run at suitable priorities. For threads created by other packages the documentation provided by those packages should indicate any requirements.
The functions cyg_thread_set_prioritycyg_thread_get_priority, and cyg_thread_get_current_priority can be used to manipulate a thread's priority.

Stacks and Stack Sizes

Each thread needs its own stack for local variables and to keep track of function calls and returns. Again it is expected that this stack is provided by the calling code, usually in the form of static data, so that the kernel does not need any dynamic memory allocation facilities. cyg_thread_create takes two arguments related to the stack, a pointer to the base of the stack and the total size of this stack. On many processors stacks actually descend from the top down, so the kernel will add the stack size to the base address to determine the starting location.
The exact stack size requirements for any given thread depend on a number of factors. The most important is of course the code that will be executed in the context of this code: if this involves significant nesting of function calls, recursion, or large local arrays, then the stack size needs to be set to a suitably high value. There are some architectural issues, for example the number of cpu registers and the calling conventions will have some effect on stack usage. Also, depending on the configuration, it is possible that some other code such as interrupt handlers will occasionally run on the current thread's stack. This depends in part on configuration options such as CYGIMP_HAL_COMMON_INTERRUPTS_USE_INTERRUPT_STACK and CYGSEM_HAL_COMMON_INTERRUPTS_ALLOW_NESTING.
Determining an application's actual stack size requirements is the responsibility of the application developer, since the kernel cannot know in advance what code a given thread will run. However, the system does provide some hints about reasonable stack sizes in the form of two constants: CYGNUM_HAL_STACK_SIZE_MINIMUM and CYGNUM_HAL_STACK_SIZE_TYPICAL. These are defined by the appropriate HAL package. The MINIMUM value is appropriate for a thread that just runs a single function and makes very simple system calls. Trying to create a thread with a smaller stack than this is illegal. The TYPICAL value is appropriate for applications where application calls are nested no more than half a dozen or so levels, and there are no large arrays on the stack.
If the stack sizes are not estimated correctly and a stack overflow occurs, the probably result is some form of memory corruption. This can be very hard to track down. The kernel does contain some code to help detect stack overflows, controlled by the configuration option CYGFUN_KERNEL_THREADS_STACK_CHECKING: a small amount of space is reserved at the stack limit and filled with a special signature: every time a thread context switch occurs this signature is checked, and if invalid that is a good indication (but not absolute proof) that a stack overflow has occurred. This form of stack checking is enabled by default when the system is built with debugging enabled. A related configuration option is CYGFUN_KERNEL_THREADS_STACK_MEASUREMENT: enabling this option means that a thread can call the function cyg_thread_measure_stack_usage to find out the maximum stack usage to date. Note that this is not necessarily the true maximum because, for example, it is possible that in the current run no interrupt occurred at the worst possible moment.

Valid contexts

cyg_thread_create may be called during initialization and from within thread context. It may not be called from inside a DSR.

Example

A simple example of thread creation is shown below. This involves creating five threads, one producer and four consumers or workers. The threads are created in the system's cyg_user_start: depending on the configuration it might be more appropriate to do this elsewhere, for example inside main.
#include 
#include 

// These numbers depend entirely on your application
#define NUMBER_OF_WORKERS    4
#define PRODUCER_PRIORITY   10
#define WORKER_PRIORITY     11
#define PRODUCER_STACKSIZE  CYGNUM_HAL_STACK_SIZE_TYPICAL
#define WORKER_STACKSIZE    (CYGNUM_HAL_STACK_SIZE_MINIMUM + 1024)

static unsigned char producer_stack[PRODUCER_STACKSIZE];
static unsigned char worker_stacks[NUMBER_OF_WORKERS][WORKER_STACKSIZE];
static cyg_handle_t producer_handle, worker_handles[NUMBER_OF_WORKERS];
static cyg_thread_t producer_thread, worker_threads[NUMBER_OF_WORKERS];

static void
producer(cyg_addrword_t data)
{
    …
}

static void
worker(cyg_addrword_t data)
{
    …
}

void
cyg_user_start(void)
{
    int i;

    cyg_thread_create(PRODUCER_PRIORITY, &producer, 0, "producer",
                      producer_stack, PRODUCER_STACKSIZE,
                      &producer_handle, &producer_thread);
    cyg_thread_resume(producer_handle);
    for (i = 0; i < NUMBER_OF_WORKERS; i++) {
        cyg_thread_create(WORKER_PRIORITY, &worker, i, "worker",
                          worker_stacks[i], WORKER_STACKSIZE,
                          &(worker_handles[i]), &(worker_threads[i]));
        cyg_thread_resume(worker_handles[i]);
    }
}
      

Thread Entry Points and C++

For code written in C++ the thread entry function must be either a static member function of a class or an ordinary function outside any class. It cannot be a normal member function of a class because such member functions take an implicit additional argument this, and the kernel has no way of knowing what value to use for this argument. One way around this problem is to make use of a special static member function, for example:
class fred {
  public:
    void thread_function();
    static void static_thread_aux(cyg_addrword_t);
};

void
fred::static_thread_aux(cyg_addrword_t objptr)
{
    fred* object = static_cast(objptr);
    object->thread_function();
}

static fred instance;

extern "C" void
cyg_start( void )
{
    …
    cyg_thread_create( …,
                      &fred::static_thread_aux,
                      static_cast(&instance),
                      …);
    …
}
      
Effectively this uses the entry_data argument to cyg_thread_create to hold the this pointer. Unfortunately this approach does require the use of some C++ casts, so some of the type safety that can be achieved when programming in C++ is lost.



cyg_handle_t thread = 0;
cyg_uint16 id;
cyg_thread_info thread_info;
cyg_stack_info stack_info;

    if(cyg_thread_get_info(thread, id, &thread_info))
    {
        diag_printf("name: %s, handle: 0x%08x, id: 0x%04x, "
                    "stack_base: 0x%08x, stack_size: %d, stack_used: %dn",
                    thread_info.name, thread_info.handle, thread_info.id,
                    thread_info.stack_base, thread_info.stack_size, thread_info.stack_used);
    }
    else
    {
        diag_printf("ERROR: get thread info failed, handle: 0x%08x, id: 0x%04xn",
                    thread, id);
    }




留言

熱門文章