Bonjour,
Dans un système constitué de multiples tâches collaborant il est nécessaire de mettre en œuvre une communication garantissant la cohérence des données échangées entre les tâches.
L’un des mécanismes préconisés en Ada pour fournir une solution est fondé sur les types protégés.
On en trouve quelques explications par exemple ici mais le livre de Monsieur John Barnes est certainement plus didactique.
Ce mécanisme est déjà utilisé dans « Ada for Automation » notamment par exemple pour les interfaces entre la tâche principale et les tâches clients Modbus TCP ou entre la tâche principale et la tâche serveur Modbus TCP.
Pour faire court, un type protégé encapsule les données privées et protégées et doit fournir les fonctions et procédures permettant la manipulation des données. Les fonctions permettent uniquement la lecture des données protégées et comme elles ne peuvent modifier celles-ci elles peuvent être appelées de façon concurrente sans problème tandis que les procédures permettent la lecture et l’écriture des données protégées et bloquent le temps de leur exécution les appels des fonctions et procédures concurrentes.
Cependant, il se trouve que le besoin était jusque là considéré comme spécifique et cela a donc conduit à des mises en œuvre spécifiques.
Mais si l’on considère le besoin d’une façon générale on peut aboutir à une solution plus générique qui peut convenir dans une grande majorité des cas d’utilisation.
Ainsi, dans « Ada for Automation », la tâche principale est par défaut associée à une tâche périodique. Ces tâches jusque là tournaient indépendamment l’une de l’autre et j’avais remis aux calendes de les faire communiquer entre elles autrement que par variables partagées, sans gestion de la cohérence donc.
En cette fin d’année les calendes sont donc arrivées !
J’ai nommé le mécanisme « Dual Port Memory » générique par déformation professionnelle, en référence à la fameuse Dual Port Memory Hilscher dont il est souvent question dans les pages de ce site. Et aussi bien sûr parce que la similitude est bien réelle.
Vous voudrez bien vous référer au schéma ci-après pour les explications qui suivent.
Si le mécanisme est générique, les données échangées sont structurées dans des types de données propres à l’application.
Le paquetage implémentant le mécanisme générique est instancié en lui fournissant les types de données Entrées et Sorties et figure dans la partie privée du paquetage « Interface ». Entrée et Sortie sont vues ainsi par l’une des tâches, l’autre voyant bien sûr l’inverse.
Ce paquetage contient également les zones d’échanges pour les programmes utilisateur de chacune des tâches, de façon similaire aux mémoires images process, ces zones étant aussi des instances des mêmes types Entrées et Sorties.
Il contient enfin les fonctions et procédures assurant la mise à jour des zones d’E/S, ces fonctions et procédures étant appelées par les tâches en amont et aval des traitements utilisateur.
Voici la spécification du paquetage générique DPM :
-- Ada for Automation --
-- --
-- Copyright (C) 2012-2015, Stephane LOS --
-- --
-- This library is free software; you can redistribute it and/or --
-- modify it under the terms of the GNU General Public --
-- License as published by the Free Software Foundation; either --
-- version 2 of the License, or (at your option) any later version. --
-- --
-- This library is distributed in the hope that it will be useful, --
-- but WITHOUT ANY WARRANTY; without even the implied warranty of --
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU --
-- General Public License for more details. --
-- --
-- You should have received a copy of the GNU General Public --
-- License along with this library; if not, write to the --
-- Free Software Foundation, Inc., 59 Temple Place - Suite 330, --
-- Boston, MA 02111-1307, USA. --
-- --
-- As a special exception, if other files instantiate generics from --
-- this unit, or you link this unit with other files to produce an --
-- executable, this unit does not by itself cause the resulting --
-- executable to be covered by the GNU General Public License. This --
-- exception does not however invalidate any other reasons why the --
-- executable file might be covered by the GNU Public License. --
-----------------------------------------------------------------------
generic
type Inputs_Type is private;
type Outputs_Type is private;
package A4A.Generic_Dual_Port_Memory is
protected type Inputs_Dual_Port_Memory_Area is
procedure Set_Data (Data_In : in Inputs_Type);
function Get_Data return Inputs_Type;
private
Data : Inputs_Type;
end Inputs_Dual_Port_Memory_Area;
protected type Outputs_Dual_Port_Memory_Area is
procedure Set_Data (Data_In : in Outputs_Type);
function Get_Data return Outputs_Type;
private
Data : Outputs_Type;
end Outputs_Dual_Port_Memory_Area;
type Dual_Port_Memory is
record
Inputs : Inputs_Dual_Port_Memory_Area;
Outputs : Outputs_Dual_Port_Memory_Area;
end record;
type Dual_Port_Memory_Access
is access all Dual_Port_Memory;
end A4A.Generic_Dual_Port_Memory;
Et le corps :
-- Ada for Automation --
-- --
-- Copyright (C) 2012-2015, Stephane LOS --
-- --
-- This library is free software; you can redistribute it and/or --
-- modify it under the terms of the GNU General Public --
-- License as published by the Free Software Foundation; either --
-- version 2 of the License, or (at your option) any later version. --
-- --
-- This library is distributed in the hope that it will be useful, --
-- but WITHOUT ANY WARRANTY; without even the implied warranty of --
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU --
-- General Public License for more details. --
-- --
-- You should have received a copy of the GNU General Public --
-- License along with this library; if not, write to the --
-- Free Software Foundation, Inc., 59 Temple Place - Suite 330, --
-- Boston, MA 02111-1307, USA. --
-- --
-- As a special exception, if other files instantiate generics from --
-- this unit, or you link this unit with other files to produce an --
-- executable, this unit does not by itself cause the resulting --
-- executable to be covered by the GNU General Public License. This --
-- exception does not however invalidate any other reasons why the --
-- executable file might be covered by the GNU Public License. --
-----------------------------------------------------------------------
package body A4A.Generic_Dual_Port_Memory is
protected body Inputs_Dual_Port_Memory_Area is
procedure Set_Data (Data_In : in Inputs_Type) is
begin
Data := Data_In;
end Set_Data;
function Get_Data return Inputs_Type is
begin
return Data;
end Get_Data;
end Inputs_Dual_Port_Memory_Area;
protected body Outputs_Dual_Port_Memory_Area is
procedure Set_Data (Data_In : in Outputs_Type) is
begin
Data := Data_In;
end Set_Data;
function Get_Data return Outputs_Type is
begin
return Data;
end Get_Data;
end Outputs_Dual_Port_Memory_Area;
end A4A.Generic_Dual_Port_Memory;
Pour l’exemple d’instanciation voici ci-après la spécification du paquetage réalisant celle nécessaire pour l’exemple d’interface entre la tâche principale et la tâche périodique 1.
On y trouve :
- la définition des types Entrées et Sorties,
- la définition des zones mémoire correspondantes pour la tâche principale et la tâche périodique 1,
- les procédures gérant les échanges entre les zones mémoire et les données protégées,
- et dans la partie privée, l’instance du paquetage générique.
Pour votre application, il est nécessaire d’adapter les types de données Entrées et Sorties comme vous l’avez sans doute compris.
-- Ada for Automation --
-- --
-- Copyright (C) 2012-2015, Stephane LOS --
-- --
-- This library is free software; you can redistribute it and/or --
-- modify it under the terms of the GNU General Public --
-- License as published by the Free Software Foundation; either --
-- version 2 of the License, or (at your option) any later version. --
-- --
-- This library is distributed in the hope that it will be useful, --
-- but WITHOUT ANY WARRANTY; without even the implied warranty of --
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU --
-- General Public License for more details. --
-- --
-- You should have received a copy of the GNU General Public --
-- License along with this library; if not, write to the --
-- Free Software Foundation, Inc., 59 Temple Place - Suite 330, --
-- Boston, MA 02111-1307, USA. --
-- --
-- As a special exception, if other files instantiate generics from --
-- this unit, or you link this unit with other files to produce an --
-- executable, this unit does not by itself cause the resulting --
-- executable to be covered by the GNU General Public License. This --
-- exception does not however invalidate any other reasons why the --
-- executable file might be covered by the GNU Public License. --
-----------------------------------------------------------------------
with A4A.Generic_Dual_Port_Memory; use A4A;
package A4A.Application.Main_Periodic1 is
--------------------------------------------------------------------
-- Main / Periodic1 Tasks interface
--------------------------------------------------------------------
type Inputs is
record
A : Boolean := False;
B : Word := 0;
C : Byte := 0;
end record;
type Outputs is
record
X : Boolean := False;
Y : Word := 0;
Z : Byte := 0;
end record;
--------------------------------------------------------------------
-- Memory areas available to Main task
--------------------------------------------------------------------
Main_Inputs : Inputs;
-- These are the Inputs from task Periodic1 to Main
Main_Outputs : Outputs;
-- These are the Outputs from task Main to Periodic1
procedure Get_Main_Inputs;
-- This procedure updates Main_Inputs from DPM
-- Main task should call it before calling Main_Cyclic
procedure Set_Main_Outputs;
-- This procedure updates DPM from Main_Outputs
-- Main task should call it after calling Main_Cyclic
--------------------------------------------------------------------
-- Memory areas available to Periodic1 task
--------------------------------------------------------------------
Periodic1_Inputs : Outputs;
-- These are the Inputs from task Main to Periodic1
Periodic1_Outputs : Inputs;
-- These are the Outputs from task Periodic1 to Main
procedure Get_Periodic1_Inputs;
-- This procedure updates Periodic1_Inputs from DPM
-- Periodic1 task should call it before calling Periodic1_Run
procedure Set_Periodic1_Outputs;
-- This procedure updates DPM from Periodic1_Outputs
-- Periodic1 task should call it after calling Periodic1_Run
private
package DPM is new
A4A.Generic_Dual_Port_Memory
(Inputs_Type => Inputs,
Outputs_Type => Outputs);
The_DPM : DPM.Dual_Port_Memory;
end A4A.Application.Main_Periodic1;
En voici le corps :
-- Ada for Automation --
-- --
-- Copyright (C) 2012-2015, Stephane LOS --
-- --
-- This library is free software; you can redistribute it and/or --
-- modify it under the terms of the GNU General Public --
-- License as published by the Free Software Foundation; either --
-- version 2 of the License, or (at your option) any later version. --
-- --
-- This library is distributed in the hope that it will be useful, --
-- but WITHOUT ANY WARRANTY; without even the implied warranty of --
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU --
-- General Public License for more details. --
-- --
-- You should have received a copy of the GNU General Public --
-- License along with this library; if not, write to the --
-- Free Software Foundation, Inc., 59 Temple Place - Suite 330, --
-- Boston, MA 02111-1307, USA. --
-- --
-- As a special exception, if other files instantiate generics from --
-- this unit, or you link this unit with other files to produce an --
-- executable, this unit does not by itself cause the resulting --
-- executable to be covered by the GNU General Public License. This --
-- exception does not however invalidate any other reasons why the --
-- executable file might be covered by the GNU Public License. --
-----------------------------------------------------------------------
package body A4A.Application.Main_Periodic1 is
procedure Get_Main_Inputs is
begin
Main_Inputs := The_DPM.Inputs.Get_Data;
end Get_Main_Inputs;
procedure Set_Main_Outputs is
begin
The_DPM.Outputs.Set_Data (Main_Outputs);
end Set_Main_Outputs;
procedure Get_Periodic1_Inputs is
begin
Periodic1_Inputs := The_DPM.Outputs.Get_Data;
end Get_Periodic1_Inputs;
procedure Set_Periodic1_Outputs is
begin
The_DPM.Inputs.Set_Data (Periodic1_Outputs);
end Set_Periodic1_Outputs;
end A4A.Application.Main_Periodic1;
Dans la tâche principale on rajoute les deux incantations autour de l’appel au programme utilisateur :
...
A4A.Application.Main_Periodic1.Get_Main_Inputs;
A4A.Application.Main_Cyclic;
A4A.Application.Main_Periodic1.Set_Main_Outputs;
...
Et on procède de même avec la tâche périodique 1 :
...
procedure Periodic1_Run is
begin
A4A.Application.Main_Periodic1.Get_Periodic1_Inputs;
A4A.Application.Periodic1_Cyclic;
A4A.Application.Main_Periodic1.Set_Periodic1_Outputs;
end Periodic1_Run;
...
Enfin, on peut jouer avec notre interface depuis la fonction principale… :
...
procedure Main_Cyclic is
My_Ident : constant String := "A4A.Application.Main_Cyclic";
begin
Map_Inputs;
Map_HMI_Inputs;
-- Playing with tasks interface
Main_Outputs.X := Main_Inputs.A;
Main_Outputs.Y := Main_Inputs.B;
Main_Outputs.Z := Main_Inputs.C;
Process_IO;
Map_Outputs;
Map_HMI_Outputs;
exception
when Error : others =>
A4A.Log.Logger.Put (Who => My_Ident,
What => Exception_Information (Error));
Program_Fault_Flag := True;
end Main_Cyclic;
...
… comme depuis la fonction périodique :
procedure Periodic1_Cyclic is
My_Ident : constant String := "A4A.Application.Periodic1_Cyclic";
begin
-- Playing with tasks interface
Periodic1_Outputs.A := not Periodic1_Inputs.X;
Periodic1_Outputs.B := Periodic1_Inputs.Y + 2;
Periodic1_Outputs.C := Periodic1_Inputs.Z + 1;
exception
when Error : others =>
A4A.Log.Logger.Put (Who => My_Ident,
What => Exception_Information (Error));
Program_Fault_Flag := True;
end Periodic1_Cyclic;
...
Il y a également un programme de test qui permet de jouer avec cette DPM que vous trouverez dans le répertoire :
Comme ça fonctionne plutôt bien, je l’ai mis à l’épreuve un peu partout et j’ai donc pas mal bougrassé le code A4A en réorganisant les différents noyaux (kernels) afin d’en faciliter la réutilisation et l’évolution et en en rajoutant d’autres, ce dont je vous entretiendrai avec plaisir l’année prochaine.
Je vous remercie pour votre attention et vous souhaite de bonnes fêtes de fin d’année.
Cordialement,
Stéphane