struct ipc_perm
{
__key_t __key; /* Key. */
__uid_t uid; /* Owner's user ID. */
__gid_t gid; /* Owner's group ID. */
__uid_t cuid; /* Creator's user ID. */
__gid_t cgid; /* Creator's group ID. */
unsigned short int mode; /* Read/write permission. */
unsigned short int __pad1;
unsigned short int __seq; /* Sequence number. */
unsigned short int __pad2;
unsigned long int __unused1;
unsigned long int __unused2;
};
These fields have the following meanings:
int msgget(key_t key, int msgflg);
#include <stdio.h> /* standard I/O routines. */
#include <sys/types.h> /* standard system data types. */
#include <sys/ipc.h> /* common system V IPC structures. */
#include <sys/msg.h> /* message-queue specific functions. */
/* create a private message queue, with access only to the owner. */
int queue_id = msgget(IPC_PRIVATE, 0600); /* <-- this is an octal number. */
if (queue_id == -1) {
perror("msgget");
exit(1);
}
struct msgbuf
struct msgbuf {
long mtype; /* message type, a positive number (cannot be zero). */
char mtext[1]; /* message body array. usually larger then one byte. */
};
/* first, define the message string */
char* msg_text = "hello world";
/* allocate a message with enough space for length of string and */
/* one extra byte for the terminating null character. */
struct msgbuf* msg =
(struct msgbuf*)malloc(sizeof(struct msgbuf) + strlen(msg_text));
/* set the message type. for example - set it to '1'. */
msg->mtype = 1;
/* finally, place the "hello world" string inside the message. */
strcpy(msg->mtext, msg_text);
int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg);
int msqid
- id of message queue, as returned from
the msgget()
call.
struct msgbuf* msg
- a pointer to a properly
initializes message structure, such as the one we prepared in the
previous section.
int msgsz
- the size of the data part (mtext) of
the message, in bytes.
int msgflg
- flags specifying how to send the message.
may be a logical "or" of the following:
IPC_NOWAIT
- if the message cannot be sent
immediately, without blocking the process, return '-1', and set
errno
to EAGAIN
.
MSG_EXCEPT
-Used with msgtyp greater than 0 to read the first
message on the queue with message type that differs from msgtyp.
MSG_NOERROR
To truncate the message text if longer than msgsz
bytes.
int rc = msgsnd(queue_id, msg, strlen(msg_text)+1, 0);
if (rc == -1) {
perror("msgsnd");
exit(1);
}
ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg);
int msqid
- id of the queue, as returned from
msgget()
.
struct msgbuf* msg
- a pointer to a pre-allocated
msgbuf structure. It should generally be large enough to contain a
message with some arbitrary data (see more below).
int msgsz
- size of largest message text we wish
to receive. Must NOT be larger then the amount of space we allocated
for the message text in 'msg'.
int msgtyp
- Type of message we wish to read.
may be one of:
int msgflg
- a logical 'or' combination of any
of the following flags:
IPC_NOWAIT
- if there is no message on the queue
matching what we want to read, return '-1', and set
errno
to ENOMSG
.
MSG_EXCEPT
- if the message type parameter
is a positive integer, then return the first message whose type
is NOT equal to the given integer.
MSG_NOERROR
- If a message with a text part
larger then 'msgsz' matches what we want to read, then truncate
the text when copying the message to our msgbuf structure.
If this flag is not set and the message text is too large,
the system call returns '-1', and errno
is set
to E2BIG
.
/* prepare a message structure large enough to read our "hello world". */
struct msgbuf* recv_msg =
(struct msgbuf*)malloc(sizeof(struct msgbuf)+strlen("hello world"));
/* use msgrcv() to read the message. We agree to get any type, and thus */
/* use '0' in the message type parameter, and use no flags (0). */
int rc = msgrcv(queue_id, recv_msg, strlen("hello world")+1, 0, 0);
if (rc == -1) {
perror("msgrcv");
exit(1);
}
msgrcv()
call
would have blocked our process until one of the following happens:
errno
would be set
to EIDRM
).
errno
would
be set to EINTR
.
int semget(key_t key, int nsems, int semflg);
SEMMSL
, as defined
in file /usr/include/sys/sem.h
.
/* ID of the semaphore set. */
int sem_set_id_1;
int sem_set_id_2;
/* create a private semaphore set with one semaphore in it, */
/* with access only to the owner. */
sem_set_id_1 = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
if (sem_set_id_1 == -1) {
perror("main: semget");
exit(1);
}
/* create a semaphore set with ID 250, three semaphores */
/* in the set, with access only to the owner. */
sem_set_id_2 = semget(250, 3, IPC_CREAT | 0600);
if (sem_set_id_2 == -1) {
perror("main: semget");
exit(1);
}
int semctl(int semid, int semnum, int cmd, ...)
/* use this to store return values of system calls. */
int rc;
/* initialize the first semaphore in our set to '3'. */
rc = semctl(sem_set_id_2, 0, SETVAL, 3);
if (rc == -1) {
perror("main: semctl");
exit(1);
}
/* initialize the second semaphore in our set to '6'. */
rc = semctl(sem_set_id_2, 1, SETVAL, 6);
if (rc == -1) {
perror("main: semctl");
exit(1);
}
/* initialize the third semaphore in our set to '0'. */
rc = semctl(sem_set_id_2, 2, SETVAL,0);
if (rc == -1) {
perror("main: semctl");
exit(1);
}
union semun
.
However, since the
SETVAL
(set value) operation only uses the int val
part of the union, we simply passed an integer to the function.
/* use this variable to pass the value to the semctl() call */
union semun sem_val;
/* initialize the first semaphore in our set to '3'. */
sem_val.val = 0;
rc = semctl(sem_set_id_2, 2, SETVAL, sem_val);
if (rc == -1) {
perror("main: semctl");
exit(1);
}
.
int semop(int semid, struct sembuf *sops, unsigned nsops);
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
/* this function updates the contents of the file with the given path name. */
void update_file(char* file_path, int number)
{
/* structure for semaphore operations. */
struct sembuf sem_op;
FILE* file;
/* wait on the semaphore, unless it's value is non-negative. */
sem_op.sem_num = 0;
sem_op.sem_op = -1; /* <-- Comment 1 */
sem_op.sem_flg = 0;
semop(sem_set_id, &sem_op, 1);
/* Comment 2 */
/* we "locked" the semaphore, and are assured exclusive access to file. */
/* manipulate the file in some way. for example, write a number into it. */
file = fopen(file_path, "w");
if (file) {
fprintf(file, "%d\n", number);
fclose(file);
}
/* finally, signal the semaphore - increase its value by one. */
sem_op.sem_num = 0;
sem_op.sem_op = 1; /* <-- Comment 3 */
sem_op.sem_flg = 0;
semop(sem_set_id, &sem_op, 1);
}
semop()
to wait on the semaphore. Supplying '-1' in sem_op.sem_op
means: If the value of the semaphore is greater then or equal to '1',
decrease this value by one, and return to the caller. Otherwise (the value
is 0 or less), block the calling process, until the value of the semaphore
becomes '1', at which point we decrement the semaphore and return to the
caller.
semop()
assure us that
when we return from this function, the value of the semaphore is 0. Why?
it couldn't be less, or else semop()
won't return.
To control a printing system, we need the producers to maintain a count of the number of files waiting in the spool directory and incrementing it for every new file placed there. The consumers check this counter, and whenever it gets above zero, one of them grabs a file from the spool, and sends it to the printer. If there are no files in the spool (i.e. the counter value is zero), all consumer processes get blocked.
/* this variable will contain the semaphore set. */
int sem_set_id;
/* semaphore value, for semctl(). */
union semun sem_val;
/* structure for semaphore operations. */
struct sembuf sem_op;
/* first we create a semaphore set with a single semaphore, */
/* whose counter is initialized to '0'. */
sem_set_id = semget(IPC_PRIVATE, 1, 0600);
if (sem_set_id == -1) {
perror("semget");
exit(1);
}
sem_val.val = 0;
semctl(sem_set_id, 0, SETVAL, sem_val);
/* we now do some producing function, and then signal the */
/* semaphore, increasing its counter by one. */
.
.
sem_op.sem_num = 0;
sem_op.sem_op = 1;
sem_op.sem_flg = 0;
semop(sem_set_id, &sem_op, 1);
.
.
.
/* meanwhile, in a different process, we try to consume the */
/* resource protected (and counter) by the semaphore. */
/* we block on the semaphore, unless it's value is non-negative. */
sem_op.sem_num = 0;
sem_op.sem_op = -1;
sem_op.sem_flg = 0;
semop(sem_set_id, &sem_op, 1);
/* when we get here, it means that the semaphore's value is '1' */
/* or more, so there's something to consume. */
.
.
int shmget(key_t key, size_t size, int shmflg);
/* this variable is used to hold the returned segment identifier. */
int shm_id;
/* allocate a shared memory segment with size of 2048 bytes, */
/* accessible only to the current user. */
shm_id = shmget(100, 2048, IPC_CREAT | IPC_EXCL | 0600);
if (shm_id == -1) {
perror("shmget: ");
exit(1);
}
IPC_EXCL
in the flags to shmget()
. In that case,
the call will succeed only if the page did not exist before.
shmat()
(shared-memory
attach) system call. Assuming 'shm_id' contains an identifier returned
by a call to shmget()
, here is how to do this:
/* these variables are used to specify where the page is attached. */
char* shm_addr;
char* shm_addr_ro;
/* attach the given shared memory segment, at some free position */
/* that will be allocated by the system. */
shm_addr = shmat(shm_id, NULL, 0);
if (!shm_addr) { /* operation failed. */
perror("shmat: ");
exit(1);
}
/* attach the same shared memory segment again, this time in */
/* read-only mode. Any write operation to this page using this */
/* address will cause a segmentation violation (SIGSEGV) signal. */
shm_addr_ro = shmat(shm_id, NULL, SHM_RDONLY);
if (!shm_addr_ro) { /* operation failed. */
perror("shmat: ");
exit(1);
}
shmat()
system call. Any kind of data may be placed
in a shared segment. Note pointer values placed in shared memory will general
note valid in another process. One can try to work around this problem by attaching
the shared segment in the same virtual address in all processes (by supplying an address as the
second parameter to shmat()
, and adding the SHM_RND
flag to its third parameter), but this might fail if the given virtual
address is already in use by the process.
shmat()
.
/* define a structure to be used in the given shared memory segment. */
struct country {
char name[30];
char capital_city[30];
char currency[30];
int population;
};
/* define a countries array variable. */
int* countries_num;
struct country* countries;
/* create a countries index on the shared memory segment. */
countries_num = (int*) shm_addr;
*countries_num = 0;
countries = (struct country*) ((void*)shm_addr+sizeof(int));
strcpy(countries[0].capital_city, "U.S.A");
strcpy(countries[0].capital_city, "Washington");
strcpy(countries[0].currency, "U.S. Dollar");
countries[0].population = 250000000;
(*countries_num)++;
strcpy(countries[1].capital_city, "Israel");
strcpy(countries[1].capital_city, "Jerusalem");
strcpy(countries[1].currency, "New Israeli Shekel");
countries[1].population = 6000000;
(*countries_num)++;
strcpy(countries[1].capital_city, "France");
strcpy(countries[1].capital_city, "Paris");
strcpy(countries[1].currency, "Frank");
countries[1].population = 60000000;
(*countries_num)++;
/* now, print out the countries data. */
for (i=0; i < (*countries_num); i++) {
printf("Country %d:\n", i+1);
printf(" name: %s:\n", countries[i].name);
printf(" capital city: %s:\n", countries[i].capital_city);
printf(" currency: %s:\n", countries[i].currency);
printf(" population: %d:\n", countries[i].population);
}
malloc()
.
shmget()
, there is no need to use malloc()
when
placing data in that segment. Instead, we do all memory management
ourselves, by simple pointer arithmetic operations. We also need to make
sure the shared segment was allocated enough memory to accommodate future
growth of our data - there are no means for enlarging the size of the
segment once allocated (unlike when using normal memory management - we can
always move data to a new memory location using the realloc()
function).
/* this structure is used by the shmctl()
system call. */
struct shmid_ds shm_desc;
/* destroy the shared memory segment. */
if (shmctl(shm_id, IPC_RMID, &shm_desc) == -1) {
perror("main: shmctl: ");
}
ftok()
ftok()
:
/* identifier returned by ftok()
*/
key_t set_key;
/* generate a "unique" key for our set, using the */
/* directory "/usr/local/lib/ourprojectdir". */
set_key = ftok("/usr/local/lib/ourprojectdir", 'a');
if (set_key == -1) {
perror("ftok: ");
exit(1);
}
/* now we can use 'set_key' to generate a set id, for example. */
sem_set_id = semget(set_key, 1, IPC_CREAT | 0600);
.
.
ftok
call with this file will generate
a different key. Thus, the file used should be a steady file, and not one
that is likely to be moved to a different disk or erased and re-created.