Notes for Shared Memory
Shared memory allows multiple processes to share virtual memory space. One process creates or allocates the shared memory segment with size and access permissions. shared memory is identified by a unique identifier that shmget system call returns. The process then attaches the shared segment, causing it to be mapped into its current data space. Once created, and if permissions permit, other processes can attach to the shared memory segment and map it into their data space. Each process accesses the shared memory relative to its attachment address. For each process involved, the mapped memory appears to be no different from any other of its memory addresses. A process can detach from it. Normally the process that created the segment is the owner, but it can grant ownership to another process. The owner is responsible for removing it.
Creating
a Shared Memory segment.
shmget system call is used to 1:) create the shared memory segment and
generate the share memory data structure or 2:) to gain access to an existing segment.
int shmget(key_t key, int size, int shmflg);
where
size is the size in bytes of the shared memory segment. If shmget is used to
access an existing segment, then it can be 0.
key could be
1:)
IPC_PRIVATE,
2:) or is not associated with an existing shared memory identifier and
IPC_CREAT
3:) or is not associated with an existing shared memory identifier and IPC_CREAT along with
IPC_EXCL flag have been set. With
IPC_CREAT and IPC_EXCL set, the user can be assured of creating a unique shared
memory segment without inadvertently gaining access to a preexisting segment.
When shmget is successful, it returns an integer shared memory identifier. At creation time,
the system data structure shmid_ds is generated and initialized. It contains lot
of maintain information.
struct shmid_ds {
struct ipc_perm shm_perm;
/* operation permission struct */
size_t shm_segsz;
/* size of segment in bytes */
__time_t shm_atime;
/* time of last shmat()
*/
unsigned long int __unused1;
__time_t shm_dtime;
/* time of last shmdt()
*/
unsigned long int __unused2;
__time_t shm_ctime;
/* time of last change by shmctl() */
unsigned long int __unused3;
__pid_t shm_cpid;
/* pid of creator
*/
__pid_t shm_lpid;
/* pid of last shmop
*/
shmatt_t shm_nattch;
/* number of current attaches */
unsigned long int __unused4;
unsigned long int __unused5;
};
Shared
Memory Control.
shmctl system call permits the user to perform a number of generalized
control operations on an existing shared memory segment, and on the system
shared memory data structure.
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
The 2nd argument, cmd, specifies the operations shmctl is to perform.
They can be IPC_STAT, IPC_SET, IPC_RMID, SHM_LOCK and SHM_UNLOCK.
The 3rd argument is a reference to a structure of the type shmid_ds.
IPC_STAT— Return the current values of the shmid_ds structure for the memory segment indicated by the shmid value. The returned information is stored in a user-generated structure, which is passed by reference as the third argument to shmctl. To specify IPC_STAT, the process must have read permission for the shared memory segment.
IPC_SET— Modify a limited number of members in the permission structure found within the shmid_ds structure. The permission structure members that can be modified are shm_perm.uid, shm_perm.gid, and shm_perm.mode. The accessing process must have the effective ID of the superuser or have an ID that is equivalent to either the shm_perm.cuid or shm_perm.uid value. To modify structure members, the following steps are usually taken. A structure of the type shmid_ds is allocated. The structure is initialized to the current system settings by calling shmctl with the IPC_STAT flag set and passing the reference to the new shmd_ds structure. The appropriate members of the structure are then assigned their new values. Finally, with the cmd argument set to IPC_SET, the shmctl system call is invoked a second time and passed the reference to the modified structure. To carry out this modification sequence, the accessing process must have read and write permissions for the shared memory segment. When IPC_SET is specified, the shm_ctime member is automatically updated with the current time.
IPC_RMID— Remove the system data structure for the referenced shared memory identifier (shmid). When specifying IPC_RMID, an address value of 0 is used for buf. The 0 address value is cast to the proper type, with (shmid_ds *). Once all references to the shared memory segment are eliminated (i.e., shm_nattch equals 0), the system will remove the actual segment. If a shmctl system call, specifying IPC_RMID, is not done, the memory segment will remain active and associated with its key value.
SHM_LOCK— Lock, in memory, the shared memory segment referenced by the shmid argument. A locked shared segment is not swapped out by the system thus avoiding I/O faults when referenced. Locking can only be specified by processes that have an effective ID equal to that of the superuser.
SHM_UNLOCK— Unlock the shared memory segment referenced by the shmid argument. Once unlocked the shared segment can be swapped out. Again, this can only be specified by processes that have an effective ID equal to that of the superuser.
Shared
Memory Operations.
void *shmat(int shmid, const void*shmaddr, int shmflg);
int shmdt ( const void *shmaddr);
shmat is used to attach (map) the referenced shared memory segment into the calling process's data segment. Normally we set the 2nd argument, shmaddr, 0, the system picks the attachment address. The third argument, shmflg, is used to specify the access permissions for the shared memory segment and to request special attachment conditions, such as an aligned address or a read-only segment. By default, attached segments are accessible for reading and writing. If needed, the SHM_RDONLY flag can be bitwise ORed with the shmflg value to indicate a read-only segment. There is no flag to specify a write-only memory segment. The SHM_RND flag is used to specify whether or not the attachment address should be aligned on a page boundary.
shmdt system call has one argument, shmaddr, which is a reference to an attached memory segment. If shmdt is successful in detaching the memory segment, it returns a value of 0. It also sets shm_atime to the current time, shm_lpid to the ID of the calling process, and decrements shm_nattch by one. If shm_nattch becomes 0 and the memory segment is marked for deletion by the operating system, it is removed. If the shmdt call fails, it returns a value of -1 and sets errno.
Sample Code:
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
#define SHM_SIZE 30
using namespace std;
extern int etext, edata, end;
int main( ) {
int shmid;
char c, *shm, *s;
if ((shmid=shmget(IPC_PRIVATE,SHM_SIZE,IPC_CREAT|0660))<
0) {
perror("shmget fail");
return 1;
}
if ((shm = (char *)shmat(shmid, 0, 0)) == (char *) -1)
{
perror("shmat : parent");
return 2;
}
cout << "Addresses in parent" << endl;
cout << "shared mem: " << hex << int(shm) << " etext: "
<< &etext << "
edata: " << &edata
<< " end: " << &end
<< endl << endl;
s = shm;
// s now references shared mem
for (c='A'; c <= 'Z'; ++c)
// put some info there
*s++ = c;
*s='\0';
// terminate the sequence
cout << "In parent before fork,
memory is: " << shm << endl;
switch (fork( )) {
case -1:
perror("fork");
return 3;
default:
wait(0);
// let the child finish
cout << "In parent after
fork, memory is : " << shm << endl;
cout << "\nParent
removing shared memory" << endl;
shmdt(shm);
shmctl(shmid, IPC_RMID, (struct
shmid_ds *) 0);
break;
case 0:
cout << "In child after
fork, memory is : " << shm << endl;
for ( ; *shm; ++shm)
// modify shared memory
*shm += 32;
shmdt(shm);
break;
}
return 0;
}
iclx012$ ./a.out
Addresses in parent
shared mem: 9556c000 etext: 0x401116 edata: 0x501788 end: 0x5018a8
In parent before fork, memory is: ABCDEFGHIJKLMNOPQRSTUVWXYZ
In child after fork, memory is : ABCDEFGHIJKLMNOPQRSTUVWXYZ
In parent after fork, memory is : abcdefghijklmnopqrstuvwxyz
Parent removing shared memory
Using
a File as Shared Memory
The user also can map a file to a process's virtual memory address by using mmap system call. Unlike memory, the contents of a file are nonvolatile.
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
The first, start, is the address for attachment. Normally you set to 0 and let the system to choose for you.When the process terminates, the system will automatically unmap a region. But you can explicitly unmap pages of memory.
int munmap(void *start, size_t length);
It passes the starting address of the memory mapping (argument start and the size of the mapping (argument length. If the call is successful, it returns a value of 0. Future references to unmapped addresses generate a SIGSEGV which is sig:11.
Sampel code: //#define _GNU_SOURCE #include <iostream> #include <cstdio> #include <sys/types.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <string.h> sing namespace std; int main(int argc, char *argv[]){ int fd, changes, i, random_spot, kids[2]; struct stat buf; char *the_file, *starting_string="ABCDEFGHIJKLMNOPQRSTUVWXYZ"; if (argc != 3) { cerr << "Usage " << *argv << " file_name #_of_changes" << endl; return 1; } if ((changes = atoi(argv[2])) < 1) { cerr << "# of changes < 1" << endl; return 2; } if ((fd = open(argv[1], O_CREAT | O_RDWR, 0666)) < 0) { perror("file open"); return 3; } write(fd, starting_string, strlen(starting_string)); // Obtain size of file if (fstat(fd, &buf) < 0) { perror("fstat error"); return 4; } // Establish the mapping if ((the_file = (char *) mmap(0, (size_t) buf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == (void *) - 1) { perror("mmap failure"); exit(5); } for (i = 0; i < 2; ++i) if ((kids[i] = (int) fork()) == 0) while (1) { cout << "Child " << getpid() << " finds: " << the_file << endl; sleep(1); } srand((unsigned) getpid()); for (i = 0; i < changes; ++i) { random_spot = (int) (rand() % buf.st_size); *(the_file + random_spot) = '*'; sleep(1); } cout << "In parent, done with changes" << endl; for (i = 0; i < 2; ++i) kill(kids[i], 9); cout << "The file now contains: " << the_file << endl; return 0; } [iclx012]~/private> ./a.out /tmp/my 10 Child 7139 finds: ABCDEFGHIJKLMNOPQRSTUVWXY* Child 7140 finds: ABCDEFGHIJKLMNOPQRSTUVWXY* Child 7139 finds: ABCDEFGHIJKLMNOPQRSTU*WXY* Child 7140 finds: ABCDEFGHIJKLMNOPQRSTU*WXY* Child 7139 finds: ABCDEFGHIJKLMNO*QRSTU*WXY* Child 7140 finds: ABCDEFGHIJKLMNO*QRSTU*WXY* Child 7139 finds: A*CDEFGHIJKLMNO*QRSTU*WXY* Child 7140 finds: A*CDEFGHIJKLMNO*QRSTU*WXY* Child 7139 finds: A*CDEFGHIJKLMNO*QRS*U*WXY* Child 7140 finds: A*CDEFGHIJKLMNO*QRS*U*WXY* Child 7139 finds: A*CDEFGHIJKLMNO*QRS*U*WXY* Child 7140 finds: A*CDEFGHIJKLMNO*QRS*U*WXY* Child 7139 finds: A*CDEFGHIJKLMNO*QRS*U*WXY* Child 7140 finds: A*CDEFGHIJKLMNO*QRS*U*WXY* Child 7139 finds: A*CDE*GHIJKLMNO*QRS*U*WXY* Child 7140 finds: A*CDE*GHIJKLMNO*QRS*U*WXY* Child 7139 finds: A*CD**GHIJKLMNO*QRS*U*WXY* Child 7140 finds: A*CD**GHIJKLMNO*QRS*U*WXY* Child 7139 finds: A*CD**GHIJKLMNO*QRS***WXY* Child 7140 finds: A*CD**GHIJKLMNO*QRS***WXY* In parent, done with changes The file now contains: A*CD**GHIJKLMNO*QRS***WXY*
Summary
of Shared Memory
Share memory provides an efficient way of communication via the sharing the data that resides in memory.
The Shared memory can be accessed in non-serial (random) manner. It means that semaphore or other synchronization method must be used to coordinate access to shared memory segment.
shmget is used the create and gain access to an existing segment.
shmctl is used to obtain the status of a memory segment, set permissions, and remove a shared memory segment.
shmat and shmdt are used to attach and detach shared memory segments.
mmap system call is used th map the virtual memory space of a process to a file. It is very useful since the data a in file, and it wont lose after process exists.