Lecture 9
Inter-Process Communication
Lecture
Outline
Examples
Signals
Never-ending program (to be used to send signals).
endless.c:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int i;
for (i = 0;; i++) {
sleep(1);
printf("%d\n", i);
}
return 0;
}
Sending signals in a C program.
killn.c:
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (kill(atoi(argv[1]), atoi(argv[2]))) {
perror("Failed to kill");
}
return 0;
}
Handling signals in C programs.
catch.c:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int sig) {
printf("Caught %d\n", sig);
}
int main(int argc, char *argv[]) {
signal(SIGINT, handler);
signal(SIGSEGV, handler);
int i;
for (i = 0;; i++) {
sleep(1);
printf("%d\n", i);
}
return 0;
}
Monitoring child processes.
waitchild.c:
#include <stdio.h>
#include <wait.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int stat;
pid_t pid;
if ((pid = fork()) == 0) {
while(1);
} else {
printf("Forking a child: %d\n", pid);
wait(&stat);
printf("And finally…\n");
if (WIFSIGNALED(stat)) {
psignal(WTERMSIG(stat), "Terminated:");
}
printf("Exit status: %d\n", stat);
}
return 0;
}
See documentation on the wait system call for details. Pay attention to the macros to get process status information.
Message Queues
Disadvantage of signals:
- one byte;
- totally asynchronous;
- idempotent (this can be cool sometimes, but still).
POSIX message queues:
- synchronous;
- can store content;
- can be queued;
- can be prioritized.
Different messages can be delivered over separate queues.
Detals on message queues can be found in the mq_overview manual page.
Creating a message queue
A message queue can be created with the mq_open system call.
crt_mq.c:
#include <mqueue.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
mqd_t mqd;
struct mq_attr attr;
attr.mq_maxmsg = 10;
attr.mq_msgsize = 2048;
mqd = mq_open(argv[1], O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR, &attr);
return 0;
}
- queue is for 10 messages 2048 bytes each;
- queue is creating for read/write if there’s no queue with the same name or else an error is generated;
- omitting
O_EXCL
allows re-creating a queue with the same name, clearing all messages, which is probably not a good idea; - permissions of the object created are
00600
(rw-------
).
Sending a message to queue
To send a message program should open a queue write-only and perform the mq_send system call:
snd_mq.c:
#include <mqueue.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
mqd_t mqd;
unsigned int prio;
mqd = mq_open(argv[1], O_WRONLY);
prio = atoi(argv[2]);
mq_send(mqd, argv[3], strlen(argv[3]), prio);
return 0;
}
- priority varies from 0 (lowest) to system-depended maximum (at least 31, 32767 in Linux);
- message content is a byte array, it does not have to be zero-terminating string.
POSIX queue provides prioritization mechanism. Earliest message from higher priority messages subset is to be delivered first.
Receiving a message
To receive a message, program has to call mq_receive:
rec_mq.c:
#include <mqueue.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
mqd_t mqd;
unsigned int prio;
void *buf;
struct mq_attr attr;
ssize_t n;
mqd = mq_open(argv[1], O_RDONLY);
mq_getattr(mqd, &attr);
buf = malloc(attr.mq_msgsize);
n = mq_receive(mqd, buf, attr.mq_msgsize, &prio);
printf("Read %ld bytes; priority = %u\n", (long) n, prio);
free(buf);
return 0;
}
- knowing nothing about message size, program must retrieve this value from queue attributes to provide an appropriate space in read buffer;
- there is no mechanism of message typification, so only size is printed.
Unlinking a message queue
To remove a queue, call mq_unlink:
unl_mq.c:
#include <mqueue.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
if (mq_unlink(argv[1])) {
perror("mq_unlink");
return -1;
}
return 0;
}
Notifying message delivery
Every mq_receive call returns a message if there as one.
If the queue is empty,
mq_receive()
can wait for message or return with fail status, depending on O_NONBLOCK
flag.
There is an alternate method to notify program by signal:
a program calls mq_notify
to subscribe on a certain queue.
Every time a message arrives in a queue,
the program gets a signal described in mq_notify()
and can handle message asynchronously.
See an example in mq_notify.
Running examples
POSIX message API is implemented in the librt
library, so compile program with -lrt
option.
A user can create queue by touch-ing arbitrary file
in the /dev/mqueue/
directory and unlink it just by removing the file object:
- Note:
mqueue
is a virtual file system. That means it holds no files on no devices, but emulates file object via OS file interface. - System administrator can also manipulate with
/proc/sys/fs/mqueue/*
files, which are, again, just implementation of Linux “configuring through virtual filesystem” concept.
Compile:
acos@acos-vm:~/mmap/mqueue$ gcc crt_mq.c -lrt -o crt_mq
acos@acos-vm:~/mmap/mqueue$ gcc snd_mq.c -lrt -o snd_mq
acos@acos-vm:~/mmap/mqueue$ gcc rec_mq.c -lrt -o rec_mq
acos@acos-vm:~/mmap/mqueue$ gcc unl_mq.c -lrt -o unl_mq
acos@acos-vm:~/mmap/mqueue$ ls
crt_mq crt_mq.c rec_mq rec_mq.c snd_mq snd_mq.c unl_mq unl_mq.c
Run:
acos@acos-vm:~/mmap/mqueue$ ./crt_mq /queue
acos@acos-vm:~/mmap/mqueue$ ls /dev/mqueue/ -l
total 0
-rw------- 1 acos acos 80 июн 9 13:30 queue
acos@acos-vm:~/mmap/mqueue$ ./snd_mq /queue 5 Five
acos@acos-vm:~/mmap/mqueue$ ./snd_mq /queue 10 Ten
acos@acos-vm:~/mmap/mqueue$ ./snd_mq /queue 5 Five_2
acos@acos-vm:~/mmap/mqueue$ ./snd_mq /queue 3 Three
acos@acos-vm:~/mmap/mqueue$ ./snd_mq /queue 7 Seven
acos@acos-vm:~/mmap/mqueue$ cat /dev/mqueue/queue
QSIZE:23 NOTIFY:0 SIGNO:0 NOTIFY_PID:0
acos@acos-vm:~/mmap/mqueue$ for n in `seq 5`; do ./rec_mq /queue; done
Read 3 bytes; priority = 10
Read 5 bytes; priority = 7
Read 4 bytes; priority = 5
Read 6 bytes; priority = 5
Read 5 bytes; priority = 3
acos@acos-vm:~/mmap/mqueue$ ./unl_mq /queue
acos@acos-vm:~/mmap/mqueue$ ls /dev/mqueue/ -l
total 0
Memory Mapping
Linux kernel has paging mechanism:
- when memory is limited, some memory pages can be swapped out;
- when a program needs one of swapped-out pages:
- TLB produces a page fault (no physical memory is provided for the virtual address);
- kernel handles the fault and loads corresponded page from disk and links to virtual memory page.
If paging out a .text
section, there is no need to provide a space on swap,
because this data is already on disk - e.g. in the binary program file, from which the process was started.
More general process of mapping file to memory is called memory map. The mmap syscall asks kernel to map selected file to the virtual memory address range. After this done, the range can be used as an ordinary array filled with file’s contents. The file has not to be read into memory completely, Linux use paging mechanism to represent corresponded file parts.
The example below is a simple cat
analog that
mmaps file and than just
writes it to stdout
.
mmcat.c:
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
int main(int argc, char *argv[]) {
char *addr;
int fd;
struct stat sb;
fd = open(argv[1], O_RDONLY);
fstat(fd, &sb);
addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
fwrite(addr, 1, sb.st_size, stdout);
return 0;
}
PROT_READ
means thatmmap
ped pages can only be read by the program;MAP_PRIVATE
means the program observe some fixed state of the file;- write to
mmap
ped area does not change the file itself; - program supposes file can not be changed while mmapped in
MAP_PRIVATE
mode; - fstat is used to determine file size (it discovers other file properties as well).
Run the example:
acos@acos-vm:~/mmap$ gcc mmcat.c -o mmcat
acos@acos-vm:~/mmap$ ./mmcat mmcat.c
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
int main(int argc, char *argv[]) {
char *addr;
int fd;
struct stat sb;
fd = open(argv[1], O_RDONLY);
fstat(fd, &sb);
addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
fwrite(addr, 1, sb.st_size, stdout);
return 0;
}
Shared Memory
Creating shared memory
crt_shm.c:
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int fd;
size_t size;
void *addr;
fd = shm_open(argv[1], O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
size = atol(argv[2]);
ftruncate(fd, size);
addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
return 0;
}
Writing to shared memory
wrt_shm.c:
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int fd;
size_t len;
char *addr;
fd = shm_open(argv[1], O_RDWR, 0);
len = strlen(argv[2]);
ftruncate(fd, len);
addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
printf("Copying %ld bytes\n", len);
memcpy(addr, argv[2], len);
return 0;
}
Reading from shared memory
rd_shm.c:
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int fd;
char *addr;
struct stat sb;
fd = shm_open(argv[1], O_RDONLY, 0);
fstat(fd, &sb);
addr = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
close(fd);
fwrite(addr, 1, sb.st_size, stdout);
printf("\n... Done");
return 0;
}
Unlinking shared memory
unl_shm.c:
#include <sys/mman.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
if (shm_unlink(argv[1])) {
perror("shm_unlink");
return -1;
}
return 0;
}
Running examples
Like mqueue
, shared memory objects can be created, viewed, and unlinked via /dev/shm
filesystem.
Moreover, viewing a shared memory file is just dumping its content.
Compile:
acos@acos-vm:~/shmem$ gcc crt_shm.c -lrt -o crt_shm
acos@acos-vm:~/shmem$ gcc wrt_shm.c -lrt -o wrt_shm
acos@acos-vm:~/shmem$ gcc rd_shm.c -lrt -o rd_shm
acos@acos-vm:~/shmem$ gcc unl_shm.c -lrt -o unl_shm
acos@acos-vm:~/shmem$ ls
crt_shm rd_shm unl_shm wrt_shm
crt_shm.c rd_shm.c unl_shm.c wrt_shm.c
Run:
acos@acos-vm:~/shmem$ ./crt_shm /shmem 0
acos@acos-vm:~/shmem$ ls -l /dev/shm/
total 0
-rw------- 1 acos acos 0 июн 9 11:52 shmem
acos@acos-vm:~/shmem$ ./wrt_shm /shmem 'Hello!'
Copying 6 bytes
acos@acos-vm:~/shmem$ ./rd_shm /shmem
Hello!
... Doneacos@acos-vm:~/shmem$ ./unl_shm /shmem
acos@acos-vm:~/shmem$ ls -l /dev/shm/
total 0
Workshop
Outline
- Practice with signals
- Study, run, and modify the lecture examples.
Notes
Sending Signals
List of signals supported in your Linux or MacOS:
kill -l
Documentation on the kill
utility that sends signals to processes:
man kill
Documentation on the kill
system call:
man 2 kill
Important signals:
SIGHUP
(1) - Terminal is closed.SIGINT
(2) - Interrupt process from terminal (Ctrl-C
).SIGQUIT
(3) - Terminate process and dumps core (Ctrl-\
).SIGKILL
(9) - Immediately terminate process (cannot be handled).SIGTERM
(15) - Request terminating process (can be handled). Sent by default.SIGSTOP
(19) - Stop a process for later resumption (cannot be handled).SIGTSTP
(20) - Request stop process (Ctrl-Z
, can be handled).SIGCONT
(18) - Continue (restart) process previously paused bySIGSTOP
orSIGTSTP
.SIGCHLD
(17) - Child process exits, is interrupted, or resumes.
See the full list in Wikipedia.
Managing processes
Utilities for managing processes:
ps
ps -a
ps -ef
pstree
ps axu
(BSD-style format)ps xf
(BSD style own process only)pidof program
ls /proc
kill proc
/kill -STOP proc
/kill -HUP proc
(use two terminals)
To get detailed documentation, use the man utility.
Foreground and background processes
- Foreground
- Interactive process (can input and output)
- Can be only one per a terminal
- Run like this:
./endless
- Background
- Can only output
- Can be any number
- Run like this:
./endless &
(& at the end)
- Changing type
Ctrl-Z
to stop (or sendSIGSTOP
/SIGTSTP
)fg
to continue in foregroundbg
to continue in background
Tasks
-
Make the
09_IPC
directory at the server. Code must reside there. -
Compile program
endless.c
from the lecture. Perform the following actions with it:- runs it in the background;
- stops it;
- resumes it in foreground;
- sends it the
SIGINT
signal to terminate:- use
ps -a
to get the list of running processes and their IDs; - use another instance of terminal to send a signal.
- use
-
Create program
proc.c
(modifyendless.c
from the lecture) that:- waits forever;
- periodically printfs its PID via getpid and the increased counter;
- uses a command-line argument to define the timeout between printfs.
For example,
./proc 5
printfs once a 5 seconds (using sleep):26475: 0 26475: 1 26475: 2 ...
- Write program
killn.c
to send a signal.- use
kill -l
and edit its output to create signal names array;- challenge: you can use the sed
utility (
sed s/regexp/replacement/g
) to eliminate handwork:- start with
kill -l | sed -z 's/\n//g
;
- start with
- challenge: you can use the sed
utility (
./killn PID NAME
sendsPID
process signalNAME
;- see kill;
- use perror if an error occurred;
- print “No such signal” if
NAME
is not found and returns1
instead of0
; - try to
./killn
runningproc 4
, non-existent process, foreign process.
- use
- Copy
proc.c
tocatchsig.c
and modify it, adding a signal handler../catchsig 5 SIGNAL_NAME1 SIGNAL_NAME2 ...
should print corresponding signal’s description (via strsignal) when catching a signal instead of falling off (still printing messages once a 5 seconds);- note not all signals can be handled.
$ ./catchsig 5 INT ABRT SEGV 26775: 0 ^C[Caught: Interrupt]26775: 1 26775: 2 [Caught: Segmentation fault]26775: 3 26775: 4 26775: 5 [Caught: Aborted]26775: 6 26775: 7 Illegal instruction $
$ kill -SEGV 26775 $ kill -ABRT 26775 $ kill -ILL 26772
- Join
catchsig.c
with child-control program from the lecture, name the resultchildctl.c
../childctl timeout signalQ signal1 ... signaln
should:- print a message once in timeout seconds;
- catch
signal1
…signaln
and print message; - peacefully exit when got
signalQ
.
Homework
Note: All required theory is provided in the lecture materials (see above).
-
Finish all programs from the workshop (and send them).
-
Modify the last program to:
- exit after getting
signalQ
three times; - check every syscall return values on error state.
- exit after getting
Bonus Tasks
Bonus tasks on message queues are here.