ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


Using Shared Memory from PHP

by Alexander Prohorenko
05/13/2004

IPC is one of the most important features of the UNIX systems. It allows two processes to communicate with each other. In this article we'll work with two System V IPC functions, semaphores and shared memory. System V IPC originated in SVR2, but has implementations by numerous vendors. It's also available in SVR4.

IPC is a complex concept. The term includes various mechanisms of data exchange between processes started on one system. IPC lets you avoid creating a huge application with a big set of the diversified functions in favor of using separate, small applications that can exchange data with each other. While this is more of a traditional Unix approach, using separate applications allows multiprocessor systems to execute applications in separate threads to reduce the time of performing specific tasks.

At a very high-level, we can divide interprocess communication into the most important and big parts:

This article introduces shared memory and synchronization (semaphores). A detailed explanation of these parts will take too much time — there's a huge amount of different material and documentation. If you're interested in further detail, there are multiple books dedicated to IPC.

IPC Identifiers

Each IPC object (whether a message queue, a semaphore, or a shared memory segment) has a unique IPC identifier. This identifier allows the kernel to determine the identify of an IPC object. For example, to refer to a specific shared memory segment, you only need to know the unique ID attached to the segment.

Beware that the IPC identifier is unique for its object's type only. That is, say, only one message queue can have the 12345 identifier, though the number 12345 can have any multitude of semaphores and/or a shared memory segment.

IPC Keys

How do you find an IPC identifier? You need a key. The first step to building an environment between applications is coordinating the use of keys. Think of it this way: to call someone, you have to know his phone number. The telephone company has to know how to pass your call to the recipient. Only when he responds can the connection take place.

In the case of System V IPC, the telephone connects IPC objects of the same type; the Telephone Company or routing method is the IPC key.

Applications need to generate their own keys. The ftok() function will do this for both the client and the server. The key returned from ftok() relies on the inode value and the lowest minor number of the first argument file and from the second argument literally. This does not guarantee originality, but the application can check for collisions and generate new keys as necessary.

key_t mykey;
mykey = ftok ("/tmp/myapp", 'a');

In the fragment above, the /tmp/myapp directory mixes with the literal identifier a to generate the key. Another widespread example is to use the current directory:

key_t mykey;
mykey = ftok(".", 'a');

The key generation algorithm relies on the discretion of the application programmer. Any method should take into account race conditions and deadlock-prevention measures. To achieve our demonstrational goals, we will limit ourselves to ftok(). If we agree that each client process should run from its own unique home directory, all generated keys will always be satisfactory.

The following IPC system calls the value of the returned key to create or improve the access to IPC objects.

The ipcs command shows the status of all System V IPC objects.

ipcs     -q: show messages queue only
ipcs     -s: show semaphore only
ipcs     -m: show shared memory only
ipcs --help: for inquisitive ones

All three object categories show by default. Let's look at the following unpretentious ipcs derivation:

------ Shared Memory Segments --------
shmid owner     perms bytes     nattch status
------ Semaphore Arrays --------
^semid owner     perms nsems status
------ Message Queues --------
msqid owner     perms used-bytes messages
0     root      660   5          1

Here we can see the solitary message queue with the identifier 0. It belongs to the root user and has the access rights 660, or -rw-rw---. The queue holds one 5-byte message.

The ipcs command is a very powerful mechanism for spying on the kernel memory of IPC objects.

The ipcrm Command

ipcrm deletes IPC objects from the kernel. However, as IPC objects can be deleted through system calls in the user's program, there is often no need to delete them by hand. The command is very simple:

ipcrm -type id

It's necessary to note the type of object to delete with a special switch. See the manpage for more details. You can find an IPC id with the ipcs command. Remember, the ID is unique only within the object's type. That's why we have to name the type when deleting the object.

Semaphores

The best way to imagine semaphores is as counters that manage access to public resources. Usually they are used as locks, disallowing one process to access something already in use by another process. Semaphores can also provide exclusive access to the resources of the current machine or to limit the number of processors using the resource simultaneously.

This module also provides the functions to work with System V shared memory. Shared memory provides access to global variables between processes. httpd daemons and even other programs (in Perl, C, and other languages) can access this data to achieve global data exchange. Remember, though, that shared memory is not protected from simultaneous access.

Table 1 show Unix variables that tune shared-memory limitations. Each Unix variant has specific documentation on these variables. For example, on FreeBSD, they're part of the kernel configuration. You'll need to recompile your kernel if you make changes.

Table 1. Unix Shared Memory Variables

SHMMAX The maximum amount of shared memory, normally 131072 bytes
SHMMIN The minimum amount of shared memory, normally 1 byte
SHMMNI The maximum number of shared memory segments in the system, normally 100
SHMSEG The maximum number of shared memory segments per process, normally 6

The messages functions can send and receive messages to and from other processes. They are a simple and effective means of data exchange without having to use Unix domain sockets.

Using Shared Memory and Semaphores

When learning something new, any practical developer wants to start using this technology in practice, even writing a simple "Hello World!" program. It's normal, so I'd like to describe the basic information you need to know before you can create that simple test code.

Shared memory is the fastest form of IPC, but it requires synchronization between storing and fetching data. You must remember that when designing your IPC. What then is the algorithm of using shared memory? In simple terms, it is:

Synchronizing with the semaphore is necessary, because using a shared memory resource is almost the same as using a file resource, where locking and unlocking helps prevent corruption.

Now the question is, "How do I use semaphores?" It's actually pretty easy and understandable in example code. The only issue is that, as semaphores lock and unlock resources, they can block each other. Without a careful design, processes can compete with each other to obtain a semaphore, causing long delays while they wait for access. Then again, even in correctly designed code, there'll always be an upper performance limit in multiprocessor systems. For more detail, see any book dedicated to multiprocessing.

Here are a few examples that show how to use different IPC shared memory and semaphore functions. I've also included a longer example code listing at the end of this article. Here are the functions listed in the order of their appearance.

int sem_get (int key [, int max_acquire [, int perm]])

This function returns a semaphore's identifier. It will be positive on success and FALSE if an error occurs. Use the returned id to access the semaphore V with the given key.

If necessary, the semaphore can have access permissions as specified in the perm parameter. The default is 0666. The max_acquire parameter governs the number of processes that can access the semaphore simultaneously. This is 1 by default.

A second call to sem_get() for the same key will return another semaphore identifier, but both identifiers identify the same semaphore.

bool sem_acquire(int sem_identifier)

This function returns TRUE on success and FALSE on failure. It will block, if necessary, until it can acquire the requested semaphore. The process of trying to receive the semaphore will block endlessly if it exceeds the max_acquire setting.

All semaphores used in a process but not cleared explicitly will close automatically. This will generate a warning, though.

int shm_attach(int key [, int memsize [, int perm]])

This creates or opens a shared memory segment. shm_attach() returns the id to use to access the shared memory with the specified key. The memory will be mem_size bytes large. The default value is that of sysvshm.init_mem in the PHP configuration file (or 10,000 bytes, if that's not set). perm is an optional set of access permissions, 0666 by default.

The first call with a given key will create the segment. The second call with the same key will return another shared memory identifier, ignoring the other two parameters. Both identifiers permit the access to the same shared memory region.

mixed shm_get_var(int id, int variable_key)

This returns a variable, identified by variable_key from a shared memory region identified by id. The variable will remain in shared memory.

int shm_put_var(int shm_identifier, int variable_key, mixed variable)

This adds or updates a variable, identified by variable_key, in the shared memory segment identified by shm_identifier. It allows variables of all types.

bool sem_release(int sem_identifier)

This releases a semaphore if it's held by the current process. If not, it generates a warning. This function returns TRUE on success and FALSE if an error occurs.

After releasing a semaphore, call sem_acquire() again to reacquire it.

int shm_remove(int shm_identifier)

This deletes a shared memory segment, destroying all contained data.

bool sem_remove(int sem_identifier)

This deletes a semaphore identified by sem_identifier, if it was created with sem_get. It returns TRUE on success and FALSE if an error occurs. If there's no semaphore with the given id, it will generate a warning. The semaphore is not available after deletion.

Sample Shared Memory Code

With the descriptions out of the way, here's some sample code that uses semaphores and shared memory functions:

<?php
MEMSIZE = 512; //  size of shared memory to allocate
$SEMKEY = 1;   //  Semaphore key
$SHMKEY = 2;   //  Shared memory key

echo "Start.\n";

// Create a semaphore
$sem_id = sem_get($SEMKEY, 1);
if ($sem_id === false)
{
    echo "Failed to create semaphore";
    exit;
}
else
    echo "Created semaphore $sem_id.\n";

// Acquire the semaphore
if (! sem_acquire($sem_id))
{
    echo "Failed to acquire semaphore $sem_id.\n";
    sem_remove($sem_id);
    exit;
}
else
    echo "Success acquiring semaphore $sem_id.\n";

// Attach shared memory
$shm_id = shm_attach($SHMKEY, $MEMSIZE);
if ($shm_id === false)
{
    echo "Fail to attach shared memory.\n";
    sem_remove($sem_id);
    exit;
}
else
    echo "Success to attach shared memory : $shm_id.\n";

// Write variable 1
if (!shm_put_var($shm_id, 1, "Variable 1"))
{
    echo "Failed to put var 1 in shared memory $shm_id.\n";

    // Clean up nicely
    sem_remove($sem_id);
    shm_remove($shm_id);
    exit;
}
else
    echo "Wrote var1 to shared memory.\n";

// Write variable 2
if (!shm_put_var($shm_id, 2, "Variable 2"))
{
    echo "Failed to put var 2 on shared memory $shm_id.\n";

    // Clean up nicely
    sem_remove($sem_id);
    shm_remove ($shm_id);
    exit;
}
else
    echo "Wrote var2 to shared memory.\n";

// Read variable 1
$var1 = shm_get_var($shm_id, 1);
if ($var1 === false)
{
    echo "Failed to retreive Var 1 from Shared memory $shm_id, " .
         "return value=$var1.\n";
}
else
    echo "Read var1=$var1.\n";

// Read variable 1
$var2 = shm_get_var ($shm_id, 2);
if ($var1 === false)
{
     echo "Failed to retrive Var 2 from Shared memory $shm_id, " .
          "return value=$var2.\n";
}
else
    echo "Read var2=$var2.\n";

// Release semaphore
if (!sem_release($sem_id))
    echo "Failed to release $sem_id semaphore.\n";
else
    echo "Semaphore $sem_id released.\n";

// Remove shared memory segment
if (shm_remove ($shm_id))
    echo "Shared memory successfully removed.\n";
else
    echo "Failed to remove $shm_id shared memory.\n";

// Remove semaphore
if (sem_remove($sem_id))
    echo "Semaphore removed successfully.\n";
else
    echo "Failed to remove $sem_id semaphore.\n";

echo "End.\n";

?>

Now here's some sample code for various shared memory operations:

<?php
// Create 100 byte shared memory block with system id of 0xff3
$shm_id = shmop_open(0xff3, "c", 0644, 100);

if(!$shm_id)
{
    echo "Couldn't create shared memory segment\n";
}

// Get the size of shared memory block
$shm_size = shmop_size($shm_id);
echo "SHM Block Size: ". $shm_size . " has been created.\n";

// Write a test string into shared memory
$shm_bytes_written = shmop_write($shm_id, "my shared memory block", 0);

if($shm_bytes_written != strlen("my shared memory block"))
{
    echo "Couldn't write the entire length of data\n";
}

// Read back the string
$my_string = shmop_read($shm_id, 0, $shm_size);

if(!$my_string)
{
    echo "Couldn't read from shared memory block\n";
}

echo "The data inside shared memory was: ".$my_string."\n";

// Delete the block and close the shared memory segment

if(!shmop_delete($shm_id))
{
    echo "Couldn't mark shared memory block for deletion.";
}

shmop_close($shm_id);

?>

For more information about these or other functions, see the official PHP manual.

Alexander Prohorenko is a certified professional, who holds Sun Certified System Administrator and Sun Certified Java Programmer certifications.


Return to the PHP DevCenter.

Copyright © 2009 O'Reilly Media, Inc.