BIT 4205 - Network Programming - Updated - Complete Notes
BIT 4205 - Network Programming - Updated - Complete Notes
DEPARTMENT OF INFORMATION
TECHNOLOGY
COURSE CODE: BIT 4206
1|Page
COURSE OUTLINE
Instructional materials/Equipment
Audio visual aids in lecture rooms, Computer in the computer laboratories.
Assessments
A learner is assessed through ;
Continuous Assessment Tests (CATs) (30%), End of semester examination (70%)
2|Page
Stephens R., Fenner B, Rudoff A, (2003) Unix Network Programming, Volume 1:
The Sockets Networking API (3rd Edition)
Graba. J. (2007), An Introduction to Network Programming with Java, Addison-Wesley
Text books for further reading
Jorgensen B., (2009) Beej's Guide to Network Programming Using Internet
Sockets
Other support materials
Various application manuals and journals.
Variety of electronic resources as may be prescribed by the lecturer.
3|Page
Table of Contents
INTRODUCTION UNIX TECHNOLOGIES .............................................................................................................................. 8
4|Page
PROCESS MANAGEMENT ................................................................................................................................................ 29
Routines:.................................................................................................................................................................. 62
Mutex Variables........................................................................................................................................................... 72
Overview.................................................................................................................................................................. 72
Overview ..................................................................................................................................................................... 79
References ................................................................................................................................................................... 84
Sockets .................................................................................................................................................................... 89
References ................................................................................................................................................................... 96
7|Page
INTRODUCTION UNIX TECHNOLOGIES
A Computer system consists of one or more CPUs, device controllers, shared memory connected through a common
bus.
Every piece of hardware is different, but the OS abstracts this disparity to give the user a unified interface that is easy
to use.
-Making a computer system convenient to use: by hiding the details of the hardware resources from the user
provides him/her with a convenient interface.
-Resource management
Systems software help create abstraction and a uniform interface for programs to use when requesting for OS
services. Examples of systems software:
8|Page
Assemblers
Compilers
Linkers
Building a web browser, such as Google Chrome might once have been considered application
programming.
However, nowadays developing such applications requires attention to system details such as resources and
efficiency (e.g., Google Chrome is multi-threaded).
Systems programming
Historically, systems programming meant programming the system (i.e., building compilers, shells, loaders,
and so on).
However, nowadays, systems programming has come to mean programming with the system (i.e., making
system calls, managing threads, and so on).
Systems programming requires a greater awareness of issues of hardware and efficiency than application
programming.
It also requires knowledge of low level languages that provides direct access to and control of system
resources; this leads us to UNIX and C
What is UNIX?
The UNIX operating system is a set of programs that act as a link between the computer and the user.
The computer programs that allocate the system resources and coordinate all the details of the computer's
internals is called the operating system or kernel.
Users communicate with the kernel through a program known as the shell. The shell is a command line
interpreter; it translates commands entered by the user and converts them into a language that is understood
by the kernel.
Unix was originally developed in 1969 by a group of AT&T employees at Bell Labs, including Ken
Thompson, Dennis Ritchie, Douglas McIlroy, and Joe Ossanna.
9|Page
There are various Unix variants available in the market. Solaris Unix, AIX, UP Unix and BSD are few
examples. Linux is also a flavor of Unix which is freely available.
Several people can use a UNIX computer at the same time; hence UNIX is called a multiuser system.
A user can also run multiple programs at the same time; hence UNIX is called multitasking.
Unix Architecture:
The main concept that unites all versions of UNIX is the following four basics:
• Kernel: The kernel is the heart of the operating system. It interacts with hardware and most of the tasks like memory
management, tash scheduling and file management.
• Shell: The shell is the utility that processes your requests. When you type in a command at your terminal, the shell
interprets the command and calls the program that you want.
The shell uses standard syntax for all commands. C Shell, Bourne Shell and Korn Shell are most famous shells which
are available with most of the Unix variants.
10 | P a g e
• Commands and Utilities: There are various command and utilities which you would use in your day to day
activities. cp, mv, cat and grep etc. are few examples of commands and utilities.
There are over 250 standard commands plus numerous others provided through 3rd party software. All the commands
come along with various optional options.
• Files and Directories: All data in UNIX is organized into files. All files are organized into directories. These
directories are organized into a tree-like structure called the file system.
UNIX has evolved over the past thirty years from its conceptual stage into a powerful and effective operating system
and the credit goes to the fundamental structure of UNIX, which is very robust due to its layered approach comprising
of the:
• Kernel
• Shell
One of the most powerful and attractive features of UNIX is its file system, which manages data, stored on the
computer's secondary storage devices. The file system facilitates organizing stored information in a very logical way,
and manipulating it as and when required.
11 | P a g e
Fig: UNIX Architecture
The Kernel
> Core of the UNIX system
> Parts of kernel deals with I/O devices, called Device Drivers
12 | P a g e
Fig: UNIX Kernel
The kernel is the Core of the UNIX system, which controls the system hardware and performs various low
level functions. Viewing the operating system as a set of layers, the kernel usually refers to the operating
system itself.
However, the kernel does not directly deal with the user. Instead, it starts a separate program, called shell,
which actually interacts with the user and interprets the commands. The utilities and applications add
special capabilities to the operating system.
Kernel functions:
• Memory management
• Process scheduling
• File management and security
• Interrupt handling and error reporting
13 | P a g e
• Input / Output services
• Date and Time services
• System accounting
The Kernel:
• Controls the execution of processes
• Allows creation, termination, suspension and communication of process.
• Enforces the security scheme and prevents unauthorized access to stored information like files and
directories.
• Handles all low level input and output services.
• Handles the interrupts and system errors.
• Schedules the Central Processor and allocates system resources to each user of the system.
If the UNIX operating system is to be loaded on different hardware platforms, the kernel has to be
customized accordingly.
All applications and utilities including the shell interact with the kernel by invoking well-structured routines
of kernel by means of messages called system calls.
The Shell:
• Is the command interpreter of UNIX
• Interface between the kernel and the user
• Provides powerful programming capabilities
The shell is the interface between a user and the operating system. When any user logs in, a copy of the shell
program is started up, displaying a prompt sign such as '$' (dollar), which informs the user that the shell is
waiting to accept a command.
In UNIX, there are three types of shells:
1. The Bourne Shell
2. The C Shell
3. The Korn Shell
From the figures you can conceive the way; various parts and portions of UNIX are arranged around the
hardware. The core of UNIX is the kernel, which schedules jobs and manages data storage, and it is very
14 | P a g e
closely interfaced with hardware. The hardware functions according to the machine instructions released
ultimately by the kernel, based on the shell's interpretation of user commands.
Surrounding the kernel are parts of software of the shell relating to:
1. Execution of commands for piping and filtering actions.
2. Tools for carrying out foreground and background processing.
3. Utilities for configuring the hardware.
4. I/O redirection and command execution utilities.
5. Filename substitution.
The shell forms an interface between the user and the kernel. Outside the shell are the different user-specific
utilities of UNIX, numbering around 300, which enhance its capability.
Above this, are the user programs and application packages, which can facilitate data entry, data
modification, query, report generation, etc.
15 | P a g e
Figure: Shell functions
The shell is a program that collects and interprets the user commands, invokes the concerned program file
from memory and directs the kernel to execute them.
As the name shell suggests, the shell envelops the kernel. Externally, the UNIX operating system is made up
of two parts. One part is a large set of system programs, each one corresponding to a command, and another
part is the shell, which interprets, manages and coordinates the execution of these programs. Most of the
users are unaware of the various complexities of the operating system or the hardware.
16 | P a g e
The user sees the shell command as one peculating down the UNIX system, and the kernel, looking up to the
user through the shell- for the next set of process. This relationship and interface of the kernel with different
users is shown in the figure below.
Every user, once accepted by the kernel as authorized, is issued a shell prompt. Each user has his own shell
(a separate and personal copy for each user) and the commands issued by him is counted and numbered
serially. A separate shell procedure is reserved for each user.
17 | P a g e
Command Execution
Refer figure below, which illustrates the cycle of command execution. To begin with, the kernel displays a
shell prompt after authorized login. The shell waits for the input from the user, decodes the command line
and searches for the program. If the program is found, the shell retrieves and submits it to the kernel to
execute the program.
The kernel delivers the output. If the command is not found, it signals the kernel to display command not
found at the terminal. The shell accepts the kernel's reply, and in both cases displays the next prompt. This
cycle continues until the user with <CTRL>
D or logout terminates it. Once the shell encounters end of input, it instructs the kernel to log out the user,
and displays the login message again.
The system directories where the command file programs are stored are the /bin and /usr/bin. On receipt of
the command, which is nothing but the executable filenames in the system directory, the shell locates them
and transfers control to the kernel to execute.
These filenames can be verified by the command pwd and Is, after changing to the directories Ibin and
/usrlbin.
Shell Environment
A separate shell process is apportioned to each user as stated earlier. The separate shell once created provides
an environment within which the user operates. The shell environment consists of the following to ensure
proper interaction between the user and the system:
18 | P a g e
1. The name of the users, and a working directory (a directory allotted to the user by the operating system).
2. A list of system directories to be searched to locate the commands given by the user.
3. The file access permission modes.
4. A record of users‟ identification (user id) and group identification number (group id).
Foreground and Background Processing
The shell can execute commands in the foreground and also in the background. Foreground commands
execute sequentially (one command cannot commence until execution of the preceding command has
completed execution). Foreground commands like editing a program or processing a text involves interaction
with the terminal and the input. Background processing jobs related commands are accompanied by &
(ampersand) symbol. Jobs like compiling, printing can be carried out as background jobs, since they do not
require any interaction with the user. There may be several background commands executed at a time. This
truly makes UNIX a multitasking system.
Review Questions
1. List the main functions of the kernel, and shell, respectively.
2. Explain the relation between UNIX commands and UNIX system filenames.
3. Define and explain system calls in UNIX, and explain how UNIX is made portable across various
platforms.
4. Sketch the cyclic process of executions of commands issued from the terminal, and various roles played
by the shell, kernel and hardware.
5. What does the shell environment contain with reference to every specific user interacting with UNIX.
6. What does background processing mean?
7. What do you mean by tools of UNIX, list the classification of tools and utilities?
8. Sketch the UNIX directory system listing the system directories and the functions of the files under them.
9. Sketch and explain the conceptual structure of UNIX system software layers.
10. Explain with a sketch, how a common user looks upon his interaction with hardware.
19 | P a g e
UNIX SYSTEM CALLS
A system call is a mechanism used by an application to request for services from the kernel.
Processes can either be user or kernel initiated and can run either in user mode or kernel mode.
Kernel mode
Kernel mode processes can:
Enable/Disable interrupts.
20 | P a g e
Library calls and system calls
The difference between system calls and library calls is that:
System calls are provided by the system and are executed in the system kernel.They are entry points into the
kernel and are therefore NOT linked into your program. These are not portable calls.
Library calls include the ANSI C standard library and are therefore portable. These functions are linked into
your program. (Recall linking in programming)
Because system calls are part of the O/S, the program has to make a context switch to the kernel when they
are called and because of this, they have a high startup overhead. The upside is that the time executing these
routines is assigned to the OS and not the user program.
Calling standard library functions does not involve system call interface. However a library function may
initiate a system call which in turn issues a system call e.g. a call to printf() may invoke write() system call to
do output.
A system call always involves using system call interface and using the kernel of OS.
21 | P a g e
Anatomy of a System call
General form of a system call is as follows:
Where:
returned_value = nonnegative integer if OK, -1 if error (in this case error code is placed in external variable
errno)
22 | P a g e
Handling System call
General Syntax:
23 | P a g e
A system call Error
When a system call discovers and error, it returns -1 and stores the reason the called failed in an external
variable named "errno".
The "/usr/include/errno.h" file maps these error numbers to manifest constants, and it these constants that
you should use in your programs.
When a system call returns successfully, it returns something other than -1, but it does not clear "errno".
"errno" only has meaning directly after a system call that returns an error.
Furthermore, when a system call discovers an error, you should use the "perror()" subroutine to print a
diagnostic message on the standard error file that describes why the system call failed.
void perror(string)
char string;
Where:
file_name is pointer to a null terminated character string that names the file.
26 | P a g e
Example 2: use of fork() and exec() to create a new directory
/* newdir.c
create a new directory, called newdir, using fork() and
exec() */
#include <stdio.h>
int main()
{
int fd;
if ( fork() != 0)
wait ((int *) 0);
else {
execl ("/bin/mkdir", "mkdir", "newdir", (char *) NULL);
fprintf (stderr, "exec failed!\n");
exit (1);
}
/* now use newdir */
if ( (fd = open("newdir/foo.bar", O_RDWR|O_CREAT, 0644))== -1)
{
fprintf (stderr, "open failed!\n");
exit (2);
}
write (fd, "Hello, world\n", 14);
close (fd);
exit (0);
}
27 | P a g e
Categories of System Calls in Unix
28 | P a g e
PROCESS MANAGEMENT
What is process?
• The simplest definition of a process is that it is a program in execution on a computer.
• Another definition; a process the basic computational element in modern computer system.
• How does it differ from a program?
• A process consists of:
A status record to keep track of the progress of the process during execution
• Each process uses resources to execute programs on data on Von Neumann computer.
• During execution, the process requests different resources according to the particular behavior defined by
the program
• Multiprogramming systems explicitly allow multiple processes to exist at any given time, where only one is
using the CPU at any given moment, while the remaining processes are performing I/O or are waiting for
resources.
Process Manager
• The process manager implements:
The process abstraction by creating a model for the way the process uses the CPU and any system
resources.
An address space (a subsection of the OS memory space where a process executes) and
29 | P a g e
A set of registers (including PC and stack pointer).
The states are (roughly)
Running
Ready (runnable)
Blocked (waiting)
Completed
With the PCB, the OS can manipulate all parts of a program's state.
PCB contents include
30 | P a g e
Saved program counter (for processes not in running state). The counter indicates the address of the
next instruction to be executed for this process.
Saved CPU register set (for processes not running) The registers vary in number and type, depending
on the computer architecture. They include accumulators, index registers, stack pointers, and
general-purpose registers, plus any condition-code information. Along with the program counter,
this state information must be saved when an interrupt occurs, to allow the process to be continued
correctly afterward
This information may include such information as the value of the base and limit registers, the page tables, or
the segment tables, depending on the memory system used by the operating system.
Accounting information. This information includes the amount of CPU and real time used, time
limits, account numbers, job or process numbers, and so on.
I/O status information: The information includes the list of I/O devices allocated to this process, a list
of open files, and so on.
User id – the process owner.
A PCB is needed because the OS does not always "pay attention" to a process so from the PCB, the
OS can determine what the process is doing, and how it should respond to some event.
When this switching is so frequent that hardly is there any execution of the process it becomes an
overhead called a context switch overhead.
31 | P a g e
Interrupts
An interrupt is an “unplanned” function call to a system routine (aka, the interrupt handler)
Unlike a normal function call, the interrupted thread cannot anticipate the control transfer or prepare
for it in any way
Time slice has expired (clock interrupt) => PCB goes on ready queue
Process makes a blocking call => PCB goes on specified queue and state is set to blocked
Types Interrupts
Several classes of interrupts exist:
Program interrupts (trap), generated by a program such as an overflow, division by zero etc.
Traps
32 | P a g e
An interrupt is an exceptional event that is automatically handled by the interrupt handler.
In the case of an overflow, memory addressing violation, and the use of privileged instruction in user
mode, the handler will abort the program
All traps are going to be considered synchronous interrupts since they are generated by the processor
itself when it encounters an error inside the code stream.
I/O Interrupts
This type of interrupt occurs when a device sends a signal to inform the CPU that an I/O operation
has been completed.
When an I/O interrupt occurs, the Program State of the running program is saved so that it can be
restarted from the same point after the interrupt has been handled.
Timer Interrupt
We can add a time register, set to a specific value before a program stops, which is decremented with
each clock tick
When the timer reaches zero, the Timer Interrupt bit (TI) is set to “1”, indicating that a timer interrupt
has occurred and transferring control to the interrupt handler
Disable interrupts
When higher priority interrupt has been processed, processor returns to previous interrupt.
Context switch process
Steps:
1. Save the context of the processor, incl. PC and other registers.
2. Update the PCB of the currently running process. Includes changing the state of the process, other fields
and reason for changing the state.
3. Move the PCB of this process to the appropriate queue.
4. Select another process for execution.
5. Update the PCB of the process selected, incl. changing the state of this process to running.
6. Update memory mgt data structures depends on how address translation is managed.
7. Restore the context of the processor to that which existed at the time the selected process was switched
out.
Execution of a new program from within the current program (with the return to the old
program)
int system(const char *string) ;
34 | P a g e
Syntax
main()
{
int status ;
. . . .
status = system( “new_prog par1 par2”) ;
Analysis of status
. . . .
}
Shell process (starting your program in foreground)
35 | P a g e
Fork()
This call makes an exact replica of process state except for PID.
The forked process is the “child” while the creator of the process is the parent.
UNIX implements through the fork() and exec() system calls an elegant two-step mechanism for
process creation and execution.
fork() is used to create the image of a process using the one of an existing one.
exec is used to execute a program by overwriting that image with the program's one.
Process creation
Once a parent creates a child process, a number of execution possibilities exist:
o The parent may immediately enter a wait state for the child to finish.
o The parent could immediately terminate;
o Both may continue to execute.
If the parent happens to terminate before the child has returned its value, then the child will
become a zombie process and may be listed as such in the process status list!
The init process(pid = 1) is the parent of all processes.it is responsible for bringing up a
Unix system after the kernel has been bootstrapped.
Initially, init duplicates (forks) several times and each child process replaces its code
(execs) with the code of the program they are running.
All orphaned child processes becomes a child of init when the parent process dies.
Example
A call to fork() of the form:
#include <sys/types.h>
pid_t childpid;
36 | P a g e
...
childpid = fork(); /* returns child's pid in the parent, 0 in
the child */
...
On creating new process:
The two processes obviously have two different process ids(pid).
In a C program process ids are conveniently represented by variables of pid_t type, The type being
defined in the sys/types.h header.
In UNIX the PCB of a process contains the id of the process's parent, - as parent id (ppid) the pid of
the process that called fork(),
Child process id is: pid.
Example 2:simpfork.c
main()
{
int i;
printf("simpfork: pid = %d\n", getpid());
i = fork();
printf("Did a fork. It returned %d. getpid = %d. getppid
= %d\n", i, getpid(), getppid());
}
UNIX> simpfork
simpfork: pid = 914 Did a fork. It returned 915. getpid = 914. getppid = 381
Did a fork. It returned 0. getpid = 915. getppid = 914
UNIX>
37 | P a g e
When simpfork is executed, it has a pid of 914. Next it calls fork() creating a duplicate process with a
pid of 915. The parent gains control of the CPU, and returns from fork() with a return value of the
915 -- this is the child's pid. It prints out this return value, its own pid, and the pid of csh, which is
still 381. Then it exits. Next, the child gets the CPU and returns from fork() with a value of 0. It
prints out that value, its pid, and the pid of the parent.
getpid() and getppid()
In order to know the pid of the child system call named getpid() is provided for this purpose,
andanother one, named getppid() is used to ask the system about the parent's id.
Both functions take no arguments and return the requested value in pid_t type, or -1 in case of failure.
UNIX defines several sophisticated inter-process communication (IPC) mechanisms, the simplest of
which is a parent's ability to test the termination status of its children.
A synchronization mechanism is provided via the wait() system call, that allows a parent to sleep
until one of its children exits, and then get its exit status.
38 | P a g e
When wait() is called, the process is suspended until one of its child processes exits, and then the call
returns with the exit status of the child process.
State diagram of unix process
Process termination
39 | P a g e
Note: since wait() returns the exit status multiplied by 256 (contained in the upper 8 bits), the status
value is shifted right 8 bits (divided by 256) to obtain the correct value.
Example 2; Process system call- execlp( )
/* myshell.c
This program is a simple command interpreter that uses execlp() to
execute commands typed in by the user.
*/
#include <stdio.h>
#define EVER ;;
int main()
{
int process;
char line[81];
for (EVER)
{
fprintf(stderr, "cmd: ");
if ( gets (line) == (char *) NULL) /* blank line
input */
exit (0);
/* create a new process */
40 | P a g e
process = fork ();
if (process > 0) /* parent */
wait((int *) 0); /* null pointer - return value not
saved */
else if (process == 0) /* child */
{ /* execute program */
execlp (line, line, (char *) NULL);
/* some problem if exec returns */
fprintf (stderr, "Can't execute %s\n", line);
exit (1);
}
else if ( process == -1) /* can't create a new process */
{
fprintf (stderr, "Can't fork!\n");
exit (2);
}
}
}
Inter-Process Communication
Mechanisms for IPC
Two basic IPC mechanism:
Shared memory
Message passing
Shared memory requires that these processes share a common buffer pool.
The code for implementing the buffer can be written explicitly by the application programmer.
Another way to achieve the same effect is for the operating system to provide the means for cooperating
processes to communicate with each other via an inter process communication
(IPC) facility.
IPC provides a mechanism to allow processes to communicate and to synchronize their actions without
sharing the same address space.
41 | P a g e
IPC is particularly useful in a distributed environment where the communicating processes may reside on
different computers connected with a network. An example is a chat program used on the World Wide Web.
Shared Memory
Shared memory offers an extremely fast way of communicating; any data written by one process to a
shared memory region can be read immediately by any other process that has mapped that region
into its address space.
To obtain synchronization, however, shared memory must be used in conjunction with another
One of the processes creates a shared segment. Other processes get ID of the created segment.
Each process, using the same key, attaches to itself the created shared segment.
Now each process may write and read data into the shared segment.
To avoid possible race condition, processes should coordinate using shared memory.
Normally, UNIX prevents a user process from accessing any data in memory belonging to another process.
42 | P a g e
The key argument is an access value associated with the semaphore ID.
The size argument is the size in bytes of the requested shared memory.
The flag argument specifies the initial access permissions and creation control flags.
When the call succeeds, it returns the shared memory segment ID. This call is also used to get the ID
of an existing shared segment (from a process requesting sharing of some existing memory
portion).
43 | P a g e
System calls for Attaching to shared memory - Shmat()
Suppose process 1, a server, uses shmget() to request a shared memory segment successfully. That
shared memory segment exists somewhere in the memory, but is not yet part of the address space of
process 1 (shown with dashed line below).
Similarly, if process 2 requests the same shared memory segment with the same key value, process 2 will be
granted the right to use the shared memory segment; but it is not yet part of the address space of process 2.
To make a requested shared memory segment part of the address space of a process, use shmat().
shmat() returns a pointer address, to the head of the shared segment associated with a valid shared
memory id from shmget().
shmdt() detaches the shared memory segment located at the address indicated by addr shmdt ( ptr ) ;
Where ptr- is result of shmat()
Features of shared memory
Efficiency (no intermediate copying of data as in pipes)
44 | P a g e
Random access (a sequential byte stream in pipes)
Many-to-many mechanism of IPC: many processes may attach the same segment (pipes provide
one-to-one mechanism of IPC)
No synchronization is provided (pipes provide a synchronization) – Disadvantage.
NB: Pipes are used extensively in message passing
Process A Process B
#include … #include …
#define MSIZ 27 #define MSIZ 27
main() main()
{ char c ; int shmid ; { char c ; int shmid ;
key_t key = 123 ; key_t key = 123 ;
char *shm, *s ; char *shm, *s ;
if ( (shmid = shmget( key,MSIZ,IPC_CREAT | 0666) ) < 0 ) if ( (shmid = shmget( key,0) ) < 0 )
{error…; exit(1) ; }
if ( (shm = shmat( shmid, NULL, 0)) == (char *) –1 )
(error … ; exit(1) ; } {error … ; exit(1) ; }
/*Put data into shared memory */
s = shm ; if ( (shm = shmat( shmid, NULL, 0)) == (char *) –1 )
for (c = „a‟ ; c <= „z‟ ; c++ ) (error … ; exit(1) ; }
*s++ = c ; /* get from shared memory */
*s = „\0‟ ; for (s=shm ; *s != „\0‟ ; s++ )
/*wait until process B changes the first character */ putchar( *s ) ; / * to display */
putchar( „\n‟) ;
while(*shm != „*‟ ) sleep( 1 ); /* Change the first char in segment */
shmdt( shm ) ; *shm = „*‟ ;
exit( 0 ) ; shmdt ( shm ) ; /* detach */
} exit( 0 ) ;
}
45 | P a g e
Message Passing IPC
The function of a message system is to allow processes to communicate with one another without the
need to resort to shared data. An IPC facility provides at least the two operations:
send( dest, &message)
receive( source, &message).
If processes P and Q want to communicate, they must send messages to and receive messages from
each other; a communication link must exist between them.
Local IPC
- takes place in the same machine.
46 | P a g e
Remote IPC
-Takes place in the different machines.
Host 1 Host 2
Client
Process A Process B
Server
OS kernel OS kernel
request
Network
reply
semaphores
Remote
signals procedure call
(RPC)
pipes
message queues Transport Layer
Interface (TLI)
shared memory
sockets
files
47 | P a g e
Process Synchronization
Since processes frequently needs to communicate with other processes therefore, there is a need for a
well Structured communication, in order to avoid synchronization problems such as Race Condition
(shared memory)
In operating systems, processes that are working together share some common storage (main
memory, file etc.) that each process can read and write. When two or more processes are reading or
writing some shared data and the final result depends on who runs precisely when, are called race
conditions.
A race condition is a flaw in a process whereby the output and/or result of the process is
unexpectedly and critically dependent on the sequence or timing of other events.
Concurrently executing threads that share data need to synchronize their operations and processing in
order to avoid race condition on shared data. Only one „customer‟ thread at a time should be allowed
to examine and update the shared variable.
Race Conditions
Race conditions are also possible in Operating Systems. If the ready queue is implemented as a linked list
and if the ready queue is being manipulated during the handling of an interrupt, then interrupts must be
disabled to prevent another interrupt before the first one completes. If interrupts are not disabled than the
linked list could become corrupt.
48 | P a g e
The key to preventing trouble involving shared memory is find some way to prohibit more than one
process from reading and writing the shared data simultaneously. That part of the program where the
shared memory is accessed is called the Critical Section. To avoid race conditions and flawed results,
one must identify codes in Critical Sections in each thread.
The characteristic properties of the code that form a Critical Section are Codes that reference one or
more variables in a “read-update-write” fashion while any of those variables is possibly being altered
by another thread.
Codes that alter one or more variables that are possibly being referenced in “read-updatewrite”
fashion by another thread. Codes use a data structure while any part of it is possibly being altered by
another thread.
Codes alter any part of a data structure while it is possibly in use by another thread.
Here, the important point is that when one process is executing shared modifiable data in its critical
section, no other process is to be allowed to execute in its critical section. Thus, the execution of
critical sections by the processes is mutually exclusive in time.
Semaphores
A semaphore is an integer variables that restricts access to shared resources. Main purpose is to synchronize
the access of processes to shared resources (files, shared memory), by enforcing mutual exclusion to the
resource.
49 | P a g e
Monitors
A higher-level abstraction that provides a convenient and effective mechanism for process
Synchronization.
Monitors are embedded in some concurrent programming languages. Java has monitors.
Monitors enforce a style of programming where complex synchronization code doesn‟t get mixed
with other code: it is separated and put in monitors.
50 | P a g e
POSIX THREADS PROGRAMMING
Pthreads Overview
What is a Thread?
A thread is defined as an independent stream of instructions that can be scheduled to run as such by
the operating system.
To the software developer, the concept of a "procedure" that runs independently from its main
51 | P a g e
Figure: Unix process
Pthreads Overview
53 | P a g e
In order to take full advantage of the capabilities provided by threads, a standardized programming interface
was required.
o For UNIX systems, this interface has been specified by the IEEE POSIX 1003.1c standard (1995).
o Most hardware vendors now offer Pthreads in addition to their proprietary API's.
The POSIX standard has continued to evolve and undergo revisions, including the Pthreads specification.
Pthreads are defined as a set of C language programming types and procedure calls, implemented with a
pthread.h header/include file and a thread library - though this library may be part of another library, such
Why Pthreads?
In the world of high performance computing, the primary motivation for using Pthreads is to realize potential
program performance gains.
When compared to the cost of creating and managing a process, a thread can be created with much less
operating system overhead. Managing threads requires fewer system resources than managing processes.
For example, the following table compares timing results for the fork() subroutine and the
pthread_create() subroutine. Timings reflect 50,000 process/thread creations, were performed with the
Note: don't expect the sytem and user times to add up to real time, because these are SMP systems
with multiple CPUs working on the problem at the same time. At best, these are approximations run
on local machines, past and present.
fork() pthread_create()
Platform
real user sys real user sys
Intel 2.6 GHz Xeon E5-2670 (16 cores/node) 8.1 0.1 2.9 0.9 0.2 0.3
Intel 2.8 GHz Xeon 5660 (12 cores/node) 4.4 0.4 4.3 0.7 0.2 0.5
AMD 2.3 GHz Opteron (16 cores/node) 12.5 1.0 12.5 1.2 0.2 1.3
54 | P a g e
AMD 2.4 GHz Opteron (8 cores/node) 17.6 2.2 15.7 1.4 0.3 1.3
IBM 4.0 GHz POWER6 (8 cpus/node) 9.5 0.6 8.8 1.6 0.1 0.4
IBM 1.9 GHz POWER5 p5-575 (8 cpus/node) 64.2 30.7 27.6 1.7 0.6 1.1
IBM 1.5 GHz POWER4 (8 cpus/node) 104.5 48.6 47.2 2.1 1.0 1.5
INTEL 2.4 GHz Xeon (2 cpus/node) 54.9 1.5 20.8 1.6 0.7 0.9
INTEL 1.4 GHz Itanium2 (4 cpus/node) 54.5 1.1 22.2 2.0 1.2 0.6
All threads within a process share the same address space. Inter-thread communication is more
efficient and in many cases, easier to use than inter-process communication.
Threaded applications offer potential performance gains and practical advantages over non-threaded
applications in several other ways:
o Overlapping CPU work with I/O: For example, a program may have sections where it is
performing a long I/O operation. While one thread is waiting for an I/O system call to
complete, CPU intensive work can be performed by other threads.
o Priority/real-time scheduling: tasks which are more important can be scheduled to supersede
or interrupt lower priority tasks.
o Asynchronous event handling: tasks which service events of indeterminate frequency and
duration can be interleaved. For example, a web server can both transfer data from previous
requests and manage the arrival of new requests.
The primary motivation for considering the use of Pthreads on an SMP architecture is to achieve
optimum performance. In particular, if an application is using MPI for on-node communications,
there is a potential that performance could be greatly improved by using Pthreads for on-node data
transfer instead.
For example:
o MPI libraries usually implement on-node task communication via shared memory, which
involves at least one memory copy operation (process to process).
55 | P a g e
o For Pthreads there is no intermediate memory copy required because threads share the same
address space within a single process. There is no data transfer, per se. It becomes more of a
cache-to-CPU or memory-to-CPU bandwidth (worst case) situation. These speeds are much
higher.
Parallel Programming:
On modern, multi-cpu machines, pthreads are ideally suited for parallel programming, and whatever applies to
parallel programming in general, applies to parallel pthreads programs.
There are many considerations for designing parallel programs, such as:
o Problem partitioning
o Load balancing
o Communications
o Data dependencies
o Memory issues
o I/O issues
o Program complexity
o Programmer effort/costs/time
In general though, in order for a program to take advantage of Pthreads, it must be able to be organized into
discrete, independent tasks which can execute concurrently. For example, if routine1 and routine2 can be
interchanged, interleaved and/or overlapped in real time, they are candidates for threading.
56 | P a g e
Programs having the following characteristics may be well suited for pthreads:
o Work that can be executed, or data that can be operated on, by multiple tasks simultaneously:
o Manager/worker: a single thread, the manager assigns work to other threads, the workers. Typically,
the manager handles all input and parcels out work to the other tasks. At least two forms of the
manager/worker model are common: static worker pool and dynamic worker pool.
o Pipeline: a task is broken into a series of suboperations, each of which is handled in series, but
concurrently, by a different thread. An automobile assembly line best describes this model.
o Peer: similar to the manager/worker model, but after the main thread creates other threads, it
participates in the work.
Thread-safeness:
For example, suppose that your application creates several threads, each of which makes a call to the same
library routine:
o As each thread calls this routine it is possible that they may try to modify this global structure/memory
location at the same time.
o If the routine does not employ some sort of synchronization constructs to prevent data corruption, then
it is not thread-safe.
58 | P a g e
The implication to users of external library routines is that if you aren't 100% certain the routine is thread-safe,
then you take your chances with problems that could arise.
Recommendation: Be careful if your application uses libraries or other objects that don't explicitly guarantee
thread-safeness. When in doubt, assume that they are not thread-safe until proven otherwise. This can be done
by "serializing" the calls to the uncertain routine, etc.
Thread Limits:
Although the Pthreads API is an ANSI/IEEE standard, implementations can, and usually do, vary in ways not
specified by the standard.
Because of this, a program that runs fine on one platform, may fail or produce wrong results on another
platform.
For example, the maximum number of threads permitted, and the default thread stack size are two important
limits to consider when designing your program.
Several thread limits are discussed in more detail later in this tutorial.
59 | P a g e
The Pthreads API
The original Pthreads API was defined in the ANSI/IEEE POSIX 1003.1 - 1995 standard. The POSIX standard
has continued to evolve and undergo revisions, including the Pthreads specification.
Copies of the standard can be purchased from IEEE or downloaded for free from other sites online.
The subroutines which comprise the Pthreads API can be informally grouped into four major groups:
1. Thread management: Routines that work directly on threads - creating, detaching, joining, etc. They
also include functions to set/query thread attributes (joinable, scheduling etc.)
2. Mutexes: Routines that deal with synchronization, called a "mutex", which is an abbreviation for
"mutual exclusion". Mutex functions provide for creating, destroying, locking and unlocking mutexes.
These are supplemented by mutex attribute functions that set or modify attributes associated with
mutexes.
3. Condition variables: Routines that address communications between threads that share a mutex.
Based upon programmer specified conditions. This group includes functions to create, destroy, wait
and signal based upon specified variable values. Functions to set/query condition variable attributes
are also included.
Naming conventions: All identifiers in the threads library begin with pthread_. Some examples are shown
below.
pthread_mutex_ Mutexes
60 | P a g e
pthread_condattr_ Condition attributes objects
The concept of opaque objects pervades the design of the API. The basic calls work to create or modify
opaque objects - the opaque objects can be modified by calls to attribute functions, which deal with opaque
attributes.
The Pthreads API contains around 100 subroutines. This tutorial will focus on a subset of these - specifically,
those which are most likely to be immediately useful to the beginning Pthreads programmer.
For portability, the pthread.h header file should be included in each source file using the Pthreads library.
The current POSIX standard is defined only for the C language. Fortran programmers can use wrappers around
C function calls. Some Fortran compilers (like IBM AIX Fortran) may provide a Fortram pthreads API.
Several examples of compile commands used for pthreads codes are listed in the table below.
icc -pthread C
INTEL
Linux
icpc -pthread C++
pgcc -lpthread C
PGI
Linux
pgCC -lpthread C++
61 | P a g e
bgxlc_r / bgcc_r C (ANSI / non-ANSI)
IBM
Blue Gene
bgxlC_r, bgxlc++_r C++
Thread Management
Creating and Terminating Threads
Routines:
pthread_create (thread,attr,start_routine,arg)
pthread_exit (status)
pthread_cancel (thread)
pthread_attr_init (attr)
pthread_attr_destroy (attr)
Creating Threads:
Initially, your main() program comprises a single, default thread. All other threads must be explicitly created by
the programmer.
pthread_create creates a new thread and makes it executable. This routine can be called any number of times
from anywhere within your code.
pthread_create arguments:
o thread: An opaque, unique identifier for the new thread returned by the subroutine.
o attr: An opaque attribute object that may be used to set thread attributes. You can specify a thread
attributes object, or NULL for the default values.
o start_routine: the C routine that the thread will execute once it is created.
o arg: A single argument that may be passed to start_routine. It must be passed by reference as a pointer
cast of type void. NULL may be used if no argument is to be passed.
The maximum number of threads that may be created by a process is implementation dependent. Programs
that attempt to exceed the limit can fail or produce wrong results.
62 | P a g e
Querying and setting your implementation's thread limit - Linux example shown. Demonstrates querying the
default (soft) limits and then setting the maximum number of processes (including threads) to the hard limit.
Then verifying that the limit has been overridden.
Once created, threads are peers, and may create other threads. There is no implied hierarchy or
dependency between threads.
Thread Attributes:
By default, a thread is created with certain attributes. Some of these attributes can be changed by the
programmer via the thread attribute object.
For example, threads can be scheduled to run FIFO (first-in first-out), RR (round-robin) or OTHER
(operating system determines). It also provides the ability to set a thread's scheduling priority value.
The Pthreads API does not provide routines for binding threads to specific cpus/cores. However,
local implementations may include this functionality - such as providing the non-standard
pthread_setaffinity_np routine. Note that "_np" in the name stands for "non-portable".
Also, the local operating system may provide a way to do this. For example, Linux provides the
sched_setaffinity routine.
Terminating Threads
There are several ways in which a thread may be terminated:
o The thread returns normally from its starting routine. It's work is done.
o The thread makes a call to the pthread_exit subroutine - whether its work is done or not.
o The entire process is terminated due to making a call to either the exec() or exit()
The pthread_exit() routine allows the programmer to specify an optional termination status parameter.
This optional parameter is typically returned to threads "joining" the terminated thread (covered
later).
In subroutines that execute to completion normally, you can often dispense with calling pthread_exit() -
unless, of course, you want to pass the optional status code back.
64 | P a g e
Cleanup: the pthread_exit() routine does not close files; any files opened inside the thread will remain
open after the thread is terminated.
o There is a definite problem if main() finishes before the threads it spawned if you don't call
pthread_exit() explicitly. All of the threads it created will terminate because main() is done and
o By having main() explicitly call pthread_exit() as the last thing it does, main() will block and be
kept alive to support the threads it created until they are done.
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 5
65 | P a g e
Output
In main: creating thread 0
In main: creating thread 1
Hello World! It's me, thread #0!
In main: creating thread 2
Hello World! It's me, thread #1!
Hello World! It's me, thread #2!
In main: creating thread 3
In main: creating thread 4
Hello World! It's me, thread #3!
Hello World! It's me, thread #4!
/******************************************************************************
* FILE: hello_arg1.c
* DESCRIPTION:
* A "hello world" Pthreads program which demonstrates one safe way
* to pass arguments to threads during thread creation.
* AUTHOR: Blaise Barney
* LAST REVISED: 01/29/09
******************************************************************************/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 8
char *messages[NUM_THREADS];
sleep(1);
id_ptr = (int *) threadid;
66 | P a g e
taskid = *id_ptr;
printf("Thread %d: %s\n", taskid, messages[taskid]);
pthread_exit(NULL);
}
for(t=0;t<NUM_THREADS;t++) {
taskids[t] = (int *) malloc(sizeof(int));
*taskids[t] = t;
printf("Creating thread %d\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello, (void *) taskids[t]);
if (rc) {
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
pthread_exit(NULL);
}
/******************************************************************************
* FILE: hello_arg2.c
* DESCRIPTION:
* A "hello world" Pthreads program which demonstrates another safe way
* to pass arguments to threads during thread creation. In this case,
* a structure is used to pass multiple arguments.
* AUTHOR: Blaise Barney
* LAST REVISED: 01/29/09
******************************************************************************/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 8
char *messages[NUM_THREADS];
67 | P a g e
struct thread_data
{
int thread_id;
int sum;
char *message;
};
sleep(1);
my_data = (struct thread_data *) threadarg;
taskid = my_data->thread_id;
sum = my_data->sum;
hello_msg = my_data->message;
printf("Thread %d: %s Sum=%d\n", taskid, hello_msg, sum);
pthread_exit(NULL);
}
sum=0;
messages[0] = "English: Hello World!";
messages[1] = "French: Bonjour, le monde!";
messages[2] = "Spanish: Hola al mundo";
messages[3] = "Klingon: Nuq neH!";
messages[4] = "German: Guten Tag, Welt!";
messages[5] = "Russian: Zdravstvytye, mir!";
messages[6] = "Japan: Sekai e konnichiwa!";
messages[7] = "Latin: Orbis, te saluto!";
for(t=0;t<NUM_THREADS;t++) {
sum = sum + t;
thread_data_array[t].thread_id = t;
thread_data_array[t].sum = sum;
thread_data_array[t].message = messages[t];
printf("Creating thread %d\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello, (void *)
&thread_data_array[t]);
if (rc) {
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
pthread_exit(NULL);
}
Output
Creating thread 0
68 | P a g e
Creating thread 1
Creating thread 2
Creating thread 3
Creating thread 4
Creating thread 5
Creating thread 6
Creating thread 7
Thread 0: English: Hello World! Sum=0
Thread 1: French: Bonjour, le monde! Sum=1
Thread 2: Spanish: Hola al mundo Sum=3
Thread 3: Klingon: Nuq neH! Sum=6
Thread 4: German: Guten Tag, Welt! Sum=10
Thread 5: Russian: Zdravstvytye, mir! Sum=15
Thread 6: Japan: Sekai e konnichiwa! Sum=21
Thread 7: Latin: Orbis, te saluto! Sum=28
Routines:
pthread_join (threadid,status)
pthread_detach (threadid)
pthread_attr_setdetachstate (attr,detachstate)
pthread_attr_getdetachstate (attr,detachstate)
Joining:
69 | P a g e
The pthread_join() subroutine blocks the calling thread until the specified threadid thread terminates.
The programmer is able to obtain the target thread's termination return status if it was specified in the target
thread's call to pthread_exit().
A joining thread can match one pthread_join() call. It is a logical error to attempt multiple joins on the same
thread.
Two other synchronization methods, mutexes and condition variables, will be discussed later.
Joinable or Not?
When a thread is created, one of its attributes defines whether it is joinable or detached. Only threads that are
created as joinable can be joined. If a thread is created as detached, it can never be joined.
The final draft of the POSIX standard specifies that threads should be created as joinable.
To explicitly create a thread as joinable or detached, the attr argument in the pthread_create() routine is used.
The typical 4 step process is:
4. When done, free library resources used by the attribute with pthread_attr_destroy()
Detaching:
The pthread_detach() routine can be used to explicitly detach a thread even though it was created as joinable.
Recommendations:
If a thread requires joining, consider explicitly creating it as joinable. This provides portability as not all
implementations may create threads as joinable by default.
If you know in advance that a thread will never need to join with another thread, consider creating it in a
detached state. Some system resources may be able to be freed.
70 | P a g e
Example: Pthread Joining
This example demonstrates how to "wait" for thread completions by using the Pthread join routine. Since some
implementations of Pthreads may not create threads in a joinable state, the threads in this example are explicitly
created in a joinable state so that they can be joined later.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define NUM_THREADS 4
71 | P a g e
/* Free attribute and wait for the other threads */
pthread_attr_destroy(&attr);
for(t=0; t<NUM_THREADS; t++) {
rc = pthread_join(thread[t], &status);
if (rc) {
printf("ERROR; return code from pthread_join()
is %d\n", rc);
exit(-1);
}
printf("Main: completed join with thread %ld having a status
of %ld\n",t,(long)status);
}
Output
ain: creating thread 0
Main: creating thread 1
Thread 0 starting...
Main: creating thread 2
Thread 1 starting...
Main: creating thread 3
Thread 2 starting...
Thread 3 starting...
Thread 1 done. Result = -3.153838e+06
Thread 0 done. Result = -3.153838e+06
Main: completed join with thread 0 having a status of 0
Main: completed join with thread 1 having a status of 1
Thread 3 done. Result = -3.153838e+06
Thread 2 done. Result = -3.153838e+06
Main: completed join with thread 2 having a status of 2
Main: completed join with thread 3 having a status of 3
Main: program completed. Exiting.
Mutex Variables
Overview
Mutex is an abbreviation for "mutual exclusion". Mutex variables are one of the primary means of
implementing thread synchronization and for protecting shared data when multiple writes occur.
A mutex variable acts like a "lock" protecting access to a shared data resource. The basic concept of a
mutex as used in Pthreads is that only one thread can lock (or own) a mutex variable at any given
time. Thus, even if several threads try to lock a mutex only one thread will be successful. No other
72 | P a g e
thread can own that mutex until the owning thread unlocks that mutex. Threads must "take turns"
accessing protected data.
Mutexes can be used to prevent "race" conditions. An example of a race condition involving a bank
transaction is shown below:
In the above example, a mutex should be used to lock the "Balance" while a thread is using this
shared data resource.
Very often the action performed by a thread owning a mutex is the updating of global variables. This
is a safe way to ensure that when several threads update the same variable, the final value is the same
as what it would be if only one thread performed the update. The variables being updated belong to a
"critical section".
73 | P a g e
o The owner thread performs some set of actions
When several threads compete for a mutex, the losers block at that call - an unblocking call is
available with "trylock" instead of the "lock" call.
When protecting shared data, it is the programmer's responsibility to make sure every thread that
needs to use a mutex does so. For example, if 4 threads are updating the same data, but only one uses
a mutex, the data can still be corrupted.
pthread_mutex_init (mutex,attr)
pthread_mutex_destroy (mutex)
pthread_mutexattr_init (attr)
pthread_mutexattr_destroy (attr)
Usage:
Mutex variables must be declared with type pthread_mutex_t, and must be initialized before they can
be used. There are two ways to initialize a mutex variable:
2. Dynamically, with the pthread_mutex_init() routine. This method permits setting mutex
object attributes, attr.
74 | P a g e
The mutex is initially unlocked.
The attr object is used to establish properties for the mutex object, and must be of type
pthread_mutexattr_t if used (may be specified as NULL to accept defaults). The Pthreads standard
defines three optional mutex attributes:
o Protocol: Specifies the protocol used to prevent priority inversions for a mutex.
Note that not all implementations may provide the three optional mutex attributes.
The pthread_mutexattr_init() and pthread_mutexattr_destroy() routines are used to create and destroy
mutex attribute objects respectively.
pthread_mutex_lock (mutex)
pthread_mutex_trylock (mutex)
pthread_mutex_unlock (mutex)
Usage:
The pthread_mutex_lock() routine is used by a thread to acquire a lock on the specified mutex variable. If the
mutex is already locked by another thread, this call will block the calling thread until the mutex is unlocked.
pthread_mutex_trylock() will attempt to lock a mutex. However, if the mutex is already locked, the routine will
return immediately with a "busy" error code. This routine may be useful in preventing deadlock conditions, as
in a priority-inversion situation.
75 | P a g e
pthread_mutex_unlock() will unlock a mutex if called by the owning thread. Calling this routine is required after
a thread has completed its use of protected data if other threads are to acquire the mutex for their work with the
protected data. An error will be returned if:
There is nothing "magical" about mutexes...in fact they are akin to a "gentlemen's agreement" between
participating threads. It is up to the code writer to insure that the necessary threads all make the the mutex lock
and unlock calls correctly. The following scenario demonstrates a logical error:
Creative Question: When more than one thread is waiting for a locked mutex, which thread will be granted the lock
first after it is released?
This example program illustrates the use of mutex variables in a threads program that performs a
dot product. The main data is made available to all threads through a globally accessible structure.
Each thread works on a different part of the data. The main thread waits for all the threads to
complete their computations, and then it prints the resulting sum.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
/*
The following structure contains the necessary information
to allow the function "dotprod" to access its input data and
place its output into the structure.
*/
typedef struct
{
double *a;
double *b;
76 | P a g e
double sum;
int veclen;
} DOTDATA;
#define NUMTHRDS 4
#define VECLEN 100
DOTDATA dotstr;
pthread_t callThd[NUMTHRDS];
pthread_mutex_t mutexsum;
/*
The function dotprod is activated when the thread is created.
All input to this routine is obtained from a structure
of type DOTDATA and all output from this function is written into
this structure. The benefit of this approach is apparent for the
multi-threaded program: when a thread is created we pass a single
argument to the activated function - typically this argument
is a thread number. All the other information required by the
function is accessed from the globally accessible structure.
*/
len = dotstr.veclen;
start = offset*len;
end = start + len;
x = dotstr.a;
y = dotstr.b;
/*
Perform the dot product and assign result
to the appropriate variable in the structure.
*/
mysum = 0;
for (i=start; i<end ; i++)
{
mysum += (x[i] * y[i]);
}
/*
Lock a mutex prior to updating the value in the shared
structure, and unlock it upon updating.
*/
pthread_mutex_lock (&mutexsum);
dotstr.sum += mysum;
77 | P a g e
pthread_mutex_unlock (&mutexsum);
pthread_exit((void*) 0);
}
/*
The main program creates threads which do all the work and then
print out result upon completion. Before creating the threads,
the input data is created. Since all threads update a shared structure,
we need a mutex for mutual exclusion. The main thread needs to wait for
all threads to complete, it waits for each one of the threads. We specify
a thread attribute value that allow the main thread to join with the
threads it creates. Note also that we free up handles when they are
no longer needed.
*/
dotstr.veclen = VECLEN;
dotstr.a = a;
dotstr.b = b;
dotstr.sum=0;
pthread_mutex_init(&mutexsum, NULL);
pthread_attr_destroy(&attr);
78 | P a g e
/* Wait on the other threads */
for(i=0; i<NUMTHRDS; i++)
{
pthread_join(callThd[i], &status);
}
Condition Variables
Overview
Condition variables provide yet another way for threads to synchronize. While mutexes implement
synchronization by controlling thread access to data, condition variables allow threads to synchronize based
upon the actual value of data.
Without condition variables, the programmer would need to have threads continually polling (possibly in a
critical section), to check if the condition is met. This can be very resource consuming since the thread would
be continuously busy in this activity. A condition variable is a way to achieve the same goal without polling.
Main Thread
o Declare and initialize global data/variables which require synchronization (such as "count")
Thread A Thread B
Lock associated mutex and check value of a o Change the value of the global variable
79 | P a g e
global variable that Thread-A is waiting upon.
Continue
Main Thread
Join / Continue
pthread_cond_init (condition,attr)
pthread_cond_destroy (condition)
pthread_condattr_init (attr)
pthread_condattr_destroy (attr)
Usage:
Condition variables must be declared with type pthread_cond_t, and must be initialized before they can be used.
There are two ways to initialize a condition variable:
2. Dynamically, with the pthread_cond_init() routine. The ID of the created condition variable is returned
to the calling thread through the condition parameter. This method permits setting condition variable
object attributes, attr.
80 | P a g e
The optional attr object is used to set condition variable attributes. There is only one attribute defined for
condition variables: process-shared, which allows the condition variable to be seen by threads in other
processes. The attribute object, if used, must be of type pthread_condattr_t (may be specified as NULL to accept
defaults).
Note that not all implementations may provide the process-shared attribute.
The pthread_condattr_init() and pthread_condattr_destroy() routines are used to create and destroy condition
variable attribute objects.
pthread_cond_wait (condition,mutex)
pthread_cond_signal (condition)
pthread_cond_broadcast (condition)
Usage:
pthread_cond_wait() blocks the calling thread until the specified condition is signalled. This routine
should be called while mutex is locked, and it will automatically release the mutex while it waits.
After signal is received and thread is awakened, mutex will be automatically locked for use by the
thread. The programmer is then responsible for unlocking mutex when the thread is finished with it.
The pthread_cond_signal() routine is used to signal (or wake up) another thread which is waiting on
the condition variable. It should be called after mutex is locked, and must unlock mutex in order for
pthread_cond_wait() routine to complete.
Proper locking and unlocking of the associated mutex variable is essential when using these routines. For
81 | P a g e
example:
Failing to lock the mutex before calling pthread_cond_wait() may cause it NOT to block.
Failing to unlock the mutex after calling pthread_cond_signal() may not allow a matching
pthread_cond_wait() routine to complete (it will remain blocked).
This simple example code demonstrates the use of several Pthread condition variable routines. The
main routine creates three threads. Two of the threads perform work and update a "count" variable.
The third thread waits until the count variable reaches a specified value.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 3
#define TCOUNT 10
#define COUNT_LIMIT 12
int count = 0;
int thread_ids[3] = {0,1,2};
pthread_mutex_t count_mutex;
pthread_cond_t count_threshold_cv;
/*
Check the value of count and signal waiting thread when condition is
reached. Note that this occurs while mutex is locked.
*/
if (count == COUNT_LIMIT) {
pthread_cond_signal(&count_threshold_cv);
printf("inc_count(): thread %ld, count = %d Threshold reached.\n",
my_id, count);
}
printf("inc_count(): thread %ld, count = %d, unlocking mutex\n",
82 | P a g e
my_id, count);
pthread_mutex_unlock(&count_mutex);
/*
Lock mutex and wait for signal. Note that the pthread_cond_wait
routine will automatically and atomically unlock mutex while it waits.
Also, note that if COUNT_LIMIT is reached before this routine is run by
the waiting thread, the loop will be skipped to prevent pthread_cond_wait
from never returning.
*/
pthread_mutex_lock(&count_mutex);
while (count<COUNT_LIMIT) {
pthread_cond_wait(&count_threshold_cv, &count_mutex);
printf("watch_count(): thread %ld Condition signal received.\n", my_id);
count += 125;
printf("watch_count(): thread %ld count now = %d.\n", my_id, count);
}
pthread_mutex_unlock(&count_mutex);
pthread_exit(NULL);
}
83 | P a g e
/* Clean up and exit */
pthread_attr_destroy(&attr);
pthread_mutex_destroy(&count_mutex);
pthread_cond_destroy(&count_threshold_cv);
pthread_exit(NULL);
Output
Starting watch_count(): thread 1
inc_count(): thread 2, count = 1, unlocking mutex
inc_count(): thread 3, count = 2, unlocking mutex
watch_count(): thread 1 going into wait...
inc_count(): thread 3, count = 3, unlocking mutex
inc_count(): thread 2, count = 4, unlocking mutex
inc_count(): thread 3, count = 5, unlocking mutex
inc_count(): thread 2, count = 6, unlocking mutex
inc_count(): thread 3, count = 7, unlocking mutex
inc_count(): thread 2, count = 8, unlocking mutex
inc_count(): thread 3, count = 9, unlocking mutex
inc_count(): thread 2, count = 10, unlocking mutex
inc_count(): thread 3, count = 11, unlocking mutex
inc_count(): thread 2, count = 12 Threshold reached. Just sent signal.
inc_count(): thread 2, count = 12, unlocking mutex
watch_count(): thread 1 Condition signal received.
watch_count(): thread 1 count now = 137.
inc_count(): thread 3, count = 138, unlocking mutex
inc_count(): thread 2, count = 139, unlocking mutex
inc_count(): thread 3, count = 140, unlocking mutex
inc_count(): thread 2, count = 141, unlocking mutex
inc_count(): thread 3, count = 142, unlocking mutex
inc_count(): thread 2, count = 143, unlocking mutex
inc_count(): thread 3, count = 144, unlocking mutex
inc_count(): thread 2, count = 145, unlocking mutex
Main(): Waited on 3 threads. Final value of count = 145. Done
References
1. POSIX Standard: www.unix.org/version3/ieee_std.html
2. "Pthreads Programming". B. Nichols et al. O'Reilly and Associates.
3. "Threads Primer". B. Lewis and D. Berg. Prentice Hall
4. "Programming With POSIX Threads". D. Butenhof. Addison Wesley
5. www.awl.com/cseng/titles/0-201-63392-2
6. "Programming With Threads". S. Kleiman et al. Prentice Hall
84 | P a g e
SOCKET PROGRAMMING
Introduction to sockets
A socket is a communications connection point (endpoint) that you can name and address in a
network. The processes that use a socket can reside on the same system or on different systems on
different networks. Sockets are useful for both stand-alone and network applications.
Sockets commonly are used for client/server interaction. Typical system configuration places the
server on one machine, with the clients on other machines. The clients connect to the server,
exchange information, and then disconnect.
Socket characteristics:
Sockets share the following characteristics:
o A socket exists as long as the process maintains an open link to the socket.
o You can name a socket and use it to communicate with other sockets in a communication domain.
o Sockets perform the communication when the server accepts connections from them, or when it exchanges
messages with them.
Sockets are useful for both stand-alone and network applications. Sockets allow you to exchange
information between processes on the same machine or across a network, distribute work to the most
efficient machine, and allows access to centralized data easily.
Socket application program interfaces (APIs) are the network standard for TCP/IP. A wide range of
operating systems support socket APIs. OS/400® sockets support multiple transport and networking
protocols. Socket system functions and the socket network functions are threadsafe.
Socket programming shows how to use socket APIs to establish communication links between remote
and local processes. Programmers who use Integrated Language Environment® (ILE) C can use the
85 | P a g e
information to develop socket applications. You can also code to the sockets API from other ILE
languages, such as RPG.
Socket application program interfaces (APIs) are the network standard for TCP/IP.
A wide range of operating systems support socket APIs. – Ms Windows, Unix
Socket system functions and the socket network functions are threadsafe.
NB: The Java language also supports a socket programming interface.
Typical system configuration places the server on one machine, with the clients on other machines.
The clients connect to the server, exchange information, and then disconnect.
In a connection-oriented client-to-server model, the socket on the server process waits for
requests from a client.
To do this, the server first establishes (binds) an address that clients can use to find the server.
When the address is established, the server waits for clients to request a service.
The client-to-server data exchange takes place when a client connects to the server through a socket.
The server performs the client's request and sends the reply back to the client.
86 | P a g e
Figure : A typical flow of events for connection-oriented socket session
1. The socket() API creates an endpoint for communications and returns a socket descriptor
that represents the endpoint.
2. Bind () - When an application has a socket descriptor, it can bind a unique name to the
socket. Servers must bind a name to be accessible from the network.
4. The client application uses a connect() API on a stream socket to establish a connection to
the server.
5. The server application uses the accept() API to accept a client connection request.
6. Data Exchange -When a connection is established between stream sockets (between client
and server), then can use any of the socket API f data transfer.
87 | P a g e
7. Close() - When a server or client wants to stop operations, it issues a close() API to release
any system resources acquired by the socket.
Note: The socket APIs are located in the communications model between the application layer
and the transport layer of OSI model.
Socket characteristics
Sockets share some common characteristics:
2. A socket exists as long as the process maintains an open link to the socket.
3. You can name a socket and use it to communicate with other sockets in a communication
domain.
4. Sockets perform the communication when the server accepts connections from them, or when
it exchanges messages with them.
5. You can create sockets in pairs (only for sockets in the AF_UNIX address family).
1. connection-oriented or
2. connectionless
1. Connection-oriented communication
Implies that a connection is established first, and a dialog between the programs follows.
The server program establishes the available socket that is enabled to accept incoming connection
requests.
Optionally, the server can assign a name to the service that it supplies, which allows clients to
identify where to obtain and how to connect to that service.
The client program must request the service of the server program.
The client request service by connecting to the distinct name or to the attributes associated with the
distinct name that the server program has designated- similar dialing a telephone number.
88 | P a g e
2. Connectionless communication
No connection is established first, over which a dialog or data transfer can take place.
Instead, the server program designates a name that identifies where to reach it (much like a post-
office box) - If you send a letter to a post office box, you cannot be absolutely sure that the receiver
got the letter. You might need to wait for a response to your letter.
Sockets
Sockets: abstract representation of a communication endpoint.
work with Unix I/O services just like files, pipes & FIFOs.
establishing a connection
Socket address
{ protocol, address-data }
For example,
Associations
An association is the 5-tuple that completely specifies the two end-points that comprise a connection:
e.g.:
89 | P a g e
E.g.
{tcp, 130.245.1.44, 23}
or
{tcp, 130.245.1.45, 1024}
synonyms: socket address , transport address.
Socket descriptors
A data structure maintained by the operating system and stores state information about an object
instance, e.g.
o File
o Socket
Creating a Socket
Socket_desc=socket(int family,int type,int proto);
90 | P a g e
family specifies the protocol family (PF_INET for TCP/IP).
protocol specifies the specific protocol e.g IP(0 implies the default).
Example:
s = socket(AF_INET , SOCK_STREAM , 0)
The socket()
o Allocates resources needed for a communication endpoint - but it does not deal with endpoint addressing.
Bind() is used to specify for a socket the protocol port number where it will be waiting for messages.
- Since the system pre-defined a lot of ports between 1 and 7000 ( /etc/services ) we choose the port
number 1500
Bind ( )
int bind( SOCKET sockfd, const struct sockaddr *myaddr, int addrlen);
The bind() system call is used to assign an endpoint address to an existing socket.
Bind parameters:
91 | P a g e
o address length: integer
Bind inserts the endpoint address information in the socket descriptor data structure.
Connection-oriented (e.g.TCP)
listen():(server)
– listen for incoming connection requests
• connect():(client)
• accept():(server)
– backlog: the number of incoming connections the kernel should be able to keep track of
(queue).
connect()
int connect(SOCKET sockfd, const struct sockaddr *server, socklen_t
addrlen);
92 | P a g e
sockfd: an already created TCP socket.
server: the address of the server (IP Address and TCP port number)
accept()
int accept( SOCKET sockfd, struct sockaddr* cliaddr, socklen_t
*addrlen);
close()
int close( SOCKET sockfd);
Either end of the connection can call the close() system call.
Examples:
/* client.c */
#include<sys/socket.h>
93 | P a g e
#include<sys/types.h>
#include<netinet/in.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
int create_socket;
printf("\x1B[2J");
address.sin_family = AF_INET;
address.sin_port = htons(15000);
inet_pton(AF_INET,argv[1],&address.sin_addr);
if (connect(create_socket,(struct sockaddr
*)&address,sizeof(address)) == 0)
do{
recv(create_socket,buffer,bufsize,0);
if (strcmp(buffer,"/q"))
94 | P a g e
printf("Message to send: ");
gets(buffer);
send(create_socket,buffer,bufsize,0);
}while (strcmp(buffer,"/q"));
close(create_socket);
/* server.c */
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
main()
{
int cont,create_socket,new_socket,addrlen;
int bufsize = 1024;
char *buffer = malloc(bufsize);
struct sockaddr_in address;
printf("\x1B[2J");
if ((create_socket = socket(AF_INET,SOCK_STREAM,0)) > 0)
printf("The socket was created\n");
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(15000);
if (bind(create_socket,(struct sockaddr *)&address,sizeof(address)) == 0)
printf("Binding Socket\n");
listen(create_socket,3);
addrlen = sizeof(struct sockaddr_in);
new_socket = accept(create_socket,(struct sockaddr *)&address,&addrlen);
if (new_socket > 0){
printf("The Client %s is connected...\n",inet_ntoa(address.sin_addr));
for(cont=1;cont<5000;cont++)
printf("\x7");
}
do{
printf("Message to send: ");
gets(buffer);
95 | P a g e
send(new_socket,buffer,bufsize,0);
recv(new_socket,buffer,bufsize,0);
printf("Message recieved: %s\n",buffer);
}while(strcmp(buffer,"/q")); //user „q‟ to quit
close(new_socket);
close(create_socket);
}
o ./client
To run the program with server IP address or name as an argument, use IP address.
You can try running the server and client program at different machines.
References
1. UNIX Network Programming Volume 1, 2nd Edition
2. Stevens - Unix Network Programming - Vol. I - The Sockets Networking API 3e (2003).
3. Unix Network Programming - Volume I - The Sockets Networking API%2C 3rd Edition
SAMPLE EXAMINATIONS
96 | P a g e
UNIVERSITY EXAMINATION
SCHOOL OF APPLIED AND SOCIAL SCIENCES
TIME: 2HRS
INSTRUCTIONS
97 | P a g e
(ii). Open a file [3marks]
(iii). Creating a shared memory segment [3marks]
(iv). Creating a socket [3marks]
(d) Using illustration, explain the layered structure of Unix operating system. [6marks]
(e) With the help syntax expression, explain two basic operations of message passing. [5marks]
Question 2 (20marks)
(a) Using example describe the components of typical UNIX shell session. [6marks]
(b) Using examples differentiate between library and system calls. [4marks]
(c) Using example, write code segments for : [9marks]
i. Creating a new socket
ii. Binding newly created socket
iii. Accept incoming requests
(d) Explain one disadvantage of message passing system in comparison with shared memory segment.
[1marks]
Question 3 (20marks)
(d) Write the syntax code for the following Pthread operation, explaining its elements.[4marks]
(i). Creating new thread
(ii).Terminating a thread
Question 4 (20marks)
Question 5 (20marks)
98 | P a g e
(a) What is the system call used to detach a process from the shared memory segment once it no longer needs it. Write its
syntax. [3marks]
(b) Briefly explain how mutex variable works in thread management. [4marks]
(c) With the help of illustrative diagram, explain how server and client interacts using socket in a connection oriented
environment. Clearly show the flow of events. [8marks]
(d) Write a code to demonstrate the structure of Socket address. Explain its components. [5marks]
99 | P a g e