A fully working demonstration of a php page running in Apache on the Raspberry Pi being able to safely access shared memory created by a C application also running on the Raspberry Pi. A semaphore is used to avoid access conflicts.
The C program creates a byte array. The php shared memory functions work using strings, but this code does the conversion to and from byte arrays giving access to the raw C byte variables and therefore working round the differences in the variable types between the two platforms.
The C Application
Header File
//----- SEMAPHORE -----
//On linux systems this union is probably already defined in the included sys/sem.h, but if not use this default basic definition:
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
//----- SHARED MEMORY -----
struct shared_memory1_struct {
char some_data[1024];
};
#define SEMAPHORE_KEY 291623581 //Semaphore unique key (MAKE DIFFERENT TO PHP KEY)
#define SHARED_MEMORY_KEY 672213396 //Shared memory unique key (SAME AS PHP KEY)
static int semaphore1_get_access(void);
static int semaphore1_release_access(void);
static int semaphore1_id;
void *shared_memory1_pointer = (void *)0;
struct shared_memory1_struct *shared_memory1;
int shared_memory1_id;
C File
#include <sys/shm.h> //Used for shared memory
#include <sys/sem.h> //Used for semaphores
//********************************
//********************************
//********** INITIALISE **********
//********************************
//********************************
void initialise (void)
{
//..... Do init stuff ....
//-----------------------------------------------
//----- CREATE SHARED MEMORY WITH SEMAPHORE -----
//-----------------------------------------------
printf("Creating shared memory with semaphore...\n");
semaphore1_id = semget((key_t)SEMAPHORE_KEY, 1, 0666 | 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)
//Initialize the semaphore using the SETVAL command in a semctl call (required before it can be used)
union semun sem_union_init;
sem_union_init.val = 1;
if (semctl(semaphore1_id, 0, SETVAL, sem_union_init) == -1)
{
fprintf(stderr, "Creating semaphore failed to initialize\n");
exit(EXIT_FAILURE);
}
//Create the shared memory
shared_memory1_id = shmget((key_t)SHARED_MEMORY_KEY, sizeof(struct shared_memory1_struct), 0666 | 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)
{
fprintf(stderr, "Shared memory shmget() failed\n");
exit(EXIT_FAILURE);
}
//Make the shared memory accessible to the program
shared_memory1_pointer = shmat(shared_memory1_id, (void *)0, 0);
if (shared_memory1_pointer == (void *)-1)
{
fprintf(stderr, "Shared memory shmat() failed\n");
exit(EXIT_FAILURE);
}
printf("Shared memory attached at %X\n", (int)shared_memory1_pointer);
//Assign the shared_memory segment
shared_memory1 = (struct shared_memory1_struct *)shared_memory1_pointer;
//----- SEMAPHORE GET ACCESS -----
if (!semaphore1_get_access())
exit(EXIT_FAILURE);
//----- WRITE SHARED MEMORY -----
int Index;
for (Index = 0; Index < sizeof(struct shared_memory1_struct); Index++)
shared_memory1->some_data[Index] = 0x00;
//Write initial values
shared_memory1->some_data[0] = 'H';
shared_memory1->some_data[1] = 'e';
shared_memory1->some_data[2] = 'l';
shared_memory1->some_data[3] = 'l';
shared_memory1->some_data[4] = 'o';
shared_memory1->some_data[5] = 0x00;
shared_memory1->some_data[6] = 1;
shared_memory1->some_data[7] = 255;
shared_memory1->some_data[8] = 0;
shared_memory1->some_data[9] = 0;
printf("stored=%s\n", shared_memory1->some_data);
//----- SEMAPHORE RELEASE ACCESS -----
if (!semaphore1_release_access())
exit(EXIT_FAILURE);
}
//***********************************
//***********************************
//********** MAIN FUNCTION **********
//***********************************
//***********************************
int main(int argc, char **argv)
{
//**********************
//**********************
//***** INITIALISE *****
//**********************
//**********************
//GENERAL INITIALISE
initialise();
//*********************
//*********************
//***** MAIN LOOP *****
//*********************
//*********************
while (1) // Do forever
{
delayMicroseconds(100); //Sleep occasionally so scheduler doesn't penalise us (THIS REQUIRES -lrt ADDING AS A COMPILER FLAG OR IT WILL CAUSE LOCK UP)
//--------------------------------------------------------------------------------
//----- CHECK FOR EXIT COMMAND RECEIVED FROM OTHER PROCESS VIA SHARED MEMORY -----
//--------------------------------------------------------------------------------
//----- SEMAPHORE GET ACCESS -----
if (!semaphore1_get_access())
exit(EXIT_FAILURE);
//----- ACCESS THE SHARED MEMORY -----
//Just an example of reading 2 bytes values passed from the php web page that will cause us to exit
if ((shared_memory1->some_data[8] == 30) && (shared_memory1->some_data[9] = 255))
break;
//----- SEMAPHORE RELEASE ACCESS -----
if (!semaphore1_release_access())
exit(EXIT_FAILURE);
}
//------------------------------------------
//------------------------------------------
//----- EXIT MAIN LOOP - SHUTTING DOWN -----
//------------------------------------------
//------------------------------------------
//----- DETACH SHARED MEMORY -----
//Detach and delete
if (shmdt(shared_memory1_pointer) == -1)
{
fprintf(stderr, "shmdt failed\n");
//exit(EXIT_FAILURE);
}
if (shmctl(shared_memory1_id, IPC_RMID, 0) == -1)
{
fprintf(stderr, "shmctl(IPC_RMID) failed\n");
//exit(EXIT_FAILURE);
}
//Delete the Semaphore
//It's important not to unintentionally leave semaphores existing after program execution. It also may cause problems next time you run the program.
union semun sem_union_delete;
if (semctl(semaphore1_id, 0, IPC_RMID, sem_union_delete) == -1)
fprintf(stderr, "Failed to delete semaphore\n");
#ifndef __DEBUG
if (do_reset)
{
system("sudo shutdown \"now\" -r");
}
#endif
return 0;
}
//***********************************************************
//***********************************************************
//********** WAIT IF NECESSARY THEN LOCK SEMAPHORE **********
//***********************************************************
//***********************************************************
//Stall if another process has the semaphore, then assert it to stop another process taking it
static int semaphore1_get_access(void)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1; /* P() */
sem_b.sem_flg = SEM_UNDO;
if (semop(semaphore1_id, &sem_b, 1) == -1) //Wait until free
{
fprintf(stderr, "semaphore1_get_access failed\n");
return(0);
}
return(1);
}
//***************************************
//***************************************
//********** RELEASE SEMAPHORE **********
//***************************************
//***************************************
//Release the semaphore and allow another process to take it
static int semaphore1_release_access(void)
{
struct sembuf sem_b;
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)
{
fprintf(stderr, "semaphore1_release_access failed\n");
return(0);
}
return(1);
}
The PHP Page
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Test Page</title>
</head>
<body>
<?php
//--------------------------------
//--------------------------------
//----- ACCESS SHARED MEMORY -----
//--------------------------------
//--------------------------------
//----- SHARED MEMORY CONFIGURATION -----
$SEMAPHORE_KEY = 291623581; //Semaphore unique key (MAKE DIFFERENT TO RPi App KEY)
$SHARED_MEMORY_KEY = 672213396; //Shared memory unique key (SAME AS RPi App KEY)
//Create the semaphore
$semaphore_id = sem_get($SEMAPHORE_KEY, 1); //Creates, or gets if already present, a semaphore
if ($semaphore_id === false)
{
echo "Failed to create semaphore. Reason: $php_errormsg<br />";
exit;
}
//Acquire the semaphore
if (!sem_acquire($semaphore_id)) //If not available this will stall until the semaphore is released by the other process
{
echo "Failed to acquire semaphore $semaphore_id<br />";
sem_remove($semaphore_id); //Use even if we didn't create the semaphore as something has gone wrong and its usually debugging so lets no lock up this semaphore key
exit;
}
//We have exclusive access to the shared memory (the other process is unable to aquire the semaphore until we release it)
//Setup access to the shared memory
$shared_memory_id = shmop_open($SHARED_MEMORY_KEY, "w", 0, 0); //Shared memory key, flags, permissions, size (permissions & size are 0 to open an existing memory segment)
//flags: "a" open an existing shared memory segment for read only, "w" read and write to a shared memory segment
if (empty($shared_memory_id))
{
echo "Failed to open shared memory.<br />"; //<<<< THIS WILL HAPPEN IF THE C APPLICATION HASN'T CREATED THE SHARED MEMORY OR IF IT HAS BEEN SHUTDOWN AND DELETED THE SHARED MEMORY
}
else
{
//--------------------------------------------
//----- READ AND WRITE THE SHARED MEMORY -----
//--------------------------------------------
echo "Shared memory size: ".shmop_size($shared_memory_id)." bytes<br />";
//----- READ FROM THE SHARED MEMORY -----
$shared_memory_string = shmop_read($shared_memory_id, 0, 10); //Shared memory ID, Start Index, Number of bytes to read
if($shared_memory_string == FALSE)
{
echo "Failed to read shared memory";
sem_release($semaphore_id);
exit;
}
//Display as a string
echo "Shared memory string: $shared_memory_string <br />";
//CONVERT TO AN ARRAY OF BYTE VALUES
$shared_memory_array = array_slice(unpack('C*', "\0".$shared_memory_string), 1);
echo "Shared memory bytes: ";
for($i = 0; $i < 10; $i++)
{
echo $shared_memory_array[$i] . ", ";
}
echo "<br />";
//----- WRITE TO THE SHARED MEMORY -----
if(isset($_REQUEST['shutdown'])) //Include "?shutdown" at the end of the url to write these bytes which causes the C application to exit
{
//The array to write
$shared_memory_array = array(30, 255);
//Convert the array of byte values to a byte string
$shared_memory_string = call_user_func_array(pack, array_merge(array("C*"), $shared_memory_array));
echo "Writing bytes: $shared_memory_string<br />";
shmop_write($shared_memory_id, $shared_memory_string, 8); //Shared memory id, string to write, Index to start writing from
//Note that a trailing null 0x00 byte is not written, just the byte values / characters, so in this example just 2 bytes are written.
}
//Detach from the shared memory
shmop_close($shared_memory_id);
}
//Release the semaphore
if (!sem_release($semaphore_id)) //Must be called after sem_acquire() so that another process can acquire the semaphore
echo "Failed to release $semaphore_id semaphore<br />";
//Delete the shared memory (only do this if we created it and its no longer being used by another process)
//shmop_delete($shared_memory_id);
//Delete the semaphore (use only if none of your processes require the semaphore anymore)
//sem_remove($semaphore_id); //Destroy the semaphore for all processes
echo "Complete<br />";
?>
<p><a href="this_page_name.php?shutdown">Shutdown C App</a></p>
</body>
</html>
PHP Converting To And From Byte Values
//----- READ INT16 WORDS EXAMPLE -----
//Convert bytes value read to Int16 words
$shared_memory_array = array_slice(unpack('C*', "\0".$shared_memory_string), 1);
$index = 0;
$word_value_0 = $shared_memory_array[$index++];
$word_value_0 |= (int)($shared_memory_array[$index++]) << 8;
$word_value_1 = $shared_memory_array[$index++];
$word_value_1 |= (int)($shared_memory_array[$index++]) << 8;
$word_value_2 = $shared_memory_array[$index++];
$word_value_2 |= (int)($shared_memory_array[$index++]) << 8;
//----- WRITE INT16 WORDS EXAMPLE -----
//Convert Int16 words to byte values ready to write
$shared_memory_array = array(
($word_value_0 & 0x00ff),
($word_value_0 >> 8),
($word_value_1 & 0x00ff),
($word_value_1 >> 8),
($word_value_2 & 0x00ff),
($word_value_2 >> 8)
);
$shared_memory_string = call_user_func_array(pack, array_merge(array("C*"), $shared_memory_array));
Issues
If you change the overall memory size then you will get shared memory access failure if the RPi has run an earlier version of your app since boot as linux seems not to tolerate the size changing for a previously used SHARED_MEMORY_KEY. Change the key or reboot to work around.
10 years ago
Thank you very much for sharing this piece of code. Unfortunately, it’s incomple/buggy.
The following global variables are missing in the C code:
semaphore1_id ; shared_memory1_id, shared_memory1_pointer
The much bigger bug is, that PHP is unable to access a semaphore which is created in the C app with semget((key_t)SEMAPHORE_KEY, 1, 0666 | IPC_CREAT);
The second Parameter has to be “3” even if only one semaphore is used, because PHP can only access a set of 3. Otherwise, you get the error “sem_get: Invalid arguments”
There’s also a typo ( === ) in line 25 of the PHP script.
The errorhandling of the C app could be better, because in case of an error, the semaphores aren’t released, so you have to increase your key every time while debugging, but this is only a minor issue.
Without this example, I wouldn’t be able to create my application – a temperature/state logger for my central heating which logs the tempatures of several points in the system and the momentary situation also can be accessed on the internal web, for which the php script gets the values from the logging application (written in Freebasic, for which I’ve translated the script above).
Also, I’m going to look for better ways to write/access the shared memory block in my case. For the C Application, perhaps copying a complete Array or String with memcpy to the given pointer is an option, and for the PHP script, I’m going to take a look at serialize() or something else, because, unfortunately, PHP doesn’t support real pointer operations.
10 years ago
Thanks for the feedback. The above wasn’t buggy for us but maybe there are different implementation issues at play. We hadn’t bothered with extensive error handling as without this working on our projects there was no error fall back position so error handling wasn’t needed…
4 years ago
You great Adam !
4 years ago
Hi,
I have tryed this code and there is alot of missing defined variables.
Is it possible for you to post it fully operational.
Thanks ,
Joao