⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 kernel.sgml

📁 eCos/RedBoot for勤研ARM AnywhereII(4510) 含全部源代码
💻 SGML
📖 第 1 页 / 共 5 页
字号:
Mutexes serve a very different purpose from the other primitives. A
mutex allows multiple threads to share a resource safely: a thread
locks a mutex, manipulates the shared resource, and then unlocks the
mutex again. The other primitives are used to communicate information
between threads, or alternatively from a DSR associated with an
interrupt handler to a thread.
      </para>
      <para>
When a thread that has locked a mutex needs to wait for some condition
to become true, it should use a condition variable. A condition
variable is essentially just a place for a thread to wait, and which
another thread, or DSR, can use to wake it up. When a thread waits on
a condition variable it releases the mutex before waiting, and when it
wakes up it reacquires it before proceeding. These operations are
atomic so that synchronization race conditions cannot be introduced.
      </para>
      <para>
A counting semaphore is used to indicate that a particular event has
occurred. A consumer thread can wait for this event to occur, and a
producer thread or a DSR can post the event. There is a count
associated with the semaphore so if the event occurs multiple times in
quick succession this information is not lost, and the appropriate
number of semaphore wait operations will succeed.
      </para>
      <para>
Mail boxes are also used to indicate that a particular event has
occurred, and allows for one item of data to be exchanged per event.
Typically this item of data would be a pointer to some data structure.
Because of the need to store this extra data, mail boxes have a
finite capacity. If a producer thread generates mail box events
faster than they can be consumed then, to avoid overflow, it will be
blocked until space is again available in the mail box. This means
that mail boxes usually cannot be used by a DSR to wake up a
thread. Instead mail boxes are typically only used between threads.
      </para>
      <para>
Event flags can be used to wait on some number of different events,
and to signal that one or several of these events have occurred. This
is achieved by associating bits in a bit mask with the different
events. Unlike a counting semaphore no attempt is made to keep track
of the number of events that have occurred, only the fact that an
event has occurred at least once. Unlike a mail box it is not
possible to send additional data with the event, but this does mean
that there is no possibility of an overflow and hence event flags can
be used between a DSR and a thread as well as between threads.
      </para>
      <para>
The eCos common HAL package provides its own device driver API which
contains some of the above synchronization primitives. These allow
the DSR for an interrupt handler to signal events to higher-level
code. If the configuration includes the eCos kernel package then
the driver API routines map directly on to the equivalent kernel
routines, allowing interrupt handlers to interact with threads. If the
kernel package is not included and the application consists of just a
single thread running in polled mode then the driver API is
implemented entirely within the common HAL, and with no need to worry
about multiple threads the implementation can obviously be rather
simpler. 
      </para>
    </refsect1>

<!-- }}} -->
<!-- {{{ Threads and interrupts         -->

    <refsect1 id="kernel-overview-threads-interrupts">
      <title>Threads and Interrupt Handling</title>
      <para>
During normal operation the processor will be running one of the
threads in the system. This may be an application thread, a system
thread running inside say the TCP/IP stack, or the idle thread. From
time to time a hardware interrupt will occur, causing control to be
transferred briefly to an interrupt handler. When the interrupt has
been completed the system's scheduler will decide whether to return
control to the interrupted thread or to some other runnable thread.
      </para>
      <para>
Threads and interrupt handlers must be able to interact. If a thread
is waiting for some I/O operation to complete, the interrupt handler
associated with that I/O must be able to inform the thread that the
operation has completed. This can be achieved in a number of ways. One
very simple approach is for the interrupt handler to set a volatile
variable. A thread can then poll continuously until this flag is set,
possibly sleeping for a clock tick in between. Polling continuously
means that the cpu time is not available for other activities, which
may be acceptable for some but not all applications. Polling once
every clock tick imposes much less overhead, but means that the thread
may not detect that the I/O event has occurred until an entire clock
tick has elapsed. In typical systems this could be as long as 10
milliseconds. Such a delay might be acceptable for some applications,
but not all.
      </para>
      <para>
A better solution would be to use one of the synchronization
primitives. The interrupt handler could signal a condition variable,
post to a semaphore, or use one of the other primitives. The thread
would perform a wait operation on the same primitive. It would not
consume any cpu cycles until the I/O event had occurred, and when the
event does occur the thread can start running again immediately
(subject to any higher priority threads that might also be runnable).
      </para>
      <para>
Synchronization primitives constitute shared data, so care must be
taken to avoid problems with concurrent access. If the thread that was
interrupted was just performing some calculations then the interrupt
handler could manipulate the synchronization primitive quite safely.
However if the interrupted thread happened to be inside some kernel
call then there is a real possibility that some kernel data structure
will be corrupted. 
      </para>
      <para>
One way of avoiding such problems would be for the kernel functions to
disable interrupts when executing any critical region. On most
architectures this would be simple to implement and very fast, but it
would mean that interrupts would be disabled often and for quite a
long time. For some applications that might not matter, but many
embedded applications require that the interrupt handler run as soon
as possible after the hardware interrupt has occurred. If the kernel
relied on disabling interrupts then it would not be able to support
such applications.
      </para>
      <para>
Instead the kernel uses a two-level approach to interrupt handling.
Associated with every interrupt vector is an Interrupt Service Routine
or ISR, which will run as quickly as possible so that it can service
the hardware. However an ISR can make only a small number of kernel
calls, mostly related to the interrupt subsystem, and it cannot make
any call that would cause a thread to wake up. If an ISR detects that
an I/O operation has completed and hence that a thread should be woken
up, it can cause the associated Deferred Service Routine or DSR to
run. A DSR is allowed to make more kernel calls, for example it can
signal a condition variable or post to a semaphore.
      </para>
      <para>
Disabling interrupts prevents ISRs from running, but very few parts of
the system disable interrupts and then only for short periods of time.
The main reason for a thread to disable interrupts is to manipulate
some state that is shared with an ISR. For example if a thread needs
to add another buffer to a linked list of free buffers and the ISR may
remove a buffer from this list at any time, the thread would need to
disable interrupts for the few instructions needed to manipulate the
list. If the hardware raises an interrupt at this time, it remains
pending until interrupts are reenabled.
      </para>
      <para>
Analogous to interrupts being disabled or enabled, the kernel has a
scheduler lock. The various kernel functions such as
<function>cyg_mutex_lock</function> and
<function>cyg_semaphore_post</function> will claim the scheduler lock,
manipulate the kernel data structures, and then release the scheduler
lock. If an interrupt results in a DSR being requested and the
scheduler is currently locked, the DSR remains pending. When the
scheduler lock is released any pending DSRs will run. These may post
events to synchronization primitives, causing other higher priority
threads to be woken up.
      </para>
      <para>
For an example, consider the following scenario. The system has a high
priority thread A, responsible for processing some data coming from an
external device. This device will raise an interrupt when data is
available. There are two other threads B and C which spend their time
performing calculations and occasionally writing results to a display
of some sort. This display is a shared resource so a mutex is used to
control access.
      </para>
      <para>
At a particular moment in time thread A is likely to be blocked,
waiting on a semaphore or another synchronization primitive until data
is available. Thread B might be running performing some calculations,
and thread C is runnable waiting for its next timeslice. Interrupts
are enabled, and the scheduler is unlocked because none of the threads
are in the middle of a kernel operation. At this point the device
raises an interrupt. The hardware transfers control to a low-level
interrupt handler provided by eCos which works out exactly which
interrupt occurs, and then the corresponding ISR is run. This ISR
manipulates the hardware as appropriate, determines that there is now
data available, and wants to wake up thread A by posting to the
semaphore. However ISR's are not allowed to call
<function>cyg_semaphore_post</function> directly, so instead the ISR
requests that its associated DSR be run and returns. There are no more
interrupts to be processed, so the kernel next checks for DSR's. One
DSR is pending and the scheduler is currently unlocked, so the DSR can
run immediately and post the semaphore. This will have the effect of
making thread A runnable again, so the scheduler's data structures are
adjusted accordingly. When the DSR returns thread B is no longer the
highest priority runnable thread so it will be suspended, and instead
thread A gains control over the cpu.
      </para>
      <para>
In the above example no kernel data structures were being manipulated
at the exact moment that the interrupt happened. However that cannot
be assumed. Suppose that thread B had finished its current set of
calculations and wanted to write the results to the display. It would
claim the appropriate mutex and manipulate the display. Now suppose
that thread B was timesliced in favour of thread C, and that thread C
also finished its calculations and wanted to write the results to the
display. It would call <function>cyg_mutex_lock</function>. This
kernel call locks the scheduler, examines the current state of the
mutex, discovers that the mutex is already owned by another thread,
suspends the current thread, and switches control to another runnable
thread. Another interrupt happens in the middle of this
<function>cyg_mutex_lock</function> call, causing the ISR to run
immediately. The ISR decides that thread A should be woken up so it
requests that its DSR be run and returns back to the kernel. At this
point there is a pending DSR, but the scheduler is still locked by the
call to <function>cyg_mutex_lock</function> so the DSR cannot run
immediately. Instead the call to <function>cyg_mutex_lock</function>
is allowed to continue, which at some point involves unlocking the
scheduler. The pending DSR can now run, safely post the semaphore, and
thus wake up thread A.
      </para>
      <para>
If the ISR had called <function>cyg_semaphore_post</function> directly
rather than leaving it to a DSR, it is likely that there would have
been some sort of corruption of a kernel data structure. For example
the kernel might have completely lost track of one of the threads, and
that thread would never have run again. The two-level approach to
interrupt handling, ISR's and DSR's, prevents such problems with no
need to disable interrupts.
      </para>
    </refsect1>

<!-- }}} -->
<!-- {{{ Calling contexts               -->

    <refsect1 id="kernel-overview-contexts">
      <title>Calling Contexts</title>
      <para>
eCos defines a number of contexts. Only certain calls are allowed from
inside each context, for example most operations on threads or
synchronization primitives are not allowed from ISR context. The
different contexts are initialization, thread, ISR and DSR.
      </para>
      <para>
When eCos starts up it goes through a number of phases, including
setting up the hardware and invoking C++ static constructors. During
this time interrupts are disabled and the scheduler is locked. When a
configuration includes the kernel package the final operation is a
call to <link
linkend="kernel-schedcontrol"><function>cyg_scheduler_start</function></link>.
At this point interrupts are enabled, the scheduler is unlocked, and
control is transferred to the highest priority runnable thread. If the
configuration also includes the C library package then usually the C
library startup package will have created a thread which will call the
application's <function>main</function> entry point.
      </para>
      <para>
Some application code can also run before the scheduler is started,
and this code runs in initialization context. If the application is
written partly or completely in C++ then the constructors for any
static objects will be run. Alternatively application code can define
a function <function>cyg_user_start</function> which gets called after
any C++ static constructors. This allows applications to be written
entirely in C.
      </para>
      <programlisting width=72>
void
cyg_user_start(void)
{
    /* Perform application-specific initialization here */
}
      </programlisting>
      <para>
It is not necessary for applications to provide a
<function>cyg_user_start</function> function since the system will
provide a default implementation which does nothing.
      </para>
      <para>
Typical operations that are performed from inside static constructors
or <function>cyg_user_start</function> include creating threads,
synchronization primitives, setting up alarms, and registering

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -