munotes®

Shared Memory, in Code That Runs

Get access to whole semester resourcesSemester Pass

Chapter Twenty-Two

Syllabus topic Module 1, "Processes - Inter-process Communication"; Computer Science Practical 3, Module 1, "Process Communication using Shared Memory"

Pages 85 to 88 of 452

In one line

Shared memory is one piece of physical memory that appears in two processes' address spaces at once, so that a write by one is immediately visible to the other.

Why it looks the way it does

Chapter fourteen said each process has its own memory. The memory management unit of Chapter sixty eight is what makes that true: it maps each process's addresses to different physical memory. Shared memory is that same machinery used deliberately the other way: the kernel maps one piece of physical memory into two processes' page tables, at whatever address each of them happens to have free.

The two processes need not see it at the same address, and usually do not. That is the single most important practical consequence, and it is examined: you may not store a pointer in shared memory, because a pointer is an address and the other process's addresses are different. Store offsets, or plain data.

The four calls

There are two families. System V shared memory is what MU's text book describes and what ipcs reports, so it is taught here; POSIX shared memory is mentioned at the end.

CallWhat it does
shmget(key, size, flags)make or find a segment, and return its id
shmat(id, NULL, 0)attach it: map it into this process, and return the address
shmdt(address)detach it: unmap it from this process
shmctl(id, IPC_RMID, NULL)remove it from the system

A key is a name and an id is a handle. The key is a number two unrelated programs agree on in advance, so that both can find the same segment. IPC_PRIVATE as the key means that no name is needed and the kernel should give out a fresh segment, which is enough when the other process is a child and inherits the id.

shmdt and shmctl(IPC_RMID) are not the same thing, and this is where marks are lost. Detaching removes it from one process. Removing destroys it for everybody. A program that detaches and forgets to remove leaves the segment in the system for ever, and the next chapter's ipcs output is how you find them.

Seen from outside, with no program at all

The shell can make a segment, list it and remove it, which is the quickest way to see that a segment is a thing the system owns rather than a thing a process owns.

$ ipcmk -M 4096
Shared memory id: 0
$ ipcs -m | awk '$3 == "student" {print "id", $2, "owner", $3, "perms", $4, "bytes", $5, "attached", $6}'
id 0 owner student perms 644 bytes 4096 attached 0
$ ipcrm -m 0
$ ipcs -m | awk '$3 == "student" {print "a segment is still there"} END {print "the table has been read"}'
the table has been read
munotes.in85

Shared Memory, in Code That Runs

A segment of 4096 bytes existed, owned by student, with nattch of 0 because no process had attached it. Then it was removed and the table was empty again. No program was running at any point. The segment outlived the command that made it, which is exactly the property that makes System V shared memory useful and dangerous.

Two processes, one segment

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <unistd.h>

struct board {
    int ready;
    int count;
    char message[64];
};

int main(void)
{
    int id = shmget(IPC_PRIVATE, sizeof(struct board), IPC_CREAT | 0600);
    if (id < 0) {
        perror("shmget");
        return 1;
    }
    printf("the segment id is %d, and it is %zu bytes\n", id, sizeof(struct board));

    pid_t child = fork();
    if (child == 0) {
        struct board *b = shmat(id, NULL, 0);
        strcpy(b->message, "the child wrote this straight into memory");
        b->count = 7;
        b->ready = 1;                     /* last, so the parent sees a whole message */
        printf("child : written, and detaching\n");
        shmdt(b);
        _exit(0);
    }
    wait(NULL);

    struct board *b = shmat(id, NULL, 0);
    printf("parent: ready is %d, count is %d\n", b->ready, b->count);
    printf("parent: message is \"%s\"\n", b->message);
    shmdt(b);
    shmctl(id, IPC_RMID, NULL);           /*  without this the segment stays */
    printf("parent: segment removed\n");
    return 0;
}
$ gcc -std=c17 -Wall -Wextra -o shareit shareit.c
$ ./shareit
the segment id is 1, and it is 72 bytes
child : written, and detaching
parent: ready is 1, count is 7
parent: message is "the child wrote this straight into memory"
parent: segment removed
$ ipcs -m | awk '$3 == "student" {print "a segment is still there"} END {print "the table has been read"}'
the table has been read

Notice what is not in that program: no send, no receive, no system call for the transfer. The child assigned to a structure and the parent read the structure. Between those two lines the kernel did nothing at all, and that is the whole reason shared memory exists.

Notice also the order of the child's three writes: the message first, the count next, the ready flag last. That is not an accident and it is the right habit: the flag says "everything else is valid", so it must be written after everything else. Chapters thirty three to forty two are about doing this properly rather than carefully.

The danger, named now and solved later

The program above is safe only because the parent called wait, so the two processes never touched the segment at the same moment. Take that away and the program is broken in a way that usually works, which is the worst kind of broken.

munotes.in86

Shared Memory, in Code That Runs

Shared memory with no synchronisation is a race condition waiting to be reported as an intermittent bug. Chapter thirty four makes one happen on purpose; Chapters thirty seven to forty two are the tools that fix it. The practical's own instruction is "Explore issues of race conditions and how to avoid them", and that is the honest order: see the mechanism here, see the danger in Chapter thirty four, see the fix in Chapter thirty nine.

The POSIX family, in one paragraph

POSIX shared memory does the same job with the file system as its naming scheme. shm_open("/name", ...) gives a descriptor, ftruncate sets the size, and mmap maps it. The name looks like a file name and appears under /dev/shm, so an ordinary ls finds leftovers. It is newer and easier to clean up; the System V family is what an examination question will name, because it is what MU's text book uses.

Distinctions that carry marks

shmdtshmctl(id, IPC_RMID, NULL)
Effectdetaches from this processdestroys the segment for everybody
Other processesunaffectedlose it
Forget it andthis process's mapping goes when it exitsthe segment stays in the system for ever
Shared memoryMessage passing
Kernel involved per transfernoyes
Speedmemory speedtwo copies and two system calls
Synchronisationyours to arrangethe kernel's
May contain pointersno: the addresses differ per processnot applicable
KeyId
Isa name two programs agree ona handle the kernel gives back
Chosen bythe programmer, or IPC_PRIVATEthe kernel
Used byshmget to find the segmentevery other call

What it does not mean

Shared memory is not a shared variable. There is no declaration that makes two processes share a variable. There is a region of bytes, and both programs must agree on its layout.

Attaching is not copying. shmat maps; nothing is copied, which is why it is fast however large the segment is.

Removing it is not immediate if somebody is attached. IPC_RMID marks it for destruction; it goes when the last process detaches. So a segment can be removed and still be usable by the processes already holding it, which is the safe way to clean up.

Quick revision

  • Shared memory maps one piece of physical memory into two address spaces. No kernel

involvement per access, so it is the fastest form of communication.

  • shmget to make or find (returns an id), shmat to attach (returns an address), shmdt

to detach, shmctl with IPC_RMID to remove.

  • A key is an agreed name; IPC_PRIVATE asks for an unnamed one, enough when a child

inherits the id.

  • The two processes may see the segment at different addresses, so never store a pointer in
munotes.in87

Shared Memory, in Code That Runs

it.

  • shmdt is not IPC_RMID. Detach affects one process; remove destroys it for all. A

forgotten remove leaves the segment in the system, and ipcs -m finds it.

  • Write the data first and the ready flag last.
  • Shared memory with no synchronisation is a race condition. Chapters thirty four and thirty

nine.

  • The POSIX family is shm_open, ftruncate, mmap, and its names appear under /dev/shm.

Test yourself

  1. Name the four System V shared memory calls and what each does. shmget makes or finds a

segment and returns its id; shmat attaches it and returns an address; shmdt detaches it from this process; shmctl with IPC_RMID removes it from the system.

  1. Why must a pointer never be stored in shared memory? Because each process may map the

segment at a different address, so an address valid in one is meaningless in the other. Store offsets.

  1. Distinguish detaching from removing. Detaching unmaps it from the calling process only.

Removing destroys it for everybody, once the last process detaches.

  1. What happens if a program forgets to remove a segment? It stays in the system after the

program ends. ipcs -m lists it and ipcrm -m removes it.

  1. Why is the ready flag written last? Because it tells the reader that everything else is

valid, so it must not become true before the data it vouches for is there.

  1. Why is shared memory faster than message passing? Because after the one time setup the

kernel is not involved: a transfer is an ordinary memory access rather than two system calls and two copies.

  1. What is the price of that speed? Nothing co-ordinates the two processes, so preventing

race conditions becomes the programmer's job.

munotes.in88

The rest of this subject

These notes are cut from the University's printed syllabus. Open the syllabus itself, or the past papers, for the same subject.

Issue
Done!