Tutorial
[Linux] Kommunikation zwischen Programmen mit Shared Memory
von stephanbrunker | Seite 1 von 3 |
Shared Memory in Linux
Linux als Betriebssystem stellt über die System V-Anweisungen die Möglichkeit bereit, Informationen zwischen Anwendungen im Arbeitsspeicher auszutauschen. Wozu braucht man so was? In meinem Fall habe ich eine Datenerfassung, die auf einem Raspberry Pi läuft und die über ein Webinterface abgefragt werden kann. Der einfache Weg hätte darin bestanden, die Informationen bei jeder Akutualisierung in eine Datei zu schreiben und diese dann aus der Web-Anwendung auszulesen. Da der RPi aber mit einer SD-Karte läuft, hätten diese ständigen Zugriffe die Speicherkarte in kürzester Zeit degeneriert. Also musste eine Lösung her, die die Informationen ins RAM schreibt, was ja auch naheliegend ist.
Als Ausgangspunkt gibt es dieses Tutorial, welches die Interaktion von einen PHP-Skript mit einer C-Anwendung beschreibt: www.raspberry-projects.com/pi/programming-in-c/memory/shared-memory-between-c-application-and-php-web-server Das Prinzip funktioniert aber ganz allgemein in Linux und zwischen Programmen, solange man auf die Systembefehle zugreifen kann, es muss nicht unbedingt PHP sein, sondern zweimal Freebasic ginge auch.
Im Grunde ist jetzt eigentlich nichts mehr zu tun als die C-header und das Programm nach Freebasic zu übersetzen. Da ich mir die Arbeit schon gemacht habe, gibt es hier die nötigen header ipc.bi, sem.bi und shm.bi. Diese sind zwar nicht vollständig, beinhalten aber die nötigen Funktionen und laufen auf dem RPi.
Das Freebasic-Programm
Das Freebasic-Programm beginnt wie üblich mit den Deklarationen:
'-----------------------------
' DECLARATIONS
'-----------------------------
#include "sys/shm.bi"
#include "sys/sem.bi"
#include once "crt/errno.bi"
type shared_memory_1024
some_data as zstring * 1024
end type
#define SEMAPHORE_KEY 291623558
#define SHARED_MEMORY_KEY 672213396
declare function semaphore1_get_access() as integer
declare function semaphore1_release_access() as integer
dim shared semaphore1_id as integer
dim shared shared_memory1_id as integer
dim shared shared_memory1_pointer as shared_memory_1024 ptr
Der Semaphore Key und der Shared Memory Key sind eigentlich frei wählbare Zahlen, es gibt nur die unwahrscheinliche Möglichkeit, das ein anderer laufender Prozess den gleichen Key verwendet, dann gibt es ein Problem. Als Puffer für den Datenaustausch bietet sich bei Freebasic ein selbst konstruierter Type an, der die gewünschten Variablen enthält, hier einen 1024 Zeichen langen String. Wichtig ist nur eine fixe Größe des Types.
Der nächste Schritt ist die Initialisierung des Shared Memory. Das sind eigentlich zwei Schritte, das Shared Memory einerseits und ein Semaphore andererseits. Der Semaphore erfüllt die gleiche Funktion wie ein Mutex beim Multi-Threading, nämlich das Verhindern eines gleichzeitigen Zugriffs auf den Speicher. Die eine Anwendung sperrt also den Speicher, schreibt und gibt ihn wieder frei, während die andere Anwendung, falls sie auf eine Sperre trifft, wartet bis er wieder frei ist oder der Timeout erreicht ist.
Die Initalisierung ruft zuerst semget() auf, mit dem man vom System eine SemaphoreID erhält. Diese ID wird mit semctl() initialisiert. Als nächstes wird das Shared Memory reserviert, ebenfalls mit zwei Funktionen: shmget() arbeitet wie ALLOCATE, und gibt nach Angabe von Zugriffsart, Rechten und Größe eine ID zurück, und shmat() gibt dann nach Angabe der ID einen Pointer zum Speicher zurück. Diesen Zugriff können wir in Freebasic elegant mit NEW zu einem UDT zuweisen. Wenn wir dann den UDT ändern, ändert sich direkt der Wert im Speicher.
'-----------------------------
' INITALIZE
'-----------------------------
sub initalise()
'..... Do init stuff ....
'-----------------------------------------------
'----- CREATE SHARED MEMORY WITH SEMAPHORE -----
'-----------------------------------------------
print "Creating shared memory with semaphore..."
semaphore1_id = semget(SEMAPHORE_KEY, 3, &o0666 or IPC_CREAT)
'Semaphore key, number of semaphores required, flags
' Semaphore key
' Unique non zero integer (usually 32 bit).
' Needs to avoid clashing with another other processes semaphores
'(you just have to pick a random value and hope -
'ftok() can help with this but it still doesn't guarantee to avoid colision)
if semaphore1_id = -1 then
print "Failed to get Semaphore ID, errno: ";errno
else
print "Semaphore ID: ";semaphore1_id
end if
'Initialize the semaphore using the SETVAL command in a semctl call (required before it can be used)
dim sem_union_init as integer = 1
if semctl(semaphore1_id, 0, SETVAL, sem_union_init) = -1 then
print "Creating semaphore failed to initialize"
print "errno: ";errno
sleep:end
end if
'Every semaphore_id can have multiple semaphores attached to it (the semnum argument).
'If the val_ variable is 1, the semaphore is unlocked, is it 0, the semaphore is locked.
'after semctl, the val_ should be 1, so test it with GETVAL:
dim sem0val as integer
sem0val = semctl(semaphore1_id, 0, GETVAL)
if sem0val = -1 then
print "Semaphore get value failed, error: ";errno
else
print "Semaphore1(0) Val: ";sem0val
end if
'Create the shared memory
shared_memory1_id = shmget(SHARED_MEMORY_KEY, sizeof(shared_memory_1024), &o0666 or IPC_CREAT)
'Shared memory key , Size in bytes, Permission flags
' Shared memory key
' Unique non zero integer (usually 32 bit). Needs to avoid clashing with another other processes shared memory (you just have to pick a random value and hope - ftok() can help with this but it still doesn't guarantee to avoid colision)
' Permission flags
' Operation permissions Octal value
' Read by user 00400
' Write by user 00200
' Read by group 00040
' Write by group 00020
' Read by others 00004
' Write by others 00002
' Examples:
' 0666 Everyone can read and write
if shared_memory1_id = -1 then
print "Shared memory shmget() failed"
sleep:end
end if
'Make the shared memory accessible to the program
dim shmptr as shared_memory_1024 ptr
shmptr = shmat(shared_memory1_id, 0, 0)
if shmptr = -1 then
print "Shared memory shmat() failed"
sleep: end
end if
print "Shared memory attached at ";shmptr
'Assign the shared_memory segment
shared_memory1_pointer = new (shmptr) shared_memory_1024
end sub
Wie im Kommentar beschrieben, kann jede SemaphoreID mehrere Semaphoren zugeordnet haben. Auch wenn wir nur eine brauchen, fragen wir bei semget drei Stück an, denn bei der PHP-Seite sind es immer drei, mit nur einer funktioniert es nicht und weil das damals nirgendwo stand, war das einen ziemlich harte Nuss zu knacken.
Für den Zugriff gibt es dann die zwei Funktionen: semaphore1_get_access() und semaphore1_release_access().
'-----------------------------------------
' WAIT IF NECESSARY THEN LOCK SEMAPHORE
'-----------------------------------------
'Stall if another process has the semaphore, then assert it to stop another process taking it
function semaphore1_get_access() as integer
dim sem_b as sembuf
sem_b.sem_num = 0
sem_b.sem_op = -1 'decreases the val of the semaphore to 0 - lock it
sem_b.sem_flg = SEM_UNDO
if semop(semaphore1_id, @sem_b, 1) = -1 then 'function sleeps until the semaphore can be locked
print "semaphore1_get_access failed"
return 0
end if
return 1
end function
'-----------------------------------------
' RELEASE SEMAPHORE
'-----------------------------------------
'Release the semaphore and allow another process to take it
function semaphore1_release_access() as integer
dim sem_b as sembuf
sem_b.sem_num = 0
sem_b.sem_op = 1 '* V() */
sem_b.sem_flg = SEM_UNDO
if semop(semaphore1_id, @sem_b, 1) = -1 then
print "semaphore1_release_access failed"
return 0
end if
return 1
end function
Zusätzliche Informationen und Funktionen | |||||||
---|---|---|---|---|---|---|---|
|
|