WO2014072904A1 - Procede d'ordonnancement avec contraintes d'echeance, en particulier sous linux, realise en espace utilisateur. - Google Patents

Procede d'ordonnancement avec contraintes d'echeance, en particulier sous linux, realise en espace utilisateur. Download PDF

Info

Publication number
WO2014072904A1
WO2014072904A1 PCT/IB2013/059916 IB2013059916W WO2014072904A1 WO 2014072904 A1 WO2014072904 A1 WO 2014072904A1 IB 2013059916 W IB2013059916 W IB 2013059916W WO 2014072904 A1 WO2014072904 A1 WO 2014072904A1
Authority
WO
WIPO (PCT)
Prior art keywords
task
cpu
state
scheduler
execution
Prior art date
Application number
PCT/IB2013/059916
Other languages
English (en)
Inventor
Sébastien BILAVARN
Muhammad Khurram BHATTI
Cécile BELLEUDY
Original Assignee
Centre National De La Recherche Scientifique
Priority date (The priority date is an assumption and is not a legal conclusion. Google has not performed a legal analysis and makes no representation as to the accuracy of the date listed.)
Filing date
Publication date
Application filed by Centre National De La Recherche Scientifique filed Critical Centre National De La Recherche Scientifique
Priority to EP13792761.2A priority Critical patent/EP2917834A1/fr
Priority to US14/441,070 priority patent/US9582325B2/en
Publication of WO2014072904A1 publication Critical patent/WO2014072904A1/fr

Links

Classifications

    • GPHYSICS
    • G06COMPUTING; CALCULATING OR COUNTING
    • G06FELECTRIC DIGITAL DATA PROCESSING
    • G06F9/00Arrangements for program control, e.g. control units
    • G06F9/06Arrangements for program control, e.g. control units using stored programs, i.e. using an internal store of processing equipment to receive or retain programs
    • G06F9/46Multiprogramming arrangements
    • G06F9/48Program initiating; Program switching, e.g. by interrupt
    • G06F9/4806Task transfer initiation or dispatching
    • G06F9/4843Task transfer initiation or dispatching by program, e.g. task dispatcher, supervisor, operating system
    • G06F9/4881Scheduling strategies for dispatcher, e.g. round robin, multi-level priority queues
    • G06F9/4887Scheduling strategies for dispatcher, e.g. round robin, multi-level priority queues involving deadlines, e.g. rate based, periodic
    • GPHYSICS
    • G06COMPUTING; CALCULATING OR COUNTING
    • G06FELECTRIC DIGITAL DATA PROCESSING
    • G06F9/00Arrangements for program control, e.g. control units
    • G06F9/06Arrangements for program control, e.g. control units using stored programs, i.e. using an internal store of processing equipment to receive or retain programs
    • G06F9/46Multiprogramming arrangements
    • G06F9/48Program initiating; Program switching, e.g. by interrupt
    • G06F9/4806Task transfer initiation or dispatching
    • G06F9/4843Task transfer initiation or dispatching by program, e.g. task dispatcher, supervisor, operating system
    • G06F9/4881Scheduling strategies for dispatcher, e.g. round robin, multi-level priority queues
    • GPHYSICS
    • G06COMPUTING; CALCULATING OR COUNTING
    • G06FELECTRIC DIGITAL DATA PROCESSING
    • G06F1/00Details not covered by groups G06F3/00 - G06F13/00 and G06F21/00
    • G06F1/26Power supply means, e.g. regulation thereof

Definitions

  • the invention relates to a method for performing a single processor, multiprocessor scheduling or multi-task scheduling with time constraints.
  • Task Scheduler with Deadline Constraints means that each task has a completion time that must not exceed.
  • the scheduling performed supports execution on one or more processors. It is run under Linux, or any operating system that supports the POSIX standard, and in particular its POSIX.1c extension, but is not integrated with the kernel: it works in user space.
  • User space is a mode of operation for user applications, as opposed to kernel space which is a supervisory mode of operation that has advanced features with all rights, especially that of accessing all the resources of a microprocessor.
  • the manipulation of resources is nevertheless made possible in user space by so-called API ("Application Programming Interface") functions, which themselves rely on the use of Peripheral drivers.
  • API Application Programming Interface
  • Peripheral drivers The development of a solution in user space makes it possible and facilitates the development of schedulers with temporal constraints compared to a direct integration into the operating system kernel which is made very difficult by its complexity. Another advantage is to bring increased stability because a runtime error in user space does not have a serious consequence for the integrity of the rest of the system.
  • the method of the invention relies in particular on the mechanisms defined by the POSIX standard ("Portable Operating System Interface - X", ie "portable interface for the systems "X” expressing the Unix inheritance) and its POSIX.1c extension, including the POSIX task structure, POSIX threads ("pthreads") and POSIX task management APIs.
  • POSIX Portable Operating System Interface - X
  • pthreads POSIX threads
  • POSIX task management APIs POSIX task management APIs.
  • Linux is a system where time management is called “time share” as opposed to “real time” management.
  • time share as opposed to “real time” management.
  • several factors related to the operation of the system such as resource sharing, input / output management, interruptions, the influence of virtual memory, etc. cause temporal uncertainties about task execution times. Therefore, it is not possible to guarantee a hard real time solution, that is to say where the execution of each task is performed within strict time limits and inviolable.
  • the realization of the proposed scheduler is nevertheless suitable for real-time systems known as "soft” (in English “soft real time”), or "with constraints of expiry", which accept variations in the processing of data order from second to maximum. This is the case of a large number of real-time applications, for example multimedia.
  • auxiliary real-time kernel is a priority and deals directly with real-time tasks.
  • Other tasks are delegated to the standard Linux kernel which is considered a lower priority task (or job).
  • RT Linux, RTAI, XENOMAI real real-time scheduler
  • XENOMAI real real-time scheduler
  • This auxiliary real-time kernel is a priority and deals directly with real-time tasks.
  • Other tasks are delegated to the standard Linux kernel which is considered a lower priority task (or job).
  • the development of a specific kernel-specific scheduler is very difficult since it requires a thorough knowledge of the kernel and involves heavy kernel developments, with all the complexity and instability that entails.
  • Linux real-time extensions are supported by a smaller number of platforms.
  • a scheduling method according to the invention is particularly suitable for the implementation of low-consumption oriented scheduling policies, for example an EDF policy ("earliest deadline first", that is to say, priority over nearest deadline) - which is a special type of scheduling with time constraints - with dynamic voltage-frequency change (DVFS) to minimize consumption, which is important in embedded applications.
  • EDF policy earliest deadline first
  • DVFS dynamic voltage-frequency change
  • An object of the invention is therefore a task scheduling method with deadlines constraints, based on a model of independent periodic tasks and realized in user space, in which:
  • Each task to be scheduled is associated with a data structure, defined in user space and containing at least one temporal information (time and / or period) and information indicative of a state of activity of the task, said state of activity being selected from a list comprising at least:
  • each task modifies said information indicative of its state of activity and, if necessary, according to a predefined scheduling policy, calls a scheduler that is executed in user space;
  • Said scheduling policy may be a pre-emptive policy, for example chosen from a policy of the EDF type and its derivatives such as DSF ("Deterministic Stretch to Fit", that is to say “extension and deterministic adjustment of the frequency” ) and AsDPM ("Assertive Dynamic Power Management"), RM ("Rate Monotonic” or “Monotonic Rate Scheduling"), DM ⁇ "Deadiine Monotonic” or “Scheduling in maturity function "), LLF (“ Least Laxity First "or scheduling based on the lowest laxity).
  • the method can be implemented in a multiprocessor platform, said data structure also comprising information relating to a processor to which the corresponding task is assigned, and wherein said scheduler assigns to a system processor each task ready to be executed.
  • Said scheduler can change the clock frequency and the supply voltage of the or at least one processor according to a DVFS policy.
  • the method may include an initialization step, during which:
  • the tasks to be scheduled are created, allocated to the same processor and placed in a waiting state of a recovery condition, a global variable called an appointment being incremented or decremented during the creation of each said task;
  • said scheduler is executed for the first time.
  • Said data structure may also contain information indicative of a pthread associated with said task and its execution time in the worst case.
  • the method can be run under an operating system compatible with a POSIX.Ic standard, which can in particular be a Linux system. In that case :
  • the method may include using a MUTEX to perform a single instance of the scheduler at a time.
  • Assigning a task to a processor can be done using the CPU Affinity API.
  • Another object of the invention is a computer program product for implementing a scheduling method according to one of the preceding claims.
  • FIG. 1 the principle of implementing a scheduling method according to the invention in Linux user space
  • Figure 2 an example of a set of independent periodic tasks
  • FIG. 3 a diagram of the states and transitions of the application tasks in a scheduling method according to the invention.
  • FIG. 1 An application is seen as a set of tasks to be scheduled 2.
  • the scheduler 1 controls the execution of these tasks by means of three specific functions (reference 3): "prempt (Task)", “resume (Task)” and " run_on (Task, CPU) ".
  • These functions - whose names are arbitrary and given only as a non-limiting example - allow the preemption of a task, the recovery of a task and the allocation of a task to a processor, respectively. They rely on the use of features provided by the Linux API (reference 5) to control tasks and processors from the user space.
  • the scheduler For its operation, the scheduler must have specific information that derives from the task model used. For a model of periodic and independent tasks, it is at least information relating to the expiry of each task, its period and its state of activity; other temporal information can also be provided: Worst Case Execution Time (WCET), later deadline (that is, current time plus due date) to avoid confusion with "late maturity", “maturity” is sometimes referred to as “absolute maturity”), etc. In the case of a multiprocessor platform, the scheduler also requires information indicative of the processor to which the task is assigned. In addition, each task is associated with a POSIX thread ⁇ "pthread”), which must also be known to the scheduler.
  • POSIX thread ⁇ "pthread POSIX thread
  • UTEX associated with the task
  • condition associated with the task a condition associated with the task
  • Linux identifier of the task a condition associated with the task.
  • BCET best execution time
  • AET actual execution time
  • the application model consists of periodic and independent tasks.
  • a periodic task model refers to the specification of homogeneous sets of jobs ("jobs") that are repeated at periodic intervals; a "work” is a particular form of task, and more precisely an independent task whose execution is strictly independent of the results of the other "works”.
  • a model of independent tasks refers to a set of tasks for which the execution of one task is not subordinate to the situation of another. This model supports synchronous and asynchronous execution of tasks, it is applicable in a large number of real applications that must respect temporal constraints. Knowledge of the temporal characteristics and constraints of the tasks (due date, period, worst execution time, etc.) is therefore necessary to apply this type of scheduling. As explained above, this information is added to a specific structure that extends the standard task type under Linux.
  • FIG. 2 illustrates an application model of periodic and independent tasks comprising four tasks T1 - T4.
  • Tasks T1 and T2 must run before their deadline ("deadiine" DDLN) of 21 ms (milliseconds),
  • Each task executes in an effective time AET ("Actuai Execution Time” ⁇ f, which is between a minimum value BCET ("Best Case Execution Time”) and a maximum value WCET ("Worst Case Execution Time”).
  • AET Effective Time
  • BCET Best Case Execution Time
  • WCET Wide Case Execution Time
  • Figure 2 refers in particular to a video application where each of the four tasks corresponds to a particular treatment of an image. These four tasks are therefore repeated every 40 ms (period) in order to process 25 frames per second.
  • One or more queues are used to store tasks. At least one queue is required for the execution of a scheduling with deadlines, to memorize the list of ready tasks.
  • a priority is also assigned to each task to define their order of execution.
  • the priority criterion depends on the scheduling policy. For the EDF (Earliest Deadline First) policy, for example, the priority criterion is the proximity of the deadline: the task with the most expiry date. near is the highest priority.
  • the queue of ready tasks is generally sorted in descending order of priority.
  • the priority criterion considered here is different from the priority criteria used in native Linux schedulers (timesharing). The scheduler is called at specific times, called scheduling instants, which are triggered by the tasks themselves at characteristic moments of their execution.
  • task events (reference 2 in figure 1), they correspond for example to their activation (“onActivate”), their end of execution (“onBlock”), their reactivation (“onUnBlock”) or their termination (“onTerminate”) - these names being arbitrary and given only by way of non-limiting example.
  • the events are triggered, at the appropriate moments of the execution of an application task, by calls to functions "onActivate ()", “onBlock ()”, “onUnBlock ()”, “onTerminate ()", inserted in his code.
  • Each task event updates the fields of the task structure for the task concerned (state, future due date, period, etc.) and calls the scheduler if necessary.
  • the scheduler's non-scheduling call by a task event depends on the scheduling policy. For an EDF policy for example, the scheduler is called on the events "onActivate", “onBlock” and “onUnBlock”.
  • the event "onActivate” corresponds to the creation of a task. It marks the transition from “nonexistent” to “ready”. For reasons of synchronization when creating tasks (explained in 15.), the onActivate event increments, at the end of its execution, an "appointment” variable, then puts the task on hold for a condition of activation of the scheduler, for example by calling the "pthread_cond_wait” function of the "Pthread Condition Variable" of the POSiX.lc standard.
  • the onBlock event is triggered when a task completes its effective execution (AET). It then goes to the "waiting" state where elie waits until reaching its period. When it reaches its period, the task triggers the "onUnBlock” event, which makes it "ready”, and then puts it on hold for a recovery condition, for example by calling the POSIX function pthread_cond_wait. This condition will be signaled by the scheduler by means of the function "pthread_cond_broadcast” which also belongs to the "Pthread Condition Variable" of the POSiX.lc standard.
  • a task can be pre-empted by another task that has become more priority (in the case of an EDF algorithm, because its deadline has become closer to that of the task being executed). Preemption of a task changes it from "running" to "ready”. Conversely, the task that resumes following a preemption goes from the "ready" state to the "in execution" state.
  • the scheduler performs, by means of a main function "select_" (name given as a non-limiting example) the following actions: establishment of a queue of ready tasks, sorting of the queue priority in decreasing order of priority (highest priority task at the beginning of the list), preemption of non-priority tasks in progress, allocation of eligible tasks to free processors and start of eligible tasks by sending a recovery condition .
  • the determination of eligible tasks requires the scheduler to know the status of all existing tasks.
  • the action list given here corresponds to an EDF scheduling, but can be modified - and in particular enriched - for the realization of other scheduling policies.
  • Mutex MUTual Exclusion Device
  • the Mutex is locked at the beginning of the scheduler run, and released at the end of the scheduler call. This process ensures that only one instance of the scheduler executes at a time, allowing the shared variables to be changed without risk.
  • the first execution of the scheduler occurs just after the creation of the tasks. To ensure scheduler control of tasks, a synchronization must be performed to ensure that all tasks have finished being created before running the scheduler and that tasks do not start running until the job is completed. the scheduler did not give them the order. For this, first of all, a global variable of is initialized to 0 before creating jobs. Then all the tasks are created and placed on the first processor of the system. At the very beginning of their creation, in other words when the onActivate () event is executed, each task increments the "rendezvous" variable and immediately suspends its execution by waiting for a restart condition ( use of the POSIX function "pthread_cond_wait").
  • the scheduler can be executed. During this execution, the scheduler resumes the execution of the eligible tasks by informing them of their recovery condition (function "pthread_cond_broadcast”).
  • the scheduler To control the execution of the application tasks, the scheduler requires the use of two specific functions for the suspension and the resumption of a task, called respectively "preempt (Task)” and “resume (Task)” in figure 1 (reference 3), these names being given solely by way of non-limiting example.
  • the preempt function (Task) is based on the use of ⁇ "Signa! Of the POSIX standard.
  • the signal S1GUSR1 is sent to the corresponding pthread (POSIX function "pthread_kill”).
  • the associated signal handler (“sigusrl") puts the job on hold for a resume condition ("pthread " cond_wait ”) upon receipt of the signal.
  • the resume function (Task) reports the proper resume condition to the task in question to resume execution. It is therefore strictly equivalent to calling the Linux function pthread_cond_broadcast; in fact, the creation of a specific function is essentially justified for the sake of legibility of the code. This mechanism exploits the fact that in practice, the signal handler SIGUSR1 is executed by the Linux "pthread" which receives the signal.
  • the signai manager can identify the "pthread” to suspend (necessary to send the waiting condition to the "pthread” concerned by the function pthread_cond_wait) by comparing for example its own process identifier (tid) with that of all "Pthreads" of the application.
  • the scheduler does not use the preemption and job recovery mechanisms provided by the kernel because they are not accessible in user space.
  • the scheduler requires an explicit function to fix the execution of a task on a given processor.
  • there is an API for controlling Linux "pthreads" by the CPUs of a CPU Affinity there is no specific function for allocating a task to a processor.
  • a specific function "change_freq” (name given only as a non-limiting example) is used in the case of a scheduler using DVFS techniques to control the dynamic change of voltage / frequency of the processors ( Figure 1, reference 3).
  • This function uses a Linux APi called “CPUFreq” which allows to change the frequency of each processor via the virtual file system "sysfs". For example, simply write the desired frequency to a file - the file "/ sys / devices / system / cpu / cpuO / cpufreq / scaling_setspeed" on Linux - to change the frequency of processor 0.
  • the scheduler can use the function change_freq to allow the modification of the frequency by writing back to the file system.
  • the scheduler of the invention has been applied to the realization of a "DSF"("Deterministic Stretch ⁇ to-Fit” type scheduling, in which the frequency of the processors (and therefore the voltage, which depends on it) ) is recalculated at each scheduling event, using Actual Execution Time (AET) for completed tasks and Worst Execution Time (WCET) for others. , to allocate the beat time of the previous task to the next task, which reduces the operating frequency of the processor concerned.
  • DSF Deterministic Stretch ⁇ to-Fit
  • AET Actual Execution Time
  • WCET Worst Execution Time
  • the appendix contains the source code, written in C language, of a computer program for implementing a scheduling method according to the invention, using the DSF-DVFS technique described above. This code is given only by way of non-limiting example.
  • the code consists of a plurality of files contained in two directories: DSF_Scheduler and pm_drivers.
  • the DSF_Scheduler directory contains the core of the DSF scheduler code. It includes, in particular, the N_scheduler prototype file, h which contains the definition of the necessary types (task structure, processor structure, etc.), the global variables (task list, processor list, scheduler parameters, etc.), and your prototypes of the exported functions.
  • the DSF_Scheduler.c file then describes the main functions of the scheduler. The most important is the select_ function, which describes the entire scheduling process performed at each scheduling time. OnActivate, onBlock, onUnBlock, and onTerminate task events that trigger scheduling times are also described in this file. The scheduling function relies on a slow_down function that calculates and applies the frequency change. Finally, the particularities of the scheduler at the start of the application required the development of a specific function start_sched. This one does not run only once at the very beginning of the launch of the application and is largely based on the code of the main scheduler (select_). For all other scheduling times, the select_ function is called via the call_scheduter function.
  • the Makefile file is used for compilation,
  • N_application.c. It is also the file containing the "hand" of the program. This one carries out the necessary initializations, creates the POSIX tasks, launches the scheduler and synchronizes the whole thing. It should be noted here that an application is viewed as a set of tasks, each with temporal characteristics such as the worst case run time (WCET), period, deadlines, and so on. The tasks used perform a simple processing (multiplication of the elements of an array of integers) until a certain execution time (AET) - which is described by the function usertask_actualexec - then go into standby mode until 'to their reactivation (usertask_sieepexec) when they reach their period.
  • AET execution time
  • N_utiis.c This contains for example functions of initialization, preemption of tasks, allocation of tasks to the processors, sorting, display, etc.
  • the pmjdrivers directory contains the lowest level functions on which the scheduler relies. More specifically, these are the prototype files inte__ core 5_m520.h, pm_typedef.h and the file pm_cpufreq.c.
  • the prototype file intel_core_i5_m520.h contains the description of the target platform whose name it bears. This is information about the number of processors in the platform and the possible frequencies for each of the cores, represented as states.
  • the types used e.g. the processor states
  • the pm_cpufreq.c file contains in particular the functions allowing the effective dynamic frequency change on the runtime platform, relying on Linux CPUfreq,
  • the dynamic change of frequency voltage is managed by policies called governors (governors) under Linux, known in themselves. More precisely, to be able to change the frequency (s) processor (s), one must first use a governor said userspace. A first function set_governor is present for this. Then, the interaction with the Linux frequency change driver is done via exchange files located in / sys / devices / system / cpu / cpuX / cpufreq / where X represents the number of the concerned processor.
  • the open_cpufreq and close_cpufreq functions allow you to open and close the scaling_set $ peed interchange file.
  • the change of frequency is done by the function CPU_state_manager_apply_state.
  • the scheduling method of the invention can be applied to a single-processor platform and / or not implement mechanisms for reducing consumption.
  • a scheduler according to the invention can be implemented under another operating system compatible with the POSIX standard and more specifically with the IEEE POSIX 1c standard (or, which is equivalent, POSIX 1003.1c), provided that it supports an equivalent of APis CPU Affinity (for multiprocessor applications) and CPUfreq (for applications using dynamic frequency voltage change).
  • DSF Deterministic Stretch ⁇ to ⁇ Fit
  • AsDPM Automatic Dynamic Power Management
  • the reproduction of a task scheduler under user space deadlines constraints as described above is only feasible under certain conditions that depend on the operating system (called OS for Operating System). System). These conditions are for example the following.
  • OS Operating System
  • the method according to the invention must support the notion of preemptive scheduling in user space.
  • the explicit preemption of a task from user space is a feature that is not feasible in all OSs.
  • the task template must include maturity and state constraint attributes. The ability to extend the task template with these attributes, accessible in user mode, depends on the OS.
  • the method according to the invention does not require any explicit intervention of the supervisor mode and is based exclusively on user mode mechanisms in the form of APis.
  • the standard POS! X Linux (pthread) task structure can be generalized to a standard POSIX (thread) task structure under an OS that enables it to be realized.
  • the implementation of the invention for this generalized task structure requires, like the POSIX Linux standard task structure (pthread) of Figure 1, their extension by parameters accessible in user space.
  • the information concerning the temporal characteristics and constraints of the tasks are added in a specific structure that extends the type of POSIX standard task by parameters made accessible in user space.
  • the method of preemption of POSIX tasks in user space implemented in the invention does not exist in so-called "general public" OS, that is to say devoid of real-time constraints.
  • the scheduler in general, to control the execution of the application tasks, the scheduler requires an explicit preemption function for POSIX tasks in user space, which is based on the use of two specific functions for the suspension. and the resumption of a task.
  • fioat aet // effective execution time (wcet ⁇ aet ⁇ bcet) fioat ret; // RET: execution time, remaining
  • fioat deadline // absolute deadline float next_deadiine; // relative (deadiine)
  • total_eet [job-> id-1] total_eetjjob-> id-1] + (abs_eet [job-> id-1] / SRjotalfjob-
  • job-> preemptdelay sched_time - job-> last__preempt_time
  • job-> rei job-> aet - total__eet [job->id-1];
  • job-> ret G ° b-> aet - totai_eet [job-> id-1]);
  • job-> ret job-> aet
  • printf ⁇ 'T% d is assigned to CPU% d ⁇ n', job ⁇ > id, P-> cpu_id);
  • job-> last_preempt_time job-> begintime
  • begin__sched_time get_time ();
  • randomvalue ⁇ doub! e) rand () / RAND_MAX;
  • task-> aet (randomvalue * (task-> wcei-task-> bcet) + task-> bcet);
  • task-> aet (randomvalue * ⁇ task-> wcet-task-> bcet) + task-> bcet);
  • task-> next_deadline task-> next__persod + task-> deadline;
  • task-> next_period task-> nexi_period + task-> period; task-> preemptdelay - 0 .;
  • heap k-> state a n ex ist in g;
  • CPU _CLR (i, &mask);
  • proc_id job-> id, proc_id, wcet_Fn_1 rjob-> id-1], SRJotaifprocjd], estimated__WCET_Fn_1 [procjd]);
  • procjd job-> id, procjd, aet_Fn_1 [job-> id-1], SR_totai [proc_id], estimated_AET_Fn_1 [proc_id]);
  • t_available_Fn_1 [proc_id] wcet_Fn_1 [job-> id-1] + slack_local [proc_id];
  • proc_id job-> id, procjd, wcetJFn_1 [job-> id-1], slackjocalfprocjd], t_avaHable_FnJ [procjd]);
  • procjd proc_id, procjd, t_available_Fn_1 [proc_id], estimated_WCET_Fn J [procjd], SRJocal [proc_idj);
  • wceLFnJ [job ->! d-1] (float) (wcel_Fn ⁇ 1 [job-> id-1] * SR_local (proc_id));
  • aet_Fn_1 [job-> id-1] (double) (aet_Fn_1 [job-> id-1] * SRJocal [proc_id]);
  • CPU_state_manager apply_state ⁇ proc_id, &State);
  • SRJotallproc jd] SR_total [proc_id] * SR_local [procjd];
  • nb_exec [i] 0;
  • IDLE_to_RUNN NG! 0;
  • printffWARNiNG T% d IS ALLOCATED MORE THAN ONE CPU ⁇ n ", T [i] -> id);
  • iist [i] iist [min]
  • tickCount ++ task-> begintime - sched_time

Abstract

Procédé d'ordonnancement de tâches avec contraintes d'échéances, basé sur un modèle de tâches périodiques indépendantes et réalisé en espace utilisateur au moyen des API POSIX.

Description

PROCEDE D'ORDONNANCEMENT AVEC CONTRAINTES D'ECHEANCE, EN PARTICULIER SOUS LINUX, REALISE EN ESPACE UTILISATEUR
L'invention porte sur un procédé permettant la réalisation d'un ordonnancement monoprocesseur, multiprocesseur ou muiticœurs de tâches avec contraintes d'échéances. Ordonnanceur de tâches avec contraintes d'échéances signifie que chaque tâche possède un temps de terminaison qu'elles ne doit pas dépasser. L'ordonnancement réalisé supporte l'exécution sur un seul ou plusieurs processeurs. Il est réalisé sous Linux, ou tout autre système d'exploitation supportant la norme POSIX, et plus particulièrement son extension POSIX.1c, mais n'est pas intégré au noyau : il fonctionne en effet en espace utilisateur.
L'espace utilisateur est un mode de fonctionnement pour les applications utilisateur, par opposition à l'espace noyau qui est un mode de fonctionnement superviseur qui dispose de fonctionnalités avancées ayant tous les droits, en particulier celui d'accéder à toutes les ressources d'un microprocesseur. La manipulation des ressources est néanmoins rendue possible en espace utilisateur par des fonctionnalités dites API (« Application Programming Interface », c'est-à-dire Interfaces de Programmation d'Applications), qui elles-mêmes s'appuient sur l'utilisation de pilotes périphériques. Le développement d'une solution en espace utilisateur rend possible et facilite le développement d'ordonnanceurs à contraintes temporelles par rapport à une intégration directe dans le noyau du système d'exploitation qui est rendue très difficile par sa complexité. Un autre avantage est d'apporter une stabilité accrue car une erreur d'exécution en espace utilisateur n'entraîne pas de conséquence grave pour l'intégrité du reste du système.
Pour réaliser un ordonnancement en espace utilisateur, le procédé de l'invention s'appuie en particulier sur les mécanismes définis par la norme POSIX {« Portable Operating System Interface - X », c'est-à-dire « interface portable pour les systèmes d'exploitation », le « X » exprimant l'héritage Unix) et son extension POSIX.1 c, et notamment sur la structure de tâche POSIX, les threads POSIX (« pthreads ») et les API de gestion de tâche POSIX. Il convient de rappeler qu'un processus est une instance de programme en cours d'exécution, une « tâche » est un découpage d'un processus et un « thread » est la réalisation d'une tâche sous Linux aux moyen des API POSIX ; un thread ou tâche ne peut pas exister sans processus, mais il peut y avoir plusieurs threads ou tâches par processus. On pourra se rapporter à ce propos à l'ouvrage de Robert Love « LINUX System programming », O'ReiNy, 2007.
Linux est un système où la gestion du temps est dite à « temps partagé », par opposition à une gestion « temps réel ». Dans ce type de gestion, plusieurs facteurs liés au fonctionnement du système comme le partage des ressources, la gestion des entrées/sorties, les interruptions, l'influence de la mémoire virtuelle, etc. entraînent des incertitudes temporelles sur les temps d'exécution des tâches. Dès lors, il n'est pas possible de garantir une solution temps réel « dur » (en anglais « hard real time »), c'est-à-dire où l'exécution de chaque tâche est remplie dans des limites temporelles strictes et inviolables. La réalisation de l'ordonnanceur proposé convient néanmoins pour des systèmes temps réel dit « souple » (en anglais « soft real time »), ou « avec contraintes d'échéance », qui acceptent des variations dans le traitement des données de l'ordre de la seconde au maximum. C'est le cas d'un grand nombre d'applications temps réel, par exemple multimédia.
N'étant pas nativement un système temps réel, plusieurs solutions techniques sont cependant possibles pour rendre ie noyau Linux compatible avec les contraintes temps réel. La solution la plus courante consiste à lui associer un noyau temps réel auxiliaire disposant d'un véritable ordonnanceur temps réel (RT Linux, RTAI, XENOMAI). Ce noyau temps réel auxiliaire est prioritaire et traite directement les tâches temps réel. Les autres tâches (non temps réel) sont déléguées au noyau standard Linux qui est considéré comme une tâche (ou un travail) de fond moins prioritaire. Cependant, le développement d'un ordonnanceur spécifique intégré au noyau est très difficile puisqu'il nécessite une connaissance approfondie du noyau et implique des développements noyau lourds, avec toute la complexité et instabilité que cela entraîne. Par ailleurs, les extensions temps réel Linux sont supportées par un nombre plus réduit de plateformes. La solution proposée permet une réalisation en espace utilisateur de ce fait plus simple, plus stable et applicable à n'importe quelle plateforme supportant un noyau Linux standard. Cette approche ne permet pas de garantir les contraintes temps réel dures, mais simplifie considérablement le développement d'un ordonnanceur tout en convenant pour les applications temps réei souple (la grande majorité des applications).
Un procédé d'ordonnancement selon l'invention convient en particulier à ia mise en œuvre de politiques d'ordonnancement orientées faible consommation, par exemple une politique EDF (« earliest deadline first », c'est- à-dire de priorité à l'échéance la plus proche) - qui est un type particulier d'ordonnancement avec contraintes d'échéances - avec changement dynamique de tension-fréquence (DVFS) afin de minimiser la consommation, ce qui est important dans les applications embarquées. Voir à ce propos les articles suivants :
R. Chéour et al. « EDF scheduier technique for wireless sensors networks: case study » Fourth International Conférence on Sensing Technology, 3 - 5 juin 2010, Lecce, Italie;
- R. Chéour et al. « Exploitation of the EDF scheduling in the wireless sensors networks », Measurement Science Technology Journal (IOP), Spécial Issue on Sensing Technology: Devices, Signais and Materials, 2011.
Il existe de nombreux travaux sur l'ordonnancement faible consommation, mais qui restent pour l'essentiel théoriques et ne sont pratiquement jamais intégrés à un système d'exploitation à cause de la complexité de ce travail. La solution proposée, comme elle est entièrement réalisée en espace utilisateur, facilite grandement cette intégration.
Un objet de l'invention est donc un procédé d'ordonnancement de tâches avec contraintes d'échéances, basé sur un modèle de tâches périodiques indépendantes et réalisé en espace utilisateur, dans lequel :
• chaque tâche à ordonnancer est associée à une structure de données, définie en espace utilisateur et contenant au moins une information temporelle (échéance et/ou période) et une information indicative d'un état d'activité de ia tâche, ledit état d'activité étant choisi dans une liste comprenant au moins :
- un état de tâche en exécution ;
- un état de tâche en attente de la fin de sa période d'exécution ; et - un état de tâche prête à être exécutée, en attente d'une condition de reprise;
• au cours de son exécution, chaque tâche modifie ladite information indicative de son état d'activité et ie cas échéant, en fonction d'une politique d'ordonnancement prédéfinie, appelle un ordonnanceur qui est exécuté en espace utilisateur ;
• à chaque appel, ledit ordonnanceur :
- établit une file d'attente des tâches prêtes à être exécutées, en attente d'une condition de reprise;
- trie ladite file d'attente en fonction d'un critère de priorité prédéfini ;
- si nécessaire, préempte une tâche en exécution en lui envoyant un signal la forçant à passer dans ledit état de tâche prête à être exécutée, en attente d'une condition de reprise ; et
- envoie ladite condition de reprise au moins à la tâche se trouvant en tête de ladite file d'attente
Selon différents modes de réalisation du procédé de l'invention :
Ladite politique d'ordonnancement peut être une politique préemptive, par exemple choisie parmi une politique de type EDF et ses dérivés tels que DSF {« Deterministic Stretch to Fit », c'est-à-dire « extension et ajustement déterministe de la fréquence ») et AsDPM (« Assertive Dynamic Power Management », ou « politique assertive de gestion dynamique des modes repos »), RM (« Rate Monotonie », ou « ordonnancement à taux monotone »), DM {« Deadiine Monotonie » ou « ordonnancement en fonction de l'échéance »), LLF (« Least Laxity First » ou ordonnancement en fonction de la laxité la plus faible).
Le procédé peut être mis en œuvre dans une plateforme multiprocesseur, ladite structure de données comprenant également une information relative à un processeur auquel la tâche correspondante est affectée, et dans lequel ledit ordonnanceur affecte à un processeur du système chaque tâche prête à être exécutée. Ledit ordonnanceur peut modifier la fréquence d'horloge et la tension d'alimentation du ou d'au moins un processeur en fonction d'une politique DVFS.
Le procédé peut comporter une étape d'initialisation, au cours de laquelle :
- les tâches à ordonnancer sont créées, affectées à un même processeur et placées dans un état d'attente d'une condition de reprise, une variable globale dite de rendez-vous étant incrémentée ou décrémentée lors de la création de chaque dite tâche ;
- lorsque ladite variable de rendez-vous prend une valeur prédéfinie indiquant que toutes les tâches ont été créées, ledit ordonnanceur est exécuté pour la première fois.
Ladite structure de données peut contenir également des informations indicatives d'un pthread associé à ladite tâche et à son temps d'exécution dans le pire cas.
Le procédé peut être exécuté sous un système d'exploitation compatible avec une norme POSIX.Ic, qui peut en particulier être un système Linux. Dans ce cas :
A chaque appel de l'ordonnanceur, un «pthread» est crée pour assurer son exécution.
Le procédé peut comporter l'utilisation d'un MUTEX pour assurer l'exécution d'une seule instance à la fois de l'ordonnanceur.
L'affectation d'une tâche à un processeur peut être effectuée au moyen de l'API CPU Affinity.
Un autre objet de l'invention est un produit programme d'ordinateur pour la mise en œuvre d'un procédé d'ordonnancement selon l'une des revendications précédentes.
D'autres caractéristiques, détails et avantages de l'invention ressortiront à fa lecture de la description faite en référence aux dessins annexés donnés à titre d'exemple et qui représentent, respectivement :
La figure 1 , le principe de réalisation d'un procédé d'ordonnancement selon l'invention en espace utilisateur Linux ; La figure 2, un exemple d'un ensemble de tâches périodiques indépendantes ; et
La figure 3, un diagramme des états et des transitions des tâches applicatives dans un procédé d'ordonnancement selon l'invention.
Le principe de réalisation d'un ordonnanceur en espace utilisateur selon un mode de réalisation de l'invention, basé sur un système d'exploitation Linux, est illustré à la figure 1 . Une application est vue comme un ensemble de tâches à ordonnancer 2. L'ordonnanceur 1 contrôle l'exécution de ces tâches au moyen de trois fonctions spécifiques (référence 3) : « prempt(Task) », « resume(Task) » et « run_on(Task, CPU) ». Ces fonctions - dont les noms sont arbitraires et donnés uniquement à titre d'exemple non limitatif - permettent la préemption d'une tâche, la reprise d'une tâche et l'allocation d'une tâche à un processeur, respectivement. Elles s'appuient sur l'utilisation de fonctionnalités fournies par les API Linux (référence 5) permettant le contrôle des tâches et des processeurs depuis l'espace utilisateur.
Pour son fonctionnement, l'ordonnanceur doit disposer d'informations spécifiques qui découlent du modèle de tâches utilisé. Pour un modèle de tâches périodiques et indépendantes, il s'agit au moins d'informations relatives à l'échéance de chaque tâche, sa période et son état d'activité ; d'autres informations temporelles peuvent également être fournies : pire cas de temps d'exécution (WCET, de l'anglais « Worst Case Execution Time), échéance ultérieure (c'est-à-dire : temps courant plus échéance - pour éviter une confusion avec Γ « échéance ultérieure », « échéance » est parfois appelée « échéance absolue »), etc. Dans le cas d'une plateforme multiprocesseur, l'ordonnanceur nécessite également une information indicative du processeur auquel la tâche est affectée. En outre, chaque tâche est associée à un thread POSIX {«pthread»), qui doit également être connu de l'ordonnanceur.
D'autres informations susceptibles d'être nécessaires à l'ordonnanceur sont : un UTEX associé à la tâche, une condition associée à la tâche, un identifiant Linux de la tâche.
Le meilleur cas du temps d'exécution (BCET, de l'anglais « Best Case Execution Time ») et loe temps d'exécution effectif (AET, de l'anglais « Actual Execution Time ») sont des informations qui, sans être indispensables pour réaliser l'ordonnancement, sont très utiles pour la mise au point car ils permettent (en particulier ΓΑΕΤ) de fixer le temps d'exécution d'une tâche (le temps d'exécution d'une tâche varie normalement d'une exécution à l'autre). Cela facilite la vérification de l'ordonnancement en permettant de le comparer à des résultats de simulations (avec des tâches ayant les mêmes paramètres et surtout exactement ies mêmes temps d'exécution).
Dans ia structure de tâche standard POSIX Linux (pthread) ces informations ne sont pas accessibles en espace utilisateur. Pour cette raison, la mise en œuvre de l'invention nécessite une extension de cette structure de tâche, par la création d'un type personnalisé à l'aide d'une structure de données.
Le modèle applicatif est constitué de tâches périodiques et indépendantes. Un modèle de tâches périodiques se réfère à la spécification d'ensembies homogènes de travaux (« jobs ») qui se répètent à des intervalles périodiques ; un « travail » est une forme particulière de tâche, et plus précisément une tâche indépendante dont son exécution est strictement indépendante des résultats des autres « travaux ». Un modèle de tâches indépendantes se réfère à un ensemble de tâches pour lequel l'exécution d'une tâche n'est pas subordonnée à la situation d'une autre. Ce modèle supporte l'exécution synchrone et asynchrone de tâches, il est applicable dans u grand nombre d'applications réelles qui doivent respecter des contraintes temporelles. La connaissance des caractéristiques et contraintes temporelles des tâches (échéance, période, pire temps d'exécution, etc.) est donc nécessaire pour appliquer ce type d'ordonnancement. Comme expliqué plus haut, ces informations sont rajoutées dans une structure spécifique qui étend le type de tâche standard sous Linux.
La figure 2 illustre un modèle applicatif de tâches périodiques et indépendantes comprenant quatre tâches T1 - T4. Les tâches T1 et T2 doivent s'exécuter avant leur échéance (« deadiine » DDLN) de 21 ms (millisescondes),
T3 avant son échéance de 31 ms et T4 avant son échéance de 40 ms. Chaque tâche s'exécute en un temps effectif AET (« Actuai Execution Time »} f, qui est compris entre une valeur minimum BCET (« Best Case Execution Time ») et une valeur maximum WCET (« Worst Case Execution Time »). Au terme de son exécution, une tâche se met en attente jusqu'à atteindre sa période où elle devient à nouveau prête pour réexécution. Ainsi, une tâche prend à chaque instant un état parmi ies suivants :
- un état de tâche en exécution ;
un état de tâche en attente de la fin de sa période d'exécution ; et
un état de tâche prête à être exécutée, en attente d'une condition de reprise;
auxquels on peut ajouter un état de tâche inexistante, avant son actïvation.
L'exemple de la figure 2 se réfère en particulier à une application vidéo où chacune des quatre tâches correspond à un traitement particulier d'une image. Ces quatre tâches sont donc répétées toutes ies 40 ms (période) afin de pouvoir traiter 25 images par seconde.
Une ou plusieurs files d'attente sont utilisées pour mémoriser les tâches. Au moins une file d'attente est nécessaire pour la réalisation d'un ordonnancement à contraintes d'échéances, pour mémoriser la liste des tâches prêtes. Une priorité est également associée à chaque tâche pour définir leur ordre d'exécution. Le critère de priorité dépend de la politique d'ordonnancement. Pour la politique EDF (« Earliest Deadline First », c'est-à- dire priorité à l'échéance la plus proche) par exemple, le critère de priorité est la proximité de l'échéance : la tâche ayant l'échéance la plus proche est la plus prioritaire. La file des tâches prêtes est généralement triée par ordre de priorité décroissante. Le critère de priorité considéré ici est différent des critères de priorité utilisés dans les ordonnanceurs Linux natifs (temps partagé). L'ordonnanceur est appelé à des instants précis, appelés instants d'ordonnancement, qui sont déclenchés par les tâches elles-mêmes à des moments caractéristiques de leur exécution. Ces moments sont appelés des événements de tâche (référence 2 sur la figure 1 ), ils correspondent par exemple à leur activation (« onActivate »), leur fin d'exécution (« onBlock »), leur réactivation (« onUnBlock ») ou leur terminaison (« onTerminate ») - ces noms étant arbitraires et donnés uniquement à titre d'exemple non limitatif. Les événements sont déclenchés, aux moments adéquats de l'exécution d'une tâche applicative, par des appels à des fonctions « onActivate() », « onBlock() », « onUnBlock() », « onTerminate() », insérés dans son code. Chaque événement de tâche met à jour les champs de ia structure de tâche pour la tâche concernée (état, échéance ultérieure, période, etc.) et appelle l'ordonnanceur si nécessaire. L'appel pu non de l'ordonnanceur par un événement de tâche dépend de la politique d'ordonnancement. Pour une politique EDF par exemple, l'ordonnanceur est appelé sur les événements « onActivate », « onBlock » et « onUnBlock ».
L'événement « onActivate » correspond à la création d'une tâche. Il marque le passage de l'état « inexistante » à « prête ». Pour des raisons de synchronisation à la création des tâches (expliquées en 15.), l'événement onActivate incrémente, à la fin de son exécution, une variable « rendez-vous », puis met la tâche en attente d'une condition d'activation de l'ordonnanceur, par exemple en appelant la fonction « pthread_cond_wait » de ΓΑΡΙ « Pthread Condition Variable » de la norme POSiX.lc.
L'événement « onBlock est » déclenché lorsqu'une tâche termine son exécution effective (AET). Elle passe alors à l'état « en attente » où elie se met en attente jusqu'à atteindre sa période. Lorsqu'elle atteint sa période, la tâche déclenche l'événement « onUnBlock », qui la fait passer à l'état « prêt », puis la met en attente d'une condition de reprise, par exemple en appelant la fonction POSIX pthread_cond_wait. Cette condition sera signalée par l'ordonnanceur au moyen de la foncion « pthread_cond_broadcast » qui appartient elle aussi à ΓΑΡΙ « Pthread Condition Variable » de ia norme POSiX.lc.
A tout moment de son exécution, une tâche peut être préemptée par une autre tâche devenue plus prioritaire (dans le cas d'un algorithme EDF, parce que son échéance est devenue plus proche de celle de la tâche en cours d'exécution). La préemption d'une tâche la fait passer de l'état « en exécution » à « prête ». Réciproquement, la tâche qui reprend à la suite d'une préemption passe de l'état « prête » à l'état « en exécution ».
Les transitions entre états déclenchées par les événements de tâches sont illustrées par la figure 3. Pour éviter les phénomènes d'inter biocage, l'ordonnanceur n'est pas appelé directement par les événements de tâches mais par l'intermédiaire d'un «pthread». En d'autres termes, la fonction qui réalise un événement de tâche (onActivate, onBlock, onUnBlock, onTerminate) appelle une autre fonction « cail_scheduler » (nom donné à titre d'exemple non limitatif), qui crée un «pthread» d'ordonnancement pour exécuter l'ordonnanceur.
A chaque appel, l'ordonnanceur effectue, au moyen d'une fonction principale « select_ » (nom donné à titre d'exemple non limitatif) les actions suivantes : établissement d'une file d'attente des tâches prêtes, tri de la file d'attente par ordre de priorité décroissante (tâche la plus prioritaire en début de liste), préemption des tâches non prioritaires en cours d'exécution, allocation des tâches éligibles aux processeurs libres et démarrage des tâches éligibles par envoi d'une condition de reprise. La détermination des tâches éligible nécessite la connaissance par l'ordonnanceur de l'état de toutes les tâches existantes. La liste d'actions donnée ici correspond à un ordonnancement EDF, mais peut être modifiée - et en particulier enrichie - pour la réalisation d'autres politiques d'ordonnancement.
Du fait de l'exécution multitâche et multiprocesseur, plusieurs instances de l'ordonnanceur pourraient être appelées à s'exécuter en même temps . Pour empêcher une telle éventualité, un Mutex (de l'anglais « MUTual Exclusion device », c'est-à-dire dispositif d'exclusion mutuelle) est utilisé pour protéger certaines variables partagées comme la file d'attente des tâches prêtes. Le Mutex est verrouillé au début de l'exécution de l'ordonnanceur, puis libéré à la fin de l'appel à l'ordonnanceur. Ce procédé garantit qu'une seule instance de l'ordonnanceur ne s'exécute à la fois, ce qui permet la modification sans risque des variables partagées.
La première exécution de l'ordonnanceur intervient juste après la création des tâches. Afin de garantir le contrôle des tâches par l'ordonnanceur, une synchronisation doit être réalisée pour être sûr que toutes les tâches ont bien fini d'être créées avant d'exécuter l'ordonnanceur et que les tâches ne démarrent pas leur exécution tant que l'ordonnanceur ne leur en a pas donné l'ordre. Pour cela, tout d'abord, une variable globale de type rendez- vous est initialisée à 0 avant la création des tâches. Ensuite, toutes les tâches sont créées et placées sur le premier processeur du système. Au tout début de leur création, en d'autres termes à l'exécution l'événement onActivate(), chaque tâche incrémente la variable « rendez-vous » puis suspend immédiatement son exécution en se mettant en attente d'une condition de reprise (utilisation de la fonction POSIX « pthread_cond_wait »). Lorsque la valeur de la variable « rendez-vous » est égale au nombre de tâches, l'ordonnanceur peut être exécuté. Au cours de cette exécution, l'ordonnanceur reprend l'exécution des tâches éligibles en leur signalant leur condition de reprise (fonction « pthread_cond_broadcast »).
Pour contrôler l'exécution des tâches applicatives, l'ordonnanceur nécessite l'utilisation de deux fonctions spécifiques pour la suspension et la reprise d'une tâche, appelées respectivement « preempt(Task) » et « resume(Task) » sur la figure 1 (référence 3), ces noms étant donnés uniquement à titre d'exemple non limitatif. La fonction preempt(Task) se base sur l'utilisation de ΑΡΙ « Signa! » de la norme POSIX. Pour préempter une tâche, le signal S1GUSR1 est envoyé au pthread correspondant (fonction POSIX « pthread_kill »). Le gestionnaire de signal associé (« sigusrl ») met la tâche en attente d'une condition de reprise (« pthreadcond_wait ») dès la réception du signal. îl existe une condition de reprise pour chaque tâche de l'application. La fonction resume(Task) signale la condition de reprise adéquate à la tâche concernée pour reprendre son exécution. Elle est donc strictement équivalente à appeler la fonction Linux pthread_cond_broadcast ; en fait, la création d'une fonction spécifique se justifie essentiellement pour des raisons de lisibilité du code. Ce mécanisme exploite le fait qu'en pratique, le gestionnaire de signal SIGUSR1 est exécuté par le «pthread» Linux qui reçoit le signal. Ainsi, le gestionnaire de signai peut identifier le «pthread» à suspendre (nécessaire pour envoyer la condition d'attente au «pthread» concerné par la fonction pthread_cond_wait) en comparant par exemple son propre identifiant de processus (tid) avec celui de tous les « pthreads » de l'application. Il importe de noter que l'ordonnanceur n'utilise pas les mécanismes de préemption et reprise de tâche fournis par le noyau, car ces derniers ne sont pas accessibles en espace utilisateur. Pour contrôler l'exécution des tâches applicatives dans une plateforme multiprocesseur, l'ordonnanceur nécessite une fonction explicite permettant de fixer l'exécution d'une tâche sur un processeur donné. Bien qu'il existe une API pour ie contrôle des «pthread» Linux par les processeurs d'une plateforme multiprocesseur (« CPU affinity »), il n'existe pas de fonction spécifique pour l'allocation d'une tâche sur un processeur. La réalisation de cette fonction (indiquée par « run_on » sur la figure 1 , référence 3, ce nom étant donné à titre d'exempte non limitatif) affecte le processeur « CPU » à l'exécution de la tâche « Task » en s'appuyant sur ΓΑΡΙ Linux « CPU affinity ». Ceci se base sur la spécification du processeur CPU (uniquement) dans le masque d'affinité de la tâche Task, ce masque d'affinité est ensuite affecté à la tâche (par la fonction pthread_setaffinity_np de l'APi « CPU affinity »). Ensuite, pour garantir que l'ordonnanceur n'exécute jamais plus d'une tâche par processeur, on empêche toutes les autres tâches applicatives d'utiliser le processeur CPU, On vérifie par ailleurs qu'une tâche n'est jamais affectée à plus d'un seul processeur.
Une fonction spécifique « change_freq » (nom donné uniquement à titre d'exemple non limitatif) est utilisée dans le cas d'un ordonnanceur utilisant les techniques DVFS pour contrôler le changement dynamique de tension/fréquence des processeurs (figure 1 , référence 3). Cette fonction utilise une APi Linux appelée « CPUFreq » qui permet de changer la fréquence de chaque processeur par l'intermédiaire du système de fichier virtuel « sysfs ». il suffit par exemple d'écrire la fréquence désirée dans un fichier - le fichier « /sys/devices/system/cpu/cpuO/cpufreq/scaling_setspeed » sous Linux - pour modifier la fréquence du processeur 0. L'ordonnanceur peut utiliser la fonction change_freq pour permettre la modification de la fréquence en récrivant dans le système de fichier. Il nécessite par ailleurs d'utiliser préalablement le gouverneur « Userspace » qui est ie seul mode DVFS sous Linux qui permette à un utilisateur ou à une application de changer la fréquence processeur à volonté. « Userspace » est un des cinq gouverneurs DVFS Unix et son nom indique que la fréquence (et donc la tension) peuvent être modifiées à volonté par l'utilisateur. Une technique possible d'ordonnancement à faible consommation consiste à exploiter le « siack » dynamique (temps fourni par une tâche après son exécution) pour ajuster la fréquence et la tension de fonctionnement du ou des processeurs (DVFS) afin d'économiser l'énergie tout en offrant des garanties de délai. Par exemple, l'ordonnanceur de l'invention a été appliqué à la réalisation d'un ordonnancement de type « DSF » (« Deterministic Stretch~to-Fit », dans lequel la fréquence des processeurs (et donc la tension, qui en dépend) est recalculée à chaque événement d'ordonnancement, en utilisant le temps d'exécution réel (AET - « Actual Execution Time ») pour les tâches accomplies et le pire délai d'exécution (WCET - « Worst Execution Time ») pour les autres, pour allouer le temps de battement de la tâche précédente à la tâche suivante, ce qui permet de diminuer la fréquence de fonctionnement du processeur concerné.
L'annexe contient le code source, écrit en langage C, d'un programme d'ordinateur permettant la mise en oeuvre d'un procédé d'ordonnancement selon l'invention, utilisant la technique DSF-DVFS décrite ci- dessus. Ce code est donné uniquement à titre d'exemple non limitatif.
Le code se compose d'une pluralité de fichiers contenus dans deux répertoires : DSF_Scheduler et pm_drivers.
Le répertoire DSF_Scheduler contient le c ur du code de l'ordonnanceur DSF. Il comprend notamment le fichier prototype N_scheduler,h qui contient ia définition des types nécessaires (structure de tâche, structure processeur, etc.), les variables globales (liste de tâches, liste de processeurs, paramètres de l'ordonnanceur, etc.), et tes prototypes des fonctions exportées.
Le fichier DSF_Scheduler.c décrit ensuite les fonctions principales de l'ordonnanceur. La plus importante est la fonction select_, c'est elle qui décrit tout le processus d'ordonnancement réalisé à chaque instant d'ordonnancement. Les événements de tâches onActivate, onBlock, onUnBlock et onTerminate qui déclenchent les instants d'ordonnancement sont également décrits dans ce fichier. La fonction d'ordonnancement s'appuie sur une fonction slow_down qui calcule et applique le changement de fréquence. Enfin, les particularités de l'ordonnanceur au démarrage de l'application ont nécessité le développement d'une fonction spécifique start_sched. Celie-ci ne s'exécute qu'une seule fois au tout début du lancement de l'application et se base en grande partie sur le code de l'ordonnanceur principal (select_). Pour tous les autres instants d'ordonnancement, c'est la fonction select_ qui est appelée, via la fonction call_scheduter. Le fichier Makefile est utilisé pour la compilation,
L'application à ordonnancer est décrite dans le fichier
N_application.c. C'est aussi le fichier contenant le « main » du programme. Celui-ci réalise les diverses initialisations nécessaires, créé les tâches POSIX, lance l'ordonnanceur et synchronise le tout. Il convient de préciser ici qu'une application est vue comme un ensemble de tâches, chacune comportant des caractéristiques temporelles telles que le pire cas du temps d'exécution (WCET), la période, les échéances (deadlines), etc. Les tâches utilisées effectuent un traitement simple (multiplication des éléments d'un tableau d'entiers) jusqu'à un certain temps d'exécution (AET) - ce qui est décrit par la fonction usertask_actualexec - puis se mettent en mode d'attente jusqu'à leur réactivation (usertask_sieepexec) lorsqu'elles atteignent leur période.
Enfin, pour garder une structure du code la plus claire possible en gardant uniquement les fonctions essentielles de l'ordonnanceur dans le fichier N_Schedu!er.c, les fonctions secondaires nécessaires ont été regroupées elles dans un autre dans un fichier : N_utiis.c. Celui-ci contient par exemple des fonctions d'initialisation, de préemption de tâches, d'allocation de tâches aux processeurs, de tri, d'affichage, etc.
Le répertoire pmjdrivers contient les fonctions plus bas niveau sur lequel s'appuie l'ordonnanceur. II s'agit plus précisément des fichiers prototypes intei_ core 5_m520.h, pm_typedef.h et du fichier pm_cpufreq.c.
Le fichier prototype intel_core_i5_m520.h contient la description de la plateforme cible dont il porte le nom. Il s'agit des informations sur le nombre de processeurs de la platforme et sur les fréquences possibles pour chacun des cœurs, représentés sous forme d'états. Les types utilisés (e.g. les états processeur) sont définis dans le fichier pm_typedef.h,
Le fichier pm_cpufreq.c contient en particulier les fonctions permettant le changement effectif dynamique de fréquence sur la plateforme d'exécution, en se basant sur ΓΑΡΙ Linux CPUfreq, Le changement dynamique de tension fréquence est géré par des politiques appelées governors (gouverneurs) sous Linux, connues en elles-mêmes. Plus précisément, pour pouvoir changer la (les) fréquence(s) processeur(s), il faut d'abord utiliser un gouverneur dit userspace. Une première fonction set_governor est présente pour cela. Ensuite, l'interaction avec le pilote Linux de changement de fréquence se fait via des fichiers d'échanges localisés dans /sys/devices/system/cpu/cpuX/cpufreq/ où X représente le numéro du processeur concerné. Les fonctions open_cpufreq et close_cpufreq permettent d'ouvrir et de fermer le fichier d'échange scaling_set$peed. Le changement de fréquence se fait par la fonction CPU_state_manager_apply_state.
Les différentes fonctions utilisent notamment les API POSIX suivantes :
API Pthreads :
• pthread_creaie, pour la création d'un Pthread,
• pthread Join, pour synchroniser îa terminaison des Pthreads,
• pthread_exit, à la fin de l'exécution d'un Pthread,
• pthread_cancel, pour forcer îa terminaison d'un Pthread.
- API Signal :
• pthread_kill
' API Variables conditions
• pthread_cond_wait
• pthread cond broadcast
" API Mutex
• pthread mutexjock
• pthread_mutex_unlock
Les différentes fonctions utilisent également les APis non POSIX suivantes :
« API CPU affinity » :
• CPU ZERO pour vider la liste des processeurs alloués à une tâche,
• CPU_SET pour ajouter un processeur à la liste des processeurs alloués, • CPU_CLR pour enlever un processeur à la liste des processeurs alloués,
* CPU_ISSET pour tester si une tâche est affectée à un processeur donné et
· pthread_setaffinity_np pour fixer le masque d'affinité à une tâche.
API CPUfreq pour le changement dynamique de tension- fréquence.
L'invention a été décrite en détail en référence à un mode de réalisation particulier : système multiprocesseur sous Linux, politique d'ordonnancement EDF avec changement dynamique de tension-fréquence (DVFS). Il ne s'agit pas, cependant, de limitations essentielles.
Ainsi, le procédé d'ordonnancement de l'invention peut être appliqué à une plateforme monoprocesseur et/ou ne pas mettre en œuvre de mécanismes de réduction de la consommation.
La mise en œuvre de l'invention est particulièrement aisée sous LINUX, car les API décrites précédemment sont disponibles. Cependant, un ordonnanceur selon l'invention peut être réalisé sous un autre système d'exploitation compatible avec la norme POSIX et plus précisément avec la norme IEEE POSIX 1 .c (ou, ce qui est équivalent, POSIX 1003.1c), sous réserve qu'il supporte un équivalent des APis CPU Affinity (pour les applications multiprocesseurs) et CPUfreq (pour les applications exploitant le changement dynamique de tension fréquence).
D'autres politiques d'ordonnancement avec contraintes d'échéance peuvent être mises en œuvre, comme par exemple des politiques « Rate Monotonie » (ordonnancement à taux monotone, RM), « Deadline Monotonie » (ordonnancement en fonction de l'échéance des processus, DM) ou « Latest Laxity First » (ordonnancement en fonction de la laxité plus faible, LLF).
De même, diverses techniques de réduction de la consommation peuvent être mises en œuvre. On peut citer à titre d'exemples non limitatifs la technique DSF (« Deterministic Stretch~to~Fit », c'est-à-dire « extension et ajustement déterministe de la fréquence »), exploitant le changement dynamique de tension fréquence (il s'agit de la technique utilisée dans l'exemple qui vient d'être décrit) et AsDPM (« Assertive Dynamic Power Mangement » c'est à dire « politique assertive de gestion dynamique des modes repos ») exploitant les modes repos processeur.
il est à remarquer que la reproduction d'un ordonnanceur de tâches sous contraintes d'échéances en espace utilisateur telle que décrite ci- dessus n'est réalisable qu'à certaines conditions qui dépendent du système d'exploitation (dénommé en anglais OS pour Operating System). Ces conditions sont par exemple les suivantes. Le procédé selon l'invention doit supporter la notion d'ordonnancement préemptif en espace utilisateur. La possibilité de préemption explicite d'une tâche depuis l'espace utilisateur est une caractéristique qui n'est pas réalisable dans tous les OS. Le modèle de tâche doit intégrer des attributs de contrainte d'échéance et d'état. La possibilité d'extension du modèle de tâche avec ces attributs, accessibles en mode utilisateur dépend de l'OS.
Il est à remarquer également que la réalisation de politiques d'ordonnancement orientées faible consommation en espace utilisateur n'est réalisable qu'à certaines conditions qui dépendent de l'OS. La possibilité de mise en sommeil ou de changement dynamique de fréquence d'un processeur en espace utilisateur n'est pas réalisable dans tous les systèmes d'exploitation, notamment sous Windows.
Il est à noter également que le procédé selon l'invention ne nécessite aucune intervention explicite du mode superviseur et se base exclusivement sur des mécanismes en mode utilisateur sous forme d'APis.
La structure de tâche standard POS!X Linux (pthread), décrite dans le mode de réalisation particulier de la Figure 1 , peut être généralisée à une structure de tâche standard POSIX (thread) sous un OS qui permet de la réaliser. La mise en œuvre de l'invention pour cette structure de tâche généralisée nécessite à l'instar de la structure de tâche standard POSIX Linux (pthread) de la Figure 1 leur extension par des paramètres accessibles en espace utilisateur. De même, à l'instar du modèle applicatif décrit pour la Figure 1 , les informations concernant les caractéristiques et contraintes temporelles des tâches sont rajoutées dans une structure spécifique qui étend le type de tâche standard POSIX par des paramètres rendus accessibles en espace utilisateur.
Il est à remarquer que le procédé de préemption de tâches POSIX en espace utilisateur mis en oeuvre dans l'invention n'existe pas dans les OS dits « grand public », c'est-à-dire dépourvus de contraintes temps réel. Selon l'invention et de manière générale, pour contrôler l'exécution des tâches applicatives, l'ordonnanceur nécessite de réaliser une fonctionnalité de préemption explicite de tâches POSIX en espace utilisateur, qui se base sur l'utilisation de deux fonctions spécifiques pour la suspension et la reprise d'une tâche.
ANNEXE : CODE SOURCE
1. DSF_Scheduler
N scheduler.h
#ifndef N_SCHEDULER
#define N^SCHEDULER
#include <stdbool.h>
#include <unistd.h>
#snclude <sys/types.h>
#include <sys/time.h>
#inciude <pthread.h>
#include <semaphore.h> // PRIORITE MAXIMALE POUR LA POLITIQUE SCHED_FIFO
// UTILISEE POUR DONNER UNE PRIORITE MAXIMALE A L'ORDONNANCEUR
#define MAX_PR!0 99
// NOMBRE DE TACHES DE L'APPLICATION
#define NU _THREADS 4
// NOMBRE DE PROCESSEURS UTILISES PAR L'ORDONNANCEUR
// (PEUT ETRE INFERIEUR OU EGAL AU NOMBRE DE PROCESSEUR TOTAL DISPONIBLE) #define CPU size 2
// DEFINITION DU TYPE ENUMERE STATE_T: ETAT D'UNE TACHE
enum state_t { unexisting, running, waiting, ready };
// unexisting = inexistante; running = en exécution; waiting = en attente; ready = prête // DEFINITION DU TYPE TASK (EXTENSION DU TYPE PTHREAD)
typedef struct task
{
short id; // identifiant de tâche utilisée seulement pour le débogage ftoat wcet; // temps d'exécution au pire cas
fioat bcet; // temps d'exécution au meilleur cas
fioat aet; // temps d'exécution effectif (wcet < aet < bcet) f ioat ret; // RET :temps d'exécuton, restant
fioat deadline; // échéance ("deadline") absolue float next_deadiine; // échéance ("deadiine") relative
unsigned int cpu; // processeur exécutant cette tâche
float period; // période de la tâche = échéance
float nexLperiod; // période relative
double begintime; // temps de début du travail
double endtime; // temps de fin du travail
int préemption; // 1 : préemptée; 0: non préemptée.
double preempidelay; // durée de la dernière préemption
double last_preemp _time; // temps auquel s'est produite la dernière préemption double total_preempt_duration; // durée de préemption totale si le travail est préempté
// plusieurs fois - peut être calcutée à partir de preemptdelay // et last_preempttime
enum statej state; // état de la tâche: waiting, ready, running, unexisting pthread_mutex_t mut_wait; // Mutex utilisé pour suspendre et reprendre une tâche pthread_cond_t cond_resume; / Variable condition utilisée pour suspendre et reprendre
// une tâche
pthread__t *pthread; // thread posix associé à une tâche
pid J thread_pid; // identifiant linux {process ID, ou pid) du thread posix associé
// à la tâche
}task;
// DEFINITION DU TYPE ENUMERE STATE_P: ETAT D'UN PROCESSEUR
enum state_p { RUNNING,STAND_BY,IDLE,SLEEP, DEEP_SLEEP };
// DEFINITION DU TYPE CPU (état courant, numéro du CPU)
typedef struct cpu
{
enum state__p state;
int cpu Jd;
}cpu;
// DECLARATION DES VARIABLES GLOBALES DE l'ORDONNANCEUR
// LISTE DES PROCESSEURS UTILISES POUR LORDONNANCMENT
cpu CPUs[CPU_size];
// LISTE DES TACHES APPLICATIVES A ORDONNANCER
task *T[NUM_THREADS];
// LISTE DES TACHES PRETES
task *list_ready[NUM_THREADS];
int list_ready_size; // LISTE DES TACHES NON PRETES
task *lisi_noready[NU _THREADS];
int list_noready_size;
struct timevai start_time, current_time;
int rdv; //Pour synchroniser les threads à leur création
bool OTE;
// PARAMETRES POUR LE CALCUL DU FACTEUR DE RALENTISSEMENT DANS LA FONCTION //SLOW DOWN
float wcei_Fn_1 [NUM_THREADS]; // WCET avant le calcul d'une nouvelle fréquence float aet_Fn_1 [NUM_THREADS]; // AET avant le calcul d'une nouvelle fréquence float abs_eet[NUM_THREADSJ; // temps d'exécution (absolu) de la tâche depuis
// l'instant d'activation
float total_eet [N U M__TH READS] ; // temps total écoulé d'exécution de la tâche
// (considéré à Fmax) depuis l'activation de la tâche {requis // en cas de préemption)
float total_abs_eet[NU _THREADSÎ; J; // temps total (absolu) écoulé d'exécution
// de la tâche depuis son activation
double task_durationJNU _THREADS]; // durée (absolue) de la tâche depuis l'instant
// d'activation
float last__resume_time[NUM_THREADS]; // Dernier temps auquel la tâche a été reprise (à la
// suite d'une préemption). Ce temps est remis à zéro // à chaque période de la tâche
float siackJocal[CPUsize]; // "slack": temps restant dû au fait que la tâche
// précédente est terminée avant son WCET float t_available_Fn_1 [CPU_size]; // slack avant le calcul d'une nouvelle fréquence float estimated_WCET_Fn_1 [CPU_size];// WCET estimé considérant le slack précédent et
// avant le calcul d'une nouvelle fréquence float estimated_AET_Fn_1 [CPU_sizej; // AET estimé considérant le slack précédent et
// avant le calcul d'une nouvelle fréquence float SRJocal[CPU_size]; // Facteur de ralentissement local (par rapport à la
// fréquence précédente)
float SRJotal[CPU_size]; // Facteur de ralentissement total (par rapport à la
// fréquence maximale)
double Fn_1 ; // fréquence précédente
double Fn; // fréquence actuelle
float offset_delaylNUM_THREADSI; // Temps additionnel à prendre en compte pour
// l'exécution de la tâche, dû à la possibilité d'offset // (étant donnée une tâche qui normalement // commence à T0, l'offset permet de démarrer la // tâche à T0 + offset).
exec|NUM_THREADSJ; // PARAMETRES POUR LA GESTION DU TEMPS
double schedjsme;
double begin__sched_time;
double end_sched_time;
double sched_duration[150];
short nb_sched_call;
double simulationjîme;
// UTEX POUR EMPECHER L'EXECUTION PARALELLE DE 2 INSTANCES L'ORDONNACEUR
pthread_mutex_t sched_mut_waît;
pthread_co nd_t sched_con d_res urne;
// VARIABLES POUR CALCULER LE NOMBRE DE TRANSITIONS D'ETATS PROCESSEURS int STAND_BY_to_RUNNING;
int DEEP_SLEEP_to_RUNNING;
int SLEEP_to_RUNNiNG;
int !DLE_to_RUNNING;
int Preemption_counter;
// PROTOTYPES DES FONCTIONS EXPORTEES
void start_sched{);
void slow_down(task *T, cpu *P); void o n Activa te (tas k *T);
void onUnBiock(task *T);
void onBlock{task *T);
void onTerminateftask *T);
#endif
DSF__Scheduler, c
#define _GNU_SOURCE ffinclude <stdlib.h> #inc1ude <sidio.h>
#include <signal.h>
#include <sîring.h>
#tnclude <math.h>
#include <sched.h>
#include <semaphore.h>
#include <sys/syscall.h> #include "N_utils.h"
#include "../p m_dn vers/i n te l_co re j 5_m 520, h "
//#define DEBUG
// FONCTION PRINCIPALE DE L'ORDONNANCEUR, APPELEE DEPUIS "CALL^SCHEDULER" // LE COEUR DU TRAITEMENT DE L'ORDONNANCEUR SE TROUVE DANS CETTE FONCTION void *select_() {
int i,j,rc;
double r__nxt;
schedjime = get_time();
OTE == false;
// DEFINITION DE LA LISTE DES TACHES PRETES (LIST_READY)
// DEFINITION DE LA LISTE DES TACHES NON-PRETES (LiST_NO_READY) iist_ready_size = 0;
Hst_noready_size - 0;
for(i=0; i<NUM_THREADS; {
if((T[i]->state == ready) H {T[i]->state ~ running)) {
list__ready[list_ready_size] - T[i];
list_ready_size++;
}
else {
listnoready[iist_noready__sizeJ = Tji];
list_noready_size++;
}
> // TRI DES LISTES DE TACHES PAR ORDRE D'ECHEANCE CROISSANTE (LA PLUS PROCHE EN PREMIER)
so rt( I i st_re ad y , I i sf_ready_s ize) ;
sort(list_noready, list_noready_size);
printf("\n*************Sched ροίη¾:%.4Γ**************\η",8θΙΐΘά_ίίΓηβ);
// MESSAGES DE DEBUG QUI AFFICHE LA LISTE DES TACHES PRETES
prin f("Sor ed eady List:\ ");
Display_list(list_ready, list_ready_size);
// MESSAGES DE DEBUG QUI AFFICHE LA LISTE DES TACHES NON PRETES
#ifdef DEBUG
printf("Sorted No_Ready ListAt");
DisplayJist(list_noready, list_noready_size);
#endif
// PREEMPTION DES TACHES NON PRIORITAIRES DE LA LISTE DES TACHES PRETES ET EN //COURS D'EXECUTION
for (i=CPU_size; i<list_ready_size; {
task *job = list_readyEi];
if (job->state == running) {
abs_eet[job->id-1j = schedjime - iast_resume_time[job->id-1]; total_abs_eet|job->id-1] = total_abs_eetrjob->id-1 ] + abs_eet[job->(d-1];
total_eet[job->id-1] = total_eetjjob->id-1 ] + (abs_eet[job->id-1 ] / SRjotalfjob-
>cpu]);
if {!{sched_time < job->begintime + job->offset))
T_preempt(job);
}
}
// MISE A JOUR DU "RET" DE CHAQUE TACHE (REMAINING EXECUTION TIME)
for (i=0; i<NUM_THREADS; i++) {
task *job = T[i];
if Gob->preemption == 1 ) {
job->preemptdelay = sched_time - job->last__preempt_time;
job->totai_j)reempt_duration ~ job->total_preempt_duraiion + job- >preem tdelay;
}
if (job->state == running) job->rei = job->aet - total__eet[job->id-1 ];
else if{job->state == ready) {
if (job->preemption == 1 )
job->ret = G°b->aet - totai_eet[job->id-1]);
else
job->ret = job->aet;
}
else if(job->state == waiting)
job->ret = 0.0;
}
// Le mutex suivant permet d'éviter d'exécuter plusieurs instances de l'ordonnanceur
// simultannément, ce qui conduirait à des accès concurrents et conflictuels sur certaines variables
// partagées
pthread_mutex_lock{&sched jnut_wait);
// ALLOCATION DES TACHES PRETES AUX PROCESSEURS LIBRES
for (i=0; (i<CPU_size) && (i<list_ready_size); i++) {
task *]ob = list_ready[i];
if (job->state != running) {
cpu *P = NULL;
for (j = 0; j<CPU_size; j++) {
P = &CPUs[jJ;
if (PjsRunning(P->cpuJd) == 0) {
if(P->state == DEEP_SLEEP) DEEP^SLEEPJo_RUNNING++;
if(P->state == IDLE) !DLE_to_RUNNING++;
if(P->staie == STAND_BY) STAND_BY to RUNNING++;
if(P->state == SLEEP) SLEEPJoJ¾UNNING++;
// CALCUL DU RALENTISSEMENT (FREQUENCE MINIMUM) DU PROCESSEUR ALLOUE
if(nb_exec[P->cpujd] != 0) {
siow_down(job, P);
OTE = faîse;
>
etse
{
SR_local[P->cpu_id] = 1 .0;
SR_total[P->cpu_id3 - 1 .0;
}
pthread_mutex_lock(&job->mut_wait); // sans ce, mutex,
//parfois un travail serait recommence avant d'avoir été assigné à un processeur.
T_runningOn(job, P->cpu_id); pthread_mutex_unlock(&job->mut_wait);
#ifdef DËBUG
printf{'T%d is assigned to CPU%d\n",job~>id,P->cpu_id);
#endif
if(job->preemption == 1 ) {
job->preemption = 0;
iast__resume_time[job->id-1 ] - sched_time;
}
eise if{job->preemption == 0) {
job->begintime = sched_time;
job->last_preempt_time = job->begintime;
last_resume_time[job->id-1 ] = job->begintime;
}
// DEMARRAGE DE LA TACHE PRETE SUR LE PROCESSEUR ALLOUE pthread_mutexJock(¾ob->mut_wait);
pthread_cond_broadcast(&job->cond_resume);
pthread_mutex_unlock(&job->mut_wait);
break;
}
}
}
}
pthread_m utex_u n lock(&sched_m u t_wa it); int nb = 0;
// MESSAGES DE DEBUG QU! AFFICHENT L'ETAT POUR CHAQUE PROCESSEUR (ACTIVITE // ET/OU TACHE ALLOUEE)
#ifdef DEBUG
printf("Processors StaieAn");
#endif
for 0=0; i<CPU_size; i++) {
cpu *P = &CPUs[i];
#ifdef DEBUG
printf("CPU%d -> " ,P->cpujd);
if (P->state == RUNNING) {
printf{"RUNN!NG ");
for (j=0; j<NU _THREADS; j++)
if ( (T[j]->state == running) && (T ]->cpu == P->cpujd) ) printf("T%d ", T[j]->id); printf("\n");
} eise if (P->state STANDBY) phntffSTAND BY\n");
eise if (P->state IDLE) printf("IDLE\n");
eise if (P->state SLEEP) prinif("SLEEP\n");
else if (P->sta†e DEEPJ3LEEP) printf("DEEP SLEEPVn");
else prinîf("UNDEFÎNED\n");
#endif
if (PjsRunning(P->cpu_id) == 1 ) {
nb++;
}
else {
slackJocai[P->cpu_id] = 0;
#ifdef DEBUG
prinif("-> slack_local[CPU%d] = 0\n", P->cpu_id);
#endif
}
}
#ifdef DEBUG
printf("Total Préemptions = %d\n" reemption_counter);
#endif
>
// FONCTION D'APPEL DE L'ORDONNANCEUR
// CREE UN THREAD AVEC LA FONCTION SELECT_ PRECEDENTE
void call_scheduler{) {
int i, rc;
pid__t pid;
pthread__t schedjhread;
begin__sched_time = get_tïme();
/* Création du pthread de l'ordonnanceur */
if(pthread_create{&schedjhread, NULL, seiect , "4") < 0) {
printf("pihread_create: error creating sched thread\n");
exit{1 );
}
end__sched_time = get_time();
// LE TEMPS D'EXECUTION DE L'ORDONNANCEUR EST MESURE POUR ANALYSE DES TEMPS
// DE REPONSE
sched_duration[nb_sched_call++J = end_sched_time - begin__sched_time;
ffifdef DEBUG printffSCHEDULER DURATION #%d %f\n", nb_sched_call, sched___duration[nb_sched__ca!i - 1 ]);
#endif
}
// LES FONCTIONS SUIVANTES CORRESPONDENT AUX EVENEMENTS DE TACHES:
// - ONACTIVATE
// - ONBLOCK
// - ONUNBLOCK
// - ONTERMINATE
// ONACTIVATE
// CET EVENEMENT INTERVIENT A LA CREATION D'UNE TACHE
void onActivate(task *task) {
double randomvalue;
schedjime = 0.;
task->next__period = schedjime + task->period;
task->next_deadline = task->deadline;
// LE TEMPS D'EXECUTION (AET) EST TiRE AU HASARD ENTRE BCET ET WCET
randomvalue = {doub!e)rand()/RAND_MAX;
task->aet = ( randomvalue * (task->wcei-task->bcet)+task->bcet );
// INITIALISATION DE TOUTES LES VARIABLES NECESSAIRE A L'ORDONNANCEUR
// (DECLAREES/DETAILLEES DANS N_SCHEDULER,H)
aet_Fn_1 [task->id-1 ] ~ task->aet;
abs_eet[task->id-1 ] = 0.;
task->ret ~ task->aet;
task->preemptde!ay = 0.;
task->state = ready;
printf("T%d\t %.4ftt\t %.4f\I %.4f\t %.4f\n",task->id,task->wcei,iask->deadline,task-
>period,task->offset);
// SYNCHRONISATION PAR RDV: UNE FOIS CREEE, LA TACHE INCREMENTE RDV PUIS // ATTEND LE SiGNAL D'ACTIVATION task->cond_resume
// CE SIGNAL EST ENVOYE LORSQUE TOUTES LES TACHES SONT CREEES (LE. RDV = // NB_THREADS)
rdv++;
pthread_∞nd_wait(&task->cond_resume, &task->mut_wait); task->begintime = sched_time;
task->last_preempt_time = task->begintime;
last_resume_time[task->id-1] = task->begintime; }
// ONBLOCK
// CET EVENEMENT INTERVIENT A LA FIN DE L'EXECUTION D'UNE TACHE
void onBlock(task *task) { sched_time = get_time();
task->endtime = sched_time; task->state = waiting;
slack_local[task->cpu] - task->wcet - task->aeî;
#ifdef DEBUG
printf("T%d is terminated at iime = %.4f and generated slackJoca![CPU%d] = %.4f\n", task->id,task->endiime,task->cpu, slack_locai[iask->cpu]);
#endif
CPUs[iask->cpu].state = IDLE;
task->cpu ~ -1 ;
#ifdef DEBUG
printf("T%d FINISHES EXECUTION AT %.2ftn^iask->id,schedjime);
#endif
// SCHEDULSNG POINT: APPEL DE L'ORDONNANCEUR
ca!l_scheduier{); }
// ONUNBLOC
// CET EVENEMENT INTERVIENT A LA FIN DE LA PERIODE D'UNE TACHE (REACTIVATION) void onUnBlock(task *task) {
double randomvalue; sched_time = get_time();
// LE TEMPS D'EXECUTION (AET) EST TIRE AU HASARD ENTRE BCET ET WCET randomvalue = (double)rand()/RAND_MAX;
task->aet = ( randomvalue * {task->wcet-task->bcet)+task->bcet );
// REINITIALISATION DE TOUTES LES VARIABLES NECESSAIRE A L'ORDONNANCEUR // (DECLAREES/DETAILLEES DANS N_SCHEDULER.H)
aet_FnJ [task->id-1 ] = task->aet; wcet_Fn_1 [task->id-1 ] = task->wcet; abs__eet[task->id-1 ] = 0,;
total__eet[task->id-1] = 0.;
total_abs_eet[task->id-1 ] = 0.;
task->preemptdelay = 0.;
task->state = ready;
task->next_deadline = task->next__persod + task->deadline;
task->next_period = task->nexi_period + task->period; task->preemptdelay - 0.;
task->total_preempt_duration = 0.;
#ifdef DEBUG
printf("T%d REACHES PERIOD AT %.2f \n", task->id, sched_time);
#endif
// SCHEDULING POINT: APPEL DE L'ORDONNANCEUR
call_scheduler();
// LA TACHE EST ARRETEE, ELLE SERA REDEMARREE PAR L'ORDONNANCEUR AU MOMENT // OPPORTUN
pthreadjtill{*{task->pthread), SIGUSR1);
// ONTERMINATE
// CET EVENEMENT INTERVIENT A LA TERMINAISON D'UNE TACHE (FIN DE LA DERNiERE // EXECUTION)
void onTerminate(task *task) {
int i;
sched_time = get_time();
tas k->state = u n ex ist in g ;
#ifdef DEBUG
printf("T%d IS TERMINATED AT %.4f and generated a slack_local[CPU%d]= %.4f n", task->id,sched_time,task->cpu, slackJocal[task->cpu]);
#endif
// A CE STADE, LA SIMULATION EST TERMINEE. ON FORCE L'ARRET DE TOUTES LES // TACHES
for (i=0; i<NU _THREADS; i++)
pthread_cancel(*(T[i]->pthread));
} // LE PREMIER APPEL A L'ORDONNANCEUR EST PARTICULIER DU FAIT DE LA CREATION ET DE // L'INITIALISATION DES TACHES
// CE TRAITEMENT SPECIFIQUE EST CONFIE A LA FONCTION START_SCHED, CORRESPONDANT // AU PREMIER APPEL DE L'ORDONNANCEUR
void start_sched() {
int i, j, err, rc;
pid_t pid; printff\n*************Sched point:%.4f***************\n",sched_iime); gettimeofday(&start_time,NULL); // TOUTES LES TACHES SONT AFFECTEES AU CPUO
cpu_set_t mask;
for (j=0; j<NUM_THREADS; j++) {
CPU_ZERO(&mask);
CPU_SET(0, &mask);
/* PREVENT TASK TO USE OTHER CPUs 7
// EMPECHER LA TACHE COURANTE DE S'EXECUTER SUR LES PROCESSEURS
AUTRES
// QUE CPUO
for (i-1 ; i<CPU_size;
CPU _CLR(i, &mask);
err = pthread_setaffinity_np(*(T[j]->pthread), sizeof{cpu__set_t), &mask);
}
// DEFINITION DE LA LSSTE DES TACHES PRETES {LSST_READY)
iist_ready_size - 0;
for {i=0; i<NUM_THREADS; {
lisi_ready[i] = T[i];
lisi_ready_size++;
}
// TRI DE LA READY_ LIST PAR ORDRE DE D'ECHEANCE LA PLUS PROCHE sort(lisi_ready, list_ready_size);
#ifdef DEBUG
printf("Sorted Ready ListAt");
Displayjist(lisi__ready, lisi_ready_size); #endsf
// ALLOCATION DES TACHES PRETES AUX PROCESSEURS LIBRES
for (i=0; {i<CPU_size) && (i<listjready_size); i++) {
task *job = list_ready{i];
T_runningOn(job, i);
job->begintime = sched_time;
]ob->last_preempt__time = job->begintime;
lastj*esumeJirne[job->id-1] = job->begintime;
// DEMARRAGE DES TACHES PRETES SUR LES PROCESSEURS ALLOUES
for (i=0; (i<list_ready_size) && (i<CPU_size) ; i++) {
pthread_mutex_!ock(&list_ready[i]->mut_wait);
pthread_cond_broadcast(&!ist_ready[i]->cond_resume);
pihread_mutex_unlock{&list_ready[i]->mut_waii);
}
}
// CETTE FONCTION EST APPELEE PAR L'ORDONNANCEUR POUR CALCULER LE
// CHANGEMENT DE FREQUENCE A PARTIR DU FACTEUR DE RALENTISSEMENT
// POUR UNE TACHE ALLOUEE SUR UN PROCESSEUR, ON CALCULE LA FREQUENCE A LAQUELLE
// LA TACHE PEUT ETRE EXECUTEE, ET ON CHANGE LA FREQUENCE PROCESSEUR A CETTE VALEUR
void slow_down{task *job, cpu *P) {
int i;
int procjd = P->cpu_id;
char *setspeed__cpu_filename;
FILE *setspeed_cpu;
// RECUPERER LA FREQUENCE COURANTE DU CPU CONCERNE
Fn = {double)CPU_state_manager_query_current_freq{procjd);
#ifdef DEBUG
printff'Frequency Adjusfment on CPU%d before executing T%d:\n",proc_id,job->id);
#endif
// CALCUL DU FACTEUR DE RALENTISSEMENT (SRjotal et SRJocal)
estimated_WCET_FnJ [procjd] = wcet_Fn_1 [job->id-1 ] * SR_totalIproc_id];
estimated_AET_Fn_1 [procjd] = aet_Fn_1 [job->id-1] * S _total[proc_id]; #ifdef DEBUG
printf(''slack_local[CPU%d]\t***\t\t\t\t\t\i\t*i*\t\t\t= %.4f\n", proc jd, siack_local[proc_id]); prinif("SRJocal[CPU%d]\t\t***\t\t\t\t\t\t\t***\t\t\t= % .4f n", proc_id, SR_local[proc_id]);
printf("SRJotal[CPU%d]\t\i***\t\t\t\i\t\t\i***\t\t\t= %.4f\n", proc jd, SRJotal[proc_id]);
printf("esiimated_WCET_Fn_1 [CPU%d]= WCET[T%d]*SR_total[CPU%d]\t\t\t\t=
%.4f"%.4f\t\t= %.4f\n",
proc_id, job->id, proc_id, wcet_Fn_1 rjob->id-1], SRJotaifprocjd], estimated__WCET_Fn_1 [procjd]);
printf("estimated_AET_Fn_1 [CPU%dj= AET[T%d]*SRJoial[CPU%d]\t\t\t\t= %.4f*%.4f\t\t= %.4f\n",
procjd, job->id, procjd, aet_Fn_1 [job->id-1], SR_totai[proc_id], estimated_AET_Fn_1 [proc_id]);
#endif if{OTE ~ false) {
t_available_Fn_1 [proc_id] = wcet_Fn_1 [job->id-1] + slack_local[proc_id];
#ifdef DEBUG
printf("t_available_Fn_1 [CPU%d]\t= WCET[T%d]+stackJocal[CPU%d]\t\t\t\t= %.4f+%.4f \t= %.4f\n",
proc_id, job->id, procjd, wcetJFn_1 [job->id-1], slackjocalfprocjd], t_avaHable_FnJ [procjd]);
#endif
if (job->preemption ~~ false) {
SR_loca![proc_id] = (float) {t_availabie_Fn_1 [procjd] / estimated_WCET_Fn_1 [procjd]);
#ifdef DEBUG
printf("NEW SR_local[CPU%d]\t= t_available_Fn_1[CPU%dJ/estimated_WCET_Fn_1[CPU%d]\i= %.4f/%.4ftttt= %.4f \n",
procjd, proc_id, procjd, t_available_Fn_1 [proc_id], estimated_WCET_Fn J [procjd], SRJocal[proc_idj);
#endif
>
else if(job->preemption == true) {
/* mise à jour de slack, SR, ret. CPU mis à la fréquence maximale */ siackJocal[proc_id] = 0.0;
SRJocal[proc_id] = 1.0;
SR_totai[proc_id] = 1.0;
Fn = (double)_CPU_STATE[proc_id][0].freq;
#ifdef DEBUG printfi" PREEMPTION BY T%d OCCURED AT TIME=%.4f, slackjoca!=0.0, SR_local=1.0, SR_total=1.0, Fn=maxfreq\n", job->id, sched_iirne);
#endif
}
}
if <SRJocal[procjd] >= 1.00) {
#ifdef DEBUG
printffNEW WCET(T%d]\t\t= WCET_Fn_1 [T%d]*SRJocal[CPU%d]\t\t\t\t= %.4f*%.4ftt\t= ",
job->id, job->id, procjd, wceî_Fn_1 [job->id-13, SRJocal[proc_id]); #endif
wceLFnJ [job->!d-1] = (float)(wcel_Fn^1 [job->id-1 ] * SR_local(proc_id]);
#ifdef DEBUG
printf("%.4f\n", wcet_Fn_1 jjob->id-1 ]);
#endif
#ifdef DEBUG
printf("NEW AET[T%d]\t\t= AET_Fn^1 [T%d]*SRJocal[CPU%d]\t\i\t\t= %.4f*%.4Mt= job->id, job->td, procjd, aet_Fn_1Dob->id-1], SRJocal[procjd]);
#endif
aet_Fn_1 [job->id-1] = (doubie)(aet_Fn_1 [job->id-1] * SRJocal[proc_id]);
#ifdef DEBUG
printf("%.4f n", aet_Fn_1 [job->id-1 ]);
#endif
}
else if (S RJocal [procjd] < 1 .00) {
#ifdef DEBUG
printffNEW WCET[T%d]\t\t= esi_WCET_Fn_1 [CPU%dfSRJocal[CPU%d]\t\t\t= %.4f*%.4f\t\t= ",
job->id, procjd, procjd, estimated_WCET_Fn_1 [procjd], SR_iocai[procjd]); #endif
wcet_Fn_1[job->id-1] - (estimated_WCET_Fn_1 [procjd] * SRJocal[procjd]);
#ifdef DEBUG
printf{"%.4f\n", wcet_Fn_1 [job->id-1]);
#endif
#ifdef DEBUG
printf("NEW AET[T%d]\t\i= est_AET_Fn_1 [CPU%d]*SR_local[CPU%d]\t\t\t= %.4f*%.4f\t\t= ",
job->id, procjd, procjd, estimated_AET_Fn_1 [procjd], SRJocal[proc_id]); #endif
aet_Fn_1 [job->id-1J = (estimated__AET_Fn_1 [procjd] * SRJocalJprocJdj); #ifdef DEBUG
printf("%.4f\n", aet_Fn_1 |j"ob->id-1 ]):
#endif
}
/* CALCUL DE LA NOUVELLE FREQUENCE "THEORIQUE"
double Fn_1 = (double)(Fn / SRJocal[proc_idJ);
// RECHERCHE DE LA NOUVELLE FREQUENCE EFFECTIVE {LES FREQUENCE PROCESSEUR // SONT PREDEFINIES, DONC DIFFERENTES DE LA FREQUENCE THEORIQUE)
Elementary_state State;
State = __CPU_STATE[procid]fO];
for (i-0; i<NB_STATES_ CPU-1 ; i++)
if ( ((int)Fn_K=_CPU„STATE[proc_id][i].freq) && ((int)Fnm1 <=_CPU_STATE[proc_id][i+1].freq) )
State = _CPU_STATE[proc_id][i+1 ï;
#ifdef DEBUG
printf("FREQUENCY TO SWiTCH (%.0f): %d\t", Fn_1 , (int)State.freq);
#endif
// APPEL DE LA FONCTION DE CHANGEMENT DE FREQUENCE
CPU_state_manager apply_state{ proc_id, &State );
// LE FACTEUR DE RALENTISSEMENT DOIT ETRE RECALCULE EN TENANT COMPTE DU // CHANGEMENT EFFECTIF DE LA FREQUENCE PROCESSEUR
S RJocal [procjd] = Fn / State.freq;
SRJotallproc jd] = SR_total[proc_id] * SR_local[procjd];
#ifdef DEBUG
printf("NEW SRJocal[CPU%d]\t= Fn / State.freq = %.0f / %d = %.4f n", proc jd, Fn, (int)State.freq, SRJocal[proc_idJ);
printf("NEW SR_total[CPU%d]\t= SR_total[CPU%d] * SRJocal[CPU%d] = %.4f\n", procjd, procjd, procjd, SR_totai[proc_id]);
#endif
// REMISE DU SLACK A ZERO CAR ON VIENT DE CONSOMMER LE SLACK PRECEDENT POUR // DIMINUER LA FREQUENCE
slackjocalfprocj'd] = 0.0;
} N_utifs.c
#define _GNU_SOURCE
#inciude <stdio.h>
#include <unistd.h>
#inciude <sys/time.h>
#include <signal.h>
#include <sched.h>
#include <time.h>
#include <errno.h>
#include <pthread.h>
#include "N_utils.h"
#define TARGET_CPU_DEFINED
#i ncl ude " .. /p m_d ri vers/i ntel_coreJ5_m 520. h "
#define DEBUG
// FONCTION D'INITIALISATION DES PROCESSEURS DE LA PLATEFORME
void init_CPUs()
{
printf("\nlntializing CPUs... \n");
int i;
// FAIRE LA LISTE DES PROCESSEURS DISPONIBLES ET METTRE A JOUR LES ETATS CPUs for (i=0;i<CPU_size;ï++) {
CPUs[i].cpu_id = i;
CPUs[i].state = IDLE;
}
printffSystem has %d processor(s)\n",NB_CPU_MAX);
printf{"System uses %d processor(s)\n",CPU_size);
#ifdef DEBUG
for (i=0; i<CPU_size; {
printf("CPU%d is ", CPUs[i].cpu_id);
if (CPUs[i].state == RUNNING) printf("RUNNING\n");
else if(CPUs[i].staie == STAND_BY) printf("STAND BY\n");
else if (CPUsfsJ.state == IDLE) printf("IDLE\n");
efse if (CPUs[i].state == SLEEP) printf("SLEEP\n");
else if (CPUs[i].state == DEEP^SLEEP) printf("DEEP_SLEEP\n");
else printf{"UNDEF!NED\n"); #endif
// OUVERTURE DES FICHIERS POUR LE CHANGEMENT DE FREQUENCE (API CPUfreq) printffSetting cpufreq\n"); for (i=0; i<CPU__size; {
setjgovernor(CPUs[i].cpu_id,"userspace");
open_cpufreq(i);
}
// ON DEMARRE A LA PFREQUENCE MAX (SPECIFQUE A L'ALGORITHME DSF) printff'Setting CPUs to maximum frequency\n");
for (i=0; i<CPU_size; i++)
CPU_state_manager_apply__state{ i, &_CPU_STATE[i][0] ); printf("End CPUs initializationAn");
}
// FONCTION DE LIBERATION DES PROCESSEURS DE LA PLATEFORME
voîd exit_CPUs()
{
int i;
// FERMETURE DES FICHIERS POUR LE CHANGEMENT DE FREQUENCE (API CPUfreq) for {i=0; i<CPU_size; i++)
close cpufreq(i);
}
// FONCTION D'INITIALISATION DES TACHES APPLICATIVES
void init_TASKs(task task[NUM_THREADS], pthreadj thread[NUM_THREADS])
{
int i, rc;
struct sched__param my_sched_params; printi("\ninitiaHzing tasks.,.\n");
printff'Application has %d task(s)\n", NUM_THREADS);
// SAUVEGARDE DU WCET POUR LA FREQUENCE COURANTE
for (i=0; i<NUMJ"HREADS; i++) wcet_Fn_1 [i] = task[i].wcet;
// INITIALISATION DES MUTEX, UTILISES COMME MECANISME DE PREEMPTION pi h read_m utexj nit( &s ched_m ut_wait, ULL);
for (F0; i<NUM„THREADS; {
pthread__mutex_inii(&task[i],mut_wait, NULL);
pt read_cond_init(&task[r].cond_resume, NULL);
}
// INITIALISATION DES PARAMETRES DE TACHES
for (i=0; i<NUM_THREADS; i++)
{
T[i] = &task|i];
T[i]->pthread = &thread[i];
nb_exec[i] = 0;
tota[_eet[i] = 0.0;
total_abs_eet[i] = 0.0;
}
// INITIALISATION DES PARAMETRES DE L'ORDONNANCEUR
for (i=0; i<CPU_size; i++)
{
s[ack_local[i] ~ 0.0;
SRJocalfi] = 1.0;
SR_total[i] = 1.0;
Lavailable_Fn_1 [i] = 0.0;
estimaied_WCET_Fn_1 [i] = 0.0;
estimated_AET_Fn_1 [i] = 0.0;
}
STAND_BY_to_RUNNING=0;
SLEEPjo_RUNNiNG=0;
DEEP_SLEEPJo_RUNNING=0;
IDLE_to_RUNN!NG=0;
Preemption_counter=0; nb_sched_call = 0;
rdv = 0; printf("End tasks irtitializationAn"); // FONCTION PERMETTANT DE PREEMPTER UNE TACHE
void T_preempt(task *task)
{
int i, rc;
struct sched__param mymsched_params; printf("T%d[CPU%d] preempted at %.4f with ABS_EET=%.4f \n", iask->id, task->cpu, sched time, abs__eet[task->id-1 ]);
Preemption_counter++;
task->lastj?reempt_time = sched_time;
task->preemption = 1 ;
task->preemptdelay = 0.;
task->state = ready;
CPUs[task->cpu].state = IDLE;
task->cpu = -1 ;
// LE MECANISME DE PREMPTiON CONSISTE A ENVOYER UN SIGNAL A DESTINATION DE // LA TACHE
pthread_kil!(*(task->pthread)J SIGUSR1 );
// LORSQUE CELLE-CI LE REÇOIT, LE SIGNAL HANDLER sigusri EXECUTE
// PTHREAD_COND_WAIT QUI A POUR EFFET DE SUSPENDRE LA TACHE
// VOIR sigusri dans APPLiCATiON.C
}
// ALLOUE UNE TACHE A UN PROCESSUR
void T_runningOn{task *task, int cpujd)
{ int i, j, err;
cpu_set_t mask;
CPU _ZERO(&mask);
Γ SET TASK TO CPUJD */
// Alloue la tâche 'task' au processeur numéro 'cpujd'
CPU_SET(cpujd, &mask);
/* PREVENT TASK TO USE OTHER CPUs 7
// Faire en sorte que la tâche 'task' ne puisse s'exécuter sur aucun autre processeur for (i=0; i<CPU_size; i++)
if (i!=cpujd) CPU jOLRii, &mask);
err = pthread_setaffinity__np(*(iask->pthread), sizeof(cpu_set__t), &mask);
tf (err != 0) { printf (1 ) PTH RE AD_S ET AFFI ITY RETURNED; %d \n", err); exit(0); > task->cpu = cpujd;
task->state = running;
CPUs[cpujd].state = RUNNING;
nbj3xec|cpujd]++;
/* PREVE T ALL OTHER TASK TO USE CPUJD*/
// Faire en sorte que toute tâche autre que 'task' ne puissent pas s'exécuter
// sur le processeur numéro cpu_id
for {i=0; i<NUM_THREADS; i++)
if (T[i]->id != task->id) {
err = pthread_getaffinity_np{*{T[!]->pthread), sizeof{cpu_set_i), &mask);
if (err != 0) { printf ("**** (2) PTHREAD_SETAFFINITY RETURNED: %d \n", err); exit(0); }
if ( (T[i]->id != task->id) )
CPlLCLR(cpu_td, &mask);
/* CHECK THAÏ EACH THREAD CAN NOT USE
MORE THAN ONE CPU (default: CPUO) */
// Vérifier que chaque tache ne peut pas utiliser plus d'un seul CPU {par défaut, CPUO) short cpu_aiiocated = 0;
for (j=0; j<CPUsize; j++)
if ( CPUJSSET(j, &mask) } cpu_allocated++;
if (cpu_allocaied == 0) CPU_SET(0, &mask);
else if {cpu_allocated > 1 ) printffWARNiNG: T%d IS ALLOCATED MORE THAN ONE CPU\n", T[i]->id);
err = pthread_setaffinity_np(*(T[i]->pthread), sizeof(cpu_setJ), &mask);
if (err != 0) { printf ("**** (3) PTH RE AD_S ET AF F ! ITY RETURNED: %d \n", err); exit(0); }
}
}
// FONCTION PERMETTANT DE TESTER SI UN PROCESSEUR EST UTILISE {1 ) OU NON (0) int P_isRunning(int cpujd)
{
int i; int cpu_isRunning = 0;
if (CPUs[cpu_id].state == RUNNiNG) cpujsRunning = 1 ;
return cpujsRunning;
} // TRI D'UNE LISTE DE TACHES PAR ORDRE DE D'ECHEANCE CROISSANTE (LA PLUS PROCHE EN // PREMIER)
void sort{iask *list[J, int list_size)
{
task *temp;
int i, jt min;
for (i = 0; i < list__size- 1 ;
{
min = i;
for (j=i+1 ; j < list_size; j++)
{
if (Hst[j]->next_deadline < !ist[min]->next_deadline)
{
m.in=j;
}
>
if (i != min)
{
temp=list[i];
iist[i]=iist[min];
iist[minj=temp;
}
}
// FONCTION QUI RETOURNE LE TEMPS ECOULE DEPUIS LE DEBUT DE LA SIMULATION double get_time{)
{
double îime;
struct ismeval currenijime; ïf {gettimeofday(&currentJirne,NULL) == -1 )
{
putsfERROR : getJime_ ofday");
return -1 ; }
time = current_time.tv_.sec - start_time.iv_sec \
+0.000001 *(current_time.tv_usec - start_tirr¾e.tv_usec); return time;
}
// TACHES APPLICATIVES:
// LES TACHES APPLICATIVES SE DIVISENT EN DEUX PHASES:
// - UNE PHASE D'EXECUTION EFFECTIVE (CORRESPONDANT A usertask_actualexec)
// - UNE PHASE D'ATTENTE (CORRESPONDANT A usertask_sleepexec). LA TACHE DOIT ATTENDRE // LE TEMPS DE SA PERIODE AVANT DE POUVOIR ETRE A NOUVEAU PRETE POUR REEXECUTION
int usertask_actualexec(task *task)
{
double duration;
double data[262144J;
int i = 0;
nbTicks++; task->begintime - sched_time;
#ifdef DEBUG
printf("T%d Starts exécution on CPU%d with AET = %.4f (*%.4f=%.4f) at %.4An",
task->id, task->cpu, task->ret, SR_total[task->cpu], task->ret*SRJotal[task->cpu], get_time()/*task->begintime*/);
#endif
// LES TACHES EFFECTUENT UN SIMPLE ACCES TABLEAU / MULTIPLICATION ENTIERE // JUSQU'A ATTEINDRE ΙΆΕΤ
// (EN TE ANT COMPTE DES PREEMPTiONS ET CHANGEMENT DE FREQUENCE POSSIBLES) do
{
duration - get_îime() - task->begintime;
daia[i%262144j = duration*i++;
} while (duration < iotal_abseet[task->id-1] + task->total_preempt_duraiion + (task->ret * SR_totai[task->cpu3) + offset_delay[task->id-1 ]);
task_duration[task->id-1J = duration; return 0;
} int usertask_s!eepexec(task *îask) double sleep - task->next_period - schedjime;
if(sleep < 0)
sleep = 0.0;
usleep({unsigned iong)(1000000*sleep));
task->preemptdelay = 0; return 0;
}
// FONCTIONS D'AFFICHAGE UTILISEE POUR LE DEBUG
void DispiayQ
{
int i; sched_time=getjime();
printf("Scheduling point: %.4f, sched_time);
printf("\nTask \t cpu \t ihread_pid \t nxt ddlne \t prio \t starts at \t stops at \t idle until \t state\n");
for (i=0; i<NUM_THREADS; i++)
printf("T%d \t %d \t %d \t %.4f \t \t %d \t %,4f \t \t %.4f \t \t %.4f \t\t %d\n",
T[i]->id, T[i]->cpu, T[i]->thread_pid, T[t]->next_dead!ine,
T[i]->prio, T[i3->begintime, T[i]->endtime, sched_time, T[i]->state);
printf("
**\n");
} void Dispiayjist{task *listfl. int list_size)
{
int i;
printf("\t Task \t Next_deadline\n");
for (i = 0; i < list_size; i++)
printf("\i \t \t \t T%d \t (%.4f) \n", list[i]->id, list[i]->next_deadline);
printf("\n");
} N applica tion. c
#define _GNU_SOURCE
#include <sched.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#inciude <sys/time.h>
#include <signal.h>
#include <string.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/syscaSi.h> #include "N utils. h"
// L'EXEMPLE APLICATIF UTILISE ICI EST TIRE D'UNE APPLICATION VIDEO H264 ENCODEUR r
4 TASKS / 2 CPUS
- T1 -> Motion Estimation #1 Estimation de mouvement sur la demi-image numéro 1
- T2 -> Motion Estimation #2 Estimation de mouvement sur la demi-image numéro 2
- T3 -> Inv. Prédiction + Texture Encoding + Syntax Writing Prédiction inverse + Encodage Texture + Ecriture syntaxique
- T4 -> Loop Filter Filtre de déblocage
TH!S VERSION REQUIRES 2 CPUs Cet exemple nécessite 2 CPUS
*/ statîc task Task[NUM_THREADS]= {
/* Task[0] = */{
1 , // id
20.63e-1 , // wcet
5.65e-1 , // bcet
10.40e-1 , // aet
0, //ret
21 .0e-1 , // deadline
0, // nex_deadline
-1 , // cpu
40.0e-1 , // period
0, // next_period
-1 , // begintime -1, //endtime
0, // préemption
0, // preemptdeiay
0.0, // last_preemp_time
0.0, // totaLpreempt_duration unexisting, // staie
PTHREAD_ UTEXJNITIALIZER,
PT H READ_CONDJ NIT!ALIZER,
NULL, // adresse du pthread associé
0 // t read_pid
}.
/*Task[1] = 7{
2, // id
20.63e-1, //wcet
5.65e-1 , // bcet
10.50e-1, //aet
0, /ret
21.0Θ-1, //deadline
0, // next dead!ine
-1 , // cpu
40.0e-1, //period
0, // next_period
-1, // beginttme
-1, //endtime
0, // préemption
0, // preemptdelay
0.0, // tast_preempt__time
0.0, // total_preempt_du ration unexisting, // state
PTHREAD__MUTEX_!NITIALÎZER,
PTHREAD_CONDJN!TIALIZER,
NULL, // adresse du pthread associé
0 // threadjaid
}.
Γ Task[2] = {
3, // id 8.25e-1, //wcet
3.38e-1, //bcet
5.96e-1, //aet
0, //ret
31.0e-1, //deadline
0, // next deadline
-1 , // cpu
40.0e-1, //period
0, // next_period
-1 , // begintime
-1, // endtime
0, // préemption
0, // preemptdelay
0.0, // !ast_preempt_time
0.0, // total_preempt_duration unexisting, // state
PTHREAD_MUTEX_iN!T)ALIZER,
PTHREAD_CONDJNITIALIZER,
NULL, // adresse du pthread associé
0 // ihread_pid
},
/* Task[3] = */{
4, // id
5.78e-1, //wcet
1.81Θ-1, /bcet
3.27Θ-1, //aet
0, //ret
40.0e-1, //deadiine
0, // next deadline
-1 , // cpu
40.0e-1, //period
0, // rsext_period
-1, // beg intime
-1 , // endtime
0, // préemption
0, // preemptdelay 0.0, // lastpreempt_time
0.0, // total_preempt_duration
unexisting, // state
PTHREADJvlUTEXJNlTIALIZER,
PTHREAD_COND_INITiAUZER,
NULL, // adresse du pthread associé
0 // thread_pid
} };
// MECANISME UTILISE POUR PREEMPTER UNE TACHE
void sigusrl (int dummy)
{
int i;
pidj pid;
// Récupérer le pid du thread courant
pid = (long)syscal!(SYS_gettid);
// Comparer le pid du thread courant avec celui de tous les threads de l'application
// afin d'identifier le thread à suspendre. On exploite ici le fait que le signal handier (sigusrl ) // s'exécute avec le pid du thread qui l'a reçu,
for {F0; i<NUM_THREADS; i++) {
if (Task[iJ.thread_pid == pid) {
Task[i].state = ready;
pthread_cond_wait{&Task[i].cond_resume, &Task{iî.mut_waiî);
// POUR REDEMMARRER LA TACHE: pthread_cond_broadcast(&Task[i].cond__resume)
TaskfiJ.staie = running;
}
}
// DECLARATION DES TACHES APPLICATIVES, AVEC LEURS EVENEMENTS DE TACHE
// - OIMACTIVATE AU DEBUT DE L4EXECUTION
// - ONOFFSET: PAS UTILISE
// - ONBLOCK: A LA FIN DE L'EXECUTION EFFECTIVE D'UNE TACHE
// - ONUNBLOCK: LORSQU'UNE TACHE ATTEINT SA PERIOD
// - ONTERMINATE: LORSQU'UNE TACHE EST COMPLETEMENT TERMINEE
// CHAQUE TACHE CORRESPOND A UNE FONCTION, QUI SERA ENCAPSULEE DANS UN THREAD // POSIX PAR LA SUITE:
void *task0{void * arg)
{
int i; identifiant de tâche requis par l'ordonnanceur */ TaskjO].thread_pid = (long)syscall(SYS_gettid); onActivate(&Task[0]);
do
{ if (usertask_actualexec(&Task[0]) == -1 ) { puts("ERROR : usertask_actualexec"); pthread_exit(0);
}
onBlock(&Task[0]);
if (usertask_sleepexec(&Task[0]) == -1 ) { puts("ERROR : usertask_sleepexec"); pthread_exit{0);
}
onUnBlock(&Task{0]);
} whiie (sched_time<simulation_time);
onTerminate(&Task{0]);
pthread_exit{0);
}
void *iask1 (void * arg)
{
int i;
Γ Identifiant de tâche requis par l'ordonnanceur */ Task[1 ].threadj)id = (tong)syscall(SYS_gettid); onActivate(&Task[1 ]);
do
{ if (usertask_actualexec{&Task[1]) == -1 ) { putsfERROR : usertask_actualexec"); pthread_exit(0); }
onBlock(&Task[1]);
if {usertask_sleepexec{&Task[1]) == -1) { puts{"ERROR : usertask_sleepexec"); pthread_exit(0);
}
onUnBlock(&Task[13);
} while (sched_time<simulation_time);
onTerminate(&Task[1 ]);
pthread_exit(0);
} void *task2(void * arg)
{
int i;
/* Identifiant de tâche requis par l'ordonnanceur */ Task[2J.threadpid = (tong)syscall(SYS_gettid); onActivate(&Task[2J);
do
{ if (usertask_actua!exec(&Task[2]) == -1) { puts{"ERROR : usertask_aciualexec"); pthread_exit(0);
}
onBlock(&Task[2]);
if (usertask_sleepexec{&Task[2]) == -1 ) { putsfERROR : usertask_sieepexec"); pthread_exit(0);
}
onUnBlock(&Task[2]);
} while (sched__time<simu!aÎion_time);
onTerminate(&Task[2]);
pthreadmexii(0);
} void *task3(void * arg)
{
int i; /* identifiant de tâche requis par l'ordonnanceur */
Task[3].thread_pid = (long)syscall{SYS_gettid);
onActivate(&Task[3]);
do
{ if (usertask_actuaiexec(&Task[3]) == -1 ) {
putsfERROR : usertask_actualexec");
pthread_exit(0);
}
onBlock{&Task[3]);
if (usertask_sleepexec(&Task[3]) «- -1 ) puts("ERROR : usertask_sleepexec");
pt read_exit(0);
}
onUnBlock(&Task[3]);
} whiie (sched_time<simulation_time);
onTerminate(&Task[3]);
pthread_exit(0);
rnt main {)
{
int i, n;
void *ret;
pthreadj Thread[NUM_THRt=ADS]; simuiaiionjime = 80e~1 ;
// INITIALISATIONS RELATIVES AUX PROCESSEURS init_CPUs();
// INITIALISATIONS RELATIVES AUX TACHES APPLICATIVES init_TASKs(Task, Thread);
// Initiaiisaiion du gestionnaire de signal sigusrl
if (signaUSIGUSRI , sigusrl ) == SIG_ERR) {
perror("signaln);
exit(1 );
} printf("\nTask \t WCET \t\t Deadline \t Period \t Offset\n");
// CREATION DES THREADS POSIX
if(pthread_create(&Thread[OJ, NULL, taskO, "0") < 0)
{
printf{"pthread_create: error creating thread 1\n");
exit(1);
}
if(pthreadcreate(&Thread[1 ], NULL, taskl , Ί ") < 0)
{
printf{"pthread_create: error creating thread 2\n");
exit(1 );
}
if(pthread_create(&Thread[2], NULL, task2, "2") < 0)
{
printf{"pthread_create: error creating thread 3\n");
exit(1 );
}
if(pthread_create(&Thread[3], NULL, task3, "3") < 0)
{
printf("pthread_create: error creating thread 4\n");
exit(1 );
}
Γ SYNCHRONISATION: ATTENDRE LA FIN EFFECTIVE DE CREATION
DE TOUS LES THREADS AVANT DE LANCER START_SCHED
while (rdv != NUMTHREADS) { } start_sched();
(void)pthreadJoin(Thread[0],&rei);
(void)pthreadJoin(Thread[1 ],&ret);
(void)pthread_join(Thread[2],&ret);
(void)pthreadJoin(Thread[3],&rei);
// FERMETURE DES FICHIERS DE CHANGEMENT DE FREQUENCE
exst_CPUs();
// MESURE DU TEMPS DE TRAITEMENT DE L'ORDONNANCEUR (MOYEN PAR APPEL, TOTAL) double average__sched_duration; double total_sched_duration = 0;
prinîf("NUMBER OF SCHEDULER CALLS: %d\n", nb_sched_call);
for (i=0; i<nbmsched_cal!; {totai_sched_duration += sched_du ration [ij; printf("%f ", sched_duration[i]); }
printf("\nTOTAL SCHEDULER CALL DURATION: %f n", total_sched_duration);
average_sched__duration = total_sched_duration / nb_sched_call;
prinîfC'AVERAGE SCHEDULER CALL DURATION: %f n", average_sched_duration); return 0;
2. pm_drivers
in tel__core_i5_m 520. h
* intel_core_i5_m520.h
7
#ifndef INTEL_CORE_I5_M520
#define !NTEL_CORE_I5_M520
#include "pm_typedef.h"
#define NB_CPU_MAX 4
Γ fréquences disponibles
/* IMPORTANT: liste des frequencies disponibles en ordre décroissant 2400000 2399000 2266000 2133000 1999000 1866000 1733000 1599000 1466000 1333000 1199000 */
/*definition des états possible de CPU07
#define NB_STATES_CPU 1 #ifdef TARGET_ CPU_DEFINED
Elementary_state CPU_STATE[NB_CPU_MAX][NB__STATES_CPU];
#else Bementary_state _CPU_STATE[NB_CPU_MAX]INB_STATES_CPU]
freq/elementary_state_id/core_id
{ 2400000, 0, 0 }, // CPU0[0]
{ 2399000, 1,0}, // CPU0[1]
{ 2266000, 2, 0 }, // CPU0[2]
{2133000, 3,0}, // CPU0[3]
{1999000, 4,0}, // CPU0[4]
{1866000, 5, 0}, // CPU0[5]
{ 1733000, 6, 0 }, // CPU0[6]
{ 1599000, 7, 0 }, // CPU0[7]
{1466000, 8, 0}, // CPU0[8]
{ 1333000, 9, 0 }, // CPU0[9]
{1199000, 10,0}}, // CPU0[10]
{ // fréquence / numéro de l'état élémentaire (fréquence) concerné
// /numéro du CPU concerné— Ces champs sont définis dans
// pm_typedf.h
{ 2400000, 0, 1 }, // CPU1[0]
{2399000, 1, 1 }, //CPU1[1]
{ 2266000, 2, 1 }, //CPU1[2]
{2133000, 3, 1 }, //CPU1[3]
{ 1999000, 4, 1 }, //CPU1[4J
{ 1866000, 5, 1 }, //CPU1[5]
{ 1733000, 6, 1 }, //CPU1[6]
{1599000, 7, 1 }, // CPU1(7]
{1466000, 8, 1 }, //CPU1[8]
{ 1333000, 9, 1 }, //CPU1{9]
{ 1199000, 10, 1 }}, //CPU1[10]
// fréquence / numéro de l'état élémentaire (fréquence) concerné
// /numéro du CPU concerné
{ 2400000, 0, 2 }, // CPU2(0]
{2399000, 1,2}, // CPU2I1]
{ 2266000, 2, 2 }, // CPU2[2]
{2133000, 3,2}, // CPU2Î3]
{1999000, 4,2}, // CPU2[4]
{1866000, 5,2}, // CPU2[5]
{1733000, 6,2}, // CPU2[6]
{1599000, 7,2}, // CPU2[7] { 1466000,8,2}, // CPU2[8]
{ 1333000, 9, 2 }, // CPU2[9]
{ 1199000, 10, 2 }}, // CPU2[10]
// fréquence / numéro de l'état élémentaire (fréquence) concerné
/numéro du CPU concerné
{ 2400000, 0, 3 }, // CPU3[0]
{2399000, 1, 3}, //CPU3[1]
{ 2266000, 2, 3 }, // CPU3[2]
{2133000, 3,3}, // CPU3[3]
{1999000, 4,3}, // CPU3[4]
{ 1866000, 5, 3}, // CPU3[5]
{1733000, 6,3}, // CPU3[6]
{ 599000, 7, 3}, il CPU3[7]
{ 1466000, 8, 3 }, // CPU3[8]
{ 1333000, 9, 3}, // CPU3[9]
{1199000, 10,3}} //CPU3[10]
#endif
#endif
pm typedef.h
#ifndef PM TYPEDEF
#define PM_TYPEDEF
/*— Structures de données— */ typedef unsigned long long Core_freq; typedef struct Elemeniary_state {
Core_freq freq;
unsigned long elementary statejd;
unsigned long corejd;
} Elementary__state; typedef struct Global_state {
Elementary_state* state; unsigned int nb values;
f!oat curr;
float voit;
} Globai_state; typedef struct
Global_state_and_transition {
float delay;
float energy;
Global_state target;
} Globai_state_and_transition;
typedef struct Global_states_and_Jransitiorts {
Global_state_and_transition* values;
unsigned long nb values;
} Global_states_andJransitions; typedef struct Corejds {
unsigned long* values;
unsigned long nb_yalues;
} Corejds;
/*— Opérations d'interface— */
/*
Co re jds* CP U_state_m a nager_q u ery_core_i ds() ;
Global_siates_and_transitions* CPU_statejïianager jueryjiext__.possible_states(); void CPU_state_manager_apply_staie{ Globai_state* s );
#endif
pm_cpufreq.c
!*
* pm_cpufreq.c
* fonctions pour changer la fréquence des processeurs utilisant cpufreq
7
#include <stdio.h> #inc!ude <string.h>
#inciude <fcntt.h>
#include "pm_typedef.h"
#define DEBUG
#define CPUFREQPATH "/sys/devices/system/cpu/cpu"
#define GOVPATH "/cpufreq/scaling^governor"
#define FREQPATH "/cpufreq/scaling_setspeed"
FILE *setspeed_cpu[4]; void itoa (int n, char *u) {
int i=0J;
char s[17]; do {
s[i++]=(char)( n%10 + 48 );
n-=n%10;
} while ((n/=10)>0); for {p0;j<i;j++)
u[i-1 -j]=s[j]; u[j]='\0';
} void set_governor(int cpu.char * ¾ι °v) {
F ( LE *cpu_governor;
char cpu_gov filename
// initialisation du nom de fichier complet (incluant le chemin) pour le gouverneur du coeur numéro
'cpu' char cpu_gov_sÎr[17J;
tioa{cpu, cpu_gov_str);
strcpy(cpu_gov_filename, CPUFREQPATH);
sircat(cpu_gov_ftlename, cpu_gov__str);
strcat(cpu_gov_f i lena me, GO VPAT H); char strbufOI [20];
char strbuf02[20]; // AFFICHE LE GOUVERNEUR CPU COURANT
cpu_governor = fopen{cpu_gov_filename, "r");
fscanf(cpu_governor, "%s", strbufOI);
#ifdef DEBUG
printf("For CPU%d\t",cpu);
prinif{"CPU%d current governor:%s\t", cpu, strbufOI };
#endif
fclose(cpu__governor);
// MODIFIE LE GOUVERNEUR CPU COURANT
cpu_governor = fopen(cpu_gov_filename, "w");
fprintf(cpu_governor, "%s", gov);
fclose(cpu_governor);
// lAFFICHE LE NOUVEAU GOUVERNEUR CPU COURANT {POUR VERIFICATION) cpu_governor = fopen{cpu_gov_filename, "r");
fscanf(cpu_govemor, "%s", strbuf02);
#ifdef DEBUG
printf("->\iCPU%d new governor:%s\n", cpu, strbuf02);
#endif
fclose(cpu_governor);
void open_cpufreq(int cpu) {
char cpu_freq_filename[60];
#ifdef DEBUG
printf('Opening\t\t\t\t\t/sys/devices/system/cpu/cpu%d/cpufreq/scaling_setspeed\n'', cpu); #endif
// initialisation du nom de fichier complet {incluant le chemin)
// pour le fichier de spécification de fréquence (scaling_setspeed) du coeur numéro 'cpu' char cpu_freq_str[17J;
itoa(cpu, cpu_jfreq_str);
strcpy(cpujreqjilename, CPUFREQPATH);
strcat(cpu_freq_filename, cpu_freq_str);
strcat(cpu_freq_filename, FREQPATH);
setspeed_cpu[cpu] = fopen(cpu_freq_filenameI "r+w"); > void CPU_state__rnanager_apply_state{ int cpu, E[ementary_state *s) { // FILE *setspeed_cpu;
int freqO, freql ;
// AFFICHE LA FREQUENCE COURANTE DE CPU
fscanf(setspeed_cpu[cpu], "%d", &freqO);
fseek(setspeed_cpu{cpu], 0, SEEK_SET);
#ifdef DEBUG
printf{"CPU%d CURRENT FREQUENCY:%d\t",cpu,freqO);
#endif fprintf(setspeed_cpu[cpu], "%llu", s->freq);
fseek(setspeed__cpu[cpu], 0, SEEKJ3ET);
freql =-1 ;
fscanf(setspeed_cpu[cpuî, "%d", &freq1 );
fseek(setspeed_cpu[cpu], 0, SEEKJ3ET);
#ifdef DEBUG
printf("->\tCPU%d NEW FREQUENCY:%d\n",cpu, freql);
#endif
} int CPU_statejîianager_query_current_freq{int cpu) {
int freqO; fscanf(setspeed_cpu[cpu], "%d", &freqO);
fseek{setspeed_cpu[cpu], 0, SEEK_SET);
return freqO;
} void close_cpufreq(int cpu) {
#ifdef DEBUG
printf('Olossng\t\i\t\t/sys/devices/system/cpu/cpu%d/cpufreq/scaling_setspeed\n", cpu); #endif
f close(s etspeed__cpu [cpu ] ) ;
}

Claims

REVENDICATIONS
1 . Procédé d'ordonnancement de tâches avec contraintes d'échéances, basé sur un modèle de tâches périodiques indépendantes et réalisé en espace utilisateur, dans lequel :
• chaque tâche à ordonnancer est associée à une structure de données, définie en espace utilisateur et contenant au moins une information temporelle et une information indicative d'un état d'activité de la tâche, ledit état d'activité étant choisi dans une liste comprenant au moins :
- un état de tâche en exécution ;
un état de tâche en attente de la fin de sa période d'exécution ; et
un état de tâche prête à être exécutée, en attente d'une condition de reprise;
· au cours de son exécution, chaque tâche modifie ladite information indicative de son état d'activité et le cas échéant, en fonction d'une politique d'ordonnancement prédéfinie, appelle un ordonnanceur qui est exécuté en espace utilisateur ;
• à chaque appel, ledit ordonnanceur :
- établit une file d'attente des tâches prêtes à être exécutées, en attente d'une condition de reprise;
trie ladite file d'attente en fonction d'un critère de priorité prédéfini ;
si nécessaire, préempte une tâche en exécution en lui envoyant un signal la forçant à passer dans ledit état de tâche prête à être exécutée, en attente d'une condition de reprise ; et
envoie ladite condition de reprise au moins à la tâche se trouvant en tête de ladite file d'attente.
2. Procédé d'ordonnancement selon la revendication 1 dans lequel ladite politique d'ordonnancement est une politique préemptive, telle que EDF, RM, DM ou LLF.
3. Procédé d'ordonnancement selon l'une des revendications précédentes, mis en oeuvre dans une plateforme multiprocesseur, dans lequel ladite structure de données comprend également une information relative à un processeur auquel la tâche correspondante est affectée, et dans lequel ledit ordonnanceur affecte à un processeur du système chaque tâche prête à être exécutée.
4. Procédé d'ordonnancement selon l'une des revendications précédentes, dans lequel ledit ordonnanceur modifie la fréquence d'horloge et ia tension d'alimentation du ou d'au moins un processeur en fonction d'une politique DVFS.
5. Procédé d'ordonnancement selon l'une des revendications précédentes comportant une étape d'initialisation, au cours de iaquelîe :
- les tâches à ordonnancer sont créées, affectées à un même processeur et placées dans un état d'attente d'une condition de reprise, une variable globale dite de rendez-vous étant incrémentée ou décrémentée lors de la création de chaque dite tâche ;
- lorsque ladite variable de rendez-vous prend une valeur prédéfinie indiquant que toutes les tâches ont été créées, ledit ordonnanceur est exécuté pour la première fois.
6. Procédé d'ordonnancement selon î'une des revendications précédentes, dans lequel ladite structure de données contient également des informations indicatives d'un thread associé à ladite tâche et à son temps d'exécution dans le pire cas.
7. Procédé d'ordonnancement selon l'une des revendications précédentes, exécuté sous un système d'exploitation compatible avec une norme POSiX.
8. Procédé d'ordonnancement selon ia revendication 7, dans lequel ledit système d'exploitation est un système Linux.
9. Procédé d'ordonnancement selon l'une des revendications 7 ou 8 dans lequel, à chaque appel de l'ordonnanceur, un «pthread» est créé pour assurer son exécution.
10. Procédé d'ordonnancement selon l'une des revendications 7 à 9, comportant l'utilisation d'un MUTEX pour assurer l'exécution d'une seule instance à la fois de l'ordonnanceur.
1 1 . Procédé d'ordonnancement selon l'une des revendications 9 ou 10 lorsqu'elle dépend des revendications 3 et 8, dans lequel l'affectation d'une tâche à un processeur est effectuée au moyen de ΑΡΙ CPU Affinity.
12. Produit programme d'ordinateur pour la mise en oeuvre d'un procédé d'ordonnancement selon l'une des revendications précédentes.
PCT/IB2013/059916 2012-11-06 2013-11-05 Procede d'ordonnancement avec contraintes d'echeance, en particulier sous linux, realise en espace utilisateur. WO2014072904A1 (fr)

Priority Applications (2)

Application Number Priority Date Filing Date Title
EP13792761.2A EP2917834A1 (fr) 2012-11-06 2013-11-05 Procede d'ordonnancement avec contraintes d'echeance, en particulier sous linux, realise en espace utilisateur.
US14/441,070 US9582325B2 (en) 2012-11-06 2013-11-05 Method for scheduling with deadline constraints, in particular in Linux, carried out in user space

Applications Claiming Priority (2)

Application Number Priority Date Filing Date Title
FR1260529 2012-11-06
FR1260529A FR2997773B1 (fr) 2012-11-06 2012-11-06 Procede d'ordonnancement avec contraintes d'echeance, en particulier sous linux, realise en espace utilisateur.

Publications (1)

Publication Number Publication Date
WO2014072904A1 true WO2014072904A1 (fr) 2014-05-15

Family

ID=48128392

Family Applications (1)

Application Number Title Priority Date Filing Date
PCT/IB2013/059916 WO2014072904A1 (fr) 2012-11-06 2013-11-05 Procede d'ordonnancement avec contraintes d'echeance, en particulier sous linux, realise en espace utilisateur.

Country Status (4)

Country Link
US (1) US9582325B2 (fr)
EP (1) EP2917834A1 (fr)
FR (1) FR2997773B1 (fr)
WO (1) WO2014072904A1 (fr)

Cited By (1)

* Cited by examiner, † Cited by third party
Publication number Priority date Publication date Assignee Title
EP3716552A1 (fr) * 2014-10-30 2020-09-30 AT&T Intellectual Property I, L.P. Équipement de locaux d'abonné universel

Families Citing this family (11)

* Cited by examiner, † Cited by third party
Publication number Priority date Publication date Assignee Title
CN103870327A (zh) * 2012-12-18 2014-06-18 华为技术有限公司 一种实时多任务调度方法和装置
EP3210112B1 (fr) 2014-10-22 2022-06-15 Telefonaktiebolaget LM Ericsson (publ) Planification coordonnée entre des processus en temps réel
KR102079499B1 (ko) * 2015-10-20 2020-02-21 엘에스산전 주식회사 Plc 위치 결정 시스템의 축별 제어주기 독립 할당 방법
GB2545435B (en) * 2015-12-15 2019-10-30 Advanced Risc Mach Ltd Data processing systems
DE102016200777A1 (de) * 2016-01-21 2017-07-27 Robert Bosch Gmbh Verfahren und Vorrichtung zum Überwachen und Kontrollieren quasi-paralleler Ausführungsstränge in einem ereignisorientierten Betriebssystem
US10210020B2 (en) * 2016-06-29 2019-02-19 International Business Machines Corporation Scheduling requests in an execution environment
US10289448B2 (en) 2016-09-06 2019-05-14 At&T Intellectual Property I, L.P. Background traffic management
US20200167191A1 (en) * 2018-11-26 2020-05-28 Advanced Micro Devices, Inc. Laxity-aware, dynamic priority variation at a processor
US11347544B1 (en) * 2019-09-26 2022-05-31 Facebook Technologies, Llc. Scheduling work items based on declarative constraints
CN114818570B (zh) * 2022-03-11 2024-02-09 西北工业大学 一种基于蒙特卡罗仿真的嵌入式系统时序分析方法
CN114706602B (zh) * 2022-04-01 2023-03-24 珠海读书郎软件科技有限公司 一种基于Android的通过app更新触摸屏参数的方法

Citations (3)

* Cited by examiner, † Cited by third party
Publication number Priority date Publication date Assignee Title
US20070074217A1 (en) * 2005-09-26 2007-03-29 Ryan Rakvic Scheduling optimizations for user-level threads
WO2009158220A2 (fr) * 2008-06-27 2009-12-30 Microsoft Corporation Programmation d'opérations en mode protégé
US20120180056A1 (en) * 2010-12-16 2012-07-12 Benjamin Thomas Sander Heterogeneous Enqueuinig and Dequeuing Mechanism for Task Scheduling

Family Cites Families (7)

* Cited by examiner, † Cited by third party
Publication number Priority date Publication date Assignee Title
US20020073129A1 (en) * 2000-12-04 2002-06-13 Yu-Chung Wang Integrated multi-component scheduler for operating systems
JP3938343B2 (ja) * 2002-08-09 2007-06-27 インターナショナル・ビジネス・マシーンズ・コーポレーション タスク管理システム、プログラム、及び制御方法
US8387052B2 (en) * 2005-03-14 2013-02-26 Qnx Software Systems Limited Adaptive partitioning for operating system
US8495662B2 (en) * 2008-08-11 2013-07-23 Hewlett-Packard Development Company, L.P. System and method for improving run-time performance of applications with multithreaded and single threaded routines
US8510744B2 (en) * 2009-02-24 2013-08-13 Siemens Product Lifecycle Management Software Inc. Using resource defining attributes to enhance thread scheduling in processors
EP2591419B1 (fr) * 2010-07-06 2018-10-31 Saab AB Simulation et essai d'avionique
FR2969776B1 (fr) * 2010-12-23 2013-01-11 Thales Sa Procede de gestion de la consommation energetique d'une application executable dans differents environnements et architecture logicielle mettant en oeuvre un tel procede

Patent Citations (3)

* Cited by examiner, † Cited by third party
Publication number Priority date Publication date Assignee Title
US20070074217A1 (en) * 2005-09-26 2007-03-29 Ryan Rakvic Scheduling optimizations for user-level threads
WO2009158220A2 (fr) * 2008-06-27 2009-12-30 Microsoft Corporation Programmation d'opérations en mode protégé
US20120180056A1 (en) * 2010-12-16 2012-07-12 Benjamin Thomas Sander Heterogeneous Enqueuinig and Dequeuing Mechanism for Task Scheduling

Non-Patent Citations (3)

* Cited by examiner, † Cited by third party
Title
R. CHÉOUR ET AL.: "EDF scheduler technique for wireless sensors networks: case study", FOURTH INTERNATIONAL CONFERENCE ON SENSING TECHNOLOGY, June 2010 (2010-06-01), pages 3 - 5
R. CHÉOUR ET AL.: "Exploitation of the EDF scheduling in the wireless sensors networks", MEASUREMENT SCIENCE TECHNOLOGY JOURNAL (IOP), SPECIAL ISSUE ON SENSING TECHNOLOGY: DEVICES, SIGNAIS AND MATERIALS, 2011
ROBERT LOVE: "LINUX system programming", 2007

Cited By (3)

* Cited by examiner, † Cited by third party
Publication number Priority date Publication date Assignee Title
EP3716552A1 (fr) * 2014-10-30 2020-09-30 AT&T Intellectual Property I, L.P. Équipement de locaux d'abonné universel
US10931574B2 (en) 2014-10-30 2021-02-23 At&T Intellectual Property I, L.P. Universal customer premise equipment
US11502950B2 (en) 2014-10-30 2022-11-15 Ciena Corporation Universal customer premise equipment

Also Published As

Publication number Publication date
US20150293787A1 (en) 2015-10-15
FR2997773B1 (fr) 2016-02-05
US9582325B2 (en) 2017-02-28
FR2997773A1 (fr) 2014-05-09
EP2917834A1 (fr) 2015-09-16

Similar Documents

Publication Publication Date Title
WO2014072904A1 (fr) Procede d&#39;ordonnancement avec contraintes d&#39;echeance, en particulier sous linux, realise en espace utilisateur.
Biondi et al. Achieving predictable multicore execution of automotive applications using the LET paradigm
Axer et al. Response-time analysis of parallel fork-join workloads with real-time constraints
Mraidha et al. Optimum: a marte-based methodology for schedulability analysis at early design stages
EP0536010B1 (fr) Procédé et dispositif pour la gestion temps réel d&#39;un système comprenant au moins un processeur apte à gérer plusieurs fonctions
Patel et al. Analytical enhancements and practical insights for MPCP with self-suspensions
Elliott Real-time scheduling for GPUS with applications in advanced automotive systems
Abeni et al. Hierarchical scheduling of real-time tasks over Linux-based virtual machines
Sigrist et al. Mixed-criticality runtime mechanisms and evaluation on multicores
Suzuki et al. Real-time ros extension on transparent cpu/gpu coordination mechanism
Medina et al. Directed acyclic graph scheduling for mixed-criticality systems
Beckert et al. Zero-time communication for automotive multi-core systems under SPP scheduling
Prenzel et al. Real-time dynamic reconfiguration for IEC 61499
Kumar et al. A systematic survey of multiprocessor real-time scheduling and synchronization protocol
Ruaro et al. Dynamic real-time scheduler for large-scale MPSoCs
Buttazzo et al. Ptask: An educational C library for programming real-time systems on Linux
Elliott et al. Building a real-time multi-GPU platform: Robust real-time interrupt handling despite closedsource drivers
Osborne et al. Work in progress: Combining real time and multithreading
Syed et al. Online admission of non-preemptive aperiodic mixed-critical tasks in hierarchic schedules
Gu et al. Synthesis of real-time implementations from component-based software models
Kreiliger et al. Experiments for predictable execution of GPU kernels
Chen Fundamentals of Real-Time Systems
Inam et al. Mode-change mechanisms support for hierarchical freertos implementation
Wang et al. Unleashing the Power of Preemptive Priority-based Scheduling for Real-Time GPU Tasks
Fiamberti et al. An object-oriented application framework for the development of real-time systems

Legal Events

Date Code Title Description
121 Ep: the epo has been informed by wipo that ep was designated in this application

Ref document number: 13792761

Country of ref document: EP

Kind code of ref document: A1

NENP Non-entry into the national phase

Ref country code: DE

WWE Wipo information: entry into national phase

Ref document number: 14441070

Country of ref document: US

WWE Wipo information: entry into national phase

Ref document number: 2013792761

Country of ref document: EP