Shared memory for Linux interprocess communication

Posted by FidelGonzales on Thu, 13 Jan 2022 12:27:20 +0100

Shared memory allows two unrelated processes to access the same logical memory. It is a very effective way to transfer data between two running processes. Most of the specific implementations of shared memory arrange the memory shared by different processes into the same physical memory.
Shared memory is a special address range created by IPC for a process, which will appear in the address space of the process. Other processes can connect the same piece of shared memory to their own address space. All processes can access addresses in shared memory as if they were allocated by malloc. If a process writes data to shared memory, the changes will be immediately seen by any other process that can access the same shared memory.
Shared memory provides an effective way to share and transfer data among multiple processes. Since it does not provide a synchronization mechanism, we usually need to use other mechanisms to synchronize access to shared memory. We usually use shared memory to provide effective access to large memory areas, and synchronize access to the memory by passing small messages.
There is no automatic mechanism to prevent the second process from starting to read shared memory until the first process finishes writing to it. The synchronous control of shared memory access must be the responsibility of the programmer. Figure 14-2 shows how shared memory works.

The arrows in the figure show the mapping of each process's logical address space to available physical memory. The actual situation is more complex than shown in the figure, because the available memory is actually a mixture of physical memory and memory pages that have been swapped to disk.

Shared memory function definition

#include <sys/shm.h>

void *shmat(int shm_id, const void *shm_addr, int shmflg);
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
int shmdt(const void *shm_addr);
int shmget(key_t key, size_t size, int shmflg);

shmget function

int shmget(key_t key, size_t size, int shmflg);
The shnget function is used to create shared memory. The shnget function returns a shared memory identifier that will be used for subsequent shared memory functions. There is a special key value IPC_PRIVATE, which is used to create a shared memory that belongs only to the creation process. Usually you don't use this value, and you may find that in some Linux systems, private shared memory is not really private.
The first parameter key is the shared memory segment name.
The second parameter, size, specifies the amount of memory to be shared in bytes.
The third parameter shmflg contains a 9-bit permission flag, which is the same as the mode flag used when creating the file. By IPC_ A special bit defined by creat must be bitwise or with the permission flag to create a new shared memory segment. Set IPC_ At the same time as the create flag, it is not an error to pass a key of an existing shared memory segment to the shnget function. If IPC is not required_ Creat flag, which will be quietly ignored.
Permission flags are very useful for shared memory because they allow shared memory created by a process to be written by the process owned by the creator of shared memory, and processes created by other users can only read the shared memory. We can use this function to provide an effective read-only access method to the data. By putting the data into the shared memory and setting its permissions, we can avoid the data being modified by other users.
If the shared memory is created successfully, shmget returns a non negative integer, that is, the shared memory identifier; If it fails, - 1 is returned.

shmat function

void *shmat(int shm_id, const void *shm_addr, int shmflg);

The first time a shared memory segment is created, it cannot be accessed by any process. To enable access to this shared memory, it must be connected to a process's address space. This is done by the shmat function.

First parameter shm_id is the shared memory identifier returned by shmget.
Second parameter shm_addr specifies the address location where the shared memory is connected to the current process. It is usually a null pointer that allows the system to select the address where the shared memory appears.
The third parameter shmflg is a set of bit flags. Its two possible values are SHM_ Rnd (this flag is used in conjunction with shm_addr to control the address of the shared memory connection) and SHM_RDONLY (which makes the connected memory read-only). We rarely need to control the address of the shared memory connection. We usually let the system choose an address, otherwise the application will depend too much on the hardware.
If shmat is called successfully, it returns a pointer to the first byte of shared memory; If it fails, it returns - 1.
The read and write permissions of shared memory are determined by its owner (the creator of shared memory), its access permissions and the owner of the current process. Access to shared memory is similar to access to files.
An exception to this rule is when shmflg & SHM_ When rdonly is true. At this time, even if the access permission of the shared memory allows write operation, it cannot be written.

shmdt function

The shmdt function separates the shared memory from the current process. Its parameter is the address pointer returned by shmat. It returns 0 on success and - 1 on failure. Note that separating the shared memory does not delete it, but makes the shared memory no longer available to the current process.

shmctl function

Compared with the complex semaphore control function, the control function of shared memory is slightly simpler. It is defined as follows:
int shmctl(int shm_id, int command, struct shmid_ds *buf);
shmid_ The DS structure contains at least the following members:

struct shmid_ds{
	uid_t shm_perm.uid;
	uid_t shm_perm.gid;
	mode_t shm_perm.mode;
}

First parameter shm_id is the shared memory identifier returned by shmget.
The second parameter command is the action to be taken. It can take three values, as shown in the following table.

commandexplain
IPC_STATPut shmid_ The data in the DS structure is set to the current association value of the shared memory
IPC_SETIf the process has sufficient permissions, set the current association value of shared memory to shmid_ The value given in the DS structure
IPC_RMIDDelete shared memory segment

The third parameter buf is a pointer to the structure that contains the shared memory pattern and access permissions.

Returns 0 on success and - 1 on failure. The X/Open specification does not define what happens when you try to delete a connected shared memory segment. Usually, the deleted connected shared memory segment can continue to be used until it is separated from the last process. But because this behavior is not defined in the specification, it is best not to rely on it.

Programming example

After introducing the shared memory function, we will write a pair of programs shm1 C and shm2 c. The first program (consumer) will create a shared memory segment and display the data written to it. The second program (producer) will connect to an existing shared memory segment and allow us to enter data into it.

shm_com.h

We first create a common header file to define the shared memory we want to distribute.

#define TEXT_SZ 2048

struct shared_use_st{
	int written_by_you;
	char some_text[TEXT_SZ];
};

The structure defined here will be used in both consumer and producer programs. When data is written to this structure, we use this structure
An integer flag written_by_you to inform consumers. The length of text to be transmitted 2K is at our discretion.

shm1.c

The first program shm1 C is the consumer program.

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <sys/shm.h>
#include "shm_com.h"

int main()
{
	int running = 1;
	void *shared_memory = (void*)0;
	struct shared_use_st *shared_stuff;
	int shmid;

	srand((unsigned int)getpid());

	shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);

	if(shmid == -1){
		fprintf(stderr, "shmat failed\n");
		exit(EXIT_FAILURE);
	}

	shared_memory = shmat(shmid, (void*)0, 0);
	if(shared_memory == (void*)-1){
		fprintf(stderr, "shmat failed\n");
		exit(EXIT_FAILURE);
	}

	printf("Memory attached at %p\n", shared_memory);

	shared_stuff = (struct shared_use_st*)shared_memory;
	shared_stuff->written_by_you = 0;
	while(running){
		if(shared_stuff->written_by_you){
			printf("You write :%s", shared_stuff->some_text);
			sleep(rand() %4);
			shared_stuff->written_by_you = 0;
			if(strncmp(shared_stuff->some_text, "end", 3) ==0){
				running = 0;
			}
		}
	}
	if(shmdt(shared_memory) == -1){
		fprintf(stderr, "shmdt failed\n");
		exit(EXIT_FAILURE);
	}

	exit(EXIT_SUCCESS);
}

shm2.c

The second program shm2 C is the producer program through which data is input to the consumer program.

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <sys/shm.h>
#include "shm_com.h"

int main()
{
	int running = 1;
	void *shared_memory = (void*)0;
	struct shared_use_st *shared_stuff;
	char buffer[BUFSIZ];
	int shmid;

	shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 |IPC_CREAT);

	if(shmid == -1){
		fprintf(stderr, "shmget failed\n");
		exit(EXIT_FAILURE);
	}
	shared_memory = shmat(shmid, (void*)0, 0);
	if(shared_memory == (void*)-1){
		fprintf(stderr, "shmat failed\n");
		exit(EXIT_FAILURE);
	}

	printf("Memory attached at %p\n", shared_memory);
	shared_stuff = (struct shared_use_st*)shared_memory;
	while(running){
		while(shared_stuff->written_by_you == 1){
			sleep(1);
			printf("waiting for client ...\n");
		}
		printf("Enter some text:");
		fgets(buffer, BUFSIZ, stdin);

		strncpy(shared_stuff->some_text, buffer, TEXT_SZ);
		shared_stuff->written_by_you = 1;

		if(strncmp(buffer, "end", 3) == 0){
			running = 0;
		}
	}
	if(shmdt(shared_memory) == -1){
		fprintf(stderr, "shmdt failed\n");
		exit(EXIT_FAILURE);
	}
	exit(EXIT_SUCCESS);
}

Program analysis

Output results:

The first program shm1 creates a shared memory segment and connects it to its own address space. We use a shared structure at the beginning of shared memory_ use_ st. There is a flag written in this structure_ by_ You,, set this flag when data is written in the shared memory. When this flag is set, the program reads the text from the shared memory, prints it out, and then clears this flag to indicate that the data has been read. We exit the loop with a special string end. Next, the program separates the shared memory segment and deletes it.
The second program shm2 uses the same key 1234 to get and connect to the same shared memory segment. It then prompts the user to enter some text. If the flag is written_ by_ When you are set, shm2 knows that the customer process has not finished reading the last data, so it continues to wait. When other processes clear this flag, shm2 writes new data and sets the flag. It also uses the string end to terminate and detach shared memory segments.
Note that we can only provide our own very rudimentary synchronization flag, written_by_you, it includes a very inefficient busy wait (endless loop). This can make our example simpler, but in actual programming, we should use semaphores or the method of transmitting messages (using pipes or IPC messages) and generating signals to provide a more efficient synchronization mechanism between the read and write parts of the application.

Topics: C Linux Operating System server ipc