C beim Start prüfen, ob Programm noch läuft

Vril

Well-Known Member
Hallo,
ich hab da mal ne Frage an die Programmierprofis:
Wie würdet ihr das machen, bzw. wie macht es der Programmierer?

Ich habe da ein Programm, das ich in C geschrieben hab - und das vereinfacht ausgedrückt:
zyklisch über das Netz auf ein PLC ( Siemens SPS ) zugreift, dort bestimmte Speicherbereiche ausliest,
die gefundenen Daten aufbereitet, in eine SQL Datenbank schiebt - und sich beendet.
Dieses Programm wird von cron aller 5 Minuten gestartet.

Da es beim Ablauf des PLC-read aber bedingte Wartezeiten gibt ( z.B. Datenbereich x kann erst gelesen
werden, wenn die vom PLC gesteuerte Maschine einen bestimmten Zustand hat... ist es rein theoretisch
möglich, dass das Programm länger als 5 Minuten laufen würde - und cron somit eine 2.task parallel
starten würde.

D.h. das Programm mit cron stur nach 5 Min zu starten ist letztendlich keine so gute Idee.

Wie löst man das?

1. Einmal natürlich, indem cron statt des Programms ein shellscript startet, dass schaut ob das Teil noch
läuft, die PID herausfindet - und dann je nachdem, entweder ein kill ausführt oder mit dem Start
noch wartet.

2. ein kleines weiteres c-programm schreibt, dass in einer endlosschleife mit sleep(300) und
'system' das eigentliche c-programm startet, die pid sich merkt und somit den startzyklus
überwacht und steuert

3. das eigentliche c-programm modifizieren, in einer endlosschleife laufen lassen und eventuell mit fork
kindprozesse zeit- und situationsbedingt zu starten.

( vor der 3. Variante hab ich etwas Bammel, weil ich im Programm oft mit malloc() Memory für
irgendwelche String-Geschichten reserviere - und nicht ganz sicher bin, ob mit free() wirklich immer
alles freigegeben wird.)

Gebt mir mal bitte nen Tipp - welcher Weg günstig wäre.
 
3. das eigentliche c-programm modifizieren, in einer endlosschleife laufen lassen und eventuell mit fork
kindprozesse zeit- und situationsbedingt zu starten.

( vor der 3. Variante hab ich etwas Bammel, weil ich im Programm oft mit malloc() Memory für
irgendwelche String-Geschichten reserviere - und nicht ganz sicher bin, ob mit free() wirklich immer
alles freigegeben wird.)
IMHO wäre das der richtige Weg.

Für Dein Memory-Leak Problem gibt es Lösungsansätze, zum Beispiel C++ mit RAII verwenden, oder wenn Du bei C bleiben willst kannst Du unter Umständen einfach ausreichend große Puffer auf dem Stack verwenden.
 
@KobRheTilla ... Danke, aber es wird für linux und bsd kompiliert :-(

@Kamikaze ... nun ja, C++ und RAii ??? also dieses objektorientierte zeugs lasse ich links liegen - dafür bin ich schon zu alt ;-)

Zur Zeit habe ich es mal mit nen simplen script laufen, dass vor dem start die ausgabe von ps -A filtert und prüft

ich werde 'mal schauen - wie ich den c-code ändern kann
 
Speicherlecks kann man auch recht komfortabel mit Tools wie Valgrind finden.
 
Ich würde es schematisch dargestellt so machen:

cron ruft ein script auf, welches folgendes beinhaltet:

1.) Überprüfen, ob ein flag gesetzt ist (z.B. Datei namens /tmp/foo-flag)
2.) Wenn ja, script beenden
3.) Wenn nein, flag setzten
4.) Das c Programm ausführen
5.) Wenn c Programm beendet wurde, flag entfernen
 
Ich bin Fan davon sowas mit einem PID-File zu machen und im Cronjob oder Shell Script sowas, wie

Code:
pgrep -F /var/run/prozess_name.pid || prozess_starten

Oder alernativ gleich:

Code:
pgrep prozess_name || prozess_starten

Ist aber eine Frage der Aufgabe des Programms. Wenn das Programm ohnehin standardmäßig so läuft wäre es wohl, wie Kamikaze meint im Programm besser aufgehoben. Gibt außerdem auch einige Programme die entweder eine "nur einmal machen" (ntpd -q) oder "Repeat Mode" haben (wie iostat -w zum Beispiel).

Ist halt die Frage, als was du das Programm da siehst. Wenn's ein Utility für die Nutzung in der Shell ist, dann finde ich Locking mit File sinnvoll (wie die meisten Package Manager zum Beispiel), wenn es eine Art daemon sein soll, dann einen Endlos-Loop. Wenn du Angst vor Crashes hast dann würde ich daemon(8) und dessen -r flag empfehlen. Finde ich echt cool, vor allem auch für grindige 3rd Party Sachen.

Den zweiten Ansatz würde ich eher als Skript machen, wo du alles rein hauen kannst. Ist glaube ich einfacher, auch wenn das mal andere Leute ansehen und dann nicht nach dem Code suchen müssen.

Ansonsten würde empfehlen das Programm zu fixen und damit ganz generell weniger Ärger zu haben, wenn Memoryleaks eine Sorge sind. Wenn man Sachen eher als Hack macht, dann neigen die ab und an mal Seiteneffekte zu haben mit denen man nicht rechnet und irgendwie anders was kaputt machen. Also zum Beispiel, weil es irgendwie doch öfters läuft, aber nicht richtig stirbt, oder weil es sonst schlimm irgendeine Art von Ressourcen leaken beginnt (Memory, File Descriptors, CPU), vor allem wenn man mal schnell was dazu baut.
 
Du kannst auch beim Start deines Programmes eine Resource anlegen, die systemweit nur einmal erzeugt werden kann. Oft wird dafür ein Synchronisationsobjekt wie z.B. ein Lock oder ein Mutex benutzt. Aber auch ein Socket kann dafür verwendet werden.
Sobald eine zweite Instanz deines Programmes startet, wird die erneute Erzeugung der systemweit eindeutigen Ressource fehlschlagen und somit der erneute Start verhindert.
 
...
Auf Shell-Ebene dürfte es kaum eine portable Lösung geben, die frei von race conditions ist.

Da stimme ich Dir vollkommen zu.

mein eigentliches Problem sind ja im Prinzip race-conditions - die ich letztendlich nicht beeinflussen
und auch nur schwer erkennen kann, da deren Ursachen bei Ereignissen im PLC und Anlagenbereich liegen.

Aber es ist schon so, wie auch die Mehrzahl hier meint und empfiehlt - ich muss diese Dinge im C-Source
abfangen und darf Abhängigkeiten und Bedingungen nicht via script auf die shell-Ebene bringen -
denn da wird es undurchsichtig und wird Bastelei und Murx.

Danke an Alle

und Gruss walter
 
Habe ich da was falsch verstanden? Das Programm läuft oder läuft nicht, oder? Wo wäre dann bei sowas die Möglichkeit einer Race Condition mit winem simplen While oder Pidfile raus schreiben oder bei den genannten Möglichkeiten oder einem einfachen While-Loop das das Programm startet?

Bei keiner dieser Möglichkeiten, außer dem pgrep ohne Pidfile, wenn man es denn mit Cron kombiniert sehe ich die Chance einer Race Condition. Das Programm blockt ja bis es fertig ist, oder? Ansonsten bringt ja das C-"Programm" auch nichts?

Bzw. anders gesagt, was kann man denn in Shell Scripts in dem Zusammenhang nicht portabel machen, was man in C schon kann?

Soll jetzt nicht heißen, dass ich die Lösung in C als schlecht ansehe, aber bin nur neugierig, was hier im Fall von Shell-Scripts Race Conditions verursachen kann? Gehe gerade davon aus, dass ich im Anfangsposting nicht verstanden habe, aber es erscheint mir gerade, als sollte man das vollkommen straight forward mit einem Shell-Skript lösen könnte.
 
Habe ich da was falsch verstanden? Das Programm läuft oder läuft nicht, oder? Wo wäre dann bei sowas die Möglichkeit einer Race Condition mit winem simplen While oder Pidfile raus schreiben oder bei den genannten Möglichkeiten oder einem einfachen While-Loop das das Programm startet?...

Vielleicht habe ich mich etwas unklar ausgedrückt.
Eine race-condition ist doch ein Problem, bei dem das Ergebnis von Operationen vom zeitlichen Verhalten bestimmter auf und in das Programm wirkender Bedingungen abhängt.
Und genau das habe ich, da externe Abläufe( in der SPS bzw. der von der SPS gesteuerten Maschine ), deren zeitlichen Verlauf ich nicht exakt kenne - dazu führen,
dass mein zyklisch gestartetes Programm möglicherweise länger läuft - als es der festeingestellte Zyklus erlaubt.

Externe Massnahmen - wie Programm nicht erneut starten, wenn es noch läuft - also einen odere mehrere Zyklen aussetzen, funktionieren zwar ...
haben aber den Charme einer Brechstange.

Also ... letztendlich muss ich die Struktur so ändern, dass es eben nicht extern (cron ) in fixen Zyklen gestartet wird,
sondern endlos läuft und ich variabel auf die skizzierten Dinge im c-source reagieren muss.
 
Habe ich da was falsch verstanden? Das Programm läuft oder läuft nicht, oder? Wo wäre dann bei sowas die Möglichkeit einer Race Condition mit winem simplen While oder Pidfile raus schreiben oder bei den genannten Möglichkeiten oder einem einfachen While-Loop das das Programm startet?

Simples Fehlerszenario: User A startet das Programm alle 5 Minuten via cron. User B macht das gleiche.
  • Ausgangslage: C-Programm läuft nicht, kein PID-File
  • Cron für User A überprüft ob PID-File existiert -> existiert nicht
  • Cron für User B überprüft ob PID-File existiert -> existiert nicht
  • Cron für User A startet das Programm -> 1. Instanz läuft
  • Cron für User B startet das Programm -> 2. Instanz läuft :eek:
  • Cron für User A schreibt PID-File
  • Cron für User B überschreibt PID-File
Das kann man jetzt mit Überprüfungen (pgrep etc.) beliebig verkomplizieren, damit verschiebt man aber nur die race condition an eine andere Stelle oder fängt an, sein eigenes Init-System zu schreiben.

Bzw. anders gesagt, was kann man denn in Shell Scripts in dem Zusammenhang nicht portabel machen, was man in C schon kann?

Es gibt zwar Lösungen auf Shell-Ebene, keine davon aber im POSIX-Standard und meines Wissens keine, deren Verfügbarkeit auf jedem Unix gegeben wäre. Unter Linux und FreeBSD könnte man noch mit dem flock-Wrapper Erfolg haben, unter Solaris (nicht verfügbar) und OpenBSD (meines Wissens nur in den Ports) fliegt man damit aber schon auf die Schnauze.

Damit hat man aber immer noch den Faktor Mensch, der versehentlich das C-Programm direkt starten kann.

Gehe gerade davon aus, dass ich im Anfangsposting nicht verstanden habe, aber es erscheint mir gerade, als sollte man das vollkommen straight forward mit einem Shell-Skript lösen könnte.

Es hat einen Grund, warum SysVinit und Konsorten das Problem seit einem Viertelhundert nicht zuverlässig gelöst bekommen und viele Shell-Init-Skripte mit sleep arbeiten. :ugly:

Vril kann es entweder trivial und zuverlässig im C-Code lösen oder auf die harte Tour herausfinden, wie abartig viele Fehlerkonstellationen beim Prozessmanagement mit Shell und PID-Files existieren.
 
Was ist mit
Code:
( set -C; : > $pidfile ) >/dev/null 2>&1 || exit 1

...
? Danach kann man die PID reinschreiben und das Programm starten.

Das sollte doch atomic sein.
 
Danach kann man die PID reinschreiben und das Programm starten.

Uns interessiert die PID des C-Programms. Um die zu bekommen, müssen wir erst das C-Programm starten.

Falls das C-Programm abstürzt, wird es nicht mehr neugestartet, weil $pidfile schon existiert. Jetzt muss man Logik einbauen, die anhand PID, Prozessname u.ä. das Vorhandensein des Prozesses überprüft und wiederum unweigerlich eine race condition zwischen Überprüfung und Neustart des Programms hat.

Inzwischen sind wir bei ein paar Dutzend Zeilen völlig unnötigen Shell-Codes angelangt, haben immer noch keine zuverlässige Lösung und keinerlei Mehrwert. Den Fehlerfall des manuellen Starts des C-Programms haben wir immer noch nicht abgefangen.
 
Dann lass das C-Programm halt im Vordergrund und das Shell-Skript räumt hinterher auf. Ich sehe da überhaupt kein Problem.

Code:
( set -C; : > $pidfile ) >/dev/null 2>&1 || exit 1

cprogramm

rm $pidfile

Das startet man über cron. Fertig. Da gibt's keine race condition und nix. Man braucht nicht mal die konkrete PID des Programms. Das $pidfile dient als Flag starten oder nicht starten.
 
Das startet man über cron. Fertig. Da gibt's keine race condition und nix. Man braucht nicht mal die konkrete PID des Programms. Das $pidfile dient als Flag starten oder nicht starten.

Nach einem harten Reset der Maschine wird das C-Programm beim Neuanlauf nicht neu gestartet. Das C-Programm läuft nicht mehr, das PID-File existiert aber noch.

Selbst die Anlage des PID-File in /tmp hilft nicht bei jedem Betriebssystem, weil nicht jedes Betriebssystem /tmp beim Neustart aufräumt.
 
Wenn dein System beim Booten nicht /var/run cleart, dann kauf dir mal ein ordentliches System.

Wenn ich dieses ganze Gefrickel nur haben muss, weil ich sowas Grundlegendes missachte wie /var/run beim Booten einmal zu säubern, dann hat irgendwer unterwegs was nicht verstanden.

Ein "sane system" setzen wir hier doch mal voraus. Ansonsten wäre die erste Aufgabe erstmal, so einen Zustand herzustellen, bevor man Dienste betreiben will.
 
Wenn dein System beim Booten nicht /var/run cleart, dann kauf dir mal ein ordentliches System.

/var/run benötigt root-Rechte.

Statt 5 Zeilen zusätzlichem C-Quellcode sind wir jetzt bei einem Shell-Skript mit root-Rechten oder einem eigenen Init-Skript angelangt, um ein simples C-Programm als Benutzer laufen zu lassen. Mit der Shell-Lösung haben wir aber noch immer nicht den versehentlichen Start des C-Programms abgefangen.

Wenn ich dieses ganze Gefrickel nur haben muss, weil ich sowas Grundlegendes missachte wie /var/run beim Booten einmal zu säubern, dann hat irgendwer unterwegs was nicht verstanden.

Das Gefrickel kommt nur daher, weil sich das Problem innerhalb der Shell nicht sauber lösen lässt. The right tool for the right job.

Ein "sane system" setzen wir hier doch mal voraus. Ansonsten wäre die erste Aufgabe erstmal, so einen Zustand herzustellen, bevor man Dienste betreiben will.

Nachdem es um portable Lösungen ging, war meine Prämisse schon die Lauffähigkeit auf jeder 08/15-Installation ohne nennenswerte Anpassungen.
 
Achso, ich dachte, hier ging es darum, ein konkretes Problem zu lösen und sich nicht um jede Randbedingung kümmern zu wollen.

Dann startet man das Programm halt nicht manuell bzw. nimmt dort auch das Script. Wo ist das Problem?

Soll das jetzt ein Tool sein, was systemweit nur einmal laufen kann oder soll das jeder User einfach so benutzen können? Klang eher nach Ersterem. Wo ist dann das Problem, dass der Admin dafür einen User anlegt und in /var/run ein Verzeichnis, in das der User reinschreiben darf?

Das komplette "Problem" lässt sich mit den hier angerissenen Bits und handelsüblicher Admintätigkeit lösen. Mir wäre ein periodisch gestartetes Programm lieber, das nach der Arbeit vom OS komplett abgerissen wird. Wenn die eigentliche Arbeit des Tools ein one-off job ist, dann wäre es Unsinn, in das Programm noch einen Mechanismus einzubauen, der nur der Selbstverwaltung dient und noch dazu erfordert, dass das Programm auf eine unbestimmte Laufzeit hin programmiert wird (Speicherlecks etc.).
 
So .. dann 'mal abschliesend vielen Dank an Alle,
hab von Euch ganz wertvolle Tipps bekommen.

Gelöst habe ich es ohne script - also im c-source,
indem ich ( ganz grob beschrieben )
1. die Struktur komplett verändert habe und
2. zusätzlich ein Lock-File erzeuge, lösche und dessen Existenz beim Programm-Start prüfe -
und sicherstelle, dass es nach einem shutdown bzw. restart der maschine nicht mehr vorhanden ist.


Code:
( set -C; : > $pidfile ) >/dev/null 2>&1 || exit 1
cprogramm
rm $pidfile

was macht dieses?
Code:
set -C;: > $pidfile
kannst Du mir da n stichwort geben? .. ich versteh das einfach nicht :-(
 
Das Sind 2 Kommando
was macht dieses?
Code:
set -C;: > $pidfile
kannst Du mir da n stichwort geben? .. ich versteh das einfach nicht :-(
Das sind 2 Kommandos set -C und : > $pidfile.

set -C verbietet im aktuellen Prozess (jede Klammer ist ein neuer Prozess) dem > Operator Dateien zu überschreiben. : macht gar nichts aber > würde dann ins pidfile schreiben und einen Fehler verursachen wenn das schon existiert.
 
Zurück
Oben