A Signal Is Similar to a Hardware Interrupt but Does Not Employ Priorities

In this department nosotros look at two example existent-time operating systems. Outset, we look at the POSIX standard for Unix-way operating systems such every bit Linux. We and then introduce Windows CE.

6.nine.1 POSIX

POSIX is a version of the Unix operating arrangement created by a standards organization. POSIX-compliant operating systems are source-code compatible—an awarding tin be compiled and run without modification on a new POSIX platform assuming that the application uses only POSIX-standard functions. While Unix was not originally designed as a real-fourth dimension operating system, POSIX has been extended to support existent-time requirements. Many RTOSs are POSIX-compliant and it serves as a good model for bones RTOS techniques. The POSIX standard has many options; item implementations practise not accept to support all options. The existence of features is determined past C preprocessor variables; for case, the FOO option would be available if the _POSIX_FOO preprocessor variable were defined. All these options are defined in the system include file unistd.h.

The Linux operating arrangement has become increasing pop equally a platform for embedded calculating. Linux is a POSIX-compliant operating system that is available equally open source. Withal, Linux was not originally designed for existent-time operation [Yag08, Hal11] [Yag08] [Hal11] . Some versions of Linux may showroom long interrupt latencies, primarily due to large critical sections in the kernel that delay interrupt processing. Two methods accept been proposed to ameliorate interrupt latency. A dual-kernel approach uses a specialized kernel, the co-kernel, for real-fourth dimension processes and the standard kernel for non-real-time processes. All interrupts must go through the co-kernel to ensure that existent-time operations are predictable. The other method is a kernel patch that provides priority inheritance to reduce the latency of many kernel operations. These features are enabled using the PREEMPT_RT mode.

Creating a process in POSIX requires somewhat cumbersome code although the underlying principle is elegant. In POSIX, a new process is created by making a re-create of an existing process. The copying process creates two unlike processes both running the aforementioned lawmaking. The complication comes in ensuring that one process runs the code intended for the new procedure while the other process continues the piece of work of the old process.

A process makes a copy of itself by calling the fork() function. That role causes the operating system to create a new procedure (the child process) which is a nigh exact copy of the process that chosen fork() (the parent process). They both share the same code and the same data values with one exception, the render value of fork(): the parent process is returned the process ID number of the kid procedure, while the child process gets a return value of 0. Nosotros can therefore examination the return value of fork() to determine which process is the child:

childid = fork();

if (childid == 0) { /* must be the child */

  /* do child process hither */

}

However, it would be clumsy to have both processes have all the code for both the parent and kid processes. POSIX provides the exec facility for overloading the lawmaking in a process. Nosotros can use that mechanism to overlay the child process with the appropriate code. There are several versions of exec; execv() takes a list of arguments to the process in the form of an array, merely equally would be accepted by a typical UNIX program from the shell. And so here is the process call with an overlay of the child's code on the kid procedure:

childid = fork();

if (childid == 0) { /* must exist the kid */

  execv("mychild",childargs);

  perror("execv");

  get out(1);

}

The execv() role takes as argument the name of the file that holds the child's lawmaking and the assortment of arguments. It overlays the process with the new code and starts executing it from the main() function. In the absenteeism of an error, execv() should never return. The code that follows the call to perror() and exit(), have care of the case where execv() fails and returns to the parent process. The exit() function is a C function that is used to get out a procedure; information technology relies on an underlying POSIX role that is called _exit().

The parent process should utilize one of the POSIX wait functions before calling exit() for itself. The wait functions not just return the child procedure'due south status, in many implementations of POSIX they brand sure that the kid'due south resources (namely memory) are freed. So we tin can extend our code as follows:

childid = fork();

if (childid == 0) { /* must be the child */

  execv("mychild",childargs);

  perror("execl");

  exit(1);

  }

else { /* is the parent */

  parent_stuff(); /* execute parent functionality */

  expect(&cstatus);

  exit(0);

  }

The parent_stuff() function performs the piece of work of the parent office. The wait() function waits for the child procedure; the function sets the integer cstatus variable to the return value of the child process.

POSIX does not implement lightweight processes. Each POSIX process runs in its own address space and cannot direct admission the data or code of other processes.

Real-time scheduling in POSIX

POSIX supports real-time scheduling in the _POSIX_PRIORITY_SCHEDULING resource. Not all processes have to run under the aforementioned scheduling policy. The sched_setscheduler() role is used to decide a process'southward scheduling policy and other parameters:

#include <sched.h>

int i, my_process_id;

struct sched_param my_sched_params;

i = sched_setscheduler(my_process_id,SCHED_FIFO,&sched_params);

This phone call tells POSIX to utilise the SCHED_FIFO policy for this procedure, along with some other scheduling parameters.

POSIX supports rate-monotonic scheduling in the SCHED_FIFO scheduling policy. The name of this policy is unfortunately misleading. It is a strict priority-based scheduling scheme in which a process runs until information technology is preempted or terminates. The term FIFO simply refers to the fact that, within a priority, processes run in starting time-come first-served order.

We already saw the sched_setscheduler() role that allows a procedure to set a scheduling policy. Two other useful functions allow a process to decide the minimum and maximum priority values in the system:

minval = sched_get_priority_min(SCHED_RR);

maxval = sched_get_priority_max(SCHED_RR);

The sched_getparams() function returns the current parameter values for a process and sched_setparams() changes the parameter values:

int i, mypid;

struct sched_param my_param;

mypid = getpid();

i = sched_getparam(mypid,&my_params);

my_params.sched_priority = maxval;

i = sched_setparam(mypid,&my_params);

Whenever a procedure changes its priority, information technology is put at the back of the queue for that priority level. A process can also explicitly motility itself to the stop of its priority queue with a call to the sched_yield() function.

SCHED_RR is a combination of real-time and interactive scheduling techniques: within a priority level, the processes are timesliced. Interactive systems must ensure that all processes become a hazard to run, and then time is divided into quanta. Processes get the CPU in multiple of quanta. SCHED_RR allows each process to run for a quantum of time, then gives the CPU to the next process to run for its quantum. The length of the quantum tin can vary with priority level.

The SCHED_OTHER is divers to let non-real-fourth dimension processes to intermix with real-time processes. The precise scheduling mechanism used by this policy is non defined. It is used to bespeak that the process does not need a real-time scheduling policy.

Remember that different processes in a system can run with unlike policies, so some processes may run SCHED_FIFO while others run SCHED_RR.

POSIX supports semaphores but it also supports a direct shared memory mechanism.

POSIX supports counting semaphores in the _POSIX_SEMAPHORES pick. A counting semaphore allows more than than one process admission to a resource at a fourth dimension. If the semaphore allows up to N resource, then it volition not block until Northward processes have simultaneously passed the semaphore; at that point, the blocked process tin resume simply afterward one of the processes has given up its semaphore. The simplest way to remember virtually counting semaphores is that they count down to 0—when the semaphore value is 0, the process must wait until another process gives up the semaphore and increments the count.

Because in that location may be many semaphores in the organisation, each one is given a name. Names are similar to file names except that they are not arbitrary paths—they should always showtime with "/" and should have no other "/". Here is how yous create a new semaphore chosen /sem1, so close it:

int i, oflags;

sem_t *my_semaphore; /* descriptor for the semaphore */

my_semaphore = sem_open("/sem1",oflags);

/* do useful work here */

i = sem_close(my_semaphore);

The POSIX names for P and Five are sem_wait() and sem_post() respectively. POSIX also provides a sem_trywait() function that tests the semaphore simply does not block. Here are examples of their apply:

int i;

i = sem_wait(my_semaphore); /* P */

/* do useful work */

i = sem_post(my_semaphore); /* V */

/* sem_trywait tests without blocking */

i = sem_trywait(my_semaphore);

POSIX shared retentiveness is supported under the _POSIX_SHARED_MEMORY_OBJECTS option. The shared memory functions create blocks of memory that tin be used past several processes.

The shm_open() function opens a shared memory object:

objdesc = shm_open("/memobj1",O_RDWR);

This lawmaking creates a shared memory object called /memobj1 with read/write admission; the O_RDONLY fashion allows reading only. It returns an integer which we tin use as a descriptor for the shared retention object. The ftruncate() function allows a process to set the size of the shared memory object:

if (ftruncate(objdesc,1000) < 0) { /* error */ }

Before using the shared retention object, we must map it into the process retention space using the mmap() function. POSIX assumes that shared retentivity objects fundamentally reside in a backing store such equally a deejay and are then mapped into the address space of the process. The value returned by shm_open(), objdesc, is the origin of the shared memory in the backing store. mmap allows the process to map in a subset of that space starting at the offset. The length of the mapped space is len. The outset of the block in the process's memory space is addr. mmap() also requires you to prepare the protection manner for the mapped retentiveness (O_RDWR, etc.). Hither is a sample phone call to mmap():

if (mmap(addr,len,O_RDWR,MAP_SHARED,objdesc,0) == NULL) {

  /* fault*/

}

The MAPS_SHARED parameter tells mmap to propagate all writes to all processes that share this memory block. You lot use the munmap() office to unmap the retentivity when the procedure is washed with it:

if (munmap(startadrs,len) < 0) { /* error */ }

This role unmaps shared memory from startadrs to startadrs+len. Finally, the close() function is used to dispose of the shared memory block:

close(objdesc);

Only i process calls shm_open() to create the shared memory object and close() to destroy it; every process (including the one that created the object) must utilise mmap() and mummap() to map it into their address space.

The pipe is very familiar to Unix users from its shell syntax:

% foo file1 | baz > file2

In this control, the output of foo is sent directly to the baz program'southward standard input by the operating system. The vertical bar (|) is the shell'due south annotation for a pipe; programs utilize the pipe() function to create pipes.

A parent process uses the pipe() function to create a pipe to talk to a child. It must practise so before the kid itself is created or it won't have any way to pass a arrow to the pipage to the child. Each terminate of a piping appears to the programs as a file—the procedure at the head of the pipe writes to one file descriptor while the tail process reads from another file descriptor. The pipe() function returns an array of file descriptors, the first for the write terminate and the second for the read end.

Here is an example:

if (pipe(pipe_ends) < 0) { /* create the pipe, check for errors */

  perror("pipe");

  interruption;

}

/* create the process */

childid = fork();

if (childid == 0) { /* the child reads from pipe_ends[one]*/

  childargs[0] = pipe_ends[1];

  /* laissez passer the read finish descriptor to the new incarnation of child */

  execv("mychild",childargs);

  perror("execv");

  exit(one);

}

else { /* the parent writes to pipe_ends[0] */

 

}

POSIX also supports message queues under the _POSIX_MESSAGE_PASSING facility. The advantage of a queue over a pipe is that, considering queues have names, nosotros don't have to create the pipe descriptor before creating the other process using it, as with pipes.

The proper name of a queue follows the aforementioned rules as for semaphores and shared memory: information technology starts with a "/" and contains no other "/" characters. In this code, the O_CREAT flag to mq_open() causes it to create the named queue if it doesn't yet exist and but opens the queue for the procedure if it does already exist:

struct mq_attr mq_attr; /* attributes of the queue */

mqd_t myq; /* the queue descriptor */

mq_attr.mq_maxmsg = 50; /* maximum number of messages */

mq_attr.mq_msgsize = 64; /* maximum size of a message */

mq_attr.mq_flags = 0; /* flags */

myq = mq_open("/q1",O_CREAT | RDWR,S_IRWXU,&mq_attr);

We use the queue descriptor myq to enqueue and dequeue messages:

char data[MAXLEN], rcvbuf[MAXLEN];

if (mq_send(myq,data,len,priority) < 0) { /* fault */ }

nbytes = mq_receive(myq,rcvbuf,MAXLEN,&prio);

Messages tin can exist prioritized, with a priority value between 0 and MQ_PRIO_MAX (at that place are at least 32 priorities available). Messages are inserted into the queue such that they are subsequently all existing letters of equal or college priority and before all lower-priority messages.

When a process is washed with a queue, it calls mq_close():

i = mq_close(myq);

6.ix.ii Windows CE

Windows CE [Bol07, Ken11] [Bol07] [Ken11] supports devices such every bit smartphones, electronic instruments, etc. Windows CE is designed to run on multiple hardware platforms and instruction fix architectures. Some aspects of Windows CE, such equally details of the interrupt structure, are adamant by the hardware compages and not by the operating organization itself. We will concentrate on Windows CE 6.

Effigy half-dozen.22 shows a layer diagram for Windows CE. Applications run under the shell and its user interface. The Win32 APIs manage access to the operating system. A variety of services and drivers provide the cadre functionality. The OEM Adaption Layer (OAL) provides an interface to the hardware in much the aforementioned way that a HAL does in other software architecture. The architecture of the OAL is shown in Effigy six.23. The hardware provides certain primitives such every bit a real-time clock and an external connection. The OAL itself provides services such equally a real-fourth dimension clock, power management, interrupts, and a debugging interface. A Board Support Package (BSP) for a detail hardware platform includes the OAL and drivers.

Figure half dozen.22. Windows CE layer diagram.

Figure 6.23. OAL architecture in Windows CE.

Windows CE provides back up for virtual memory with a flat 32-bit virtual address infinite. A virtual address tin can be statically mapped into main retentivity for key kernel-mode code; an address tin also exist dynamically mapped, which is used for all user-mode and some kernel-way lawmaking. Flash likewise every bit magnetic deejay can be used as a backing store.

Figure 6.24 shows the division of the address space into kernel and user with 2 GB for the operating system and 2 GB for the user. Figure vi.25 shows the system of the user address space. The top one GB is reserved for system elements such every bit DLLs, retention mapped files, and shared organization heap. The bottom ane GB holds user elements such as code, information, stack, and heap.

Figure vi.24. Kernel and user accost spaces in Windows CE.

Figure 6.25. User address infinite in Windows CE.

WinCE threads and drivers

WinCE supports two kernel-level units of execution: the thread and the driver. Threads are defined by executable files while drivers are defined by dynamically-linked libraries (DLLs). A process can run multiple threads. All the threads of a process share the aforementioned execution environment. Threads in dissimilar processes run in different execution environments. Threads are scheduled directly by the operating system. Threads may be launched past a process or a device driver. A driver may be loaded into the operating system or a process. Drivers can create threads to handle interrupts.

Each thread is assigned an integer priority. Lower-valued priorities signify higher priority: 0 is the highest priority and 255 is the lowest possible priority. Priorities 248 through 255 are used for non-real-time threads while the higher priorities are used for various categories of existent-fourth dimension execution. The operating organisation maintains a queue of ready processes at each priority level. Execution is divided into fourth dimension quanta. Each thread can have a split up breakthrough, which tin can be changed using the API. If the running process does non go into the waiting state by the end of its time breakthrough, information technology is suspended and put back into the queue. Execution of a thread can also be blocked by a higher-priority thread.

Tasks may exist scheduled using either of ii policies: a thread runs until the end of its quantum; or a thread runs until a college-priority thread is ready to run. Within each priority level, circular-robin scheduling is used.

WinCE supports priority inheritance. When priorities get inverted, the kernel temporarily boosts the priority of the lower-priority thread to ensure that it can consummate and release its resource. However, the kernel will utilize priority inheritance to only one level. If a thread that suffers from priority inversion in turn causes priority inversion for some other thread, the kernel will non apply priority inheritance to solve the nested priority inversion.

Interrupt treatment is divided amidst three entities:

The interrupt service handler (ISH) is a kernel service that provides the beginning response to the interrupt.

The ISH selects an interrupt service routine (ISR) to handle the interrupt. The ISH runs in the kernel with interrupts turned off; equally a issue, it should be designed to practise as little straight piece of work as possible.

The ISR in plough calls an interrupt service thread (IST) which performs most of the work required to handle the interrupt. The IST runs in the OAL then tin can be interrupted by a higher-priority interrupt.

Several different scenarios tin exist used to answer to an interrupt. Figure 6.26 shows ane of the responses. The interrupt causes the ISH to vector the interrupt to the appropriate ISR. The ISR in turn determines which IST to use to handle the interrupt and requests the kernel to schedule that thread. The ISH and so performs its work and signals the application almost the updated device status as advisable.

Figure six.26. Sequence diagram for an interrupt.

Windows CE 6.0 besides supports kernel-mode drivers. These drivers run inside the kernel protection layer so eliminate some of the overhead of user-style drivers. Even so, kernel-manner and user-mode drivers utilize the same API.

0 Response to "A Signal Is Similar to a Hardware Interrupt but Does Not Employ Priorities"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel