Μοιραζόμενη Μνήμη


Η Μοιραζόμενη Μνήμη είναι μια αποδοτική μέθοδος ανταλλαγής δεδομένων μεταξύ προγραμμάτων. Μια διεργασία δημιουργεί μια περιοχή μνήμης την οποία μπορούν να προσπελάσουν άλλες διεργασίες (αν έχουν σχετικά δικαιώματα ανάγνωσης ή και εγγραφής).

Μια διεργασία δημιουργεί ή αποκτά πρόσβαση σε ένα τμήμα μοιραζόμενης μνήμης με τη συνάρτηση shmget(). Ο δημιουργός και ιδιοκτήτης του τμήματος μοιραζόμενης μνήμης μπορεί να εκχωρήσει δικαιώματα σε άλλους με τη συνάρτηση shmctl(). Τα δικαιώματα αυτά μπορούν να ανακληθούν. Άλλες διεργασίες, με τα κατάλληλα δικαιώματα, μπορούν να εκτελέσουν διάφορες λειτουργίες ελέγχου με την ίδια συνάρτηση shmctl(). Αφού δημιουργηθεί, το τμήμα μοιραζόμενης μνήμης προσαρτάται στο χώρο διευθύνσεων μιας διεργασίας με τη συνάρτηση shmat(). Μπορεί να αποπροσαρτηθεί με τις συναρτήσεις shmdt() και shmop(). Η διεργασία που προσπαθεί να προσαρτήσει το τμήμα της μοιραζόμενης μνήμης με τη shmat() πρέπει να έχει τα κατάλληλα δικαιώματα. Ανάλογα με αυτά τα δικαιώματα μπορεί να διαβάσει ή να γράψει σε αυτό το τμήμα. Ένα τμήμα μοιραζόμενης μνήμης περιγράφεται με μια δομή ελέγχου με ένα μοναδικό ID που δείχνει σε μια περιοχή φυσικής μνήμης. Το ID του τμήματος λέγεται shmid. Οι ορισμοί της δομής ελέγχου και των συναρτήσεων βρίσκονται στη πρότυπη βιβλιοθήκη sys/shm.h.

Πρόσβαση Τμήματος Μοιραζόμενης Μνήμης

Η συνάρτηση shmget() χρησιμοποιείται για τη δημιουργία ή πρόσβαση σε ένα τμήμα μοιραζόμενης μνήμης. Ορίζεται ως:

int shmget(key_t key, size_t size, int shmflg);

Το όρισμα key είναι συνήθως ένας αριθμός που συσχετίζεται με το ID του τμήματος μοιραζόμενης μνήμης. Το όρισμα size καθορίζει το μέγεθος του δημιουργούμενου τμήματος σε bytes. Το όρισμα shmflg καθορίζει τις αρχικές άδειες πρόσβασης και  ιδιοκτησίας.

Αν η κλήση της συνάρτησης επιτύχει, επιστρέφει το ID του τμήματος. Η ίδια συνάρτηση χρησιμοποιείται από άλλες διεργασίες για να λάβουν το ID ενός υπάρχοντος τμήματος.

Το απόσπασμα που ακολουθεί δείχνει τη χρήση της συνάρτησης shmget():

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

...

key_t key; /* key to be passed to shmget() */
int shmflg; /* shmflg to be passed to shmget() */
int shmid; /* return value from shmget() */
int size; /* size to be passed to shmget() */

...

key = ...
size = ...
shmflg) = ...

if ((shmid = shmget (key, size, shmflg)) == -1) {
perror("shmget: shmget failed"); exit(1); }
else {
(void) fprintf(stderr, "shmget: shmget returned %d\n", shmid);
exit(0);
}
...

Έλεγχος Πρόσβασης

Η συνάρτηση shmctl() χρησιμοποιείται για τη μεταβολή των δικαιωμάτων πρόσβασης και άλλων χαρακτηριστικών του τμήματος μοιραζόμενης μνήμης. Ορίζεται ως εξής:

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

Τα δικαιώματα ορίζονται με βάση το shmid του τμήματος. Το όρισμα cmd έχει μια από τις παρακάτω τιμές:

SHM_LOCK
-- Κλείδωμα του τμήματος μοιραζόμενης μνήμης. Η καλούσα διεργασία πρέπει να έχει δικαίωμα ιδιοκτήτη, δημιουργού ή διαχειριστή.
SHM_UNLOCK
-- Ξεκλείδωμα του τμήματος μοιραζόμενης μνήμης. Η καλούσα διεργασία πρέπει να έχει δικαίωμα ιδιοκτήτη, δημιουργού ή διαχειριστή.
IPC_STAT
-- Επιστροφή των πληροφοριών κατάστασης που περιέχονται στη δομή ελέγχου και τοποθετούνται στον απομονωτή που δείχνει ο buf. Η καλούσα διεργασία πρέπει να έχει δικαίωμα ανάγνωσης.
IPC_SET
-- Καθορισμός δικαιωμάτων πρόσβασης. Η καλούσα διεργασία πρέπει να έχει δικαίωμα ιδιοκτήτη, δημιουργού ή διαχειριστή.
IPC_RMID
-- Διαγραφή του τμήματος μοιραζόμενης μνήμης. Η καλούσα διεργασία πρέπει να έχει δικαίωμα ιδιοκτήτη, δημιουργού ή διαχειριστή.

Η δομή που δείχνει το buf είναι του τύπου struct shmid_ds που ορίζεται στη βιβλιοθήκη sys/shm.h

Το απόσπασμα που ακολουθεί δείχνει τη χρήση της συνάρτησης shmctl():

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

...

int cmd; /* command code for shmctl() */
int shmid; /* segment ID */
struct shmid_ds shmid_ds; /* shared memory data structure to hold results */
...

shmid = ...
cmd = ...
if ((rtrn = shmctl(shmid, cmd, shmid_ds)) == -1) {
perror("shmctl: shmctl failed");
exit(1);
}
...

Προσάρτηση και Αποπροσάρτηση

Οι συναρτήσεις shmat() και shmdt() χρησιμοποιούνται για τη προσάρτηση και αποπροσάρτηση τμημάτων μοιραζόμενης μνήμης. Ορίζονται ως ακολούθως:

void *shmat(int shmid, const void *shmaddr, int shmflg);

int shmdt(const void *shmaddr);

Η συνάρτηση shmat() επιστρέφει ένα δείκτη, τον shmaddr, στην αρχή του τμήματος μοιραζόμενης μνήμης που σχετίζεται με ένα νόμιμο shmid. Η συνάρτηση shmdt() αποπροσαρτά το τμήμα μοιραζόμενης μνήμης που δείχνει ο δείκτης shmaddr.

Ακολουθεί ένα παράδειγμα κλήσεων στις συναρτήσεις shmat() και shmdt():

#include <sys/types.h> 
#include <sys/ipc.h>
#include <sys/shm.h>

static struct state { /* Internal record of attached segments. */
int shmid; /* shmid of attached segment */
char *shmaddr; /* attach point */
int shmflg; /* flags used on attach */
} ap[MAXnap]; /* State of current attached segments. */

int nap; /* Number of currently attached segments. */

...

char *addr; /* address work variable */
register int i; /* work area */
register struct state *p; /* ptr to current state entry */
...

p = &ap[nap++];
p->shmid = ...
p->shmaddr = ...
p->shmflg = ...

p->shmaddr = shmat(p->shmid, p->shmaddr, p->shmflg);
if(p->shmaddr == (char *)-1) {
perror("shmop: shmat failed");
nap--;
} else
(void) fprintf(stderr, "shmop: shmat returned %#8.8x\n", p->shmaddr);

...
i = shmdt(addr);
if(i == -1) {
perror("shmop: shmdt failed");
} else {
(void) fprintf(stderr, "shmop: shmdt returned %d\n", i);

for (p = ap, i = nap; i--; p++)
if (p->shmaddr == addr) *p = ap[--nap];

}
...

Παράδειγμα επικοινωνίας μέσω Μοιραζόμενης Μνήμης

Παρουσιάζουμε δύο προγράμματα που επιδεικνύουν τη μεταβίβαση δεδομένων (μιας απλής συμβολοσειράς) μεταξύ δύο διαργασιών που εκτελούνται ταυτόχρονα:

shm_server.c
-- δημιουργεί μια συμβολοσειρά και ένα τμήμα μοιραζόμενης μνήμης.
shm_client.c
-- προσαρτά το τμήμα μοιραζόμενης μνήμης και χρησιμοποιεί τη συμβολοσειρά, μέσω της printf().

Ο πηγαίος κώδικας των δύο προγραμμάτων ακολουθεί:

shm_server.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

#define SHMSZ 27

main()
{
char c;
int shmid;
key_t key;
char *shm, *s;

/*
* We'll name our shared memory segment
* "5678".
*/
key = 5678;

/*
* Create the segment.
*/
if ((shmid = shmget(key, SHMSZ, IPC_CREAT | 0666)) < 0) {
perror("shmget");
exit(1);
}

/*
* Now we attach the segment to our data space.
*/
if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {
perror("shmat");
exit(1);
}

/*
* Now put some things into the memory for the
* other process to read.
*/
s = shm;

for (c = 'a'; c <= 'z'; c++)
*s++ = c;
*s = NULL;

/*
* Finally, we wait until the other process
* changes the first character of our memory
* to '*', indicating that it has read what
* we put there.
*/
while (*shm != '*')
sleep(1);

exit(0);
}

shm_client.c

/*
* shm-client - client program to demonstrate shared memory.
*/
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

#define SHMSZ 27

main()
{
int shmid;
key_t key;
char *shm, *s;

/*
* We need to get the segment named
* "5678", created by the server.
*/
key = 5678;

/*
* Locate the segment.
*/
if ((shmid = shmget(key, SHMSZ, 0666)) < 0) {
perror("shmget");
exit(1);
}

/*
* Now we attach the segment to our data space.
*/
if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {
perror("shmat");
exit(1);
}

/*
* Now read what the server put in the memory.
*/
for (s = shm; *s != NULL; s++)
putchar(*s);
putchar('\n');

/*
* Finally, change the first character of the
* segment to '*', indicating we have read
* the segment.
*/
*shm = '*';

exit(0);
}

Μοιραζόμενη Μνήμη POSIX

Η μοιραζόμενη μνήμη POSIX είναι μια παραλλαγή την απεικονιζόμενης μνήμης, η οποία συζητητείται παρακάτω. Οι κύριες διαφορές είναι οι εξής. Η δημιουργία ή πρόσβαση τμήματος μοιραζόμενης μνήμης επιτυγχάνεται με τη συνάρτηση shm_open() αντί της open(). Η αποπροσάρτηση ή και διαγραφή επιτυγχάνεται με τη συνάρτηση shm_unlink() αντί της  close(). Οι επιλογές της συνάρτησης shm_open() είναι σημαντικά λιγότερες από αυτές τις open().

Απεικονιζόμενη Μνήμη

Σε ένα σύστημα χωρίς εικονική μνήμη (virtual memory), ο χώρος διευθύνσεων των διεργασιών αντιστοιχεί σε ένα τμήμα των διευθύνσεων της φυσικής κύριας μνήμης του συστήματος. Σε ένα σύστημα με εικονική μνήμη ο χώρος διευθύνσεων επεκτείνεται στο διαμέρισμα εναλλαγής (swap partition) του σκληρού δίσκου. Ο συνολικός χώρος διευθύνσεων κάθε διεργασίας καταλαμβάνει ένα αρχείο στο διαμέρισμα εναλλαγής (ο αρχείο συνήθως ονομάζεται δευτερεύουσα μνήμη --backing store). Οι σελίδες που βρίσκονται στη κύρια μνήμη αποτελούν τμήμα του συνολικού χώρου διευθύνεσων και παρέχουν στον επεξεργαστή τις εντολές και τα δεδομένα που είναι απαραίτητα για την τρέχουσα εκτέλεση της διεργασίας.

Μια σελίδα του χώρου διευθύνσεων φορτώνεται από τη δευτερεύουσα στη κύρια μνήμη, όταν ο επεξεργαστής προσπαθεί να προσπελάσει μια διεύθυνση που δεν βρίσκεται στη κύρια μνήμη, δηλαδή προκαλείται page fault. Αφού η εκτέλεση της διεργασίας δεν μπορεί να συνεχιστεί, η διεργασία αναστέλεται μέχρι να επιλυθεί το page fault, δηλαδή να φορτωθεί η σελίδα από τη δευετρερεύουσα στη κύρια μνήμη.

Η εμφανέστερη διαφορά μεταξύ των δύο συστημάτων διαχείρισης μνήμης (με ή χωρίς εικονική μνήμη) είναι το γεγονός οτι στην εικονική μνήμη ο προγραμματιστής ( και η διεργασία) έχει στη διάθεσή του ουσιατικά απεριόριστο χώρο διευθύνσεων. Λιγότερο εμφανή πλεονεκτήματα της εικονικής μνήμης είναι η ευκολότερη και αποδοτικότερη Ε/Ε αρχείων, καθώς και το πολύ αποδοτικό μοίρασμα της μνήμης μεταξύ των διεργασιών.  

Χώρος Διευθύνσεων και Απεικόνιση

Αφού το αρχείο δευτερεύουσας μνήμης (ο χώρος διευθύνσεων της διεργασίας) βρίσκεται μόνο στο διαμέρισμα εναλλαγής, δεν περιλαμβάνεται στο ονοματισμένο σύστημα αρχείων του UNIX/Linux (δηλαδή δεν είναι αρχείο με κάποιο όνομα). Αυτό καθιστά το αρχείο αυτό μη-προσβάσιμο από άλλες διεργασίες. 

Όμως, μπορούμε να εισάγουμε στη δευτερεύουσα μνήμη μέρος, ή το σύνολο, από ένα ή περισσότερα ονοματισμένα αρχεία, καθιστώντας τα έτσι τμήμα του χώρου διευθύνσεων μιας διεργασίας. Αυτό ονομάζεται απεικόνιση (mapping). Με την απεικόνιση, οποιοδήποτε τμήμα αναγνώσιμου ή εγγράψιμου αρχείου μπορεί να συμπεριληφθεί λογικά στο χώρο διευθύνσεων μιας διεργασίας. Όπως και με τα υπόλοιπα τμήματα του χώρου διευθύνσεων, καμμία σελίδα του αρχείου δεν φορτώνεται πραγματικά στη κύρια μνήμη, παρά μόνο αν προκληθεί page fault. Επίσης, σελίδες της κύριας μνήμης γράφονται στο αρχείο μόνο αν τα περιεχόμενά τους τροποποιηθούν στη κύρια μνήμη. Έτσι η ανάγνωση και η εγγραφή σε αρχεία είναι εντελώς αυτόματη και αποδοτική. Ένα ονοματισμένο αρχείο μπορεί να απεικονιστεί σε περισσότερες από μια διεργασίες (δηλαδή σε περισσότερους από ένα χώρους διευθύνσεων). Με αυτό το τρόπο μπορούμε να επιτύχουμε πολύ αποδοτικό μοίρασμα μνήμης και αρχείων μεταξύ διεργασιών.

Σημείωση: Δεν μπορούν να απεικονιστούν όλα τα ονοματισμένα αρχεία. Οι συσκευές όπως τα τερματικά και οι δικτυακές διεπαφές δεν μπορούν να απεικονιστούν. Ο χώρος διευθύνσεων ορίζεται από όλα τα αρχεία (ή τμήματα αρχείων) που απεικονίζονται στο χώρο διευθύνσεων.  Κάθε απεικόνιση στοιχίζεται και σελιδοποιείται με βάση τους περιορισμούς του συστήματος που εκελείται η διεργασία.  

Συνοχή

Το ζήτημα της συγχρονικής (concurrent) προσπέλασης σε δεδομένα που βρίσκονται σε μοιραζόμενη μνήμη ή σε απεικονιζόμενη μνήμη, λύνεται είτε με τη χρήση σηματοφορέων ή με τεχνικές νημάτων που θα συζητηθούν αργότερα.

Δημιουργία και Χρήση Απεικονίσεων

Η συνάρτηση mmap() ορίζει μια απεικόνιση ενός ονοματισμένου αρχείου στο χώρο διευθύνσεων. Εϊναι πολύ απλή και  βασική στην απεικόνιση μνήμης..

Η συνάρτηση mmap() ορίζεται ως ακολούθως:

#include <sys/types.h>
#include <sys/mman.h>

caddr_t mmap(caddr_t addr, size_t len, int prot, int flags, int fildes, off_t off);

Η απεικόνιση που ορίζεται από την mmap() αντικαθιστά παλιότερες απεικονίσεις για το συγκεκριμένο χώρο διεύθυνσης. Οι σημαίες flags MAP_SHARED και MAP_PRIVATE καθορίζουν τον τύπο απεικόνισης, μπορεί δε να οριστεί μόνο μια σημαία. Η MAP_SHARED καθορίζει οτι το απεικονιζόμενο αντικείμενο μπορεί να τροποποιηθεί με εγγραφή. Η MAP_PRIVATE καθορίζει οτι μια αρχική εγγραφή στο απεικονιζόμενο αντικείμενο δημιουργεί ένα αντίγραφο της σελίδας και όλες οι εγγραφές αναφέρονται σε αυτή τη σελίδα. Στο τέλος αντιγράφονται μόνο σελίδες που έχουν τροποποιηθεί.

Σε περίπτωση fork()ο τύπος απεικόνισης (το flag) διατηρείται. Ο περιγραφέας αρχείου μιας συνάρτησης mmap() δεν χρειάζεται να παραμένει ανοικτός μετά την απεικόνιση. Αν ο περιγραφέας κλείσει η απεικόνιση παραμένει μέχρι να αναιρεθεί με τη συνάρτηση munmap() ή με ένα νέο mmap() που αντικαθιστά. Αν ένα απεικονιζόμενο αρχείο περιοριστεί replacing in with a new mapping. 

Ο κώδικας που ακολουθεί επιδεικνύει μια χρήση των παραπάνω συναρτήσεων. Δημιουργεί μια περιοχή αποθήκευσης για ένα πρόγραμμα, σε μια διεύθυνση που επιλέγει το σύστημα:

int fd; 
caddr_t result;
if ((fd = open("/dev/zero", O_RDWR)) == -1)
return ((caddr_t)-1);

result = mmap(0, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
(void) close(fd);

Άλλες Συναρτήσεις Ελέγχου

Η  συνάρτηση int mlock(caddr_t addr, size_t len) 

προκαλεί το κλείδωμα των σελίδων σε συγκεκριμένες διευθύνσεις της φυσικής μνήμης. Οι αναφορές σε κλειδωμένες σελίδες (από οποιαδήποτε διεργασία) δεν προκαλούν page fault. Αυτή η λειτουργία δεσμεύει τους φυσικούς πόρους και μπορεί να καθυστερήσει τη λειτουργία του συστήματος. Γι' αυτό η χρήση του mlock() περιορίζεται στον διαχειριστή. Το σύστημα αφήνει μόνο ένα συγκεκριμένο αριθμό σελίδων να κλειδωθούν και αν το όριο ξεπεραστεί η κλήση mlock() αποτυγχάνει.

Η συνάρτηση int munlock(caddr_t addr, size_t len) 

ξεκλειδώνει τις σελίδες σε συγκεκριμένες διευθύνσεις. Αν μια απεικόνιση έχει καλέσει πολλές φορές τη συνάρτηση mlock() για μια συγκεκριμένη διεύθυνση, μια απλή κλήση της συνάρτησης munlock() ακυρώνει όλες τις κλήσεις κλειδώματος. Όμως, αν διαφορετικές απεικονίσεις έχουν κλειδώσει με  mlock() την ίδια περιοχή φυσικής μνήμης, οι σελίδες δεν ξεκλειδώνονται παραμόνο αν η munlock() κληθεί από όλες τις απεικονίσεις. Το κλείδωμα μεταφέρεται κατά την αντιγραφή σελίδων με την απεικόνιση MAP_PRIVATE, έτσι ώστε τυχόν κλειδώματα σελίδων μεταφέρονται διαφανώς.

Οι συναρτήσεις int mlockall(int flags) και int munlockall(void) 

είναι όμοιες με τις mlock() και munlock(), αλλά εφαρμόζονται σε ολόκληρους χώρους διευθύνσεων. Η mlockall() κλειδώνει όλες τις σελίδες του χώρου διευθύνσεων, ενώ η munlockall() αφαιρεί όλα τα κλειδώματα σελίδων του χώρου διευθύνσεων που δημιουργήθηκαν είτε με mlock() ή mlockall().

Η συνάρτηση int msync(caddr_t addr, size_t len, int flags) 

προκαλεί την άμεση εγγραφή όλων σελίδων που έχουν τροποποιηθεί στις αρχικές τους απεικονίσεις, δηλαδή την ενημέρωση των σχετικών αρχείων. Είναι παρόμοια με την συνάρτηση fsync() για τα αρχεία.

Η συνάρτηση long sysconf(int name) 

επιστρέφει το μέγεθος της σελίδας μνήμης του συγκεκριμένου συστήματος. Για λόγους φορητότητας, οι σχετικές αφαρμογές μας δεν πρέπει να περιέχουν σταθερές που ορίζουν μεγέθη σελίδων. Σημειώστε οτι δεν είναι ασυνήθιστο  τα μεγέθη των σελίδων να διαφέρουν μεταξύ διαφορετικών υλοποιήσεων.

Η συνάρτηση int mprotect(caddr_t addr, size_t len, int prot) 

εκχωρεί τη καθορισμένη προστασία πρόσβασης σε όλες τις σελίδες στη καθορισμένη περιοχή μνήμης. Η προστασία δεν μπορεί να υπερβαίνει τα δικαιώματα που διαθέτει η καλούσα διεργασία στο απεικονιζόμενο αντικείμενο.

Παραδείγματα

Η ομάδα των προγραμμάτων που ακολουθεί μπορεί να χρησιμοποιηθεί για τη διαλογική διερεύνηση της χρήσης της μοιραζόμενης μνήμης (δες τις παρακάτω ασκήσεις).

Η μοιραζόμενη μνήμη πρέπει να αρχικοποιηθεί με το πρόγραμμα shmget.c. Ο έλεγχος και η προσπέλαση της μοιραζόμενης μνήμης διερευνάται με τα προγράμματα shmctl.c και shmop.c αντίστοιχα.

shmget.c: Επίδειξη συνάρτησηςshmget()

/*
* shmget.c: Illustrate the shmget() function.
*
* This is a simple exerciser of the shmget() function. It prompts
* for the arguments, makes the call, and reports the results.
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

extern void exit();
extern void perror();

main()
{
key_t key; /* key to be passed to shmget() */
int shmflg; /* shmflg to be passed to shmget() */
int shmid; /* return value from shmget() */
int size; /* size to be passed to shmget() */

(void) fprintf(stderr, "All numeric input is expected to follow C conventions:\n");
(void) fprintf(stderr, "\t0x... is interpreted as hexadecimal,\n");
(void) fprintf(stderr, "\t0... is interpreted as octal,\n");
(void) fprintf(stderr, "\totherwise, decimal.\n");

/* Get the key. */
(void) fprintf(stderr, "IPC_PRIVATE == %#lx\n", IPC_PRIVATE);
(void) fprintf(stderr, "Enter key: ");
(void) scanf("%li", &key);

/* Get the size of the segment. */
(void) fprintf(stderr, "Enter size: ");
(void) scanf("%i", &size);

/* Get the shmflg value. */
(void) fprintf(stderr, "Expected flags for the shmflg argument are:\n");
(void) fprintf(stderr, "\tIPC_CREAT = \t%#8.8o\n", IPC_CREAT);
(void) fprintf(stderr, "\tIPC_EXCL = \t%#8.8o\n", IPC_EXCL);
(void) fprintf(stderr, "\towner read =\t%#8.8o\n", 0400);
(void) fprintf(stderr, "\towner write =\t%#8.8o\n", 0200);
(void) fprintf(stderr, "\tgroup read =\t%#8.8o\n", 040);
(void) fprintf(stderr, "\tgroup write =\t%#8.8o\n", 020);
(void) fprintf(stderr, "\tother read =\t%#8.8o\n", 04);
(void) fprintf(stderr, "\tother write =\t%#8.8o\n", 02);
(void) fprintf(stderr, "Enter shmflg: ");
(void) scanf("%i", &shmflg);

/* Make the call and report the results. */
(void) fprintf(stderr, "shmget: Calling shmget(%#lx, %d, %#o)\n", key, size, shmflg);
if ((shmid = shmget (key, size, shmflg)) == -1) {
perror("shmget: shmget failed");
exit(1);
}
else {
(void) fprintf(stderr, "shmget: shmget returned %d\n", shmid);
exit(0);
}
}

shmctl.cΕπίδειξη συνάρτησηςshmctl()

/*
* shmctl.c: Illustrate the shmctl() function.
*
* This is a simple exerciser of the shmctl() function. It lets you
* to perform one control operation on one shared memory segment.
* (Some operations are done for the user whether requested or not.
* It gives up immediately if any control operation fails. Be careful
* not to set permissions to preclude read permission; you won't be
*able to reset the permissions with this code if you do.)
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <time.h>
static void do_shmctl();
extern void exit();
extern void perror();

main()
{
int cmd; /* command code for shmctl() */
int shmid; /* segment ID */
struct shmid_ds shmid_ds; /* shared memory data structure to hold results */

(void) fprintf(stderr, "All numeric input is expected to follow C conventions:\n");
(void) fprintf(stderr, "\t0x... is interpreted as hexadecimal,\n");
(void) fprintf(stderr, "\t0... is interpreted as octal,\n");
(void) fprintf(stderr, "\totherwise, decimal.\n");

/* Get shmid and cmd. */
(void) fprintf(stderr, "Enter the shmid for the desired segment: ");
(void) scanf("%i", &shmid);
(void) fprintf(stderr, "Valid shmctl cmd values are:\n");
(void) fprintf(stderr, "\tIPC_RMID =\t%d\n", IPC_RMID);
(void) fprintf(stderr, "\tIPC_SET =\t%d\n", IPC_SET);
(void) fprintf(stderr, "\tIPC_STAT =\t%d\n", IPC_STAT);
(void) fprintf(stderr, "\tSHM_LOCK =\t%d\n", SHM_LOCK);
(void) fprintf(stderr, "\tSHM_UNLOCK =\t%d\n", SHM_UNLOCK);
(void) fprintf(stderr, "Enter the desired cmd value: ");
(void) scanf("%i", &cmd);

switch (cmd) {
case IPC_STAT:
/* Get shared memory segment status. */
break;
case IPC_SET:
/* Set owner UID and GID and permissions. */
/* Get and print current values. */
do_shmctl(shmid, IPC_STAT, &shmid_ds);
/* Set UID, GID, and permissions to be loaded. */
(void) fprintf(stderr, "\nEnter shm_perm.uid: ");
(void) scanf("%hi", &shmid_ds.shm_perm.uid);
(void) fprintf(stderr, "Enter shm_perm.gid: ");
(void) scanf("%hi", &shmid_ds.shm_perm.gid);
(void) fprintf(stderr, "Note: Keep read permission for yourself.\n");
(void) fprintf(stderr, "Enter shm_perm.mode: ");
(void) scanf("%hi", &shmid_ds.shm_perm.mode);
break;
case IPC_RMID:
/* Remove the segment when the last attach point is detached. */
break;
case SHM_LOCK:
/* Lock the shared memory segment. */
break;
case SHM_UNLOCK:
/* Unlock the shared memory segment. */
break;
default:
/* Unknown command will be passed to shmctl. */
break;
}
do_shmctl(shmid, cmd, &shmid_ds);
exit(0);
}

/*
* Display the arguments being passed to shmctl(), call shmctl(),
* and report the results. If shmctl() fails, do not return; this
* example doesn't deal with errors, it just reports them.
*/
static void do_shmctl(shmid, cmd, buf)
int shmid, /* attach point */
cmd; /* command code */
struct shmid_ds *buf; /* pointer to shared memory data structure */
{
register int rtrn; /* hold area */

(void) fprintf(stderr, "shmctl: Calling shmctl(%d, %d, buf)\n", shmid, cmd);
if (cmd == IPC_SET) {
(void) fprintf(stderr, "\tbuf->shm_perm.uid == %d\n", buf->shm_perm.uid);
(void) fprintf(stderr, "\tbuf->shm_perm.gid == %d\n", buf->shm_perm.gid);
(void) fprintf(stderr, "\tbuf->shm_perm.mode == %#o\n", buf->shm_perm.mode);
}
if ((rtrn = shmctl(shmid, cmd, buf)) == -1) {
perror("shmctl: shmctl failed");
exit(1);
}
else {
(void) fprintf(stderr, "shmctl: shmctl returned %d\n", rtrn);
}
if (cmd != IPC_STAT && cmd != IPC_SET)
return;

/* Print the current status. */
(void) fprintf(stderr, "\nCurrent status:\n");
(void) fprintf(stderr, "\tshm_perm.uid = %d\n", buf->shm_perm.uid);
(void) fprintf(stderr, "\tshm_perm.gid = %d\n", buf->shm_perm.gid);
(void) fprintf(stderr, "\tshm_perm.cuid = %d\n", buf->shm_perm.cuid);
(void) fprintf(stderr, "\tshm_perm.cgid = %d\n", buf->shm_perm.cgid);
(void) fprintf(stderr, "\tshm_perm.mode = %#o\n", buf->shm_perm.mode);
(void) fprintf(stderr, "\tshm_perm.key = %#x\n", buf->shm_perm.key);
(void) fprintf(stderr, "\tshm_segsz = %d\n", buf->shm_segsz);
(void) fprintf(stderr, "\tshm_lpid = %d\n", buf->shm_lpid);
(void) fprintf(stderr, "\tshm_cpid = %d\n", buf->shm_cpid);
(void) fprintf(stderr, "\tshm_nattch = %d\n", buf->shm_nattch);
(void) fprintf(stderr, "\tshm_atime = %s", buf->shm_atime ? ctime(&buf->shm_atime) : "Not Set\n");
(void) fprintf(stderr, "\tshm_dtime = %s", buf->shm_dtime ? ctime(&buf->shm_dtime) : "Not Set\n");
(void) fprintf(stderr, "\tshm_ctime = %s", ctime(&buf->shm_ctime));
}

shmop.cΕπίδειξη συναρτήσεων shmat() και shmdt()

/*
* shmop.c: Illustrate the shmat() and shmdt() functions.
*
* This is a simple exerciser for the shmat() and shmdt() system
* calls. It allows you to attach and detach segments and to
* write strings into and read strings from attached segments.
*/

#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define MAXnap 4 /* Maximum number of concurrent attaches. */

static ask();
static void catcher();
extern void exit();
static good_addr();
extern void perror();
extern char *shmat();

static struct state { /* Internal record of currently attached segments. */
int shmid; /* shmid of attached segment */
char *shmaddr; /* attach point */
int shmflg; /* flags used on attach */
} ap[MAXnap]; /* State of current attached segments. */

static int nap; /* Number of currently attached segments. */
static jmp_buf segvbuf; /* Process state save area for SIGSEGV catching. */

main()
{
register int action; /* action to be performed */
char *addr; /* address work area */
register int i; /* work area */
register struct state *p; /* ptr to current state entry */
void (*savefunc)(); /* SIGSEGV state hold area */
(void) fprintf(stderr, "All numeric input is expected to follow C conventions:\n");
(void) fprintf(stderr, "\t0x... is interpreted as hexadecimal,\n");
(void) fprintf(stderr, "\t0... is interpreted as octal,\n");
(void) fprintf(stderr, "\totherwise, decimal.\n");
while (action = ask()) {
if (nap) {
(void) fprintf(stderr, "\nCurrently attached segment(s):\n");
(void) fprintf(stderr, " shmid address\n");
(void) fprintf(stderr, "------ ----------\n");
p = &ap[nap];
while (p-- != ap) {
(void) fprintf(stderr, "%6d", p->shmid);
(void) fprintf(stderr, "%#11x", p->shmaddr);
(void) fprintf(stderr, " Read%s\n", (p->shmflg & SHM_RDONLY) ? "-Only" : "/Write");
}
} else
(void) fprintf(stderr, "\nNo segments are currently attached.\n");
switch (action) {
case 1: /* Shmat requested. */
/* Verify that there is space for another attach. */
if (nap == MAXnap) {
(void) fprintf(stderr, "%s %d %s\n", "This simple example will only allow", MAXnap, "attached segments.");
break;
}
p = &ap[nap++];
/* Get the arguments, make the call, report the
results, and update the current state array. */
(void) fprintf(stderr, "Enter shmid of segment to attach: ");
(void) scanf("%i", &p->shmid);

(void) fprintf(stderr, "Enter shmaddr: ");
(void) scanf("%i", &p->shmaddr);
(void) fprintf(stderr, "Meaningful shmflg values are:\n");
(void) fprintf(stderr, "\tSHM_RDONLY = \t%#8.8o\n", SHM_RDONLY);
(void) fprintf(stderr, "\tSHM_RND = \t%#8.8o\n", SHM_RND);
(void) fprintf(stderr, "Enter shmflg value: ");
(void) scanf("%i", &p->shmflg);

(void) fprintf(stderr, "shmop: Calling shmat(%d, %#x, %#o)\n", p->shmid, p->shmaddr, p->shmflg);
p->shmaddr = shmat(p->shmid, p->shmaddr, p->shmflg);
if(p->shmaddr == (char *)-1) {
perror("shmop: shmat failed");
nap--;
}
else {
(void) fprintf(stderr, "shmop: shmat returned %#8.8x\n", p->shmaddr);
}
break;

case 2: /* Shmdt requested. */
/* Get the address, make the call, report the results and make the internal state match. */
(void) fprintf(stderr, "Enter detach shmaddr: ");
(void) scanf("%i", &addr);

i = shmdt(addr);
if(i == -1) {
perror("shmop: shmdt failed");
}
else {
(void) fprintf(stderr, "shmop: shmdt returned %d\n", i);
for (p = ap, i = nap; i--; p++) {
if (p->shmaddr == addr)
*p = ap[--nap];
}
}
break;
case 3: /* Read from segment requested. */
if (nap == 0)
break;

(void) fprintf(stderr, "Enter address of an %s", "attached segment: ");
(void) scanf("%i", &addr);

if (good_addr(addr))
(void) fprintf(stderr, "String @ %#x is `%s'\n", addr, addr);
break;

case 4: /* Write to segment requested. */
if (nap == 0)
break;

(void) fprintf(stderr, "Enter address of an %s", "attached segment: ");
(void) scanf("%i", &addr);

/* Set up SIGSEGV catch routine to trap attempts to
write into a read-only attached segment. */
savefunc = signal(SIGSEGV, catcher);

if (setjmp(segvbuf)) {
(void) fprintf(stderr, "shmop: %s: %s\n", "SIGSEGV signal caught", "Write aborted.");
}
else {
if (good_addr(addr)) {
(void) fflush(stdin);
(void) fprintf(stderr, "%s %s %#x:\n", "Enter one line to be copied", "to shared segment attached @", addr);
(void) gets(addr);
}
}
(void) fflush(stdin);

/* Restore SIGSEGV to previous condition. */
(void) signal(SIGSEGV, savefunc);
break;
}
}
exit(0);
/*NOTREACHED*/
}
/*
** Ask for next action.
*/
static
ask()
{
int response; /* user response */
do {
(void) fprintf(stderr, "Your options are:\n");
(void) fprintf(stderr, "\t^D = exit\n");
(void) fprintf(stderr, "\t 0 = exit\n");
(void) fprintf(stderr, "\t 1 = shmat\n");
(void) fprintf(stderr, "\t 2 = shmdt\n");
(void) fprintf(stderr, "\t 3 = read from segment\n");
(void) fprintf(stderr, "\t 4 = write to segment\n");
(void) fprintf(stderr, "Enter the number corresponding to your choice: ");

/* Preset response so "^D" will be interpreted as exit. */
response = 0;
(void) scanf("%i", &response);
} while (response < 0 || response > 4);
return (response);
}
/*
** Catch signal caused by attempt to write into shared memory segment
** attached with SHM_RDONLY flag set.
*/
/*ARGSUSED*/
static void
catcher(sig)
{
longjmp(segvbuf, 1);
/*NOTREACHED*/
}
/*
** Verify that given address is the address of an attached segment.
** Return 1 if address is valid; 0 if not.
*/
static
good_addr(address)
char *address;
{
register struct state *p; /* ptr to state of attached segment */

for (p = ap; p != &ap[nap]; p++)
if (p->shmaddr == address)
return(1);
return(0);
}

Ασκήσεις

Άσκηση 12771

Γράψτε 2 προγράμματα που επικοινωνούν με μοιραζόμενη μνήμη και σηματοφορείς. Τα δεδομένα ανταλλάσσονται με τη μοιραζόμενη μνήμη και οι σηματοφορείς χρησιμοποιούνται για συγχρονισμό και ειδοποίηση των διεργασιών όταν εκτελούνται λειτουργίες ανάγνωσης ή εγγραφής στης μνήμη.

Άσκηση 12772

Μεταγλωττίστε τα προγράμματα shmget.c, shmctl.c και shmop.c. Στη συνέχεια:

Άσκηση 12773

Γράψτε 2 προγράμματα που να επικοινωνούν μέσω απεικονιζόμενης μνήμης.


Dave Marshall
1/5/1999
μετάφραση και προσαρμογή στα Ελληνικά Κ.Γ. Μαργαρίτης
28/4/2008