[IPC-UNIX network programming] Chapter 12 shared memory area

1 Overview

The shared memory area is the fastest available form of IPC. Once such a memory area is mapped to the address space of the processes sharing it, the transfer of data between these processes will no longer involve the kernel [that is, processes will no longer transfer data to each other by executing system calls of any kernel]. Obviously, the kernel must allow each process to share the memory mapping relationship of the memory area, and then always manage the memory area (handling page faults, etc.).

Reverse example
Consider the usual steps involved in an example client server file replicator used to deliver various types of information:

  • The server reads from the input file. The data of the file is read into its own memory space by the kernel, and then copied from the kernel to the server process [file input - > kernel memory - > server process]
  • The server writes this data in the form of a message to a pipeline, FIFO or message queue [copy from the process to the kernel]
  • The client reads the data from the IPC channel, that is, copies the data from the kernel to the process
  • Copies this data from the client buffer specified by the second parameter of the write function to the output file

Use shared memory to copy file data from the server to the client

  • The server uses a semaphore to obtain the right to access a shared memory area object;
  • The server reads data from the input file into the shared memory object. The data buffer specified by the second parameter of the read function points to the shared memory object.
  • When the server finishes reading, it uses a semaphore to notify the client.
  • The client writes this data out of the shared memory area object to the output file.

Note: by default, a child process derived from fork does not share memory with its parent process. The following example asks the parent-child process to add 1 to a global integer named count

  • Set the standard output to non buffered because both parent and child processes have to write results to it, which can prevent the output of the two processes from crossing improperly.
    #include "unpipc.h"
    #define SEM_NAME "mysem"
    
    int count = 0;
    
    int main(int argc, char const *argv[])
    {
    	int i, nloop;
    	sem_t *mutex;
    
    	if (argc != 2)
    		err_quit("usage: incr1 <#loops>");
    	nloop = atoi(argv[1]);
    
    	// creat, initialize, and unlink semaphore
    	mutex = Sem_open(Px_ipc_name(SEM_NAME), O_CREAT | O_EXCL, FILE_MODE, 1);
    	Sem_unlink(Px_ipc_name(SEM_NAME));
    
    	// Set the standard output to non buffered because both parent and child processes have to write results to it, which can prevent the output of these two processes
    	// Improper crossing
    	setbuff(stdout, NULL); 
    	if ((Fork() == 0))
    	{
    		for ( i = 0; i < nloop; ++i)
    		{
    			Sem_wait(mutex);
    			printf("child: %d\n", count++);
    			Sem_post(mutex);
    		}
    		exit(0);
    	}
    	for ( i = 0; i < nloop; ++i)
    	{
    		Sem_wait(mutex);
    		printf("parent: %d\n", count++);
    		Sem_post(mutex);
    	}
    	exit(0);
    }
    

2 mmap, munmap and msync functions

The mmap function maps a file or a Posix shared memory area object to the address space of the calling process. This function is used for three purposes:

  • (1) Use normal files to provide memory mapped I/O;

  • (2) Use special files to provide anonymous memory mapping;

  • (3) Using shm_open to provide a Posix shared memory area between unrelated processes.

    #include <sys/mman.h>
    // If successful, the starting address of the mapped area is returned; otherwise, map is returned_ FAILED
    void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
    
  • (1) addr can specify that the descriptor fd is mapped to the starting address of the in-process space, usually NULL, to tell the kernel to select the starting address.

  • (2)len is the number of bytes mapped to the process address space.

  • (3) len starts from the offset byte from the beginning of the mapped file.

  • (4) The protection of the memory mapping area is specified by the prot parameter:

    protexplain
    PROT_READData readability
    PROT_WRITEData writable
    PROT_EXECData executable
    PROT_NONEData inaccessible
  • flag

    Flagsexplainremarks
    MAP_SHAREDMAP is sharedThe modifications made by the calling process to the mapped data are visible to all processes sharing the object. [one of the methods of sharing memory area between parent and child processes is that the parent process specifies MAP_SHARED to call mmap before calling fork]
    MAP_PRIVATEMAP is privateThe modifications made by the calling process to the mapped data are only visible to the process without changing its underlying support object (file object / shared memory area object)
    MAP_FIXED

munmap deletes a mapping relationship from the address space of a process:

#include <sys/mman.h>
// Successfully returned 0 Otherwise - 1 is returned
int munmap(void *addr, size_t len);

3 continuously increment the counter by 1 in the memory mapping file

  • Using Posix named semaphores

    Writes an integer with a value of 0 to the file

    #include "unpipc.h"
    #define SEM_NAME "mysem"
    int main(int argc, char const *argv[])
    {
    	int fd, i, nloop, zero = 0;
    	int *ptr;
    	sem_t *mutex;
    
    	if (argc != 3)
    		err_quit("usage: incr2 <#pathname> <#loops>");
    	nloop = atoi(argv[2]);
    
    	// open file, initialize to 0, map into memory
    	fd = Open(argv[1], O_RDWR | O_CREAT, FILE_MODE);
    	// Writes an integer with a value of 0 to the file
    	Write(fd, &zero, sizeof(int));
    	ptr = Mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    	// Close descriptor after mmap
    	Close(fd);
        // Using Posix named semaphores
    	mutex = Sem_open(Px_ipc_name(SEM_NAME), O_CREAT | O_EXCL, FILE_MODE, 1);
    	Sem_unlink(Px_ipc_name(SEM_NAME));
    
    	// Set standard output to unbuffered
    	setbuff(stdout, NULL); 
    	if ((Fork() == 0))
    	{
    		for ( i = 0; i < nloop; ++i)
    		{
    			Sem_wait(mutex);
    			printf("child: %d\n", (*ptr)++);
    			Sem_post(mutex);
    		}
    		exit(0);
    	}
    	for ( i = 0; i < nloop; ++i)
    	{
    		Sem_wait(mutex);
    		printf("parent: %d\n", (*ptr)++);
    		Sem_post(mutex);
    	}
    	exit(0);
    }
    
  • Memory based semaphores using Posix

struct shared
{
sem_t mutex; // Posix memory based semaphores
int count;
} shared;

#include "unpipc.h"

struct shared
{
	sem_t mutex; // Posix memory based semaphores
	int count;
} shared;
int main(int argc, char const *argv[])
{
	int fd, i, nloop;
	struct shared *ptr;

	if (argc != 3)
		err_quit("usage: incr3 <#pathname> <#loops>");
	nloop = atoi(argv[2]);

	// open file, initialize to 0, map into memory
	fd = Open(argv[1], O_RDWR | O_CREAT, FILE_MODE);
	Write(fd, &shared, sizeof(struct shared));
	ptr = Mmap(NULL, sizeof(struct shared), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	Close(fd);

	Sem_init(&ptr->mutex, 1, 1);
	// Set standard output to unbuffered
	setbuff(stdout, NULL); 
	if ((Fork() == 0))
	{
		for ( i = 0; i < nloop; ++i)
		{
			Sem_wait(mutex);
			printf("child: %d\n", ptr->count++);
			Sem_post(&ptr->mutex);
		}
		exit(0);
	}
	for ( i = 0; i < nloop; ++i)
	{
		Sem_wait(mutex);
		printf("child: %d\n", ptr->count++);
		Sem_post(&ptr->mutex);
	}	
	exit(0);
}

4 BSD anonymous memory mapping

  • BSD provides anonymous memory mapping, which completely avoids the creation and opening of files. The method is to specify the flags parameter of mmap as MAP_SHARED | MAP_ANON, specify the fd parameter as - 1

5 Summary

  • Shared memory is the fastest form of IPC available because a single copy of data in a shared memory is available to all threads or processes sharing the memory.

Keywords: Unix

Added by mrcodex on Fri, 17 Dec 2021 03:16:54 +0200