Interprocess Communication
Old trick: Passing open files across a fork
or an exec
or through the file system.
New trick: IPC.
IPC types: Pipes, FIFOs, message queues, semaphores, shared memory, message queues, sockets, STREAMS.
Pipes
Half-duplex: Data flows in only one direction
Limitations:
- Half-duplex
- Used only between processes sharing a common ancestor by
fork
.
FIFOs get around the second limitation, and UNIX domain sockets get around both.
#include <unistd.h>
int pipe(int fd[2]); // return 0 on ok, -1 on error
fd[0]
is open for reading, and fd[1]
is open for writing. They are returned value.
However, the original pipe
interface is too primitive, thus we have below:
#include <stdio.h>
FILE *popen(const char *cmdstring, const char *type);
int pclose(FILE *fp);
The function popen
does a fork
and exec
to execute the cmdstring
and returns a standard I/O file pointer. If type
is "r", the file pointer is connected to the standard output of cmdstring
. If type
is "w", the file pointer is connected to the standard input of cmdstring
.
The pclose
function closes the standard I/O stream, waits for the command to terminate, and returns the termination status of the shell.
Think about UNIX filter. A filter becomes a coprocess when the same program generates the filter's input and read filter's output.
FIFOs
FIFOs are also called "named pipes". With it, unrelated processes can exchange data.
And FIFO is a type of file, and it has a path as well.
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
The mode
is same as for open
. The mkfifoat
can be used to create a FIFO in a location relative to the directory represented by the fd
file descriptor argument.
Once FIFO is created, we open it using open
. And normal I/O functions (e.g., close
, read
, write
, unlink
) all work with FIFOs.
Two uses of FIFOs:
- Used by shell commands to pass data from one shell pipeline to another without creating intermediate temporary files
- Used as rendezvous points in client-server applications
FIFO has a name, so it can be used for nonlinear connections.
XSI IPC
Identifiers and Keys
Each IPC structure (mq, sem, sm) in the kernel is referred to by a non-negative integer identifier. It is a internal name, so the cooperating processes need an external name associated with IPC object, which is called key.
Various ways for a client and a server to rendezvous at the same IPC structure:
- By
IPC_PRIVATE
- By defining the key in a common header
- By agreeing on a pathname and project ID and call function
ftok
to convert them into key
Three get
functions: msgget
, semget
, shmget
. They all have two arguments key
and flag
. Several things to consider:
- Do we want to create a new construct or refer to the existing one?
- How do we identify the existing object -- by identifier or key?
Permission Structure
struct ipc_perm {
uid_t uid; /* owner’s effective user ID */
gid_t gid; /* owner’s effective group ID */
uid_t cuid; /* creator’s effective user ID */
gid_t cgid; /* creator’s effective group ID */
mode_t mode; /* access modes */
};
The permissions can be changed with msgctl
, semctl
or shmctl
.
Pros & Cons
Cons:
- Used systemwide without reference count
- Not known by names in file system
- Can't use the multiplexed I/O functions
Pros:
- Reliable
- Flow controlled
- record ortiented
Message Queues
MQ is a linked list of messages. A new one is created with or an existing queue is opened by msgget
. New message is appended with msgsnd
. Message is fetched by receiver with msgrcv
, based on the type field but not in FIFO order.
#include <sys/msg.h>
int msgget(key_t key, int flag);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);
The cmd
: IPC_STAT
, IPC_SET
, IPC_RMID
Each MQ has a msqid_ds
struct with it, which records information about permission, number of messages, et cetera.
Semaphores
A semaphore is a counter used to provide access to a shared data object for multiple processes. The values of semaphore is related to how many resources are available for sharing.
Problems:
- Semaphore is a set of values
- The creation (
semget
) is independent of its initialization (semctl
). - The existence of semaphore doesn't depend on processes, so a program might terminate without releasing the allocated semaphores.
#include <sys/sem.h>
int semget(ket_t key, int nsems, int flag);
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);
int semop(int semid, struct sembuf semoparray[], size_t nops);
nsems
is the number of semaphores.
The semop
atomically performs an array of operations on a semaphore set.
Shared Memory
The only trick in using shared memory is synchronizing access to a given region among multiple processes. And semaphores, mutexes or record locking are often used for such purpose.
#include <sys/shm.h>
int shmget(key_t key, size_t size, int flag);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
void *shmat(int shmid, const void *addr, int flag);
int shmdt(const void *addr);
Once a shared memory segment has been created, a process attaches it to its address space by calling shmat
.
When we are done with a shm segment, we call shmdt
to detach it.
Client-Server Properties
- Open server: Server process opens files for the client. So the permissions are more flexible.
- Daemon process: The process will be contacted using some named IPC by all clients.
The server should require the client to create the IPC structure and have the client set the access permissions to user-read and user-write only. The times associated with the IPC structure should also be verified by the server to be recent.
Exercises
15.1
If the write end of the pipe is never closed, the reader never sees an end of file. The pager program blocks forever reading from its standard input.
15.2
The parent terminates right after writing the last line to the pipe. The read end will be automatically closed. In shell, the terminal mode will be changed and interferes with the paper program, which has also modified the terminal mode.
15.3
A file pointer will be returned. But shell can't execute and print error.
15.4
Looks at process's termination status.
15.5
To do this, we first need to adjust the buffering, then after changing fputs
into printf
, we must use fflush
as well.
15.6
If call wait
, the first child to terminate is child generated by popen
, which is not created by system
, so continue to wait. When pclose
calls wait
, an error is returned.
15.7
select
would indicate that the descriptor is readable. read
returns 0. For poll
, POLLHUP
even is returned.
15.8
Anything written by the child to stderr appears wherever parent's stderr would appear.
15.9
When cmdstring
terminates, the shell is waiting for this. The shell then exists.
15.10
The trick is to open the FIFO twice.
15.11
Identifier for the queue and the queue allowing world-read access.
15.13
We never store actual addresses in a shared memory segment, instead we store offsets to other objects in the shared memory segment.
15.15 ~ 18 is all "redo the program", which is too boring ......