"Turn" initialized memory into shared memory with the SYSV API? - c

To create a new and empty shared memory segment I can do something like:
int shmid = shmget(IPC_PRIVATE, nbytes, IPC_CREAT | 0777);
int* new_array = (int*)shmat(shmid, NULL, 0);
Now suppose int* array owns a chunk of pre-existing data. Something like
int* array = malloc(2 * sizeof(int));
array[0] = 42;
array[1] = 24;
I'd like to turn this memory segment into a shared memory segment with the System V shared memory C API, on Linux.
How can I do this?
I've tried stuff like
int shmid = shmget(IPC_PRIVATE, nbytes, IPC_CREAT | 0777);
void* attach = shmat(shmid, array, SHM_RND);
and like
int shmid = shmget(IPC_PRIVATE, nbytes, IPC_CREAT | 0777);
array = (int*)shmat(shmid, 0, 0);
but either shmat returns (void*)-1 or int* array gets all its values set to zero.
Here's a full program that shows the failures:
/*
gcc shm0.c -o shm0 && ./shm0
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define NELEMENTS 3
void array_show(int* array, size_t nelements){
puts("");
for(int i=0; i < nelements; ++i)
printf("%d\n", *(array + i));
}
int main(){
const size_t nbytes = NELEMENTS * sizeof(int);
int* array;
posix_memalign((void**)&array, 16*16*16, nbytes);
int shmid = shmget(IPC_PRIVATE, nbytes, IPC_CREAT | 0777);
// 1st try...
void* attach = shmat(shmid, array, SHM_RND);
if(attach == (void*)-1)
puts("shmat() failed!");
// 2nd try...
memset(array, 1, nbytes); // Fill array with dummy data!
array_show(array, NELEMENTS);
array = (int*)shmat(shmid, 0, 0);
array_show(array, NELEMENTS);
if(array[1] == 0)
puts("array was reset!");
// 3rd try
array = (int*)shmat(shmid, array, 0); // Now accessing, say, array[0] yields a segfault!
shmdt(array);
}
Why SYSV shared memory and not mmap()? Because I'm using the X11 MIT shared memory extension, and, according to the docs:
... to be able to use this extension, your system must provide the SYSV shared memory primitives. There is not an mmap-based version of this extension. To use shared memory on Sun systems, you must have built your kernel with SYSV shared memory enabled ...
(If someone knows a way around this, or how to do this using shm_open(), I'd be delighted to hear about it.)

Related

Cannot attach shared memory in shared library

I would like to use shared memory in shared library to data exchange and operate socket.
In my library, when first process loads shared libary, shared memory is created in constructor.
Then, second process load shared library, I want to attach this shared memory.
But second process create other shared memory although key is same.
This is sample code.
char* sharedmemory;
__attribute__ ((constructor)) void attach_sharedmemory() {
key_t key = 10;
errno = 0
int seg_id = shmget(key, 1000, IPC_CREAT | IPC_EXCL | 0660);
if(seg_id == -1){
if(errno == EEXIST){
seg_id = shmget(key, 0, 0);
if(seg_id == -1){
exit(1);
}else{
shared_memory = (char*)(shmat(seg_id, 0, 0));
}
}else{
exit(1);
}
}else{
shared_memory = (char*)(shmat(seg_id, 0, 0));
}
}
void dllfunction1(){}
void dllfunction2(){}
ipcs command result is here.
Second process shmid is always first process's shmid + 1.
#result of ipcs
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000XXX 65536 user1 660 1000 1 dest <--- created first process
0x00000YYY 65537 user1 660 1000 1 dest <--- created second process
How can I fix this?
My enviroment is Ubuntu 20.04LTS.
Compiled GCC.
The second process calling shmget() fails due to the flag bit IPC_EXCL
as expected; although the next call to shmget() reused the same key and
flags changed to 0 (no IPC_EXCL), the size was 0 leading to a new segment.
By reusing the same key and size (again flags without IPC_EXCL), the second
process can get the same segment ID and attach. Sample code below is
based on the original with added shmem detach:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/shm.h>
static void *sharedmemory;
static int seg_id;
__attribute__ ((constructor))
void attach_shmem(void)
{
key_t key = 10;
size_t size = 1000;
seg_id = shmget(key, size, IPC_CREAT | IPC_EXCL | 0660);
if ((seg_id == -1) && (errno == EEXIST))
seg_id = shmget(key, size, 0);
if (seg_id == -1)
exit(1);
sharedmemory = shmat(seg_id, NULL, 0);
}
__attribute__ ((destructor))
void detach_shmem(void)
{
(void) shmdt(sharedmemory);
}

Find size of shared memory in C shmget

I was wondering if it is possible to obtain the size of a shared memory segment in C created from shmget without having the size of the segment as a part of the data? I'm trying to allocate a dynamic int array and need to find the size of the array in the child process.
Main process:
int sizeOfArray = 3;
int shm = shmget(IPC_PRIVATE, sizeof(int) * sizeOfArray, IPC_CREAT | 0666);
int *a = (int*) shmat(shm, NULL, 0);
a[0] = 0;
a[1] = 1;
a[2] = 2;
if (fork() == 0) {
char *args[3];
char shmID[11];
bzero(shmID, 11);
intToString(shm, shmID); // custom function that does what the name implies
args[0] = "slave";
args[1] = shmID;
args[2] = NULL;
execvp("slave", args);
return -1;
}
wait(NULL);
shmdt((void*) a);
shmctl(shm, IPC_RMID, NULL);
Child process (slave):
int shm = atoi(argv[1]);
int *ptr = (int*) shmat(shm, NULL, 0);
//TODO: find length of int array in shared memory
shmdt((void*) ptr);
return 0;
I found that if you use shmctl and the IPC_STAT flag, you can get the number of bytes allocated to the shared memory segment. Then you can just divide it by sizeof(int) to get the size of your array.
struct shmid_ds buf;
shmctl(shm, IPC_STAT, &buf);
int length = (int) buf.shm_segsz / sizeof(int);

Contigous allocation of memory for an array

As far as I understand, arrays are allocated contiguous blocks of memory. I wrote a program to test my understanding. It basically attaches a shared memory segment. Here is the code:
#include <stdio.h>
#include <sys/shm.h>
#include <sys/stat.h>
int main ()
{
int segment_id;
int* shared_memory;
struct shmid_ds shmbuffer;
int segment_size;
const int
shared_segment_size = 4096; //one page size
int *results;
int cpid=0;
int sum=0;
/* Allocate a shared memory segment. */
segment_id = shmget (IPC_PRIVATE, shared_segment_size,
IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
/* Attach the shared memory segment. Results now contain the starting address */
results =shmat (segment_id, 0, 0);
printf("%p\n",&results);
printf("%p\n",&results[1]);
printf("%p\n",&results[2]);
printf("%p\n",&results[3]);
return 0;
}
Here is the output:
0x7fffe4d62e40
0x7f6603987004
0x7f6603987008
0x7f660398700c
Can someone explain to me why the address of results[1] is:
0x7f6603987004
given that the address of results[0] is:
0x7fffe4d62e40
Shouldn't it be 0x7fffe4d62e44?
Am I missing something crucial?

Sharing an array of structs using mmap

I am trying to create an array of structs that is shared between a parent and child processes. I am getting a segmentation fault when trying to access the array data.
I feel certain that the problem has something to do with the way I'm using pointers, as this is an area I'm not very comfortable with.
Please note that I edited out most of the code that didn't seem relevant.
/* structure of Registration Table */
struct registrationTable{
int port;
char name[MAXNAME];
int req_no;
};
main() {
/* the registrationTable is to be a shared memory space with each child
process able to access and update. No concurrency controls are
implemented. The parent process is responsible for cleaning up after
the kiddies.
*/
struct registrationTable base_table[REG_TABLE_SIZE];
for (int i = 0; i < REG_TABLE_SIZE; i++) {
base_table[i].req_no = 0;
memset(base_table[i].name, '\0', MAXNAME);
base_table[i].port = 0;
}
printf("\nMapping Shared Memory\n");
//set up shared memory space
//void *mmap(void *addr, size_t length, int prot, int flags,
// int fd, off_t offset);
// addr = NONE, prot = PROT_NONE, flags = MAP_SHARED
struct registrationTable *table = mmap(base_table, sizeof(base_table),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
-1, 0);
while(1){
pid_t child = fork();
if (child == 0) {//is child
for(int i = 0; i < REG_TABLE_SIZE; i++) {
printf("\nExamining table looking for client at %s port: %d\n",
packet_reg.data, clientAddr.sin_port);
printf("\ntable[1].req_no: %d", ta[i].req_no);
//segmentation fault on next line
if (strcmp(table[i].name, packet_reg.data) == 0
&& table[i].port == clientAddr.sin_port) {
table[i].req_no++;
}
You haven't initialized content of the table after it was allocated by mmap. So it contains garbage. And so strcmp(table[i].name, packet_reg.data) has a great chance to go over allocated buffers and access e.g. non-allocated memory.
initialize table properly;
use strncmp for the comparison there.

How to use shared memory with Linux in C

I have a bit of an issue with one of my projects.
I have been trying to find a well documented example of using shared memory with fork() but to no success.
Basically the scenario is that when the user starts the program, I need to store two values in shared memory: current_path which is a char* and a file_name which is also char*.
Depending on the command arguments, a new process is kicked off with fork() and that process needs to read and modify the current_path variable stored in shared memory while the file_name variable is read only.
Is there a good tutorial on shared memory with example code (if possible) that you can direct me to?
There are two approaches: shmget and mmap. I'll talk about mmap, since it's more modern and flexible, but you can take a look at man shmget (or this tutorial) if you'd rather use the old-style tools.
The mmap() function can be used to allocate memory buffers with highly customizable parameters to control access and permissions, and to back them with file-system storage if necessary.
The following function creates an in-memory buffer that a process can share with its children:
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
void* create_shared_memory(size_t size) {
// Our memory buffer will be readable and writable:
int protection = PROT_READ | PROT_WRITE;
// The buffer will be shared (meaning other processes can access it), but
// anonymous (meaning third-party processes cannot obtain an address for it),
// so only this process and its children will be able to use it:
int visibility = MAP_SHARED | MAP_ANONYMOUS;
// The remaining parameters to `mmap()` are not important for this use case,
// but the manpage for `mmap` explains their purpose.
return mmap(NULL, size, protection, visibility, -1, 0);
}
The following is an example program that uses the function defined above to allocate a buffer. The parent process will write a message, fork, and then wait for its child to modify the buffer. Both processes can read and write the shared memory.
#include <string.h>
#include <unistd.h>
int main() {
char parent_message[] = "hello"; // parent process will write this message
char child_message[] = "goodbye"; // child process will then write this one
void* shmem = create_shared_memory(128);
memcpy(shmem, parent_message, sizeof(parent_message));
int pid = fork();
if (pid == 0) {
printf("Child read: %s\n", shmem);
memcpy(shmem, child_message, sizeof(child_message));
printf("Child wrote: %s\n", shmem);
} else {
printf("Parent read: %s\n", shmem);
sleep(1);
printf("After 1s, parent read: %s\n", shmem);
}
}
Here is an example for shared memory :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024 /* make it a 1K shared memory segment */
int main(int argc, char *argv[])
{
key_t key;
int shmid;
char *data;
int mode;
if (argc > 2) {
fprintf(stderr, "usage: shmdemo [data_to_write]\n");
exit(1);
}
/* make the key: */
if ((key = ftok("hello.txt", 'R')) == -1) /*Here the file must exist */
{
perror("ftok");
exit(1);
}
/* create the segment: */
if ((shmid = shmget(key, SHM_SIZE, 0644 | IPC_CREAT)) == -1) {
perror("shmget");
exit(1);
}
/* attach to the segment to get a pointer to it: */
if ((data = shmat(shmid, NULL, 0)) == (void *)-1) {
perror("shmat");
exit(1);
}
/* read or modify the segment, based on the command line: */
if (argc == 2) {
printf("writing to segment: \"%s\"\n", argv[1]);
strncpy(data, argv[1], SHM_SIZE);
} else
printf("segment contains: \"%s\"\n", data);
/* detach from the segment: */
if (shmdt(data) == -1) {
perror("shmdt");
exit(1);
}
return 0;
}
Steps :
Use ftok to convert a pathname and a project identifier to a System V IPC key
Use shmget which allocates a shared memory segment
Use shmat to attache the shared memory segment identified by shmid to the address space of the calling process
Do the operations on the memory area
Detach using shmdt
These are includes for using shared memory
#include<sys/ipc.h>
#include<sys/shm.h>
int shmid;
int shmkey = 12222;//u can choose it as your choice
int main()
{
//now your main starting
shmid = shmget(shmkey,1024,IPC_CREAT);
// 1024 = your preferred size for share memory
// IPC_CREAT its a flag to create shared memory
//now attach a memory to this share memory
char *shmpointer = shmat(shmid,NULL);
//do your work with the shared memory
//read -write will be done with the *shmppointer
//after your work is done deattach the pointer
shmdt(&shmpointer, NULL);
try this code sample, I tested it, source: http://www.makelinux.net/alp/035
#include <stdio.h>
#include <sys/shm.h>
#include <sys/stat.h>
int main ()
{
int segment_id;
char* shared_memory;
struct shmid_ds shmbuffer;
int segment_size;
const int shared_segment_size = 0x6400;
/* Allocate a shared memory segment. */
segment_id = shmget (IPC_PRIVATE, shared_segment_size,
IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
/* Attach the shared memory segment. */
shared_memory = (char*) shmat (segment_id, 0, 0);
printf ("shared memory attached at address %p\n", shared_memory);
/* Determine the segment's size. */
shmctl (segment_id, IPC_STAT, &shmbuffer);
segment_size = shmbuffer.shm_segsz;
printf ("segment size: %d\n", segment_size);
/* Write a string to the shared memory segment. */
sprintf (shared_memory, "Hello, world.");
/* Detach the shared memory segment. */
shmdt (shared_memory);
/* Reattach the shared memory segment, at a different address. */
shared_memory = (char*) shmat (segment_id, (void*) 0x5000000, 0);
printf ("shared memory reattached at address %p\n", shared_memory);
/* Print out the string from shared memory. */
printf ("%s\n", shared_memory);
/* Detach the shared memory segment. */
shmdt (shared_memory);
/* Deallocate the shared memory segment. */
shmctl (segment_id, IPC_RMID, 0);
return 0;
}
Here's a mmap example:
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*
* pvtmMmapAlloc - creates a memory mapped file area.
* The return value is a page-aligned memory value, or NULL if there is a failure.
* Here's the list of arguments:
* #mmapFileName - the name of the memory mapped file
* #size - the size of the memory mapped file (should be a multiple of the system page for best performance)
* #create - determines whether or not the area should be created.
*/
void* pvtmMmapAlloc (char * mmapFileName, size_t size, char create)
{
void * retv = NULL;
if (create)
{
mode_t origMask = umask(0);
int mmapFd = open(mmapFileName, O_CREAT|O_RDWR, 00666);
umask(origMask);
if (mmapFd < 0)
{
perror("open mmapFd failed");
return NULL;
}
if ((ftruncate(mmapFd, size) == 0))
{
int result = lseek(mmapFd, size - 1, SEEK_SET);
if (result == -1)
{
perror("lseek mmapFd failed");
close(mmapFd);
return NULL;
}
/* Something needs to be written at the end of the file to
* have the file actually have the new size.
* Just writing an empty string at the current file position will do.
* Note:
* - The current position in the file is at the end of the stretched
* file due to the call to lseek().
* - The current position in the file is at the end of the stretched
* file due to the call to lseek().
* - An empty string is actually a single '\0' character, so a zero-byte
* will be written at the last byte of the file.
*/
result = write(mmapFd, "", 1);
if (result != 1)
{
perror("write mmapFd failed");
close(mmapFd);
return NULL;
}
retv = mmap(NULL, size,
PROT_READ | PROT_WRITE, MAP_SHARED, mmapFd, 0);
if (retv == MAP_FAILED || retv == NULL)
{
perror("mmap");
close(mmapFd);
return NULL;
}
}
}
else
{
int mmapFd = open(mmapFileName, O_RDWR, 00666);
if (mmapFd < 0)
{
return NULL;
}
int result = lseek(mmapFd, 0, SEEK_END);
if (result == -1)
{
perror("lseek mmapFd failed");
close(mmapFd);
return NULL;
}
if (result == 0)
{
perror("The file has 0 bytes");
close(mmapFd);
return NULL;
}
retv = mmap(NULL, size,
PROT_READ | PROT_WRITE, MAP_SHARED, mmapFd, 0);
if (retv == MAP_FAILED || retv == NULL)
{
perror("mmap");
close(mmapFd);
return NULL;
}
close(mmapFd);
}
return retv;
}

Resources