View on GitHub

Computer Architecture and Operating Systems

Course taught at Faculty of Computer Science of Higher School of Economics

Lecture 9

Inter-Process Communication

Lecture

Slides (PDF, PPTX).

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:

POSIX message queues:

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;
}

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;
}

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;
}

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:

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:

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;
}

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

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:

See the full list in Wikipedia.

Managing processes

Utilities for managing processes:

To get detailed documentation, use the man utility.

Foreground and background processes

Tasks

  1. Make the 09_IPC directory at the server. Code must reside there.

  2. 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.
  3. Create program proc.c (modify endless.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
    ...
    
  4. 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;
    • ./killn PID NAME sends PID process signal NAME;
    • see kill;
    • use perror if an error occurred;
    • print “No such signal” if NAME is not found and returns 1 instead of 0;
    • try to ./killn running proc 4, non-existent process, foreign process.
  5. Copy proc.c to catchsig.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
    
  6. Join catchsig.c with child-control program from the lecture, name the result childctl.c.
    • ./childctl timeout signalQ signal1 ... signaln should:
      • print a message once in timeout seconds;
      • catch signal1signaln and print message;
      • peacefully exit when got signalQ.

Homework

Note: All required theory is provided in the lecture materials (see above).

  1. Finish all programs from the workshop (and send them).

  2. Modify the last program to:

    • exit after getting signalQ three times;
    • check every syscall return values on error state.

Bonus Tasks

Bonus tasks on message queues are here.

References