📄 kernel.sgml
字号:
application-specific interrupt handlers. In fact for many applications
all such creation operations happen at this time, using statically
allocated data, avoiding any need for dynamic memory allocation or
other overheads.
</para>
<para>
Code running in initialization context runs with interrupts disabled
and the scheduler locked. It is not permitted to reenable interrupts
or unlock the scheduler because the system is not guaranteed to be in
a totally consistent state at this point. A consequence is that
initialization code cannot use synchronization primitives such as
<function>cyg_semaphore_wait</function> to wait for an external event.
It is permitted to lock and unlock a mutex: there are no other threads
running so it is guaranteed that the mutex is not yet locked, and
therefore the lock operation will never block; this is useful when
making library calls that may use a mutex internally.
</para>
<para>
At the end of the startup sequence the system will call
<function>cyg_scheduler_start</function> and the various threads will
start running. In thread context nearly all of the kernel functions
are available. There may be some restrictions on interrupt-related
operations, depending on the target hardware. For example the hardware
may require that interrupts be acknowledged in the ISR or DSR before
control returns to thread context, in which case
<filename>cyg_interrupt_acknowledge</filename> should not be called
by a thread.
</para>
<para>
At any time the processor may receive an external interrupt, causing
control to be transferred from the current thread. Typically a VSR
provided by eCos will run and determine exactly which interrupt
occurred. Then the VSR will switch to the appropriate ISR, which can
be provided by a HAL package, a device driver, or by the application.
During this time the system is running at ISR context, and most of the
kernel function calls are disallowed. This includes the various
synchronization primitives, so for example an ISR is not allowed to
post to a semaphore to indicate that an event has happened. Usually
the only operations that should be performed from inside an ISR are
ones related to the interrupt subsystem itself, for example masking an
interrupt or acknowledging that an interrupt has been processed. On
SMP systems it is also possible to use spinlocks from ISR context.
</para>
<para>
When an ISR returns it can request that the corresponding DSR be run
as soon as it is safe to do so, and that will run in DSR context. This
context is also used for running alarm functions, and threads can
switch temporarily to DSR context by locking the scheduler. Only
certain kernel functions can be called from DSR context, although more
than in ISR context. In particular it is possible to use any
synchronization primitives which cannot block. These include
<function>cyg_semaphore_post</function>,
<filename>cyg_cond_signal</filename>,
<function>cyg_cond_broadcast</function>,
<function>cyg_flag_setbits</function>, and
<function>cyg_mbox_tryput</function>. It is not possible to use any
primitives that may block such as
<function>cyg_semaphore_wait</function>,
<function>cyg_mutex_lock</function>, or
<function>cyg_mbox_put</function>. Calling such functions from inside
a DSR may cause the system to hang.
</para>
<para>
The specific documentation for the various kernel functions gives more
details about valid contexts.
</para>
</refsect1>
<!-- }}} -->
<!-- {{{ Error handling -->
<refsect1 id="kernel-overview-errors">
<title>Error Handling and Assertions</title>
<para>
In many APIs each function is expected to perform some validation of
its parameters and possibly of the current state of the system. This
is supposed to ensure that each function is used correctly, and that
application code is not attempting to perform a semaphore operation on
a mutex or anything like that. If an error is detected then a suitable
error code is returned, for example the POSIX function
<function>pthread_mutex_lock</function> can return various error codes
including <literal>EINVAL</literal> and <literal>EDEADLK</literal>.
There are a number of problems with this approach, especially in the
context of deeply embedded systems:
</para>
<orderedlist>
<listitem><para>
Performing these checks inside the mutex lock and all the other
functions requires extra cpu cycles and adds significantly to the code
size. Even if the application is written correctly and only makes
system function calls with sensible arguments and under the right
conditions, these overheads still exist.
</para></listitem>
<listitem><para>
Returning an error code is only useful if the calling code detects
these error codes and takes appropriate action. In practice the
calling code will often ignore any errors because the programmer
<emphasis>“knows”</emphasis> that the function is being
used correctly. If the programmer is mistaken then an error condition
may be detected and reported, but the application continues running
anyway and is likely to fail some time later in mysterious ways.
</para></listitem>
<listitem><para>
If the calling code does always check for error codes, that adds yet
more cpu cycles and code size overhead.
</para></listitem>
<listitem><para>
Usually there will be no way to recover from certain errors, so if the
application code detected an error such as <literal>EINVAL</literal>
then all it could do is abort the application somehow.
</para></listitem>
</orderedlist>
<para>
The approach taken within the eCos kernel is different. Functions such
as <function>cyg_mutex_lock</function> will not return an error code.
Instead they contain various assertions, which can be enabled or
disabled. During the development process assertions are normally left
enabled, and the various kernel functions will perform parameter
checks and other system consistency checks. If a problem is detected
then an assertion failure will be reported and the application will be
terminated. In a typical debug session a suitable breakpoint will have
been installed and the developer can now examine the state of the
system and work out exactly what is going on. Towards the end of the
development cycle assertions will be disabled by manipulating
configuration options within the eCos infrastructure package, and all
assertions will be eliminated at compile-time. The assumption is that
by this time the application code has been mostly debugged: the
initial version of the code might have tried to perform a semaphore
operation on a mutex, but any problems like that will have been fixed
some time ago. This approach has a number of advantages:
</para>
<orderedlist>
<listitem><para>
In the final application there will be no overheads for checking
parameters and other conditions. All that code will have been
eliminated at compile-time.
</para></listitem>
<listitem><para>
Because the final application will not suffer any overheads, it is
reasonable for the system to do more work during the development
process. In particular the various assertions can test for more error
conditions and more complicated errors. When an error is detected
it is possible to give a text message describing the error rather than
just return an error code.
</para></listitem>
<listitem><para>
There is no need for application programmers to handle error codes
returned by various kernel function calls. This simplifies the
application code.
</para></listitem>
<listitem><para>
If an error is detected then an assertion failure will be reported
immediately and the application will be halted. There is no
possibility of an error condition being ignored because application
code did not check for an error code.
</para></listitem>
</orderedlist>
<para>
Although none of the kernel functions return an error code, many of
them do return a status condition. For example the function
<function>cyg_semaphore_timed_wait</function> waits until either an
event has been posted to a semaphore, or until a certain number of
clock ticks have occurred. Usually the calling code will need to know
whether the wait operation succeeded or whether a timeout occurred.
<function>cyg_semaphore_timed_wait</function> returns a boolean: a
return value of zero or false indicates a timeout, a non-zero return
value indicates that the wait succeeded.
</para>
<para>
In conventional APIs one common error conditions is lack of memory.
For example the POSIX function <function>pthread_create</function>
usually has to allocate some memory dynamically for the thread stack
and other per-thread data. If the target hardware does not have enough
memory to meet all demands, or more commonly if the application
contains a memory leak, then there may not be enough memory available
and the function call would fail. The eCos kernel avoids such problems
by never performing any dynamic memory allocation. Instead it is the
responsibility of the application code to provide all the memory
required for kernel data structures and other needs. In the case of
<function>cyg_thread_create</function> this means a
<structname>cyg_thread</structname> data structure to hold the thread
details, and a <type>char</type> array for the thread stack.
</para>
<para>
In many applications this approach results in all data structures
being allocated statically rather than dynamically. This has several
advantages. If the application is in fact too large for the target
hardware's memory then there will be an error at link-time rather than
at run-time, making the problem much easier to diagnose. Static
allocation does not involve any of the usual overheads associated with
dynamic allocation, for example there is no need to keep track of the
various free blocks in the system, and it may be possible to eliminate
<function>malloc</function> from the system completely. Problems such
as fragmentation and memory leaks cannot occur if all data is
allocated statically. However, some applications are sufficiently
complicated that dynamic memory allocation is required, and the
various kernel functions do not distinguish between statically and
dynamically allocated memory. It still remains the responsibility of
the calling code to ensure that sufficient memory is available, and
passing null pointers to the kernel will result in assertions or
system failure.
</para>
</refsect1>
<!-- }}} -->
</refentry>
<!-- }}} -->
<!-- {{{ SMP -->
<refentry id="kernel-SMP">
<refmeta>
<refentrytitle>SMP Support</refentrytitle>
</refmeta>
<refnamediv>
<refname>SMP</refname>
<refpurpose>Support Symmetric Multiprocessing Systems</refpurpose>
</refnamediv>
<refsect1 id="kernel-smp-description">
<title>Description</title>
<para>
eCos contains support for limited Symmetric Multi-Processing (SMP).
This is only available on selected architectures and platforms.
The implementation has a number of restrictions on the kind of
hardware supported. These are described in <xref linkend="hal-smp-support">.
</para>
<para>
The following sections describe the changes that have been made to the
eCos kernel to support SMP operation.
</para>
</refsect1>
<refsect1 id="kernel-smp-startup">
<title>System Startup</title>
<para>
The system startup sequence needs to be somewhat different on an SMP
system, although this is largely transparent to application code. The
main startup takes place on only one CPU, called the primary CPU. All
other CPUs, the secondary CPUs, are either placed in suspended state
at reset, or are captured by the HAL and put into a spin as they start
up. The primary CPU is responsible for copying the DATA segment and
zeroing the BSS (if required), calling HAL variant and platform
initialization routines and invoking constructors. It then calls
<function>cyg_start</function> to enter the application. The
application may then create extra threads and other objects.
</para>
<para>
It is only when the application calls
<function>cyg_scheduler_start</function> that the secondary CPUs are
initialized. This routine scans the list of available secondary CPUs
and invokes <function>HAL_SMP_CPU_START</function> to start each
CPU. Finally it calls an internal function
<function>Cyg_Scheduler::start_cpu</function> to enter the scheduler
for the primary CPU.
</para>
<para>
Each secondary CPU starts in the HAL, where it completes any per-CPU
initialization before calling into the kernel at
<function>cyg_kernel_cpu_startup</function>. Here it claims the
scheduler lock and calls
<function>Cyg_Scheduler::start_cpu</function>.
</para>
<para>
<function>Cyg_Scheduler::start_cpu</function> is common to both the
primary and secondary CPUs. The first thing this code does is to
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -