I have 2 processes, process 1 creates a boost managed_shared_memory segment and process 2 opens this segment. Process 1 is then restarted and the start of process 1 has the following,
struct vshm_remove
{
vshm_remove()
{
boost::interprocess::shared_memory_object::remove("VMySharedMemory");
}
~vshm_remove()
{
boost::interprocess::shared_memory_object::remove("VMySharedMemory");
}
} vremover;
I understand that when process 1 starts or ends the remove method will be called on my shared memory but shouldnt it only remove it if Process 2 is not attached to it? I am attaching to the shared memory in process 2 using the following,
boost::interprocess::managed_shared_memory *vfsegment;
vfsegment = new boost::interprocess::managed_shared_memory(boost::interprocess::open_only, "VMySharedMemory");
I am noticing that the shared memory is removed regardless of Process 2 being connected.
I don't believe that there is any mention in the documentation that shared_memory_object::remove will fail if a process is attached.
Please see this section for reference: Removing shared memory. Particularly:
This function can fail if the shared memory objects does not exist or it's opened by another process.
This means that a call to shared_memory_object::remove("foo") will attempt to remove shared memory named "foo" no matter what.
The implementation of that function (source here) reflects that behavior:
inline bool shared_memory_object::remove(const char *filename)
{
try{
//Make sure a temporary path is created for shared memory
std::string shmfile;
ipcdetail::tmp_filename(filename, shmfile);
return ipcdetail::delete_file(shmfile.c_str());
}
catch(...){
return false;
}
}
In my experience with released production code, I've had success not calling shared_memory_object::remove until I no longer need access to the shared memory.
I wrote a very simple example main program that you might find helpful. It will attach to, create, or remove shared memory depending on how you run it. After compiling, try the following steps:
Run with c to create the shared memory (1.0K by default) and insert dummy data
Run with o to open ("attach to") the shared memory and read dummy data (reading will happen in a loop every 10 seconds by default)
In a separate session, run with r to remove the shared memory
Run again with o to try to open. Notice that this will (almost certainly) fail because the shared memory was (again, almost certainly) removed during the previous step
Feel free to kill the process from the second step
As to why step 2 above continues to be able to access the data after a call to shared_memory_object::remove, please see Constructing Managed Shared Memory. Specifically:
When we open a managed shared memory
A shared memory object is opened.
The whole shared memory object is mapped in the process' address space.
Mostly likely, because the shared memory object is mapped into the process' address space, the shared memory file itself is no longer directly needed.
I realize that this is a rather contrived example, but I thought something more concrete might be helpful.
#include <cctype> // tolower()
#include <iostream>
#include <string>
#include <unistd.h> // sleep()
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
int main(int argc, char *argv[])
{
using std::cerr; using std::cout; using std::endl;
using namespace boost::interprocess;
if (argc == 1) {
cout << "usage: " << argv[0] << " <command>\n 'c' create\n 'r' remove\n 'a' attach" << endl;
return 0;
}
const char * shm_name = "shared_memory_segment";
const char * data_name = "the_answer_to_everything";
switch (tolower(argv[1][0])) {
case 'c':
if (shared_memory_object::remove(shm_name)) { cout << "removed: " << shm_name << endl; }
managed_shared_memory(create_only, shm_name, 1024).construct<int>(data_name)(42);
cout << "created: " << shm_name << "\nadded int \"" << data_name << "\": " << 42 << endl;
break;
case 'r':
cout << (shared_memory_object::remove(shm_name) ? "removed: " : "failed to remove: " ) << shm_name << endl;
break;
case 'a':
{
managed_shared_memory segment(open_only, shm_name);
while (true) {
std::pair<int *, std::size_t> data = segment.find<int>( data_name );
if (!data.first || data.second == 0) {
cerr << "Allocation " << data_name << " either not found or empty" << endl;
break;
}
cout << "opened: " << shm_name << " (" << segment.get_segment_manager()->get_size()
<< " bytes)\nretrieved int \"" << data_name << "\": " << *data.first << endl;
sleep(10);
}
}
break;
default:
cerr << "unknown command" << endl;
break;
}
return 0;
}
One additional interesting thing - add one more case:
case 'w':
{
managed_shared_memory segment(open_only, shm_name);
std::pair<int *, std::size_t> data = segment.find<int>( data_name );
if (!data.first || data.second == 0) {
cerr << "Allocation " << data_name << " either not found or empty" << endl;
break;
}
*data.first = 17;
cout << "opened: " << shm_name << " (" << segment.get_segment_manager()->get_size()
<< " bytes)\nretrieved int \"" << data_name << "\": " << *data.first << endl;
}
break;
The aditional option 'w' causes that the memory be attached and written '17' instead ("the most random random number"). With this you can do the following:
Console 1: Do 'c', then 'a'. Reports the memory created with value 42.
Console 2: Do 'w'. On Console1 you'll see that the number is changed.
Console 2: Do 'r'. The memory is successfully removed, Console 1 still prints 17.
Console 2: Do 'c'. It will report memory as created with value 42.
Console 2: Do 'a'. You'll see 42, Console 1 still prints 17.
This confirms - as long as it works the same way on all platforms, but boost declares that it does - that you can use this way to send memory blocks from one process to another, while the "producer" only needs confirmation that the "consumer" attached the block so that "producer" can now remove it. The consumer also doesn't have to detach previous block before attaching the next one.
Related
I have some production-critical code that has to keep running.
think of the code as
while (true){
init();
do_important_things(); //segfault here
clean();
}
I can't trust the code to be bug-free, and I need to be able to log problems to investigate later.
This time, I know for a fact somewhere in the code there is a segmentation fault getting thrown, and I need to be able to at least log that, and then start everything over.
Reading here there are a few solutions, but following each one is a flame-war claiming the solution will actually do more harm than good, with no real explanation. I also found this answer which I consider using, but I'm not sure it is good for my use case.
So, what is the best way to recover from segmentation fault on C++?
I suggest that you create a very small program that you make really safe that monitors the buggy program. If the buggy program exits in a way you don't like, restart the program.
Posix example:
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <iostream>
int main(int argc, char* argv[]) {
if(argc < 2) {
std::cerr << "USAGE: " << argv[0] << " program_to_monitor <arguments...>\n";
return 1;
}
while(true) {
pid_t child = fork(); // create a child process
if(child == -1) {
std::perror("fork");
return 1;
}
if(child == 0) {
execvp(argv[1], argv + 1); // start the buggy program
perror(argv[1]); // starting failed
std::exit(0); // exit with 0 to not trigger a retry
}
// Wait for the buggy program to terminate and check the status
// to see if it should be restarted.
if(int wstatus; waitpid(child, &wstatus, 0) != -1) {
if(WIFEXITED(wstatus)) {
if(WEXITSTATUS(wstatus) == 0) return 0; // normal exit, terminate
std::cerr << argv[0] << ": " << argv[1] << " exited with "
<< WEXITSTATUS(wstatus) << '\n';
}
if(WIFSIGNALED(wstatus)) {
std::cerr << argv[0] << ": " << argv[1]
<< " terminated by signal " << WTERMSIG(wstatus);
if(WCOREDUMP(wstatus)) std::cout << " (core dumped)";
std::cout << '\n';
}
std::cout << argv[0] << ": Restarting " << argv[1] << '\n';
} else {
std::perror("wait");
break;
}
}
}
This is for "homework" but this is not an algorithm question, but rather a programming issue. As part of a project for my Data Structures class, I have to write a class to act as a database. That part is done. I am not asking about the algorithm, but rather, trying to isolate what is clearly a stupid bug on my part.
PeopleDB has two constructors, the default one and one that takes as a parameter an input file and reads it into the database to initialize it.
Here is the code snippet, the problem is described below it:
#include "People.h" // People class definition
#include "PeopleDB.h" // People database class
#include "PrecondViolatedExcep.h"
using namespace std;
int main(int argc, char *argv[])
{
// Define variables
string infilename;
PeopleDB mydb;
// Get the filename of the text file to process
infilename = argv[1];
// Try to open import the data into a database instance
try
{
cout << "Attempting to import DB entries from "<< infilename << endl;
PeopleDB mydb(infilename);
cout << "# A total of "<< mydb.countEntries() << " DB entries loaded." << endl;
}
catch(PrecondViolatedExcep e)
{
cout << e.what() << endl;
cout << "Exiting program.";
exit(1);
}
// Display database contents
cout << endl;
cout << "# A total of "<< mydb.countEntries() << " DB entries found before display." << endl;
return 0;
} // end main
The problem is if I don't include the PeopleDB mydb; constructor at the top of the main() loop, the compiler barfs saying it doesn't recognize the mydb.countEntries() in the second to last line of the main loop. But if I do include it, it is clear that the mydb within the try loop doesn't survive because the output of the program is:
Attempting to import DB entries from testinput.txt
# A total of 7 DB entries loaded.
# A total of 0 DB entries loaded.
I didn't want to use the same variable (mydb) twice (I actually assumed this would error out during compiling), but for some reason creating the mydb instance of PeopleDB inside the try block doesn't seem to survive to be outside the block. I am sure this is something stupid on my part, but I am not seeing it. It has been a long day, so any suggestions would be appreciated.
You declare two independent mydb objects.
Either perform all actions in the try-catch block, or move connecting to another function.
PeopleDB connect(const std::string& infilename) {
try
{
cout << "Attempting to import DB entries from "<< infilename << endl;
PeopleDB mydb(infilename);
cout << "# A total of "<< mydb.countEntries() << " DB entries loaded." << endl;
return mydb;
}
catch(PrecondViolatedExcep e)
{
cout << e.what() << endl;
cout << "Exiting program.";
exit(1);
}
return PeopleDB{};
}
int main(int argc, char *argv[])
{
// Get the filename of the text file to process
string infilename = argv[1];
PeopleDB mydb = connect(infilename);
// Display database contents
cout << endl;
cout << "# A total of "<< mydb.countEntries() << " DB entries found before display." << endl;
return 0;
} // end main
You are creating two objects myDb of type PeopleDB: one at the beginning of main, the other one in the try block. That latter one loads the data, but gets gets destroyed with the end of that try block's scope.
The second one being printed is the one created in the main block, and that one has never loaded the data.
There are multiple ways to fix that, e.g. provide a method to load data and call it inside the try block. Another option is to copy/move/swap the "inside" one with the "outside" one before the try block ends (but I'd provide different names in such case). Your call, but bottom line is: at that point you have two different objects: one the data is loaded to, and the other one it is printed from (with empty results).
Try using move-assignment inside the try block:
mydb = std::move(PeopleDB(infilename));
The reason why I suggested this, is because it avoids creating a new object inside the try block because this object will disappear after the scope of the try block ends.
The reason for using move is to prevent creating the object twice:
once with the constructor call
another with the copy-constructor call
However, now I realize that the std::move is redundant because PeopleDB(infilename) is already an rvalue reference, and compiler will be smart enough to do the move itself.
So my new suggestion is to just do:
mydb = PeopleDB(infilename);
I am having difficulty using shared memory. I believe I am misunderstanding how shared memory is supposed to work. I am trying to create a template class for my project to wrap the POSIX Shared Memory API. When I call mmap() for the second time, in the same process, I am expecting to see the same ptr returned as the first time. This is not what I am seeing. My second address is offset by 0x1000 (I assume this is the page boundary). When I write to the first memory mapped location, the data does not show up in the second.
I thought possibly that the data was not getting syncd, so I tried calling msync(). This did not help.
I am most suspect of the different Addresses returned by mmap(). It seems like this might be a pointer-pointer, but no of the documentation or examples show anything about pointer-pointers. So...
Obviously, this leads me to believe either I am doing something wrong or misunderstand how named shared memory is supposed to work within the same process.
I have been pouring over the man pages and all over SO looking for answers to no avail.
Could some please tell me what I am doing wrong to access the same named shared memory location? Or point me to answer that provides the explanation I require to get me on my way again.
Please Excuse all the debug cout and excessive documentation, I was trying to understand/learn shared_memory API.
NOTE: I understand that there are better ways to share data within the same process. But, this is only a short test driver and this template would be used for in multi-process environment.
EDIT: Im not sure it matters, but I am trying to run on Suse Linux 3.0.101
"Help me Obi-Wan Kenobi, you're my only hope!"
shmem.h
// ****************************************************************************
// POSIX Shared Memory
// as document by Richard Stevens
// "UNIX Network Programming: Interprocess Communications" Vol 2, 2nd Ed.
// -------------------
//
// Shared memory is the fastest form of IPC available, because one copy of the
// data in the shared memory is available to all the threads or processes that
// share the memory. Some form of synchronization is normally required,
// however, to coordinate the threads or processes that are sharing the memory.
//
// POSIX provides two ways to share memory between unrelated processes.
// 1. Memory-mapped files: a file is opened by open, and the resulting
// descriptor is mapped into the address space of the process by mmap.
// Memory-mapped files can also be shared between unrelated processes.
// 2. Shared memory objects: the function shm_open opens a POSIX IPC name
// (perhaps a pathname in the filesystem), returning a descriptor that
// is then mapped into the address space of the process by mmap.
//
// Both techniques require the call to mmap. What differs is how the descriptor
// that is an argument to mmap is obtained: by open or shm_open.
//
// ****************************************************************************
#ifndef SHMEM_H_
#define SHMEM_H_
#include <errno.h> // error checking
#include <fcntl.h> // O_ constants
#include <semaphore.h> // semaphore API
#include <stddef.h> // defines NULL
#include <sys/mman.h> // shared memory API
#include <sys/stat.h> // mode constants
#include <unistd.h> // for close()
#include <iostream>
using namespace std;
template <class T, long count = 1>
class shmem
{
public:
// ------------------------------------------------------------------------
shmem(const char* name) :
pName(name), pShmData(0), mShmFd(0), mCreated(true)
{
cout << "START: shmem(\"" << pName << "\", " << count << ") Constructor" << endl<< flush;
// --------------------------------------------------------------------
// The two-step process involved with POSIX shared memory requires:
// 1. calling shm_open, specifying a name argument, to either
// create a new shared memory object or to open an existing
// shared memory object, followed by
// 2. calling mmap to map the shared memory into the address space
// of the calling process.
int flags = O_RDWR|O_CREAT|O_EXCL;
mode_t mode = S_IRUSR|S_IWUSR;
// flag indicating that the shared memory is the same as the data
// passed in
bool valid = true;
// Determine the amount of memory should include the
// header + the data buffer
const size_t len = sizeof(shmem_data_t);
cout << "Shmem_open()... "<< flush;
mShmFd = shm_open(pName, flags, mode);
// Check to see if the shared memory has been created yet
if (mShmFd == -1)
{
cout << "failed. ********************* errno: " << errno << endl<< flush;
// Remove flags (O_EXCL, O_CREAT) and try to open shared memory
// that already exists
flags &= ~O_EXCL;
flags &= ~O_CREAT;
cout << "Shmem_open (Again)... "<< flush;
mShmFd = shm_open(pName, flags, mode);
// Check to see if an error occurred while trying to open
valid = (mShmFd != -1);
if (valid)
{
cout << "success!" << endl<< flush;
// Indicate that the shared memory already existed
mCreated = false;
}
else
{
cout << "failed. ********************* errno: " << errno << endl<< flush;
}
} else
{
cout << "success!" << endl << flush;
}
cout << "mmap()... "<< flush;
// The mmap function maps a POSIX shared memory object (T) + Header
// into the address space of a process.
pShmData = reinterpret_cast<shmem_data_t*> (
mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, mShmFd, 0));
if (pShmData == NULL)
{
int error = errno;
switch (error)
{
default:
// Undefined Error
cout << "failed: ********************* errno: " << error << endl<< flush;
break;
}
} else
{
cout << "success: " << hex << "0x" << pShmData << dec << endl << flush;
}
// Check to see if we are the first user to request this shared memory
// location.
if (mCreated)
{
cout << "CREATED!" << endl;
cout << "Initial Header Data: Size " << pShmData->size << endl;
cout << "Initial Header Data: Count " << pShmData->len << endl;
// Initialize the header if we created the SHM
cout << "sem_init()" << endl<< flush;
sem_init(&pShmData->mutex,1,1);
cout << "sem_wait()... " << endl<< flush;
sem_wait(&pShmData->mutex);
cout << "Got It!" << endl<< flush;
pShmData->size = len;
pShmData->len = count;
cout << "release semaphore" << endl<< flush;
sem_post(&pShmData->mutex);
cout << "Initialization complete" << endl<< flush;
cout << "Header Data: Size " << pShmData->size << endl;
cout << "Header Data: Count " << pShmData->len << endl;
} else if (valid)
{
cout << "Validating Shared Memory... " ;
// Validate the Shared Memory that was acquired
valid &= (pShmData->size == len);
valid &= (pShmData->len == count);
if (valid)
cout << "success!" << endl<< flush;
else
cout << "failed. ********************* " << endl<< flush;
cout << "Header Data: Size " << pShmData->size << endl;
cout << "Header Data: Count " << pShmData->len << endl;
}
else
{
shm_unlink(pName);
exit(1);
}
// FIXME: What should we do if we aren't valid?!
cout << "END: Shmem Constructor" << endl<< flush;
}
// ------------------------------------------------------------------------
// Copy Constructor - Increment Use count for Shared Memory.
shmem(const shmem& that) :
pName(that.pName), pShmData(0), mShmFd(0)
{
cout << "START: shmem Copy Constructor" << endl << flush;
// --------------------------------------------------------------------
// The two-step process involved with POSIX shared memory requires:
// 1. calling shm_open, specifying a name argument, to either
// create a new shared memory object or to open an existing
// shared memory object, followed by
// 2. calling mmap to map the shared memory into the address space
// of the calling process.
int flags = O_RDWR;
mode_t mode = S_IRUSR|S_IWUSR;
// flag indicating that the we allocated valid shared memory is the
// same as the data passed in
bool valid = true;
// Determine the amount of memory should include the
// header + the data buffer
const size_t len = sizeof(shmem_data_t);
mShmFd = shm_open(pName, flags, mode);
// Check to see if an error occurred while trying to open
valid = (mShmFd != -1);
// The mmap function maps a POSIX shared memory object (T) + Header
// into the address space of a process.
pShmData = mmap(NULL, that.mShmFd->size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, mShmFd, 0);
cout << "close()... "<< flush;
// The close() function will deallocate the file descriptor.
// All outstanding record locks owned by the process on the file
// associated with the file descriptor will be removed (that is,
// unlocked).
//
// If a shared memory object remains referenced at the last close (that is, a
// process has it mapped), then the entire contents of the memory object persist
// until the memory object becomes unreferenced. If this is the last close of a
// memory object and the close results in the memory object becoming
// unreferenced, and the memory object has been unlinked, then the memory object
// will be removed.
close(mShmFd);
cout << "success." << endl<< flush;
cout << "END: shmem Copy Constructor" << endl << flush;
}
// ------------------------------------------------------------------------
virtual ~shmem()
{
cout << "START: ~shmem() Destructor" << endl<< flush;
if (mCreated)
{
cout << "shm_unlink( \"" << pName << "\")... "<< flush;
// The shm_unlink function removes the name of a shared memory object.
// As with all the other unlink functions, unlinking a name has no
// effect on existing references to the underlying object, until all
// references to that object are closed. Unlinking a name just prevents
// any subsequent call to open, mq_open, or sem_open from succeeding.
if(shm_unlink(pName) == -1)
{
int error = errno;
switch (error)
{
case EACCES:
// Permission is denied to unlink the named shared memory
// object.
cout << "Failed: ********************* EACCES " << endl<< flush;
break;
case ENAMETOOLONG:
// The length of the name argument exceeds {PATH_MAX} or a
// pathname component is longer than {NAME_MAX}.
cout << "Failed: ********************* ENAMETOOLONG" << endl<< flush;
break;
case ENOENT:
// The named shared memory object does not exist.
cout << "Failed: ********************* ENOENT" << endl<< flush;
break;
default:
// Undefined Error
cout << "Failed: ********************* <UNKNOWN> errno: " << error << endl<< flush;
break;
}
} else
{
cout << "Success!" << endl<< flush;
}
}
cout << "close()... " << flush;
// The close() function will deallocate the file descriptor.
// All outstanding record locks owned by the process on the file
// associated with the file descriptor will be removed (that is,
// unlocked).
//
// If a shared memory object remains referenced at the last close (that is, a
// process has it mapped), then the entire contents of the memory object persist
// until the memory object becomes unreferenced. If this is the last close of a
// memory object and the close results in the memory object becoming
// unreferenced, and the memory object has been unlinked, then the memory object
// will be removed.
close(mShmFd);
cout << "success." << endl << flush;
cout << "END: ~shmem() Destructor" << endl<< flush;
}
// ------------------------------------------------------------------------
// Returns address only to the indexed object in shared memory
T* Obj_Addr(uint32_t n = 0)
{
cout << "shmem.Obj_Addr()" << endl << flush;
return &pShmData->buf[n];
}
// ------------------------------------------------------------------------
// sync...
void Sync()
{
cout << "shmem.Sync()... ";
if (msync(pShmData, sizeof(shmem_data_t), MS_SYNC) == -1)
{
cout << "failed: ********************* errno: " << errno << endl<< flush;
} else
{
cout << "success. " << endl << flush;
}
}
// ------------------------------------------------------------------------
// Returns reference only to the indexed object in shared memory
T& Obj(uint32_t n = 0)
{
cout << "shmem.Obj()" << endl << flush;
return pShmData->buf[n];
}
// ------------------------------------------------------------------------
// Returns reference only to the indexed object in shared memory
T& operator[] (uint32_t n)
{
cout << "Accessing shmem[" << n << "] == " << flush;
cout << pShmData->buf[n] << "!" << endl << flush;
return pShmData->buf[n];
}
private:
// ------------------------------------------------------------------------
// Hide default constructor
shmem() : pName(0), pShmData(0), mShmFd(0)
{
}
private:
struct shmem_data_t
{
size_t size;
uint32_t len;
sem_t mutex;
T buf[count];
};
const char* pName;
shmem_data_t* pShmData;
int mShmFd;
// Flag indicating that we created the shared memory
bool mCreated;
};
#endif /* SHMEM_H_ */
main.cpp
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <iostream> // ** FIXME ** DEBUG
using namespace std;
#include "stdint.h"
#include "shmem.h"
bool done = false;
// ----------------------------------------------------------------------------
void my_handler(int s)
{
cout << "Goodbye! SIG: " << s << endl << flush;
done = true;
}
// ----------------------------------------------------------------------------
void test_shmem()
{
cout << endl << endl << "Testing Shmem Template" << endl;
cout << "-------------------------------------------" << endl;
shmem<int,13> x("/jco");
cout << "-------------------------------------------" << endl;
shmem<int,13> y("/jco");
cout << "-------------------------------------------" << endl;
x[5] = 7;
x.Sync();
cout << "-------------------------------------------" << endl;
cout << "X[5] = " << x[5] << endl;
cout << "-------------------------------------------" << endl;
cout << "Y[5] = " << y[5] << endl;
cout << "-------------------------------------------" << endl;
cout << endl << "*** Testing Complete." << endl << endl;
sleep(10);
}
// ----------------------------------------------------------------------------
int main()
{
cout << "MAIN" << endl;
struct sigaction sigIntHandler;
sigIntHandler.sa_handler = my_handler;
sigemptyset(&sigIntHandler.sa_mask);
sigIntHandler.sa_flags = 0;
sigaction(SIGINT, &sigIntHandler, NULL);
test_shmem();
// RUN
while(not done)
{
sleep(1);
}
return 0;
}
console output:
MAIN
Testing Shmem Template
-------------------------------------------
START: shmem("/jco", 13) Constructor
Shmem_open()... success!
mmap()... success: 0x0x7f32113ad000
CREATED!
Initial Header Data: Size 0
Initial Header Data: Count 0
sem_init()
sem_wait()...
Got It!
release semaphore
Initialization complete
Header Data: Size 104
Header Data: Count 13
END: Shmem Constructor
-------------------------------------------
START: shmem("/jco", 13) Constructor
Shmem_open()... failed. ********************* errno: 17
Shmem_open (Again)... success!
mmap()... success: 0x0x7f32113ac000
Validating Shared Memory... failed. *********************
Header Data: Size 0
Header Data: Count 0
END: Shmem Constructor
-------------------------------------------
Accessing shmem[5] == 0!
shmem.Sync()... success.
-------------------------------------------
Accessing shmem[5] == 7!
X[5] = 7
-------------------------------------------
Accessing shmem[5] == 0!
Y[5] = 0
-------------------------------------------
*** Testing Complete.
START: ~shmem() Destructor
close()... success.
END: ~shmem() Destructor
START: ~shmem() Destructor
shm_unlink( "/jco")... Success!
close()... success.
END: ~shmem() Destructor
Goodbye! SIG: 2
Edit: my first answer completely missed the mark. So I feel obligated to contribute something useful.
Both Petesh and BЈовић gave correct answers. First, you should not be using MAP_ANONYMOUS. Second, you should realize that the (virtual) address that you get back from mmap will not be the same as the first one. When you call mmap twice, you are creating two separate mappings to the same shared memory. But you can use either of the two addresses and they will be pointing at the same piece of shared memory.
I wrote this small program to demonstrate. It basically does what your program does, and shows that even though the two mmap calls return two different addresses, both addresses are reading and writing to the same shared memory.
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#define TEMP_FILENAME "shm.tmp"
int main(void)
{
int fd1 = shm_open(TEMP_FILENAME, O_CREAT | O_RDWR, 0777);
int fd2 = shm_open(TEMP_FILENAME, O_RDWR, 0777);
int *p1, *p2;
int buf[1024] = {0x12345678};
// Write initial contents to shared memory.
write(fd1, buf, 4096);
p1 = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd1, 0);
p2 = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd2, 0);
printf("fd1 = %d, p1 = %p\n", fd1, p1);
printf("fd2 = %d, p2 = %p\n", fd2, p2);
printf("p1[0] = 0x%08x, p2[0] = 0x%08x\n", p1[0], p2[0]);
p1[0] = 0xdeadbeef;
printf("p1[0] = 0x%08x, p2[0] = 0x%08x\n", p1[0], p2[0]);
close(fd2);
close(fd1);
shm_unlink(TEMP_FILENAME);
return 0;
}
And the output:
fd1 = 3, p1 = 0x7f2b3d434000
fd2 = 4, p2 = 0x7f2b3d433000
p1[0] = 0x12345678, p2[0] = 0x12345678
p1[0] = 0xdeadbeef, p2[0] = 0xdeadbeef
From mmap() man pages :
mmap() creates a new mapping in the virtual address space of the calling process.
So, what you get is a virtual address, matching to the same physical address. Since you are mmap()-ing the same memory region in 2 different processes, you are going to get different virtual address.
You should read more about linux memory management (for example here).
You've got an incorrect argument to mmap - the MAP_ANONYMOUS option.
What that does is explicitly ignore the file descriptor from shm_open and instead creates a private mapping for the process.
The manpage for mmap for MAP_ANONYMOUS indicates:
The mapping is not backed by any file; its contents are initialized to zero. The fd and offset arguments are ignored.
I am having trouble with shared memory. I have one process that creates and writes to a shared memory segment just fine. But I cannot get a second process to attach that same existing segment. My second process can create a new shared segment if I use IPC_CREATE flag but I need to attach to the existing shared segment that was created by the 1st process.
This is my code in the 2nd process:
int nSharedMemoryID = 10;
key_t tKey = ftok("/dev/null", nSharedMemoryID);
if (tKey == -1) {
std::cerr << "ERROR: ftok(id: " << nSharedMemoryID << ") failed, " << strerror(errno) << std::endl;
exit(3);
}
std::cout << "ftok() successful " << std::endl;
size_t nSharedMemorySize = 10000;
int id = shmget(tKey, nSharedMemorySize, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (id == -1) {
std::cerr << "ERROR: shmget() failed, " << strerror(errno) << std::endl << std::endl;
exit(4);
}
std::cout << "shmget() successful, id: " << id << std::endl;
unsigned char *pBaseSM = (unsigned char *)shmat(id, (const void *)NULL, SHM_RDONLY);
if (pBaseSM == (unsigned char *)-1) {
std::cerr << "ERROR: shmat() failed, " << strerror(errno) << std::endl << std::endl;
exit(5);
}
std::cout << "shmat() successful " << std::endl;
The problem is that the 2nd process always errors out on the call to shmget() with a "No such file or directory" error. But this is the exact same code I used in the 1st process and it works just fine there. In the 1st process that created the shared segment, I can write to the memory segment, I can see it with "ipcs -m" Also, if I get the shmid from the "ipcs -m" command of the segment and hard code it in my 2nd process and the 2nd process can attach to it just fine. So the problem seems to be generation of the common id that both processes use to identify a single shared segment.
I have several questions:
(1) Is there an easier way to get the shmid of an existing shared memory segment? It seems crazy to me that I have to pass three separate parameters from the 1st process (that created the segment) to the 2nd process just so the 2nd process can get the same shared segment. I can live with having to pass 2 parameters: the file name like "/dev/null" and the same shared id (nSharedMemoryID in my code). But the size of the segment that has to be passed to the shmget() routine in order to get the shmid seems senseless because I have no idea of exactly how much memory was actually allocated (because of the page size issues) so I cannot be certain it is the same.
(2) does the segment size that I use in the 2nd process have to be the same as the size of the segment used to initially create the segment in the 1st process? I have tried to specify it as 0 but I still get errors.
(3) likewise, do the permissions have to be the same? that is, if the shared segment was created with read/write for user/group/world, can the 2nd process just use read for user? (same user for both processes).
(4) and why does shmget() fail with the "No such file or directory" error when the file "/dev/null" obviously exists for both processes? I am assuming that the 1st process does not put some kind of a lock on that node because that would be senseless.
Thanks for any help anyone can give. I have been struggling with this for hours--which means I am probably doing something really stupid and will ultimately embarrass myself when someone points out my error :-)
thanks,
-Andres
(1) as a different way: the attaching process scan the existing segments of the user, tries to attach with the needed size, check for a "magic byte sequence" at the beginning of the segment (to exclude other programs of the same user). Alternatively you can check if the process attached is the one that you expect. If one of the steps fails, this is the first one and will create the segment... cumbersome yes, I saw it in a code from the '70s.
Eventually you can evaluate to use the POSIX compliant shm_open() alternative - should be simpler or at least more modern...
(2) Regarding the size, it's important that the size specified be less/equal than the size of the existing segment, so no issues if it's rounded to the next memory page size. you get the EINVAL error only if it's larger.
(3) the mode flags are only relevant when you create the segment the first time (mostly sure).
(4) The fact that shmget() fail with the "No such file or directory" means only that it hasn't found a segment with that key (being now pedantic: not id - with id we usually refer to the value returnet by shmget(), used subsequently) - have you checked that the tKey is the same? Your code works fine on my system. Just added a main() around it.
EDIT: attached the working program
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char **argv) {
int nSharedMemoryID = 10;
if (argc > 1) {
nSharedMemoryID = atoi(argv[1]);
}
key_t tKey = ftok("/dev/null", nSharedMemoryID);
if (tKey == -1) {
std::cerr << "ERROR: ftok(id: " << nSharedMemoryID << ") failed, " << strerror(errno) << std::endl;
exit(3);
}
std::cout << "ftok() successful. key = " << tKey << std::endl;
size_t nSharedMemorySize = 10000;
int id = shmget(tKey, nSharedMemorySize, 0);
if (id == -1) {
std::cerr << "ERROR: shmget() failed (WILL TRY TO CREATE IT NEW), " << strerror(errno) << std::endl << std::endl;
id = shmget(tKey, nSharedMemorySize, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | IPC_CREAT);
if (id == -1) {
std::cerr << "ERROR: shmget() failed, " << strerror(errno) << std::endl << std::endl;
exit(4);
}
}
std::cout << "shmget() successful, id: " << id << std::endl;
unsigned char *pBaseSM = (unsigned char *)shmat(id, (const void *)NULL, SHM_RDONLY);
if (pBaseSM == (unsigned char *)-1) {
std::cerr << "ERROR: shmat() failed, " << strerror(errno) << std::endl << std::endl;
exit(5);
}
std::cout << "shmat() successful " << std::endl;
}
EDIT: output
$ ./a.out 33
ftok() successful. key = 553976853
ERROR: shmget() failed (WILL TRY TO CREATE IT NEW), No such file or directory
shmget() successful, id: 20381699
shmat() successful
$ ./a.out 33
ftok() successful. key = 553976853
shmget() successful, id: 20381699
shmat() successful
SOLUTION - after in-chat (wow SO has a chat!) discussion:
At the end the problem was that in the original code he was calling shmctl() later on to tell to detach the segment as the last process detached it, before the other process was attached.
The problem is that this in fact make the segment private. It's key is marked as 0x00000000 by ipcs -m and cannot be attached anymore by other processes - it's in fact marked for lazy deletion.
I just want to post the result of all the help Sigismondo gave me and post the solution to this issue just in case anyone else has the same problem.
The clue was using "ipcs -m" and noticing that the key value was 0 which means that the shared segment is private and so the 2nd process could not attach to it.
An additional quirk was this: I was calling the following:
int nReturnCode = shmctl(id, IPC_RMID, &m_stCtrlStruct);
My intent was to set the mode for the segment so that it would be deleted when all processes that are using it have exited. However, this call has the side effect of making the segment private even though it was created without using the IPC_EXCL flag.
Hopefully this will help anyone else who trips across this issue.
And, many, many thanks to Sigismondo for taking the time to help me--I learned a lot from our chat!
-Andres
This is my code... I don't know why I'm get an error segment... could somebody explain the reason to me?
#include <iostream>
#include <string>
// Required by for routine
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int globalVariable = 2;
main()
{
string sIdentifier;
int iStackVariable = 20;
pid_t pID = vfork();
if (pID == 0) // child
{
// Code only executed by child process
sIdentifier = "Child Process: ";
globalVariable++;
iStackVariable++;
cout << "PROCESO NUMERO"<<getpid()<<sIdentifier;
// printf("Proceso hijo: PID %d - PPID %d\n", getpid(), getppid());
cout << " Global variable: " << globalVariable;
cout << " Stack variable: " << iStackVariable << endl;
return (0);
}
else if (pID < 0) // failed to fork
{
cerr << "Failed to fork" << endl;
return (1);
// Throw exception
}
else // parent
{
// Code only executed by parent process
sIdentifier = "Parent Process:";
}
// executed only by parent
cout << sIdentifier;
cout << " Global variable: " << globalVariable;
cout << " Stack variable: " << iStackVariable << endl;
return (0);
}
Is this of use ? Note the caveats surrounding modification of variables.
The vfork() function has the same effect as fork(), except that the behaviour is undefined if the process created by vfork() either modifies any data other than a variable of type pid_t used to store the return value from vfork(), or returns from the function in which vfork() was called, or calls any other function before successfully calling _exit() or one of the exec family of functions.
If you vfork() both processes are sharing an address space. You should probably only use vfork() if you are going to exec another process pretty much immediately in the child. The reason the system call was created was to avoid the overhead of copying every page in the parent process's address space only to have all those mappings discarded when the child exec's. For your case, use fork() instead.