📄 kernel-overview.html
字号:
HREF="kernel-mutexes.html">mutexes</A>,<AHREF="kernel-condition-variables.html">condition variables</A>,<AHREF="kernel-semaphores.html">counting semaphores</A>,<AHREF="kernel-mail-boxes.html">mail boxes</A> and<AHREF="kernel-flags.html">event flags</A>. </P><P>Mutexes serve a very different purpose from the other primitives. Amutex allows multiple threads to share a resource safely: a threadlocks a mutex, manipulates the shared resource, and then unlocks themutex again. The other primitives are used to communicate informationbetween threads, or alternatively from a DSR associated with aninterrupt handler to a thread. </P><P>When a thread that has locked a mutex needs to wait for some conditionto become true, it should use a condition variable. A conditionvariable is essentially just a place for a thread to wait, and whichanother thread, or DSR, can use to wake it up. When a thread waits ona condition variable it releases the mutex before waiting, and when itwakes up it reacquires it before proceeding. These operations areatomic so that synchronization race conditions cannot be introduced. </P><P>A counting semaphore is used to indicate that a particular event hasoccurred. A consumer thread can wait for this event to occur, and aproducer thread or a DSR can post the event. There is a countassociated with the semaphore so if the event occurs multiple times inquick succession this information is not lost, and the appropriatenumber of semaphore wait operations will succeed. </P><P>Mail boxes are also used to indicate that a particular event hasoccurred, 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 afinite capacity. If a producer thread generates mail box eventsfaster than they can be consumed then, to avoid overflow, it will beblocked until space is again available in the mail box. This meansthat mail boxes usually cannot be used by a DSR to wake up athread. Instead mail boxes are typically only used between threads. </P><P>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. Thisis achieved by associating bits in a bit mask with the differentevents. Unlike a counting semaphore no attempt is made to keep trackof the number of events that have occurred, only the fact that anevent has occurred at least once. Unlike a mail box it is notpossible to send additional data with the event, but this does meanthat there is no possibility of an overflow and hence event flags canbe used between a DSR and a thread as well as between threads. </P><P>The eCos common HAL package provides its own device driver API whichcontains some of the above synchronization primitives. These allowthe DSR for an interrupt handler to signal events to higher-levelcode. If the configuration includes the eCos kernel package thenthe driver API routines map directly on to the equivalent kernelroutines, allowing interrupt handlers to interact with threads. If thekernel package is not included and the application consists of just asingle thread running in polled mode then the driver API isimplemented entirely within the common HAL, and with no need to worryabout multiple threads the implementation can obviously be rathersimpler. </P></DIV><DIVCLASS="REFSECT1"><ANAME="KERNEL-OVERVIEW-THREADS-INTERRUPTS"></A><H2>Threads and Interrupt Handling</H2><P>During normal operation the processor will be running one of thethreads in the system. This may be an application thread, a systemthread running inside say the TCP/IP stack, or the idle thread. Fromtime to time a hardware interrupt will occur, causing control to betransferred briefly to an interrupt handler. When the interrupt hasbeen completed the system's scheduler will decide whether to returncontrol to the interrupted thread or to some other runnable thread. </P><P>Threads and interrupt handlers must be able to interact. If a threadis waiting for some I/O operation to complete, the interrupt handlerassociated with that I/O must be able to inform the thread that theoperation has completed. This can be achieved in a number of ways. Onevery simple approach is for the interrupt handler to set a volatilevariable. A thread can then poll continuously until this flag is set,possibly sleeping for a clock tick in between. Polling continuouslymeans that the cpu time is not available for other activities, whichmay be acceptable for some but not all applications. Polling onceevery clock tick imposes much less overhead, but means that the threadmay not detect that the I/O event has occurred until an entire clocktick has elapsed. In typical systems this could be as long as 10milliseconds. Such a delay might be acceptable for some applications,but not all. </P><P>A better solution would be to use one of the synchronizationprimitives. The interrupt handler could signal a condition variable,post to a semaphore, or use one of the other primitives. The threadwould perform a wait operation on the same primitive. It would notconsume any cpu cycles until the I/O event had occurred, and when theevent does occur the thread can start running again immediately(subject to any higher priority threads that might also be runnable). </P><P>Synchronization primitives constitute shared data, so care must betaken to avoid problems with concurrent access. If the thread that wasinterrupted was just performing some calculations then the interrupthandler could manipulate the synchronization primitive quite safely.However if the interrupted thread happened to be inside some kernelcall then there is a real possibility that some kernel data structurewill be corrupted. </P><P>One way of avoiding such problems would be for the kernel functions todisable interrupts when executing any critical region. On mostarchitectures this would be simple to implement and very fast, but itwould mean that interrupts would be disabled often and for quite along time. For some applications that might not matter, but manyembedded applications require that the interrupt handler run as soonas possible after the hardware interrupt has occurred. If the kernelrelied on disabling interrupts then it would not be able to supportsuch applications. </P><P>Instead the kernel uses a two-level approach to interrupt handling.Associated with every interrupt vector is an Interrupt Service Routineor ISR, which will run as quickly as possible so that it can servicethe hardware. However an ISR can make only a small number of kernelcalls, mostly related to the interrupt subsystem, and it cannot makeany call that would cause a thread to wake up. If an ISR detects thatan I/O operation has completed and hence that a thread should be wokenup, it can cause the associated Deferred Service Routine or DSR torun. A DSR is allowed to make more kernel calls, for example it cansignal a condition variable or post to a semaphore. </P><P>Disabling interrupts prevents ISRs from running, but very few parts ofthe system disable interrupts and then only for short periods of time.The main reason for a thread to disable interrupts is to manipulatesome state that is shared with an ISR. For example if a thread needsto add another buffer to a linked list of free buffers and the ISR mayremove a buffer from this list at any time, the thread would need todisable interrupts for the few instructions needed to manipulate thelist. If the hardware raises an interrupt at this time, it remainspending until interrupts are reenabled. </P><P>Analogous to interrupts being disabled or enabled, the kernel has ascheduler lock. The various kernel functions such as<TTCLASS="FUNCTION">cyg_mutex_lock</TT> and<TTCLASS="FUNCTION">cyg_semaphore_post</TT> will claim the scheduler lock,manipulate the kernel data structures, and then release the schedulerlock. If an interrupt results in a DSR being requested and thescheduler is currently locked, the DSR remains pending. When thescheduler lock is released any pending DSRs will run. These may postevents to synchronization primitives, causing other higher prioritythreads to be woken up. </P><P>For an example, consider the following scenario. The system has a highpriority thread A, responsible for processing some data coming from anexternal device. This device will raise an interrupt when data isavailable. There are two other threads B and C which spend their timeperforming calculations and occasionally writing results to a displayof some sort. This display is a shared resource so a mutex is used tocontrol access. </P><P>At a particular moment in time thread A is likely to be blocked,waiting on a semaphore or another synchronization primitive until datais available. Thread B might be running performing some calculations,and thread C is runnable waiting for its next timeslice. Interruptsare enabled, and the scheduler is unlocked because none of the threadsare in the middle of a kernel operation. At this point the deviceraises an interrupt. The hardware transfers control to a low-levelinterrupt handler provided by eCos which works out exactly whichinterrupt occurs, and then the corresponding ISR is run. This ISRmanipulates the hardware as appropriate, determines that there is nowdata available, and wants to wake up thread A by posting to thesemaphore. However ISR's are not allowed to call<TTCLASS="FUNCTION">cyg_semaphore_post</TT> directly, so instead the ISRrequests that its associated DSR be run and returns. There are no moreinterrupts to be processed, so the kernel next checks for DSR's. OneDSR is pending and the scheduler is currently unlocked, so the DSR canrun immediately and post the semaphore. This will have the effect ofmaking thread A runnable again, so the scheduler's data structures areadjusted accordingly. When the DSR returns thread B is no longer thehighest priority runnable thread so it will be suspended, and insteadthread A gains control over the cpu. </P><P>In the above example no kernel data structures were being manipulatedat the exact moment that the interrupt happened. However that cannotbe assumed. Suppose that thread B had finished its current set ofcalculations and wanted to write the results to the display. It wouldclaim the appropriate mutex and manipulate the display. Now supposethat thread B was timesliced in favour of thread C, and that thread Calso finished its calculations and wanted to write the results to thedisplay. It would call <TTCLASS="FUNCTION">cyg_mutex_lock</TT>. Thiskernel call locks the scheduler, examines the current state of themutex, discovers that the mutex is already owned by another thread,suspends the current thread, and switches control to another runnablethread. Another interrupt happens in the middle of this<TTCLASS="FUNCTION">cyg_mutex_lock</TT> call, causing the ISR to runimmediately. The ISR decides that thread A should be woken up so itrequests that its DSR be run and returns back to the kernel. At thispoint there is a pending DSR, but the scheduler is still locked by thecall to <TTCLASS="FUNCTION">cyg_mutex_lock</TT> so the DSR cannot runimmediately. Instead the call to <TTCLASS="FUNCTION">cyg_mutex_lock</TT>is allowed to continue, which at some point involves unlocking thescheduler. The pending DSR can now run, safely post the semaphore, andthus wake up thread A. </P><P>If the ISR had called <TTCLASS="FUNCTION">cyg_semaphore_post</TT> directlyrather than leaving it to a DSR, it is likely that there would havebeen some sort of corruption of a kernel data structure. For examplethe kernel might have completely lost track of one of the threads, andthat thread would never have run again. The two-level approach tointerrupt handling, ISR's and DSR's, prevents such problems with noneed to disable interrupts. </P></DIV><DIVCLASS="REFSECT1"><ANAME="KERNEL-OVERVIEW-CONTEXTS"></A><H2>Calling Contexts</H2><P>eCos defines a number of contexts. Only certain calls are allowed frominside each context, for example most operations on threads orsynchronization primitives are not allowed from ISR context. Thedifferent contexts are initialization, thread, ISR and DSR. </P><P>When eCos starts up it goes through a number of phases, includingsetting up the hardware and invoking C++ static constructors. Duringthis time interrupts are disabled and the scheduler is locked. When aconfiguration includes the kernel package the final operation is acall to <AHREF="kernel-schedcontrol.html"><TTCLASS="FUNCTION">cyg_scheduler_start</TT></A>.At this point interrupts are enabled, the scheduler is unlocked, andcontrol is transferred to the highest priority runnable thread. If theconfiguration also includes the C library package then usually the Clibrary startup package will have created a thread which will call theapplication's <TTCLASS="FUNCTION">main</TT> entry point. </P><P>Some application code can also run before the scheduler is started,and this code runs in initialization context. If the application iswritten partly or completely in C++ then the constructors for anystatic objects will be run. Alternatively application code can definea function <TTCLASS="FUNCTION">cyg_user_start</TT> which gets called afterany C++ static constructors. This allows applications to be writtenentirely in C. </P><TABLEBORDER="5"BGCOLOR="#E0E0F0"WIDTH="70%"><TR><TD><PRECLASS="PROGRAMLISTING">voidcyg_user_start(void){ /* Perform application-specific initialization here */} </PRE></TD></TR></TABLE><P>It is not necessary for applications to provide a<TTCLASS="FUNCTION">cyg_user_start</TT> function since the system willprovide a default implementation which does nothing. </P><P>Typical operations that are performed from inside static constructorsor <TTCLASS="FUNCTION">cyg_user_start</TT> include creating threads,synchronization primitives, setting up alarms, and registeringapplication-specific interrupt handlers. In fact for many applicationsall such creation operations happen at this time, using staticallyallocated data, avoiding any need for dynamic memory allocation orother overheads. </P><P>Code running in initialization context runs with interrupts disabledand the scheduler locked. It is not permitted to reenable interruptsor unlock the scheduler because the system is not guaranteed to be ina totally consistent state at this point. A consequence is thatinitialization code cannot use synchronization primitives such as<TT
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -