Programmation fiable dans le contexte des langages. Partie 2 - Challengers

La première partie avec les exigences fonctionnelles est ici .

Revendiqués comme langages de programmation avec un souci de fiabilité.

Par ordre alphabétique - Active Oberon, Ada, BetterC, IEC 61131-3 ST, Safe-C.

À la fois, la clause de non-responsabilité (excuse) n'est en aucun cas une campagne «tout à gauche», et la revue est plutôt académique - la langue peut non seulement avoir un environnement de développement moderne activement soutenu, mais même un compilateur pour votre plate-forme.

D'autre part, pour les langues en question, il existe des compilateurs open source, et avec le niveau actuel de développement logiciel - avec intérêt, la syntaxe n'est pas trop compliquée vous permet de créer un compilateur personnel et de l'intégrer dans une sorte d'Eclipse avec rétroéclairage et analyseur.

En tant qu'indicateur de la clarté du langage, j'ai choisi la mise en œuvre de la célèbre tâche multi-thread de Dijkstra sur les philosophes de la restauration. La mise en œuvre se fait dans les manuels sur la langue et sur les forums, ce qui a facilité mon travail - il ne reste plus qu'à s'adapter. Par exemple, un récent article habr sur le C ++ moderne contient une implémentation en C ++ 17 pour comparaison.

Oberon actif (2004)


Il a été créé en tenant compte de l'expérience de Pascal, Modula, précédents Oberons depuis 1988, Java, C #, Ada, ainsi que d'une expérience pratique dans l'application. Il a une implémentation sous forme d' OS A2 , qui peut agir en tant que runtime sur * nix ou Windows. Sources A2 et le compilateur pour le lien .

Il existe également un projet Oberon2 to C Compiler (OOC) qui n'est pas lié à l'environnement Oberon. C'est un dialecte légèrement différent, les différences sont décrites ci-dessous.

La caractéristique clé d'Oberon est la brièveté exceptionnelle de la spécification. Ce sont 16 pages sur la base Oberon-2 plus 23 pages sur l'extension Active multi-thread.

Syntaxe simple et claire qui exclut les erreurs évidentes.

Les identificateurs sont sensibles à la casse.

POO avec des objets sur le tas avec le garbage collector (GC).

Il diffère de ses prédécesseurs par la syntaxe OOP plus familière sous la forme Instance.Method (il s'agissait auparavant de Method (Instance)) et la prise en charge du multithreading avec des primitives de synchronisation.
Il n'y a pas de répartition dynamique dans l'implémentation OOP, ce qui peut facilement conduire à une situation - ils ont oublié d'ajouter un traitement pour un nouveau type.

Les flux peuvent être prioritaires et élevés / en temps réel, ils ne sont pas interrompus par le GC. Chaînes sous forme de tableaux UTF-8.

Rantime (Oberon System) offre des opportunités intéressantes pour redémarrer une procédure / module / thread défaillant en cas d'erreur d'exécution - adressage mémoire, ou, par exemple, débordement d'entier.

L'inconvénient est le manque de RAII et la gestion pratique des erreurs - tout au long des codes retour, à l'exception de l'option ci-dessous.

Oberon-2 OOC


Il est plus pratique pour les expériences, car Oberon ne nécessite pas de système d'exploitation - il compile en ANSI C et il n'y a pas de problèmes d'interopérabilité. Différences par rapport à la version active - il n'y a pas de langage multithread intégré - il y a plutôt un module pour travailler avec PThreads, mais il y a UTF16, une modularité hiérarchique et un module système pour travailler avec des exceptions.

Module 3


Il existe également un parent d'une branche de développement légèrement différente sous la forme de Modula-3. Il a été créé sur la base d'Oberon par opposition à l'Ada surdéveloppé. L'implémentation est ici .

Par rapport à Active Oberon, des génériques et des exceptions sont ajoutés, il existe des bibliothèques pour les travaux pratiques avec Unicode, GUI et même Postgress. Intégration simplifiée avec C. Autres sémantiques multithreads. RAII as WITH (similaire à l'utilisation en C #).

Mais il semble que le développement de Modula 3 se soit arrêté en 2010.

Clause de non-responsabilité. Après avoir lancé WinAOS, j'ai rencontré des TRAP (alias abort / stacktrace ou erreur d'exécution) à l'improviste - même le gestionnaire de tâches ne fonctionne pas correctement, et bien que le système / l'exécution ne se soit pas bloqué - mais seulement l'application, j'avais un certain doute que la fiabilité est déterminée par la langue programmation = (

De plus, l'AOC est suffisamment autonome, avec son approche du développement.

Source pour les philosophes de la restauration
MODULE Philo; (* Dining Philosophers Example from Active Oberon Language Report by Patrik Reali *) (* Adapted for running in AOS by Siemargl *) IMPORT Semaphores := Example8, Out; CONST NofPhilo = 5; (* number of philosophers *) VAR fork: ARRAY NofPhilo OF Semaphores.Semaphore; i: LONGINT; TYPE Philosopher = OBJECT VAR first, second: LONGINT; (* forks used by this philosopher *) PROCEDURE & Init(id: LONGINT); BEGIN IF id # NofPhilo-1 THEN first := id; second := (id+1) ELSE first := 0; second := NofPhilo-1 END END Init; PROCEDURE Think; (* Need lock console output *) BEGIN {EXCLUSIVE} Out.Int(first); Out.String(".... Think...."); Out.Ln; END Think; PROCEDURE Eat; BEGIN {EXCLUSIVE} Out.Int(first); Out.String(".... Eat...."); Out.Ln; END Eat; BEGIN {ACTIVE} LOOP Think; fork[first].P; fork[second].P; Eat; fork[first].V; fork[second].V END END Philosopher; VAR philo: ARRAY NofPhilo OF Philosopher; BEGIN FOR i := 0 TO NofPhilo DO NEW(fork[i], INTEGER(i)); NEW(philo[i], i); END; END Philo. Philo.Philo1 ~ 

Ada (1980, dernière norme 2016 valide)


En fait, à première vue, il y a tout ce que j'aimerais.

Et même un peu plus - il existe des nombres avec des calculs à virgule flottante exacts. Par exemple, il existe un planificateur de threads en temps réel, un échange entre threads et un sous-ensemble officiellement vérifié du langage SPARK. Et bien plus.

Je pense que si la fiabilité d'Ada avait besoin d'une sacrée corne, elle serait jointe avec des instructions pour appeler dans une situation difficile =)

Mise en œuvre - GNUTaya Ada , est en cours de développement, normalisé ISO / IEC.

La norme fournit une implémentation avec GC, mais pour les options compilées, elle n'est souvent pas implémentée. Une gestion manuelle de la mémoire est requise - et ici des erreurs de programmation sont possibles. Cependant, le langage est orienté vers l'utilisation de la pile par défaut et il existe le concept de types gérés avec des destructeurs. Vous pouvez également définir votre implémentation GC, libération automatique ou comptage de références pour chaque type de données.

Le Manuel de référence Ada 2012 contient 950 pages.

L'inconvénient d'Ada, outre sa complexité, est sa verbosité excessive, qui a cependant été conçue dans un souci de lisibilité. En raison de la spécificité du modèle de sécurité linguistique, l'intégration avec des bibliothèques étrangères est difficile.

Le site Ada-ru a un bon article de traduction de révision - le premier lien.

Source pour les philosophes de la restauration
 -- Code from https://rosettacode.org/wiki/Dining_philosophers#Ordered_mutexes -- ADA95 compatible so can run in ideone.com with Ada.Numerics.Float_Random; use Ada.Numerics.Float_Random; with Ada.Text_IO; use Ada.Text_IO; procedure Test_Dining_Philosophers is type Philosopher is (Aristotle, Kant, Spinoza, Marx, Russel); protected type Fork is entry Grab; procedure Put_Down; private Seized : Boolean := False; end Fork; protected body Fork is entry Grab when not Seized is begin Seized := True; end Grab; procedure Put_Down is begin Seized := False; end Put_Down; end Fork; Life_Span : constant := 20; -- In his life a philosopher eats 20 times task type Person (ID : Philosopher; First, Second : not null access Fork); task body Person is Dice : Generator; begin Reset (Dice); for Life_Cycle in 1..Life_Span loop Put_Line (Philosopher'Image (ID) & " is thinking"); delay Duration (Random (Dice) * 0.100); Put_Line (Philosopher'Image (ID) & " is hungry"); First.Grab; Second.Grab; Put_Line (Philosopher'Image (ID) & " is eating"); delay Duration (Random (Dice) * 0.100); Second.Put_Down; First.Put_Down; end loop; Put_Line (Philosopher'Image (ID) & " is leaving"); end Person; Forks : array (1..5) of aliased Fork; -- Forks for hungry philosophers -- Start philosophers Ph_1 : Person (Aristotle, Forks (1)'Access, Forks (2)'Access); Ph_2 : Person (Kant, Forks (2)'Access, Forks (3)'Access); Ph_3 : Person (Spinoza, Forks (3)'Access, Forks (4)'Access); Ph_4 : Person (Marx, Forks (4)'Access, Forks (5)'Access); Ph_5 : Person (Russel, Forks (1)'Access, Forks (5)'Access); begin null; -- Nothing to do in the main task, just sit and behold end Test_Dining_Philosophers; 


BetterC (sous-ensemble dlang 2017, original D - 2001, D 2.0 - 2007)


La mise en œuvre la plus moderne du considéré. La description complète de la langue est assez longue - 649 pages - voir le site d'origine .

En fait, c'est le langage D, mais avec des restrictions avec le commutateur -betterC. Pourquoi?!

Parce que la bibliothèque standard D est Phobos, développée par Alexandrescu et s'est avérée très astucieuse, entièrement construite sur des modèles. La clé de ce sujet est que Phobos est incontrôlable en termes de consommation de mémoire.

Les choses les plus importantes qui se perdent en mode BetterC sont le multithreading, le GC, les chaînes, les classes (les structures restent - elles sont proches en fonctionnalité - uniquement sur la pile) et les exceptions (RAII et try-enfin restent).

Il est cependant possible d'écrire une partie du programme en plein D, et la partie critique dans D-BetterC. Il existe également une fonction d'attribut système pour contrôler la non-utilisation des effets dangereux: pure safe @nogc.

Justification du régime par le créateur de la langue.

Et puis la compression - ce qui est coupé et ce qui reste disponible.

Les chaînes sont contenues dans Phobos - et les tentatives de les utiliser dans BetterC entraînent des erreurs infernales d'instanciation de modèles sur des opérations élémentaires telles que la sortie d'une chaîne vers la console ou la concaténation. Et en mode D complet, les lignes sur le tas sont également immuables, donc les opérations avec elles entraînent un encombrement de la mémoire.

J'ai dû rencontrer plusieurs fois des plaintes concernant des bogues dans le compilateur. Ce qui, cependant, n'est pas surprenant pour un langage en concurrence en complexité avec C ++. Lors de la préparation de l'article, j'ai également dû faire face à 4 erreurs - deux sont survenues lors de la création de dlangide avec un nouveau compilateur et un couple lors du portage du problème philosophe (par exemple, un crash lors de l'utilisation de beginthreadex).

Le mode n'est apparu que récemment et les erreurs causées par la restriction du mode BetterC sortent déjà au stade de la liaison. Pour en savoir plus à l'avance, quelles fonctionnalités de la langue sont découpées exactement - doivent souvent le faire de première main.

Source pour les philosophes de la restauration
 // compile dmd -betterC import core.sys.windows.windows; import core.stdc.stdio; import core.stdc.stdlib : rand; //import std.typecons; // -impossible ( //import std.string; - impossible extern (Windows) alias btex_fptr = void function(void*) /*nothrow*/; //extern (C) uintptr_t _beginthreadex(void*, uint, btex_fptr, void*, uint, uint*) nothrow; /* Dining Philosophers example for a habr.com * by Siemargl, 2019 * BetterC variant. Compile >dmd -betterC Philo_BetterC.d */ extern (C) uintptr_t _beginthread(btex_fptr, uint stack_size, void *arglist) nothrow; alias HANDLE uintptr_t; alias HANDLE Fork; const philocount = 5; const cycles = 20; HANDLE[philocount] forks; struct Philosopher { const(char)* name; Fork left, right; HANDLE lifethread; } Philosopher[philocount] philos; extern (Windows) void PhilosopherLifeCycle(void* data) nothrow { Philosopher* philo = cast(Philosopher*)data; for (int age = 0; age++ < cycles;) { printf("%s is thinking\n", philo.name); Sleep(rand() % 100); printf("%s is hungry\n", philo.name); WaitForSingleObject(philo.left, INFINITE); WaitForSingleObject(philo.right, INFINITE); printf("%s is eating\n", philo.name); Sleep(rand() % 100); ReleaseMutex(philo.right); ReleaseMutex(philo.left); } printf("%s is leaving\n", philo.name); } extern (C) int main() { version(Windows){} else { static assert(false, "OS not supported"); } philos[0] = Philosopher ("Aristotlet".ptr, forks[0], forks[1], null); philos[1] = Philosopher ("Kant".ptr, forks[1], forks[2], null); philos[2] = Philosopher ("Spinoza".ptr, forks[2], forks[3], null); philos[3] = Philosopher ("Marx".ptr, forks[3], forks[4], null); philos[4] = Philosopher ("Russel".ptr, forks[0], forks[4], null); foreach(ref f; forks) { f = CreateMutex(null, false, null); assert(f); } foreach(ref ph; philos) { ph.lifethread = _beginthread(&PhilosopherLifeCycle, 0, &ph); assert(ph.lifethread); } foreach(ref ph; philos) WaitForSingleObject(ph.lifethread, INFINITE); // Close thread and mutex handles for( auto i = 0; i < philocount; i++ ) { CloseHandle(philos[i].lifethread); CloseHandle(forks[i]); } return 0; } 


A titre de comparaison, la source est pleine D.

Sur la rosette, vous pouvez également voir les options pour d'autres langues.

IEC 61131-3 ST (1993, dernière norme 2013)


Un langage de programmation de niche pour les microcontrôleurs. La norme implique 5 options de programmation, mais écrire une application par exemple en logique à relais est toujours une aventure. Par conséquent, nous nous concentrons sur une option - le texte structuré.
Le texte de la norme GOST R IEC 61131-3-2016 - 230 pages.

Il existe des implémentations pour PC / x86 et ARM - et commerciales, dont la plus connue est CODESYS (souvent également sous-licenciée avec des noms différents) et ouverte - Beremiz - diffusée via C.

Puisqu'il y a intégration avec C, il est tout à fait possible de connecter les bibliothèques nécessaires à la programmation appliquée. D'autre part, dans ce domaine, il est admis que la logique tourne séparément et ne sert que de serveur de données pour un autre programme ou système - une interface avec un opérateur ou un SGBD qui peut déjà être écrit sur n'importe quoi - sans exigences en temps réel ou même temporaires en général ...

La programmation multithread pour un programme utilisateur est apparue relativement récemment - dans les microcontrôleurs, cela n'était pas nécessaire auparavant.

La conversion de type est uniquement explicite (détendue dans la dernière norme). Mais le contrôle du débordement dépend de l'implémentation.

Dans la dernière édition de la norme, la POO est apparue. La gestion des erreurs est effectuée par des gestionnaires d'interruption personnalisés.

On peut dire qu'il n'y a pas d'allocation de mémoire dynamique pour l'utilisateur. Cela s'est produit historiquement - la quantité de données traitées par le microcontrôleur est toujours constante et limitée par le haut.

Source (non vérifiée)
 (* Dining Philosophers example for a habr.com * by Siemargl, 2019 * ISO61131 ST language variant. Must be specialized 4 ur PLC * ) CONFIGURATION PLC_1 VAR_GLOBAL Forks : USINT; Philo_1: Philosopher; (* Instance block - static vars *) Philo_2: Philosopher; Philo_3: Philosopher; Philo_4: Philosopher; Philo_5: Philosopher; END_VAR RESOURCE Station_1 ON CPU_1 TASK Task_1 (INTERVAL := T#100MS, PRIORITY := 1); TASK Task_2 (INTERVAL := T#100MS, PRIORITY := 1); TASK Task_3 (INTERVAL := T#100MS, PRIORITY := 1); TASK Task_4 (INTERVAL := T#100MS, PRIORITY := 1); TASK Task_5 (INTERVAL := T#100MS, PRIORITY := 1); PROGRAM Life_1 WITH Task_1: Philo_1(Name := 'Kant', 0, 1, Forks); PROGRAM Life2 WITH Task_2: Philo_2(Name := 'Aristotel', 1, 2, Forks); PROGRAM Life3 WITH Task_3: Philo_3(Name := 'Spinoza', 2, 3, Forks); PROGRAM Life4 WITH Task_4: Philo_4(Name := 'Marx', 3, 4, Forks); PROGRAM Life5 WITH Task_5: Philo_5(Name := 'Russel', 4, 0, Forks); END_RESOURCE END_CONFIGURATION FUNCTION_BLOCK Philosopher; USING SysCpuHandling.library; VAR_INPUT Name: STRING; Left: UINT; Right: UINT; END_VAR VAR_IN_OUT Forks: USINT; END_VAR VAR Thinking: BOOL := TRUE; (* States *) Hungry: BOOL; Eating: BOOL; HaveLeftFork: BOOL; TmThink: TON; TmEating: TON; END_VAR TmThink(In := Thinking; PT := T#3s); TmEating(In := Eating; PT := T#5s); IF Thinking THEN (* Just waiting Timer *) Thinking := NOT TmThink.Q; Hungry := TmThink.Q; ELSIF Hungry (* Try Atomic Lock Forks *) IF HaveLeftFork IF SysCpuTestAndSetBit(Address := Forks, Len := 1, iBit := Right, bSet := 1) = ERR_OK THEN Hungry := FALSE; Eating := TRUE; ELSE RETURN; END_IF ELSIF IF SysCpuTestAndSetBit(Address := Forks, Len := 1, iBit := Left, bSet := 1) = ERR_OK THEN HaveLeftFork := TRUE; ELSE RETURN; END_IF END_IF ELSIF Eating (* Waiting Timer, then lay forks *) IF TmEating.Q THEN Thinking := TRUE; Eating := FALSE; HaveLeftFork := FALSE; SysCpuTestAndSetBit(Address := Forks, Len := 1, iBit := Right, bSet := 0); SysCpuTestAndSetBit(Address := Forks, Len := 1, iBit := Left, bSet := 0); END_IF END_IF END_FUNCTION_BLOCK 


Safe-C (2011)


C expérimental avec l'élimination des puces dangereuses et avec l'ajout de modularité et de multithreading. Site du projet
Description d'environ 103 pages. Si vous mettez en évidence les différences par rapport à C - très peu, environ 10 .

Travailler avec des tableaux et des pointeurs est une mémoire dynamique et sûre avec un comptage automatique des références - avec des vérifications de double libération et des liens pendants.

La bibliothèque standard possède un ensemble minimal de fonctions pour l'interface graphique, le multithreading, les fonctions réseau (y compris un serveur http).

Mais - cette implémentation est uniquement pour Windows x86. Bien que le compilateur et le code de bibliothèque soient ouverts.

Dans le cadre d'une autre tâche de recherche, j'ai mis en place une configuration de serveur Web qui collecte les données des capteurs IoT: un module exécutif de 75 Ko et un ensemble de mémoire partielle <1 Mo.

Source pour les philosophes de la restauration
 /* Dining Philosophers example for a habr.com * by Siemargl, 2019 * Safe-C variant. Compile >mk.exe philosafec.c */ from std use console, thread, random; enum philos (ushort) { Aristotle, Kant, Spinoza, Marx, Russell, }; const int cycles = 10; const ushort NUM = 5; uint lived = NUM; packed struct philosopher // 32-bit { philos name; byte left, right; } philosopher philo_body[NUM]; SHARED_OBJECT forks[NUM]; void philosopher_life(philosopher philo) { int age; for (age = 0; age++ < cycles; ) { printf("%s is thinking\n", philo.name'string); delay((uint)rnd(1, 100)); printf("%s is hungry\n", philo.name'string); enter_shared_object(ref forks[philo.left]); enter_shared_object(ref forks[philo.right]); printf("%s is eating\n", philo.name'string); delay((uint)rnd(1, 100)); leave_shared_object(ref forks[philo.right]); leave_shared_object(ref forks[philo.left]); } printf("%s is leaving\n", philo.name'string); InterlockedExchange(ref lived, lived-1); } void main() { philos i; assert philosopher'size == 4; philo_body[0] = {Aristotle, 0, 1}; philo_body[1] = {Kant, 1, 2}; philo_body[2] = {Spinoza, 2, 3}; philo_body[3] = {Marx, 3, 4}; philo_body[4] = {Russell, 0, 4}; for (i = philos'first; i <= philos'last; i++) { assert run philosopher_life(philo_body[(uint)i]) == 0; } while (lived > 0) sleep 0; // until all dies for (i = philos'first; i <= philos'last; i++) { destroy_shared_object(ref forks[(uint)i]); } } 


Enfin - un tableau récapitulatif de la conformité aux exigences fonctionnelles.
J'ai sûrement manqué ou mal interprété quelque chose - corrigez-le donc.

Sources de l'article sur github .

Source: https://habr.com/ru/post/fr442372/


All Articles