Os Lab Manual
Os Lab Manual
1
Lab Breakdown
Final Exam
2
Table of Contents
3
1.8.2 Read File Contents......................................................................................................... 19
1.8.3 Removing and Deleting File Commands ........................................................................ 19
1.8.4 Copying File ................................................................................................................... 20
1.8.5 Rename and Move a File ............................................................................................... 20
1.9 Directory Manipulation Commands .................................................................................. 20
1.9.1 Creating Directory ......................................................................................................... 20
1.9.2 Rename and Move a Directory ...................................................................................... 20
1.9.3 Delete a Directory.......................................................................................................... 21
1.10 Inode Number of a File ....................................................................................................... 21
1.11 Attributes of File and Directory ........................................................................................ 21
2. Lab# 02 File and Directory Management .......................................................................... 22
2.1 Learning Outcome............................................................................................................... 22
2.2 Some more important commands ...................................................................................... 22
2.2.1 More on Sorting ............................................................................................................ 23
2.3 File and Directory Security................................................................................................. 25
2.3.1 Types of Access ............................................................................................................. 25
2.3.2 File Permissions ............................................................................................................. 25
2.3 Directory Permissions ......................................................................................................... 25
2.4 Access Specification ............................................................................................................. 26
2.4.1 Checking Access............................................................................................................. 26
2.5 Manual Page ........................................................................................................................ 27
2.5.1 Options and Examples ................................................................................................... 27
2.6 Wildcards ............................................................................................................................. 29
2.6.1 Wildcard * ..................................................................................................................... 30
2.6.2 Wildcard ?...................................................................................................................... 30
2.6.3 Wildcard [] ..................................................................................................................... 30
2.6.4 Wildcard ! ...................................................................................................................... 31
3. Lab# 03 Grep, Redirection, and Piping .............................................................................. 32
3.1 Learning Outcomes ............................................................................................................. 32
3.2 Global Regular Expression Parser (grep) ......................................................................... 32
3.3 Regular Expressions ............................................................................................................ 33
3.3.1 Difference between Wildcards and Regular Expressions .............................................. 33
3.3.2 Character Classes........................................................................................................... 33
3.4 Streams ................................................................................................................................. 34
4
3.5 I/O Redirection .................................................................................................................... 34
3.5.1 Output Redirecting ........................................................................................................ 35
3.5.2 Input Redirecting ........................................................................................................... 36
3.5.3 Pipe Operator ( | ) ......................................................................................................... 37
3.5.4 File Descriptors (FD) ...................................................................................................... 37
3.5.5 Error Redirection ........................................................................................................... 38
3.6 Process Management........................................................................................................... 39
3.6.1 The ps Command ........................................................................................................ 39
3.6.2 Terminating a Process ................................................................................................... 39
3.6.3 Background and Foreground Processing ....................................................................... 39
4. Lab# 04 C Programming Introduction ............................................................................... 41
4.1 Learning Outcome............................................................................................................... 41
4.2 Why Learn C? ..................................................................................................................... 41
4.3 Difference between C and C++........................................................................................... 41
4.4 Step-by-Step Compilation Using gcc: ................................................................................ 42
4.4.1 The pre-processor ......................................................................................................... 42
4.4.2 The Compiler ................................................................................................................. 42
4.4.3 The Assembler ............................................................................................................... 42
4.4.4 The Linker: ..................................................................................................................... 42
4.5 C Output .............................................................................................................................. 43
4.6 C Input ................................................................................................................................. 43
4.7 Format Specifiers for I/O.................................................................................................... 44
4.8 I/O Multiple Values ............................................................................................................. 45
4.9 C malloc() ............................................................................................................................. 45
4.10 C free().................................................................................................................................. 46
4.11 Null-Pointer Check .............................................................................................................. 47
5. Lab# 05 Command Line Argument, Process Environments, and Error Handlining ................ 48
5.1 Learning Outcome............................................................................................................... 48
5.2 Handling Command Line Arguments ............................................................................... 48
5.3 Process Environments ......................................................................................................... 50
5.4 Error Handling .................................................................................................................... 52
5.5 Introduction to structure in C ............................................................................................ 54
5.5.1 Define Structures ........................................................................................................... 54
5.5.2 Create struct Variables .................................................................................................. 54
5
5.5.3 Access Members of a Structure ..................................................................................... 55
5.5.4 Keyword typedef ........................................................................................................... 56
6. Lab# 06 Input-Output System Calls in C ............................................................................ 58
6.1 Learning Outcome............................................................................................................... 58
6.2 open() System call ................................................................................................................ 58
6.3 close() System call ................................................................................................................ 59
6.4 write() System call ............................................................................................................... 59
6.5 read() System call ................................................................................................................ 59
6.6 More File I/O Functions...................................................................................................... 62
6.6.1 C fprintf() function ......................................................................................................... 62
6.6.2 C fscanf() function ......................................................................................................... 62
6.6.3 C fseek() function .......................................................................................................... 63
7. Lab# 07 Process Management System Calls ...................................................................... 65
7.1 Learning Outcome............................................................................................................... 65
7.2 Process ID and Parent process ID...................................................................................... 65
7.3 Creating Processes fork() .................................................................................................... 65
7.3.1 How fork() work?........................................................................................................... 65
7.4 wait() & waitpid() ................................................................................................................ 67
7.4.1 Options Parameter ........................................................................................................ 70
7.4.2 The return value of waitpid()......................................................................................... 70
8. Lab# 08 Process Management Contd ................................................................................ 72
8.1 Learning Outcome............................................................................................................... 72
8.1.1 execvp ........................................................................................................................... 72
8.1.2 execv.............................................................................................................................. 74
8.1.3 execl .............................................................................................................................. 75
8.1.4 execlp() .......................................................................................................................... 76
8.2 Zombie Process.................................................................................................................... 77
8.3 Orphan Process ................................................................................................................... 78
9. Lab# 09 Introduction to Posix Threads (pthreads) ............................................................ 81
9.1 Learning Outcome............................................................................................................... 81
9.2 What is Thread? .................................................................................................................. 81
9.3 POSIX Threads (pthreads) ................................................................................................. 81
9.4 Pthread API ......................................................................................................................... 81
9.4.1 Thread Creation .............................................................................................................. 82
6
9.4.2 Thread Completion ......................................................................................................... 82
9.4.3 Thread Join ...................................................................................................................... 83
9.5 Global Variable .................................................................................................................. 84
9.6 Local Variable ..................................................................................................................... 85
10. Lab# 10 Thread Synchronization and Mutexes .................................................................. 90
10.1 Learning Outcome............................................................................................................... 90
10.2 Thread Management ......................................................................................................... 90
10.3 Mutexes............................................................................................................................. 90
10.4 Synchronization ................................................................................................................. 90
10.5 Race Condition .............................................................................................................. 92
10.6 Critical Section ................................................................................................................... 92
10.6 Mutex Implementation (Code) .......................................................................................... 96
11. Lab# 11 Thread Synchronization Contd. Condition Variables/Barriers ...............................101
11.1 Learning Outcome............................................................................................................. 101
11.2 Barriers .............................................................................................................................. 101
11.3 Condition Variable ............................................................................................................ 104
12. Lab# 12 Open-Ended Lab ................................................................................................109
7
I. Lab Outlines
CS 311L Operating Systems Lab (1 CH) CS/CE
Pre-Requisite: Object-Oriented Analysis and Design
Instructor: Ms. Mah Rukh
Office # F-11 FCSE, Second Floor, GIK Institute
Email: mahrukh@giki.edu.pk
Office Hours: 8:00am ~ 05:00 pm
Course Introduction
The objective of this lab is to provide a strong foundation in Linux Operating System's technology and practice.
The course aims to groom students with well-informed knowledge of Linux Commands and File Structure and to
give students an excellent formal foundation in threads and system calls.
Course Learning Outcome: At the end of this course, students can give rights to files and directories and understand
system calls and thread synchronization.
Lab Contents
Installing a flavor of Linux, understanding the file structure of Linux, different commands, and permission to file
and directory.
Understanding the Process management and system calls, thread synchronization, mutexes for race conditions and
critical sections.
+
Please add the prefix "Upon successful completion of this course, the student will be able to."
*
PLOs are for BS (CE) only
8
Overall Grading Policy
Assessment Items Percentage
Lab Performance 35%
Midterm Exam 25%
Final Exam 40%
Text and Reference Books
• Lab Manual
• Linux man pages
• Online Linux and POSIX documentation
Administrative Instruction
▪ According to institute policy, 80% attendance is mandatory to appear in the final examination.
▪ Attendance is mandatory for students in all the labs. If a student is absent from a lab for any reason, he/she
will have to get written permission from the Dean to perform that lab. The Dean will allow students to
perform lab if he feels the student has a genuine excuse.
▪ Students should bring their textbooks to the lab so that they can refer to theory and diagrams whenever
required. `
▪ Labs will be graded in a double-entry fashion; one entry in the assessment sheet is given at the end of every
lab, and another entry in the instructor's record. This system of keeping records will keep students aware of
their performance throughout the lab.
▪ For queries, kindly follow the office hours in order to avoid any inconvenience.
Computer Usage
▪ Most of the tasks/assignments must be solved through computers using Linux.
9
II. Rubrics
Criteria Excellent Good Adequate Unsatisfactory
(Well executed) (Room for improvement (Meets minimum (Does not meet
exists) requirements) minimum
requirements)
10 8 6 <5
Code writing Coding style guidelines are Coding style guidelines are Coding style guidelines Below minimum
followed correctly, almost always followed are not followed and/or requirements. (See
correctly. Code is easy to code is less readable than left column)
code is exceptionally easy to read. Names are consistent it should be. Names are
read and maintain. All in style. Isolated cases exist nearly always consistent,
names are consistent with of deviation from prescribed but occasionally verbose,
prescribed style practices. practices. overly terse, ambiguous
or misleading.
Correctness Provided solution by the Provided solution by the Provided solution by the Below minimum
student correctly solves student correctly solves student solves problem in requirements. (See
problem in all cases and problem in all or nearly all some cases but has one or left column)
exceeds problem cases but may have minor more problems.
specifications. problems in some instances.
Error/exception Provided solution by the All obvious error conditions Some obvious error Below minimum
student handles erroneous or are checked for and conditions are checked requirements. (See
Handling unexpected conditions appropriate action is taken. for and some sort of left column)
gracefully and action is action is taken.
taken without surprising the
user.
Lab Evaluation
Lab Performance Viva
70% 30%
10
III. Benchmarking
The following available items were consulted
1. HEC curriculum for CS (Revised 2017)
2. ACM curriculum for CS (2013)
In 1, complete information is available for the core course Operating Systems. It is a pre-
requisite for many further courses in the degree program. However, there is no information
for separate lab courses.
The course contents of CS311L at FCSE cover mandatory topics listed in the syllabus of
Operating Systems in the above two sources. Therefore, the contents covered at FCSE are
sufficient.
11
Lab # 1
Linux Environment, Introduction to CLI and File &
Directory Management
In this lab, the students will learn about the environment of the Linux operating system. The
file system of the Linux operating system and its hierarchy. They will also remember "How to
log in and log out from the Linux operating system. In this lab, students will learn about the
Root Directory, Home Directory, and Path (absolute or relative).
1.1 Learning Outcomes
After completion of this lab, students will be able to:
• Log in and log out of the Linux operating system.
• Understand the Linux file system and directory hierarchy.
• Navigate and manipulate files and directories.
• Know the root directory, home directory, and paths.
1.3 Linux
Linux is an operating system that is a flavor of UNIX. Linux is a multi-user and multi-tasking
operating system. Because of its likeness to UNIX, all the programs written for UNIX can be
compiled and run under Linux. The Linux operating system runs on machines like
486/Pentium, Sun SPARC, PowerPC, etc. One of the essential features of Linux is
communication through TCP/IP protocols.
Linux Torvalds, at the University of Helsinki, Finland, developed Linux. UNIX programmers
around the world assisted him in the development of Linux.
1.4.1 Shell
A Shell is an interface between a user and a Linux operating system, i.e., the user interacts with
the Linux operating system through the Shell. The Shell performs two tasks; it accepts and
interprets a user's commands.
Two Shells, which are commonly used, are Bourne Shell and C Shell. One other Shell, which
is rather complex, is the Korn Shell.
1.4.2 Kernel
12
The kernel is the core of the Linux Operating System, which is operational as long as the
computer system is running. The kernel is part of the Linux Operating system, which consists
of routines that interact with underlying hardware, including system calls handling, process
management, scheduling, signals, paging, swapping, the file system, and I/O to storage devices.
So, Shell accepts commands from the user, interprets them, and delivers these interpreted
commands to the kernel for execution. After execution, the Shell displays the results of the
executed commands.
1.5 Files
A file is a mechanism through which we store information. Usually, there are two modes of
storing information.
File
Directories
1.5.1 File
A simple file stores some information. The information it has may be in text format or binary
format. In operating systems like Linux, there are three main file attributes: read (r), write (w),
and execute (x).
• Read - Designated as an "r"; allows a file to be read, but nothing can be written to or
changed in the file.
• Write - Designated as a "w"; allows a file to be written to and changed.
• Execute - Designated as an "x"; allows a file to be executed by users or the
operating system.
The significant operations which can be performed on files are given below:
· Create
· Delete
· Open
· Close
· Read
· Write
· Append
· Rename
· Get Attributes
· Set Attributes
1.5.2 Directories
Directories are particular types of files that contain information about the files stored inside
that directory and may include other directories (called Sub-directories). So, directories are
files containing vital information about the files and other directories. An operating system
uses a file (management) system that manipulates files and directories. The significant
operations which can be performed on files and directories are given below:
· Create
· Delete
· Open
· Close
· Read
· Write
· Rename
13
· Get Attributes
· Set Attributes
/(Root Directory)
sbin/ tmp
14
The top directory is called the root directory. The Linux file system's hierarchical structure
begins with a root directory. The name of the root directory is /.
1.6.4 Pathname
The pathname is a reference to identify a file within the directory structure. For example, the
following file name indicates the file in the hierarchy of directories:
/usr/users/bill/letters/pay
The first slash (/) indicates the root directory, moves down to usr, then users, then bill, then
letters, and finally to the file pay. So, this pathname references the file pay concerning the root
directory /.
A path may be of two types.
/home/abhishek/scripts/my_scripts.sh
abhishek/scripts/my_scripts.sh
15
Figure 1.1
Take a look at the scenario in figure 1.1. In this one, you want to go to the
directory prakash from the directory abhishek. You can use the cd command to switch
directories. The absolute path is quite evident here:
$ cd /home/prakash
To use the relative path, you'll have to use the particular relative path:
$ cd ../prakash
Why use ..? Because a relative path requires direction from the current directory, you have to
tell the cd command to go up a level before going down. The .. brings you to
the /home directory, and you go to the prakash directory.
$pwd
$ cd /home/prakash
$ls ./your_script.sh
This list the file your_script.sh (if you do "ls your_script.sh", the shell adds "./" by default).
1.6.5 Logging In
We know that Shell is an interface between a user and the Linux kernel. The first step you must
accomplish before using the Shell is to log in to your machine. This is usually a very
straightforward process if you have a login ID and a password
16
When your login successfully for an ordinary user account, the system will execute a program
called the Shell. And your shell process is responsible for giving you a command prompt. By
default, the prompt for a non-privileged user is a dollar ($) symbol, and for a privileged (root)
user is a hash (#) symbol.
1.7 Commands
A command is a request from a user to the Linux operating system asking for a specific function
to be performed; for example, a request to list all files in your current directory. Shell
commands operate on files, directories, and various devices like disks, printers, etc. A
command has three parts.
$ cd /
17
1.7.6 Directory Commands
Command Description
ls List the File in the directory, just like the dir command in DOS.
Options
-a Display all the files and subdirectories, including hidden
files.
-l Display detailed information about each file and directory.
-r Display files in the reverse order.
Example
As you enter the ls, command on the command prompt, just after the command cd /.
$ ls
bin dev home lib mail mnt proc sbin usr
boot etc initrd lost+found misc opt root tmp var
And when you enter ls -r on the command prompt.
$ ls -r
var tmp root opt misc lost+found initrd etc boot
usr sbin proc mnt mail lib home dev bin
Command Description
mkdir directory-name Creates a new directory. Directory-name specifies the
name of the new directory. If the name doesn't begin with
a slash, the new directory is created as a subdirectory of
the current working directory. If the name starts with a
slash, the name defines the path from the root directory
to the new directory.
Use the following command first because this will bring you back to your home directory.
$ cd
Now try mkdir command
$ mkdir books
This command will create a new directory under the home directory.
mumtaz@home
|
books
Though you have created a sub-directory of books, you are still in the home (parent directory
of books) directory, i.e., mumtaz@home$
How would you go to the directory books?
Command Description
cd Change to another directory.
For Example:
$cd dir_name
To change to a sub-directory under the current directory, use the command.
mumtaz@$ cd books
(when Enter is pressed, the prompt becomes)
mumtaz@home:~/books$
18
Do you see any difference between the two prompts?
Now you are in the books directory, a step down to home. How could you go up?
Now you will again be in your parent directory. And the prompt becomes:
mumtaz@home:~$
Example
Create another directory, chemistry under books, and move to the chemistry directory.
mumtaz@$ cd books
mumtaz@ /books/chemistry$
Now you are quite away from your home directory. How would you go to your home
directory? Your current location is
mumtaz@ /books/chemistry$
To go to your home directory:
mumtaz@/books/chemistry$ cd (enter)
The prompt will become:
mumtaz@$
Type pwd at the prompt to see where you are.
Example
Now you are in your home directory. How will you go directly to the chemistry directory?
Books
Example
How would I find where I am? If I'm in the classical directory? The command used for that is
pwd. When I entered this command in the classical directory, the following information was
printed on my screen. The path printed was an absolute path. How will you add directory
19
graphics under the directory computer while you are in the physics sub-directory classical?
Now you can create directories. How do we remove them?
Command Description
rmdir (‘remove directory”) Deletes a directory.
For example
$rmdir dirname
Note:- rmdir will only work if the directory you are trying to remove does not contain any
file. So first, remove all files from the directory.
a) You are in the books directory; from here, try to remove the sub-directory quantum
under the directory physics.
b) Now move to the directory computer, and from here, remove the sub-directory
calculus under the directory math.
c) Now recreate both of the removed directories.
Command Description
rm (‘remove’) Removes a file permanently. For example,
$rm filename
Options
-r Deletes an entire directory and all the files it contains.
-i This option puts the rm command into interactive mode and
prompts you before it removes it.
Try to remove a directory with some files in it and observe the system's response.
$rm directory_name
Now use the above command with the –i, and see what happens.
Note:- To remove all files from a directory, use
$ rm dirname/*
20
1.8.4 Copying File
Command Description
cp (‘copy’) Creates a new copy of the existing file.
Example
$cp oldfile newfile
To copy a file from the current directory to another directory, the general format is
cp oldfile directory[/newfile]
Here item in square brackets [] is optional, i.e., if you want to copy the desired file to a new
location with a new name, then you can use this option; otherwise, the file will be copied with
the original name to the new location.
Example
There are atomic-notes files in the directory books with the new name at-notes.
Copy this file to the sub-directory nuclear under the directory physics.
Command Description
mv ('moves') Renames a file or moves it from one directory to another.
For example,
$ mv oldfile newfile
The oldfile specifies the existing file you want to rename. newfile specifies the new name to
use for the file.
21
1.9.3 Delete a Directory
We can delete a directory by using the command rm when the directory is empty.
$rm book
When the directory is non-empty, we use the command rm -r.
$rm -r book1
1.10 Inode Number of a File
The system allows an inode number when creating each Linux file or directory. How can we
access that inode number? For example, to display the inode number of file notes in the
subdirectory graphics under the directory computer; on my computer, I entered the command:
mumtaz@$ ls -i computer/graphics/notes
The following information is displayed:
241467 computer/graphics/notes
The left number indicates the inode number of the file notes.
mumtaz@$ ls -l notes
-rw-r--r-- 1 mumtaz mumtaz 23 Jan 26 15:39 notes
1 2 3 4 5 6 7 8
The following information is printed in the given order:
1: File Permissions (Coming Lab)
2: File Links (Coming Next)
3: Owner Of A File
4: Group (Coming Lab)
5: The Size Of A File
6: Last Change
7: Time Of Change
8: File/Directory Name
It would be best if you remembered the sequence in which the above information is displayed.
22
Lab# 02
File and Directory Management
In the last lab, we learn Linux commands. In this lab, I will explain more Linux commands like
man, date, who, sort, wild cards, etc. You will also learn about file and directory
security and access permission and how to grant and remove access permission from a user
who reads, writes, and executes permission.
Command Description
clear Clears the screen.
echo Echoes back whatever you type on the command line after echo.
Options
-n doesn't begin a new line after echoing the information.
Examples
echo Hello there
23
echo -n Hello
Command Description
sort Sorts a column in a file in alphabetical order. The output is
default displayed on your terminal, but you can specify a
filename as the argument or redirect the output to a file.
Option
-r Sorts in reverse order
-b Ignores leading blanks
-f Ignores the distinction between lowercase and upper case
letters
-o Sorts the output in the specified file
-n Numbers are sorted by their arithmetic values
file to be sorted
File To be Sorted
25 years old
End of file
apple on the table
6 apples
$ sort –f sortfile
25 years old
6 apples
apple on the table
End of file
file to be sorted
File To be Sorted
$ sort -f -r sortfile
File To be Sorted
24
file to be sorted
End of file
apple on the table
6 apples
25 years old
Command Description
wc ('word count') Counts the number of words, lines, and characters in a
file.
Options
-c Display only the number of characters in the file.
-l Display only the number of lines in the file.
-w Display only the number of words in the file.
who who command lists the login names, terminal lines, and login times of
the users who are currently logged on to the system.
Example
$ who
mumtaz pts/3 Feb 7 14:54
qamar pts/0 Feb 7 09:21
yousuf pts/2 Feb 7 10:12
If you type whoami, Linux displays who the system thinks you are:
$ whoami
mumtaz pts/2 Feb 7 09:25
Command Description
head The head command will output the first part of the file
Example
$head file_name
This will print the first ten lines of the file if it contains more than ten lines.
$ head -n 4 sortfile
The output of this command id the first 4 lines of the file sortfile.
Command Description
tail The tail command will output the last part of the file
$tail sortfile
25
To output the last seven-line of the file, we use the command tail:
$tail -n 7 sortfile
Read: The read (r) permission in a directory means you can use the ls command to the
filenames.
Write: The write (w) permission in a directory means you can add or remove files from that
Directory.
Execute: The execute (x) permission in a directory means you can use the cd command to
change to that Directory.
Note:- Your installation has a default setup for all newly created files and directories. You can
check your default access through the ls -l command.
26
2.4 Access Specification
When you create a file or directory, it comes into existence with default access specifications.
It may give all access permissions to the owner, just read and write permissions to the group,
and just read permission to everyone, or there may be any situation. The following figure shows
how different groups and characters represent access permissions.
drwx rw− r- -
user
group
others
The first character indicates whether the file is a directory (d) or not (-) (- is for file). The next
nine places are divided into three sets, each length 3. The first set of three indicates the owner
access, the next set of three suggests the group access, and the final set of three shows the
access for everybody else. The maximum access is represented by rwx, indicating read, write,
and execute. Whenever a dash (-) appears, access permission is not given.
$ ls -l
-rw-r- - r- - 1 saleem stud 700 June 19 08:00 data.file
access links owner group size last change name
In this example, the first character is “–, “indicating that the object is a file. The owner's group
suggests that the owner, i.e., Saleem, can read and write but cannot execute. Any group member
can read the file but not execute it. Also, someone can read this file without writing it or
executing it. It is the authority of the administrator to create a group of users. Each group has
some users and is given a unique name. Each group also has a unique number (group id), and
each user has a unique number, called user-id or UID.
Command Description
id It gives the user's name, the groups they are a member of, both
names and numbers, and the user's user-id and current group-id.
Example
$ id (enter)
uid=275(john1), group=50(staff)
$id chris
uid=145(charis) gid=12(ugrads) groups=12(ugrads), 417(proj)
This shows that chris is a member of groups ugrads, and proj, with GID numbers 12 and 417,
respectively. Currently, chris is allotted to group ugrads.
The access privileges can be changed. A command chmod is used to change the access
privileges.
Command Description
chmod Change access permission for one or more files.
27
chmod user-type [operations][permissions] filelist
user-type
u User or owner of a file.
g Group that owns the file.
o Other.
a All three user types.
operations
+ Add the permission.
- Remove the permission.
= Set permission; all other permission resets.
Permissions
r Read permission.
w Write permission.
x Execute(run) permission.
Examples
$chmod u-w result.comp
Write permission for owner (user) removed from file result.comp.
r w x r w x r w x
4 2 1 4 2 1 4 2 1
Example
Change access permission to allow the read, write, and execute permission to all users.
2.5.1.1 No Option
It displays the complete manual of the command.
28
Syntax:
$man [command name]
Example
$man ls
In this example, the manual pages of command ‘ls‘ are returned in section 1.
2.5.1.3 -f Option
One may be unable to remember the sections in which a command is present. So this option
gives the section in which the given command is present.
29
Syntax:
$man -f [command name]
Example
$man -f ls
The manual is generally split into eight numbered sections, organized as follows:
Just press the Q key on the keyboard to exit the man command.
2.6 Wildcards
Wildcards are special characters (metacharacters) applied with a command. These characters
operate on a group of files/directories. Most of the time, these commands have wild cards and
operate on a group of files/directories. These have some standard features in their names. When
a wild card(s) and other characters are grouped together, they form a "pattern."
For example, in case there is a group of files in the current directory, in this group following
files have a common feature in their names that start with the character "t."
- - - t t1 tile1 tile2 - - - -
In order to select these files from the group of files, a command like 'ls' must be operated with
a suitable wildcard.
The symbols used for wild cards are the following.
30
Symbol Meaning
* It matches zero or any number of characters
? It matches any single character in a file/directory
[] It matches any one character in the bracket.
Example
Following are the files in a given directory; the results returned by different wildcards when
used with 'ls' is shown below.
2.6.1 Wildcard *
$ls *t returns fat fest t (the last character should be 't' with any number of preceding
characters)
$ls t* returns t t1 tile tt2 (the first character should be 't' with any number of following
characters)
$ls t*t returns test (first and last characters should be 't' with any number of characters
between them)
$ls f *t returns fat fest (first and last characters should be 'f' and "t" respectively, with
any number of characters between them)
$ls *oo returns foo loo zoo (the last two characters should be 'oo' with any number of
preceding characters.)
2.6.2 Wildcard ?
$ls t? returns t1 (the first character should be 't' with only one character following it)
$ls t?t returns tat (first and last character should be 't' with only one character between
them)
2.6.3 Wildcard []
$ls p[12] returns p1 p2 (starting character should be 'p' and ending character should be '1'
or '2')
31
$ls p[1-9] returns p1 p2 p7 p9 (starting character should be 'p', and ending character could
be anything between '1' to '9')
???t Four-character file name. The first three characters may be any, but the last
character should be 't.'
??[a-c] Three-character filename beginning with any two characters, but the last
character should be 'a', 'b,' or 'c.'
[a-c][1-9] Two character file name starting with 'a', 'b', or 'c' and ending with any character
between '1' and '9'
2.6.4 Wildcard !
$ls p[!1-7] returns p9 (starting character should be 'p' and ending character should not be
'1' to '7')
32
Lab# 03
Grep, Redirection, and Piping
In this lab, you will learn about the standard input and output stream, I/O Redirection
Operators, and pipes.
Command Description
grep Find lines in one or more files that contain a particular word
phrase.
Options :
-c Print the number of matches only.
-i Ignore the case.
-I Print only filenames containing the pattern.
-n Precede each matched line with its line number in the file.
-s Suppress file error messages
-v Print line NOT containing the pattern
-B 5 Display the context - i.e., 5 lines of context before a match
-A 3 Display the context - 3 lines of context after a match
A string containing symbols ( like ^, *, [] ) must be quoted in single or double apostrophes. But
it is recommended to use a single quote. Files may be specified with wild card characters.
Before going to the basic pattern, let's have a look at the grep command in general:
Examples
$ grep Text filenames
Here text is a string to be searched in the file(s).
$ grep –n S filename
Print all lines (with line number) containing a S.
$ grep -i "ha" *
Prints HA/Ha/hA/ha from all files in the current Directory.
33
$ grep -v -i -c ‘s’ testfile
Print the number of lines not containing s or S.
Symbol Description
. It matches any single text character.
* Matches the preceding characters zero or more times.
\ Turns off any special meaning of the character following.
^ It matches the beginning of a line if used at the beginning of a
regular expression.
$ It matches the end of the line; it is used at the end of a regular
expression.
[str] Matches any single character in str.
[^str ] Matches any single character not in str.
[a - b] Matches any character between a and b.
The basic regular expressions are applied through the grep command. The regular expressions
are formed using meta characters in some suitable format. The format of the pattern based on
meta-characters will depend upon our searching criteria.
Examples
h.t Matches hot, hit, hst, h8t, h&t, and so on in a file.
ho*t Matches hot (zero occurrences of the preceding pattern), hoot, hooot, etc.
\. Matches any line containing a period. Note the use of the backslash to show we
mean a period and not the special symbol for any character.
^The Matches any line starting with The.
ed$ Matches any line ending with ed.
34
Characters may be letters, numbers, punctuation marks, etc.; these are character classes. Linux
provides notations for referring to these classes, which some utilities use, like grep.
The general form of character classes is
[: classname:]
Following are the character classes:
[:alnum:] letters and digits
[:alpha:] letters
[:blank:] space and TAB
[:digit:] digits
[:cntrl:] all control letters
[:lower:] lower case letters
[:punct:] punctuation marks
[:upper:] upper-case letters
[:xdigit:] hexadecimal digits (0-9,A-F, a-f)
Examples
Following are the examples in which character classes are used in regular expressions.
Matches any 3-character string commencing with a letter.
3.4 Streams
In the Computer Science stream is an abstract idea. Information flows into the computer as
input; anything that flows out of the computer as output is referred to as a stream. Formally,
input flowing from an input device, like a keyboard to memory, is referred to as an input stream,
and output flowing from memory to an output device is referred to as an output stream. So
streams are associated with the flow of data. As we know, when a Linux command executes,
it may take input before execution and gives some results after execution. Therefore, each
command in Linux is associated with streams. Streams associated with the execution of Linux
commands are:
Usually, standard input flows from the keyboard to the memory, and standard output and
standard error flow to the terminal. When a Linux command executes, in case of errors, error
messages flow as output from the computer to the terminal, which is called the standard error
stream.
35
is used when the standard input will not be coming from the keyboard, but other sources like a
file and standard output will not be going to the screen but to other sources like a file or other
commands. In the case of I/O redirection, the shell should be informed. The following
characters are used to notify the shell to redirect the input or output of any command.
Operator Description
> Redirects output of a command to a file or device (e.g., printer). It
overwrites the existing file.
>> It is similar to >, except if the target file already exists, the new output is
appended to its end.
< Redirects input of command from a file or device.
| Sends the output of one command to become the input of another command.
The below figure (left side) shows what we can redirect stdout, by default sent to the computer
screen, to a file. Similarly, we can redirect the stdin of a command to make it take its input
from a file instead of the keyboard, as shown below in the figure (right side).
36
In the above example, we first run the date command to print the date today onto the standard
output. Then, the same command is run again. However, we use the ‘>’ symbol this time to
redirect the standard output to Ali. Using the cat command, we output the content of Ali.
Notice that the file contains the output of the previously executed date command.
Note:-
1) If the file already exists, it will be overwritten, and the contents of the existing file will
be lost.
2) If the specified filename does not exist, the shell creates one to save its output.
37
Using the ‘<’ symbol, we redirect the standard input to users.
We get 9 as our output. However, the name of the file is not printed in this case. This happens
because the command assumes it is taking its input from stdin rather than a file. Hence, the
name of the file is not printed.
Note: We can also append data to the standard input and output by using the ‘<<’ (stdin) and
‘>>’ (stdout) symbols. "[Used for “here document”]" after the << operator.
$command A | command B
The commands ls and sort can be combined with the pipe. The list of filenames output by
the ls command is piped into the sort command.
$ ls | sort
The output of the ls command is input for the sort command.
These files are always present whenever a program is run. A file descriptor is associated with
each of these files.
File Descriptor
Standard Input Stream (Stdin) 0
Standard Output Stream (Stdout) 1
Standard error Stream (Stderr) 2
By default, an error stream is displayed on the screen. Error redirection is routing the errors to
a file other than the screen. Error re-direction is one of the viral features of Linux. Frequent
UNIX users will reckon that many commands give you massive errors. For instance, while
searching for files, one typically gets permission denied errors. These errors usually do not help
the person searching for a particular file. The solution is to re-direct the error messages to a
file.
$ myprogram 2> errorfile
Above, we are executing a program names myprogram. The file descriptor for standard error
is 2. Using “2>,” we redirect the error output to a file named “errorfile.” Thus, program
output is not cluttered with errors.
Example]][ ][]||
ls –alR / 2> /dev/null | sort > myfile
39
ls –alR / 2>/dev/null | sort > myfile &
The last character (&) indicates that we want to run this process in the background. Therefore,
you get the prompt immediately.
$ ps
$ kill [PID]
$ kill 5432
$ kill -9 5432
Here –9 is the kill signal, and 5432 is the process ID, which will be killed. Try to kill a process.
First, use the ps command to list the processes. The top command is used to get the list of all
the running processes on your Linux machine.
List all running Linux processes on your system, open a terminal and enter:
$ top
40
3.6.3.3 How to execute a command/process in the background
It is possible to move some of the programs to the background so that you can work on
something else. The following example shows you how to move a process to the background.
41
Lab# 04
C Programming Introduction
In this lab, we study the C programming introduction. C is a general-purpose programming
language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a prevalent language,
despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX
operating system.
Programs written in C are compiled with the gcc compiler in a Linux environment. A C file
should have extension .c e.g. test.c. The general format to compile a C program by gcc compiler
is given below:
$ ./first
42
Here’s your first program in this lab; give it a try.
Example 1
#include <stdio.h>
int main()
{
printf(“\n\t One OS to Rule Them All !! \n”);
return 0;
}
3.4 Step-by-Step Compilation Using gcc:
The compilation is a multi-stage process involving several tools, including the GNU Compiler
itself (gcc), the GNU Assembler as, and the GNU Linker id. The sequence of commands
executed by a single invocation of GCC consists of the following stages:
$ gcc -S first.i
However, we didn’t specify a ‘–o’ option. This is because gcc would automatically name the output
file ‘first.s.’
43
commands used internally by GCC are complicated. Fortunately, though, the entire linking process
is handled transparently by gcc when invoked as follows:
3.5 C Output
In C programming, printf() is one of the main output function. The function sends formatted
output to the screen. For example
Example : C output
#include <stdio.h>
int main()
{
//Displays the string inside quotations
printf("C Programing");
return 0;
}
• All valid C programs must contain the main() function. The code execution begins
from the start of the main() function.
• The printf() is a library function to send formatted output to the screen. The
function prints the string inside quotations.
• To use printf() in our program, we need to include stdio.h header file using
the #include <stdio.h> statement.
• The return 0; statement inside the main() function is the "Exit status" of the
program. It's optional.
3.6 C Input
In C programming, scanf() is one of the commonly used functions to take input from the
user. The scanf() function reads formatted input from the standard input, such as
keyboards.
Example 2
#include <stdio.h>
int main()
{
int testInteger;
printf("Ënter an integer");
scanf("%d", &testInteger);
44
printf("Number = %d", testInteger);
return 0;
}
Here, we have used %d format specifier inside the scanf() function to take int input
from the user. When the user enters an integer, it is stored in the testInteger variable.
Notice that we have used &testInteger inside scanf(). It is because &testInteger gets
the address of testInteger, and the value entered by the user is stored in that address.
%d for int
%f for float
%lf for double
%c for char
Here's a list of commonly used C data types and their format specifiers.
45
3.8 I/O Multiple Values
Here's how you can take multiple inputs from the user and display them.
Example 3
#include <stdio.h>
int main()
{
int a;
float b;
printf("Ënter an integer and then a float");
//Taking multiple inputs
scanf("%d %f",&a,&b );
printf("You entered %d and %f", a , b);
return 0;
}
3.9 C malloc()
The name "malloc" stands for memory allocation. The malloc() function reserves a
memory block of specified bytes. And it returns a pointer of the void, which can be casted
into pointers of any form.
The above statement allocates 400 bytes of memory. It's because the size of the float is 4
bytes. And the pointer ptr holds the address of the first byte in the allocated memory.
46
The expression results in a NULL pointer if the memory cannot be allocated.
3.10 C free()
Dynamically allocated memory created with malloc() doesn't get freed independently.
You must explicitly use free() to release the space.
This statement frees the space allocated in the memory pointed by ptr.
Example 4:
In this code, we dynamically allocate memory using the malloc() function based on the
integer value entered by the user. The allocated memory is released using free()function at
the end of the code execution.
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a;
int i;
int *ptr;
int sum=0;
int n;
printf("Ënter number of elements:");
scanf("%d", &n);
ptr = (int*)malloc(n * sizeof(int));
//If memory cannot be allocated
if(ptr == NULL){
printf("Error! memory not allocated,");
exit(0);
}
printf("Enter elements: ");
for(i=0; i<n; ++i){
scanf("%d", ptr + i);
sum +=*(ptr + i);
}
printf("Sum = %d", sum);
printf(“\n”);
//Deallocating the memory
free(ptr);
return 0;
}
47
Here, we have dynamically allocated the memory for n number of int.
48
Lab# 05
Command Line Argument, Process Environments,
and Error Handlining
In today's lab, we would learn a bit about Command Line arguments Error Handlining and
Process Environments
Example
In the above statement, the command line arguments have been handled via the main()
function, and you have set the arguments where:
You must make sure that in your command line argument, argv[0] stores the name of your
program, similarly argv[1] gets the pointer to the 1st command line argument that the user
has supplied, and *argv[n] denotes the last argument of the list.
Example 1
In this example, the name of the program and the argument entered by the user are displayed.
#include <stdio.h>
int main( int argc, char *argv [] )
{
printf(" \n Name of my Program %s \t", argv[0]);
49
if( argc == 2 )
{
printf("\n Value given by user is: %s \t", argv[1]);
}
else if( argc > 2 )
{
printf("\n Many values given by users.\n");
}
else
{
printf(" \n Single value expected.\n");
}
}
Output:
Example 2
In this example, the user's first argument entered (should be a character) is converted into its
ASCII code.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main( int argc, char *argv [] )
{
int val;
char str[10];
printf(" \n Name of my Program %s \t", argv[0]);
if( argc == 2 )
{
printf("\n Value given by user is: %s \t", argv[1]);
printf("\n\n");
50
val = atoi(argv[1]);
printf("\n string values=: %s, ASCII value= %d\n",
argv[1], val);
}
else if( argc > 2 )
{
printf("\n Many values given by users.\n");
}
Output:
Example 3
In this example, all arguments entered by the user are displayed using for loop.
#include <stdio.h>
int main(int argc, char **argv)
{
for(; *argv; argv++)
printf("\n\t%s\n", *argv);
return 0;
}
The above program would print any command line arguments you pass to it. If you do not
give any arguments, it prints just the name of your executable.
name=value
The names are defined in upper case, but this is only a convention. Just like the command line
argument list, the environment list is also an array of pointers pointing to the address of a null-
51
terminated string. And the environment list can be accessed through a global
variable environ, which is defined as a pointer to a pointer to char. Here is an example of how
to use an environment list in a C program:
Example 4
This code prints the environment in which it is executed.
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
int main(int argc, char* argv[])
{
printf("\n This is a test program \n:");
char **tmp = environ;
while(*tmp != " ")
{
printf("\n %s \n, *tmp");
tmp++;
}
return 0;
}
So, as you can see, the environment variable is already defined as a global variable, so you just
have to declare it as an extern variable. Using a temp pointer variable, to which you assign the
address held by environ variable, the program loops over and prints each environment variable.
52
\
SYNOPSIS
#include <stdio.h>
void perror(const char *s);
perror() displays the string 's', followed by ':', and the error message associated with
errno. errno is an integer variable already defined for you in errno.h. It is set to the
latest/last error condition generated by your program, e.g., if your program tries to do
something that it isn't allowed to do, an error condition is reached in such a case. An example
errno value is EBADF, which means you used an invalid file descriptor value in your
program. errno is set to the appropriate integer value corresponding to that error.
Example 5
The following program shows the use of perror(). It also shows that at the start of the
program, errno is zero.
#include <stdio.h>
#include <errno.h>
int main()
53
{
printf("\n\t Value of errno : %d \n", errno);
perror("\t At the start of prog.");
return 0;
}
Ouput:
Example 6
This program shows the error number, killing a process with PID, which does not exist, and
opening a file in read mode, which also does not exist, and prints the associated error number.
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <signal.h>
/*
Program demonstrating perror() and errno.
*/
int main()
{
//Trying to kill a process that does not exist !!
kill(222222, 0);
printf("\n\t Value of errno : %d \n", errno);
perror("\t OverKill -:^)");
//Trying to open a nonexistent file...
if(fopen("RusselPeters", "r") == NULL)
{
printf("\n\t Value of errno : %d \n", errno);
perror("\t fopen");
}
return 0;
}
Output:
54
3.16 Introduction to structure in C
Structures (also called structs) are a way to group several related variables into one place.
Each variable in the structure is known as a member of the structure. Unlike an array, a
structure can contain many different data types (int, float, char, etc.) under a single
name.
For example,
Here, a derived type struct Person is defined. Now, you can create variables of this type.
55
Another way of creating a struct variable is:
In both cases,
• person1 and person2 are struct Person variables
• p[] is a struct Person array of size 20.
Person2.salary
Example 7
This example shows how to access the members of a structure.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//Create struct with person1 variable
struct person {
char name[50];
int citNo;
float salary;
}person1;
int main()
{
//assign value to the name of person1
56
printf("Name: %s\n", person1.name);
printf("Citizenship No: %d\n", person1.citNo);
printf("Salary: %.2f", person1.salary);
return 0;
}
Output:
In this program, we have created a struct named Person. We have also created a variable
of Person named person1. In main(), we have assigned values to the variables defined
in Person for the person1 object.
Notice that we have used strcpy() function to assign the value to person1.name.
This is because the name is a char array, and we cannot use the assignment operator = with
it after we have declared the string.
Finally, we printed the data of the person1.
We use the typedef keyword to create an alias name for data types. It is commonly used
with structures to simplify the syntax of declaring variables. For example, let us look at the
following code:
57
Example 8
This example shows how to access the members of a structure using typedef keyword.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//Create struct with typedef person
typedef struct person {
char name[50];
int citNo;
float salary;
}person;
int main()
{
//Create person variable
person p1;
//assign value to name of p1
strcpy(p1.name, "George Orwell");
//assign vlues to other p1 variables
p1.citNo = 1984;
p1.salary = 2500;
//print struct variables
printf("Name: %s\n", p1.name);
printf("Citizenship No: %d\n", p1.citNo);
printf("Salary: %.2f", p1.salary);
return 0;
}
Output:
58
Lab# 06
Input-Output System Calls in C
In this lab, we would learn to work with files using c library functions. We would also learn
about the System calls in C. A system call is a procedure that provides the interface between a
process and the operating system. It is how a computer program requests a service from the
operating system's kernel. Different operating systems execute different system calls.
0: Standard input
1: Standard output
2: Standard error
You can associate other file descriptors with files and devices using the open system call.
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
If successful, it returns a file descriptor that can be used in read(), write(), and other
system calls. The file descriptor is unique and isn’t shared by any other processes that may be
running. Two programs have a file open simultaneously and maintain distinct file descriptors.
If they both write to the file, they will continue to write where they left off. Their data isn’t
interleaved, but one will overwrite the other. Each keeps its own idea of how far into the file
(the offset) it has read or written.
The name of the file or device to be opened is passed as a parameter, pathname; the flags
parameter is used to specify actions to be taken on opening the file. The flags are specified as
a combination of a mandatory file access mode and other optional modes. The file access modes
are:
The call may also include a combination (using a bitwise OR) of the following optional modes
in the flags parameter:
59
• O_CREAT: Creates the file, if necessary, with permissions given in mode.
• O_EXCL: Used with O_CREAT, ensures that the caller creates the file. The open is
atomic; it’s performed with just one function call. This protects against two programs
creating the file at the same time. If the file already exists, open will fail.
open() returns the new file descriptor (always a nonnegative integer) if successful or –1 if
it fails. The new file descriptor is always the lowest-numbered unused descriptor. However,
we should keep in mind that the number of files that any running program may have open at
once is limited.
When you create a file using the O_CREAT flag with open, you must use the three-parameter
form. mode, the third parameter, is made from a bitwise OR of the following flags:
To create a file named new_file, with read permission for the owner and execute permission
for others, we could do the following:
write() arranges for the first count bytes from buf to be written to the file associated with
the file descriptor fd. It returns the number of bytes written. This may be less than the count if
there has been an error. If the function returns 0, it means no data was written; if it returns –1,
there has been an error in the write call.
60
read() reads up to count bytes of data from the file associated with the file descriptor fd and
places them in the data area buf. It returns the number of data bytes actually read, which may
be less than the number requested. If a read call returns 0, it has nothing to read; it reaches the
end of the file. An error on the call will cause it to return –1.
Example 1
C program to illustrate open()system call
#include<stdio.h>
#include<fcntl.h>
#include<errno.h>
int main()
{
// if the file does not have in the directory
// then file foo.txt is created.
int fd = open("foo.txt", O_RDONLY | O_CREAT);
printf("fd = %d\n", fd);
if (fd ==-1)
{
// print which type of error have in a code
printf("Error Number % d\n", errno);
// print program detail "Success or failure"
perror("Program");
}
close(fd);
return 0;
}
Output
Example 2
C program to illustrate write()system call.
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
int main()
{
int sz;
int fd = open("foo.txt", O_WRONLY | O_CREAT|O_TRUNC, 0644);
if(fd < 0)
61
{
perror("r1");
exit(1);
}
sz = write(fd, "hello geeks\n", strlen("hello geeks\n"));
printf("called write(% d, \"hello geeks\\n\", %d)."
" It returned %d\n", fd, strlen("hello geeks\n"), sz);
close(fd);
return 0;
}
Output
Example 3
C program to illustrate I/O System calls.
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
int main (void)
{
int fd[2];
char buf1[12] = "Hello world";
char buf2[12];
// assume foobar.txt is already created
fd[0] = open("foobar.txt", O_RDWR);
fd[1] = open("foobar.txt", O_RDWR);
write(fd[0], buf1, strlen(buf1));
write(1, buf2, read(fd[1], buf2, 12));
close(fd[0]);
close(fd[1]);
return 0;
}
Output:
62
3.22 More File I/O Functions
The read()/write() system calls are too low, and the C standard library provides high-
level functionality for doing file I/O.
63
printf("%s ", arr);
}
fclose(f);
}
Output:
Position defines the point with respect to which the file pointer needs to be moved. It has
three values:
SEEK_END : It denotes end of the file.
SEEK_SET : It denotes starting of the file.
SEEK_CUR : It denotes file pointer’s current position.
Example 6:
C Program to demonstrate the use of fseek()function by moving the file pointer to the
end of the file.
#include <stdio.h>
int main()
{
FILE *fp;
fp = fopen("test.txt", "r");
// Moving pointer to end
fseek(fp, 0, SEEK_END);
// Printing position of the pointer
printf("%ld\n", ftell(fp));
return 0;
}
Output:
64
65
Lab# 07
Process Management System Calls
In this lab, we will learn about the process management system calls and how processes are
created. We will also learn about wait() and PID.
SYNOPSIS
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
• A negative value indicates an error, i.e., unsuccessful in creating the child process.
• Returns a zero for the 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.
Example 1
In this example, fork() is called once; hence the output is printed twice (2 power 1). If
fork()is called three times, the output would be printed 8 times (2 power 3).
#include <stdio.h>
66
#include <sys/types.h>
#include <unistd.h>
int main() {
fork();
printf("Called fork() system call\n");
return 0;
}
Output
Note: Usually, after the fork() call, the child and parent processes 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 three times, the output would be printed 8 times (2 power 3). If it is called 5
times, then it prints 32 times, and so forth.
We have seen fork() create the child process, and it is time to see the details of the parent
and the child processes.
Example 2
This C program illustrates how to run different codes in parent and child and display a
process ID by getpid() and a parent process ID by getppid()systems calls.
#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");
67
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;
}
Output
A call to wait() blocks the calling process until one of its child processes exits or a signal
is received. After the child process terminates, the parent continues its execution after the
wait system call instruction.
• It calls exit();
• It returns (an int) from the main.
• It receives a signal (from the OS or another process) whose default action is to
terminate.
68
Syntax in c language:
#include<stdio.h>
#include<stdlib.h>
// take one argument status and returns
// a process ID of dead children.
pid_t wait(int *stat_loc);
If any process has more than one child process, then after calling wait(), the parent
process has to be in a wait state if no child terminates.
If only one child process is terminated, then return a wait() returns the process ID of the
terminated child process.
If more than one child's processes are terminated, then wait() to reap any arbitrary
child and return a process ID of that child process.
When wait() returns, they also define exit status (which tells us a process of why it
terminated) via a pointer, If the status is not NULL.
If any process has no child process, then wait() returns immediately “-1.”
Example 3
C program to demonstrate the working of wait(). The child waits for the parent to
display his PID in the below program.
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
int main()
69
{
pid_t cpid;
if (fork()== 0)
exit(0); /* terminate child */
else
cpid = wait(NULL); /* reaping parent */
printf("Parent pid = %d\n", getpid());
printf("Child pid = %d\n", cpid);
return 0;
}
Output
Example 4
C program to demonstrate the working of wait(). In this example below, “CT: the
child has terminated,” this sentence does not print before HC because of the wait().
#include<stdio.h>
#include<sys/wait.h>
#include<unistd.h>
int main()
{
if (fork()== 0)
printf("HC: hello from child\n");
else
{
printf("HP: hello from parent\n");
wait(NULL);
printf("CT: child has terminated\n");
}
printf("Bye\n");
return 0;
}
Output: Depending on the environment.
70
If more than one child process is terminated, wait() reaps any arbitrary child process, but
if we want to reap any specific child process, we use the waitpid() function.
Syntax in c language:
pid_t waitpid (child_pid, &status, options);
Example 5
C program to demonstrate waitpid(). I am using waitpid()and printing the exit
status of children.
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
void waitexample()
{
int i;
int stat;
pid_t pid[5];
for (i=0; i<5; i++)
{
if ((pid[i] = fork()) == 0)
{
sleep(1);
exit(100 + i);
71
}
}
// Using waitpid() and printing exit status
// of children.
for (i=0; i<5; i++)
{
pid_t cpid = waitpid(pid[i], &stat, 0);
if (WIFEXITED(stat))
printf("Child %d terminated with status: %d\n",
cpid, WEXITSTATUS(stat));
}
}
// Driver code
int main()
{
waitexample();
return 0;
}
Output:
72
Lab# 08
Process Management Contd
In this lab, we will learn about the process management system calls and exec() family of
functions. We will also learn about Zombi and Orphen processes.
SYNOPSIS
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
The exec() family of functions replaces the current process image with a new process image.
An exec call will return to the calling process only if an error occurs during the call (e.g., the
program to be executed cannot be located or it is not executable). An unsuccessful exec
function returns -1, with errno set appropriately.
8.1.1 execvp :
Using this command, the created child process does not have to run the same program as the
parent. The exec type system calls allow a process to run any program files, which include
a binary executable or a shell script.
Syntax:
73
int execvp(const char *file, char *const argv[]);
file: points to the file name associated with the file being executed.
argv: is a null-terminated array of character pointers.
Let us see a small example of how to use execvp() function in C. We will have two .c
files, EXEC.c and execDemo.c , and we will replace the execDemo.c with EXEC.c by
calling execvp() function in execDemo.c.
Example 1
//EXEC.c
#include<stdio.h>
#include<unistd.h>
int main()
{
int i;
printf("I am EXEC.c called by execvp() ");
printf("\n");
return 0;
}
Now, create an executable file of EXEC.c using the command.
Output:
74
8.1.2 execv:
This is very similar to execvp() function in terms of syntax. The syntax of execv() is
as shown below:
Syntax:
int execv(const char *path, char *const argv[]);
path: should point to the path of the file being executed.
argv[]: is a null-terminated array of character pointers.
Let us see a small example of how to use execv() function in C. This example is similar
to the example shown above for execvp(). We will have two .c files, EXEC.c
and execDemo.c, and we will replace the execDemo.c with EXEC.c by calling
execv() function in execDemo.c.
Example 3
//EXEC.c
#include<stdio.h>
#include<unistd.h>
int main()
{
int i;
printf("I am EXEC.c called by execv() ");
printf("\n");
return 0;
}
Now, create an executable file of EXEC.c using the command.
gcc EXEC.c -o EXEC
Example 4
//execDemo.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
//A null terminated array of character
//pointers
char *args[]={"./EXEC",NULL};
execv(args[0],args);
75
/*All statements are ignored after execv() call as this
whole process(execDemo.c) is replaced by another process
(EXEC.c)
*/
printf("Ending ----- ");
return 0;
}
Now, create an executable file of execDemo.c using the command.
Output:
When the file execDemo.c is compiled, as soon as the statement execv (args[0], args)
is executed, this program is replaced by the program EXEC.c. “Ending” is not printed
because as soon as the execv() function is called, this program is replaced by the program
EXEC.c.
8.1.3 execl:
In execl() system function takes the path of the executable binary file (i.e., /bin/ls) as
the first and second argument. Then, the arguments (i.e. -lh, /home) that you want to pass
to the executable are followed by NULL. Then execl() system function runs the command
and prints the output. If any error occurs, then execl() returns -1. Otherwise, it returns
nothing.
Syntax:
int execl(const char *path, const char *arg, ..., NULL);
An example of the execl() system function is given below:
Example 5
The example below illustrates the execl() to display the ls -l command by using the
execl.
#include <unistd.h>
int main(void) {
char *binaryPath = "/bin/ls";
char *arg1 = "-lh";
char *arg2 = "/home";
execl(binaryPath, binaryPath, arg1, arg2, NULL);
76
return 0;
}
Output:
8.1.4 execlp()
execl() does not use the PATH environment variable. So, the full path of the executable file
is required to run it with execl(). execlp() uses the PATH environment variable. So, if
an executable file or command is available in the PATH, then the command or the filename is
enough to run it; the full path is unnecessary.
Syntax:
int execlp(const char *file, const char *arg, …, NULL );
We can rewrite the execl() example using the execlp() system function as follows:
Example 6
The example below illustrates the execlp() to display the ls -l command using the
execlp.
#include<unistd.h>
int main(void) {
char *programName = "ls";
char *arg1 = "-lh";
char *arg2 = "/home";
execlp(programName, programName, arg1, arg2, NULL);
return 0;
}
I only passed the command name ls, not the full path /bin/ls. As you can see, I got the same
output as before.
77
8.2 Zombie Process:
A process that has finished the execution but still has an entry in the process table to report
to its parent process is known as a zombie process. A child process always becomes a zombie
before being removed from the process table. The parent process reads the child's exit status,
which reaps off the child's process entry from the process table.
Example 7
In the following code, the child finishes its execution using exit() system call while the
parent sleeps for 5 seconds; hence doesn’t call wait(), and the child process’s entry still
exists in the process table.
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
pid = fork();
switch(pid){
case -1:
perror("fork");
exit(1);
case 0:
exit(0);
default:
sleep(5);
execlp("ps", "ps", "-l", (char *)NULL);
}
}
Output:
78
8.3 Orphan Process:
A process whose parent process no longer exists, i.e., finished or terminated without waiting
for its child process to terminate, is called an orphan process. In the following code, the parent
finishes execution and exits while the child process is still executing and is called an orphan
process now.
However, the orphan process is soon adopted by the init process once its parent process
dies.
Example 8
In this example, the parent process is finished and does not wait for its child process to
terminate, and the orphan process is adopted by init. As indicated by the child PPID in the
output below.
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
pid = fork();
switch(pid){
case -1:
perror("fork");
exit(1);
case 0:
sleep(2);
execlp("ps", "ps", "-f", (char *)NULL);
default:
exit(0);
}
}
Output:
79
Example 9
This program uses the getpid, wait, and wc command. The child overwrites itself with
the wc command by calling execvp, and the parent waits for the child.
#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
printf("hello world (pid:%d)\n", (int) getpid());
int rc = fork();
if (rc < 0) {
// fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (rc == 0) {
// child (new process)
printf("hello, I am child (pid:%d)\n", (int)
getpid());
char *myargs[3];
myargs[0] = strdup("wc"); // program: "wc" (word
//count)
myargs[1] = strdup("Lab8ex8.c"); // argument: file
to //count
myargs[2] = NULL; // marks end of array
execvp(myargs[0], myargs); // runs word count
printf("this shouldn't print out");
}
else {
// parent goes down this path (original process)
int wc = wait(NULL);
printf("hello, I am parent of %d (wc:%d)
(pid:%d)\n",rc, wc, (int) getpid());
}
return 0;
}
Output:
80
81
Lab# 09
Introduction to Posix Threads (pthreads)
• 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, other threads can perform CPU-intensive work.
• Asynchronous event handling: tasks that 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.
82
9.4.1 Thread Creation
The first thing you have to be able to do to write a multi-threaded program is to create new
threads, and thus some kind of thread creation interface must exist. In POSIX, it is easy:
This declaration might look a little complex (particularly if you haven’t used function pointers
in C), but actually, it’s not too bad. There are four arguments: thread, attr, start
routine, and arg. The first thread is a pointer to a structure of type pthread_t; we’ll
use this structure to interact with this thread, and thus we need to pass it to
pthread_create() in order to initialize it.
The second argument, attr, is used to specify any attributes this thread might have.
Some examples include setting the stack size or perhaps information about the scheduling
priority of the thread. An attribute is initialized with a separate call to pthread_attr_
init(); see the manual page for details. However, in most cases, the defaults will be fine;
in this case, we will simply pass the value NULL in.
The third argument is the most complex, but is really just asking: which function should
this thread start running in? In C, we call this a function pointer, and this one tells us the
following is expected: a function name (start_routine), which is passed a single
argument of type void * (as indicated in the parentheses after start routine), and which returns
a value of type void * (i.e., a void pointer).
Finally, the fourth argument, arg, is exactly the argument to be passed to the function
where the thread begins execution. You might ask: why do we need these void pointers? Well,
the answer is quite simple: having a void pointer as an argument to the function start_
routine allows us to pass in any type of argument; having it as a return value allows the
thread to return any kind of result.
• The thread returns from its starting routine (the main routine for the initialthread).
• The entire process is terminated due to a call to either the exec or exit sub-routines.
83
pthread_exit is used to exit a thread explicitly. Typically, the pthread_exit() routine is
called after a thread has completed its work and is no longer required to exist.If main()
finishes before the threads it has created and exits with pthread_exit(), the other
threads will continue to execute. Otherwise, they will be automatically terminated when
main() finishes.
You need to do something special in order to wait for completion; in particular, you must
call the routine pthread_join().
9.4.3 Thread Join
int pthread_join(pthread_t thread, void **value_ptr);
The first parameter is the thread for which to wait, the identified that pthread_createfilled in for
us. The second argument is a pointer to a pointer that itself points to thereturn value from the
thread. This function returns zero for success and an errorcode on failure. When a thread is
created, one of its attributes defines whether the thread is joinable or detached. Only threads
that are created as joinable canbe joined. If a thread is created as detached, it can never be
joined. A thread can execute a thread join to wait until the other thread terminates. In our
case, you - the main thread - should execute a thread join, waiting for your colleague - a
child thread - to terminate. In general, thread join is for a parent (P) to join with one of its
child threads (C). Thread join has the following activities, assuming that a parent thread P
wants to join with one of its child threads C:
• When P executes a thread join to join with C, which is still running, Pis suspended
until C terminates. Once C terminates, P resumes.
• When P executes a thread join and C has already terminated, P continues as if no
such thread join has ever been executed (i.e., join has no effect).
Example 1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> //Header file for sleep(). man 3 sleep
for details.
#include <pthread.h>
84
void *myThreadFun(void *vargp)
{
sleep(1);
printf("Printing GeeksQuiz from Thread \n");
return NULL;
}
int main()
{
pthread_t thread_id;
printf("Before Thread\n");
pthread_create(&thread_id, NULL, myThreadFun, NULL);
pthread_join(thread_id, NULL);
printf("After Thread\n");
exit(0);
}
85
• The scope of the global variable is throughout the program, i.e., all the threads in
functions that are declared can access it.
• The lifetime of a global variable is throughout the program, i.e., memory to the global
variables will be allocated when the program execution is started and will become
invalid after finishing the program's execution.
Example 2
A C program to show multiple threads with global and static variables.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
// Let us create a global variable to change it in threads
int g = 0;
// The function to be executed by all threads
void *myThreadFun(void *vargp)
{
// Store the value argument passed to this thread
int *myid = (int *)vargp;
// Let us create a static variable to observe its changes
static int s = 0;
// Change static and global variables
++s; ++g;
// Print the argument, static and global variables
printf("Thread ID: %d, Static: %d, Global: %d\n", *myid,
++s, ++g);
}
86
printf("Thread ID: %d, Static: %d, Global: %d\n", *myid,
++s, ++g);
}
int main()
{
int i;
pthread_t tid;
// Let us create three threads
for (i = 0; i < 3; i++)
pthread_create(&tid, NULL, myThreadFun, (void
*)&tid);
for (i = 0; i < 3; i++)
pthread_create(&tid, NULL, myThreadFun1, (void
*)&tid);
pthread_exit(NULL);
return 0;
}
Output:
Example 3
An example is given that takes an array as an input and find its sum in parallel using threads.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//Create struct with typedef person
typedef struct data {
int* arr;
int thread_num;
}data;
int arrSize = 10;
void* halfSum(void* p){
data* ptr = (data*)p;
int n = ptr->thread_num;
int* thread_sum = (int*)malloc(sizeof(int));
if(n==0){
for(int i = 0; i<arrSize/2; i++)
87
thread_sum[0] = thread_sum[0] + ptr->arr[i];
}
else{
for(int i = arrSize/2; i<arrSize; i++)
thread_sum[0] = thread_sum[0] + ptr->arr[i];
}
pthread_exit(thread_sum);
}
int main(void)
{
int* int_arr = (int*)calloc(arrSize, sizeof(int));
for(int i = 0; i<arrSize; i++)
int_arr[i] = i+1;
data thread_data[2];
thread_data[0].thread_num = 0;
thread_data[0].arr = int_arr;
thread_data[1].thread_num = 1;
thread_data[1].arr = int_arr;
pthread_t tid[2];
pthread_create(&tid[0],NULL,halfSum,&thread_data[0]);
pthread_create(&tid[1], NULL, halfSum, &thread_data[1]);
int* sum0;
int* sum1;
pthread_join(tid[0], (void**)&sum0);
pthread_join(tid[1], (void**)&sum1);
printf("Sum of whole array = %i\n", *sum0 + *sum1);
return 0;
}
Output:
Example 4
An example of taking an array of size 100 as an input and thread 0 finding the sum of the first
25 items, thread 1 finding the sum of the following 25 items of the array, and finally, the sum
of the whole array is printed.
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
int sz=100;
int num_threads=4;
int global_sum=0;
typedef struct arguments
88
{
int st_ind;
int en_ind;
int* A;
int iter;
} arguments;
void *thread_worker(void *args1)
{
struct arguments *args = args1;
int sum = 0;
int i=0;
for (i=args->st_ind; i<args->en_ind;i++)
{
sum=sum+args->A[i];
}
printf("The sum from thread %d is %d \n",args->iter, sum);
int *val = malloc(sizeof(int));
*val = sum;
return val;
}
int main()
{
pthread_t t_id[num_threads];
int* Arr = (int*) calloc(sz, sizeof(int));
int i=0;
for (i=0; i<sz;i++)
Arr[i]=i+1;
for (i=0; i<sz;i++)
printf("%d ",Arr[i]);
printf("\n");
arguments args[num_threads];
for (i=0;i<num_threads;i++)
{
args[i].st_ind = (i*sz)/num_threads;
args[i].en_ind = ((i+1)*sz)/num_threads;
args[i].A=Arr;
args[i].iter=i;
int t_stat = pthread_create(&t_id[i],NULL,thread_worker,
&args[i]);
}
int* temp_sum = 0;
for (i=0;i<num_threads;i++){
pthread_join(t_id[i], (void**)&temp_sum);
global_sum += *temp_sum;
free(temp_sum);
}
printf("The global sum is %d\n", global_sum);
}
89
Output:
Example 5
The student will execute the below example and explain its output.
#include <assert.h>
#include <stdio.h>
#include <pthread.h>
typedef struct {
int a;
int b;
} myarg_t;
void *mythread(void *arg) {
myarg_t *args = (myarg_t *) arg;
printf("%d %d\n", args->a, args->b);
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t p;
myarg_t args = { 10, 20 };
int rc = pthread_create(&p, NULL, mythread, &args);
assert(rc == 0);
(void) pthread_join(p, NULL);
printf("done\n");
return 0;
}
90
Lab# 10
Thread Synchronization and Mutexes
In this lab, we will study thread synchronization and mutexes. Synchronization is the
cooperative action of two or more threads that ensure that each thread reaches a known point
of operation concerning other threads before continuing. Thread synchronization is the concurrent
execution of two or more threads that share critical resources. Threads should be synchronized to avoid
critical resource use conflicts. Otherwise, conflicts may arise when parallel-running threads attempt to
modify a common variable simultaneously. A mutual exclusion (mutex) is a program object that
prevents simultaneous access to a shared resource. This concept is used in concurrent
programming with a critical section, a piece of code in which processes or threads access a
shared resource. Only one thread owns the mutex at a time. Thus, a mutex with a unique name
is created when a program starts. When a thread holds a resource, it has to lock the mutex from
other threads to prevent concurrent access to the resource. Upon releasing the resource, the
thread unlocks the mutex.
10.4 Synchronization
Thread synchronization is defined as a mechanism that ensures that two or more concurrent
processes or threads do not simultaneously execute some particular program segment,
known as a critical section. Processes’ access to critical sections is controlled by using
synchronization techniques. When one thread starts executing the critical sections (a
serialized segment of the program) the other thread should wait until the first thread
finishes. If proper synchronization techniques are not applied, it may cause a race
condition where the values of variables may be unpredictable and vary depending on the
timings of context switches of the processes or threads.
Thread Synchronization Problems
An example code to study synchronization problems:
Example 1
The following code tries to implement a global variable counter from 2 threads. The final value of
the counter should reflect all the 6implementations by both threads.
91
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
//#include "common.h"
//#include "common_threads.h"
int max;
int counter = 0; // shared global variable
void *mythread(void *arg) {
char *letter = arg;
int i; // stack (private per thread)
printf("%s: begin [addr of i: %p]\n", letter, &i);
for (i = 0; i < max; i++) {
counter = counter + 1; // shared: only one
}
printf("%s: done\n", letter);
return NULL;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: main-first <loopcount>\n");
exit(1);
}
max = atoi(argv[1]);
pthread_t p1, p2;
printf("main: begin [counter = %d] [%x]\n", counter,
(unsigned int) &counter);
pthread_create(&p1, NULL, mythread, "A");
pthread_create(&p2, NULL, mythread, "B");
// join waits for the threads to finish
pthread_join(p1, NULL);
pthread_join(p2, NULL);
printf("main: done\n [counter: %d]\n [should: %d]\n",
counter, max*2);
return 0;
}
92
10.5 Race Condition
What we have demonstrated here is called a race condition (or, more specifically, a data race):
the results depend on the timing execution of the code. With some bad luck (i.e., context switches
that occur at untimely points in the execution), we get the wrong result. In fact, we may get a
different result each time; thus, instead of a nice deterministic computation (which we are used to
from computers), we call this result indeterminate, where it is not known what the output will be
and it is indeed likely to be different across runs. A race condition (or data race) arises if multiple
threads of execution enter the critical section roughly simultaneously; both attempt to update the
shared data structure, leading to a surprising (and perhaps undesirable) outcome.
Because multiple threads executing this code can result in a race condition, we call this code a
critical section. A critical section is a piece of code that accesses a shared variable (or, more
generally, a shared resource) and must not be concurrently executed by more than one thread. The
critical section is a piece of code that accesses a shared resource, usually a variable or data
93
structure.
Example 2
The following code uses a global mutex to protect the critical section of each thread and prevents
them from corrupting each other values.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int max;
pthread_mutex_t lock;
int counter = 0; // shared global variable
void *mythread(void *arg) {
char *letter = arg;
int i; // stack (private per thread)
printf("%s: begin [addr of i: %p]\n", letter, &i);
for (i = 0; i < max; i++) {
pthread_mutex_lock(&lock);
counter = counter + 1; // shared: only one
pthread_mutex_unlock(&lock);
}
printf("%s: done\n", letter);
return NULL;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: main-first <loopcount>\n");
exit(1);
}
max = atoi(argv[1]);
pthread_t p1, p2;
printf("main: begin [counter = %d] [%x]\n", counter,
(unsigned int) &counter);
pthread_create(&p1, NULL, mythread, "A");
pthread_create(&p2, NULL, mythread, "B");
// join waits for the threads to finish
pthread_join(p1, NULL);
pthread_join(p2, NULL);
printf("main: done\n [counter: %d]\n [should: %d]\n",
counter, max*2);
return 0;
}
Output:
94
Example 3
In this example, two threads(jobs) are created, and in the start function of these threads, a
counter is maintained to get the logs about the job number which is started and when it is
completed.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
pthread_t tid[2];
int counter;
void* trythis(void* arg)
{
unsigned long i = 0;
counter += 1;
printf("\n Job %d has started\n", counter);
95
for (i = 0; i < (0xFFFFFFFF); i++)
;
printf("\n Job %d has finished\n", counter);
return NULL;
}
int main(void)
{
int i = 0;
int error;
while (i < 2) {
error = pthread_create(&(tid[i]), NULL, &trythis,
NULL);
if (error != 0)
printf("\nThread can't be created : [%s]",
strerror(error));
i++;
}
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
return 0;
}
To compile a multithreaded program using gcc, we need to link it with the pthreads library.
Following is the command used to compile the program.
96
• The log ‘Job 2 has started’ is printed just after ‘Job 1 has Started’, so it can easily
be concluded that while thread 1 was processing, the scheduler scheduled thread
2.
• If we take the above assumption as true, then the value of the ‘counter’ variable
got incremented again before job 1 got finished.
• So, when Job 1 actually got finished, then the wrong value of counter produced
the log ‘Job 2 has finished’ followed by the ‘Job 2 has finished for the actual job
2 or vice versa as it is dependent on the scheduler.
• So we see that it is not the repetitive log but the wrong value of the ‘counter’
variable that is the problem.
• The actual problem was using the variable ‘counter’ by a second thread when the
first thread was using or about to use it.
• In other words, we can say that lack of synchronization between the threads while
using the shared resource ‘counter’ caused the problems, or in a word, we can say
that this problem happened due to a ‘Synchronization problem’ between two
threads.
How to solve it?
The most popular way of achieving thread synchronization is by using Mutexes.
• A Mutex is a lock that we set before using a shared resource and release after
using it.
• When the lock is set, no other thread can access the locked region of the code.
• So we see that even if thread 2 is scheduled while thread 1 was not done
accessing the shared resource and the code is locked by thread 1 using mutexes,
then thread 2 cannot even access that region of code.
• So this ensures synchronized access to shared resources in the code.
Working of a mutex
1. Suppose one thread has locked a region of code using mutex and is executing
that piece of code.
2. Now, if the scheduler decides to do a context switch, all the other threads that
are ready to execute the same region are unblocked.
3. Only one of all the threads would make it to the execution, but if this thread tries
to execute the same region of code that is already locked, then it will again go to
sleep.
4. Context switch will take place again and again, but no thread would be able to
execute the locked region of code until the mutex lock over it is released.
5. Mutex lock will only be released by the thread that locked it.
6. So this ensures that once a thread has locked a piece of code, then no other
thread can execute the same region until it is unlocked by the thread that locked
it.
Hence, this system ensures synchronization among the threads while working on shared
resources.
Example 4
An example to show how mutexes are used for thread synchronization.
97
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
pthread_t tid[2];
int counter;
pthread_mutex_t lock;
void* trythis(void* arg)
{
pthread_mutex_lock(&lock);
unsigned long i = 0;
counter += 1;
printf("\n Job %d has started\n", counter);
for (i = 0; i < (0xFFFFFFFF); i++)
;
printf("\n Job %d has finished\n", counter);
pthread_mutex_unlock(&lock);
return NULL;
}
int main(void)
{
int i = 0;
int error;
if (pthread_mutex_init(&lock, NULL) != 0) {
printf("\n mutex init has failed\n");
return 1;
}
while (i < 2) {
error = pthread_create(&(tid[i]), NULL,&trythis,
NULL);
if (error != 0)
printf("\nThread can't be created :[%s]",
strerror(error));
i++;
}
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_mutex_destroy(&lock);
return 0;
}
In the above code:
• A mutex is initialized in the beginning of the main function.
• The same mutex is locked in the ‘trythis()’ function while using the shared
resource ‘counter’.
• At the end of the function ‘trythis()’, the same mutex is unlocked.
• At the end of the main function, when both the threads are done, the mutex is
destroyed.
98
Output :
Example 5
#include <stdio.h>
#include <pthread.h>
int run(void *arg)
{
(void)arg;
static int serial = 0; // Shared static variable!
printf("Thread running! %d\n", serial);
serial++;
return 0;
}
#define THREAD_COUNT 10
int main(void)
{
pthread_t t[THREAD_COUNT];
//thrd_t t[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_create((t + i), NULL, run, (void *) (t+1));
//thrd_create(t + i, run, NULL);
}
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_join(t[i], NULL);
//thrd_join(t[i], NULL);
}
}
When I run this code, I get something that looks like this:
99
Explanation of the above program
Clearly, multiple threads are getting in there and running the printf() before anyone gets
a change to update the serial variable.
What we want to do is wrap the getting of the variable and set it into a single mutex-protected
stretch of code.
We’ll add a new variable to represent the mutex of type pthread_mutex_t in the file
scope, initialize it, and then the threads can lock and unlock it in the run() function.
Example 6
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t serial_mtx; // <-- MUTEX VARIABLE
void * run(void *arg)
{
(void)arg;
static int serial = 0; // Shared static variable!
100
// no-frills, mutex:
pthread_mutex_init(&serial_mtx, pthread_mutex_lock); // <--
//CREATE MUTEX
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_create((t + i), NULL, run, (void *) (t+1));
}
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_join(t[i], NULL);
// Done with the mutex, destroy it:
pthread_mutex_destroy(&serial_mtx); // <-- DESTROY MUTEX
}
}
Explanation of the above program
See how we initialize and destroy the mutex on lines 38 and 50 of the main(). But each
individual thread acquires the mutex on line 15 and releases it on line 24.
In between the pthread_mutex_lock() and pthread_mutex_unlock() is the critical section,
the area of code where we don’t want multiple threads mucking about at the same time.
101
Lab# 11
Thread Synchronization Contd. Condition
Variables/Barriers
In this Lab, we will study the thread synchronization problem with condition variables and
barriers. Let’s look at another problem in shared-memory programming: synchronizing the
threads by ensuring they are all at the same point in a program. Such a point of synchronization
is called a barrier because no thread can proceed beyond the barrier until all the threads have
reached it. A somewhat better approach to creating a barrier in Pthreads is provided
by condition variables. A condition variable is a data object that allows a thread to suspend
execution until a certain event or condition occurs. When the event or condition occurs, another
thread can signal the thread to “wake up.” A condition variable is always associated with a
mutex.
3.31 Barriers
Barriers are used to synchronize all running threads at a particular location within the code.
This is most useful when several threads execute the same piece of code in parallel. The threads
are made to wait at the barrier until all threads reach the barrier. Then all the threads are allowed
to continue.
Example 1
In this code, the usage of barrier is shown. The program starts with initializing the barrier before
creating the threads. The threads are created and bind to the function where the barrier is used.
At the barrier, the thread waits for the other threads to complete their tasks. All threads will
reach the barrier and wait for other threads to execute. Once all threads are done with the
execution, their wait is over, and they proceed to the next loop iteration waiting on the barrier
as described above.
102
void *thread_result;
int thread_id[NUM_THREADS];
int i;
// Barrier initialization
if(pthread_barrier_init(&barrier, NULL, NUM_THREADS)){
perror("Could not create a barrier\n");
exit(EXIT_FAILURE);
}
for(i = 0; i < NUM_THREADS; i++) {
thread_id[i] = i;
res = pthread_create(&(a_thread[i]), NULL,
thread_function, (void *)&thread_id[i]);
if (res != 0){
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
}
printf("Waiting for threads to finish...\n");
for(i = 0; i < NUM_THREADS; i++){
res = pthread_join(a_thread[i], &thread_result);
if (res == 0) {
printf("Picked up a thread\n");
}
else {
perror("pthread_join failed");
}
}
printf("All done\n");
exit(EXIT_SUCCESS);
}
void *thread_function(void *arg) {
int my_number = *(int *)arg;
int rand_num;
int result;
int i;
for (i=0; i<10; i++){
fprintf(stderr, "\tthread_function %d, %d\n",
my_number,i);
// Barrier’s usage
result = pthread_barrier_wait(&barrier);
if(result != 0 && result != PTHREAD_BARRIER_SERIAL_THREAD)
{
perror("Could not wait on barrier\n");
exit(-1);
}
}
printf("Bye from %d\n", my_number);
pthread_exit(NULL);
}
Output:
103
104
3.32 Condition Variable
Condition variables allow threads to synchronize based on the value of some shared data.
Imagine the scenario where a thread has to perform some task as soon as the value of a shared
variable is greater than 0. Checking if the value is greater than 0 has to be performed inside a
critical section. This means the thread must acquire a lock before checking the value and release
the lock afterward. Condition variables allow for the efficient handling of such scenarios.
Condition Variables are the last piece of the puzzle we need to make performant multithreaded
applications and compose more complex structures. A condition variable provides a way for
threads to go to sleep until some event on another thread occurs.
In other words, we might have several threads that are rearing to go, but they must wait until
some event is true before they continue. Basically, they’re being told, “wait for it!” until they
get notified. And this works hand-in-hand with mutexes since what we’re going to wait on
generally depends on the value of some data, and that data generally needs to be protected by
a mutex.
It’s important to note that the condition variable itself isn’t the holder of any particular data
from our perspective. It’s merely the variable by which C keeps track of the waiting/not-waiting
status of a particular thread or group of threads.
Let’s write a contrived program that reads in groups of 5 numbers from the main thread one at
a time. Then, when 5 numbers have been entered, the child thread wakes up, sums up those 5
105
numbers, and prints the result. The numbers will be stored in a global, shared array, as will the
index into the array of the about-to-be-entered number.
Since these are shared values, we at least have to hide them behind a mutex for both the main
and child threads. (The main will be writing data to them, and the child will be reading data
from them.)
But that’s not enough. The child thread needs to block (“sleep”) until 5 numbers have been
read into the array. And then, the parent thread needs to wake up the child thread so it can do
its work. And when it wakes up, it needs to be holding that mutex. And it will! When a thread
waits on a condition variable, it also acquires a mutex when it wakes up.
All this takes place around an additional variable of type pthread_cond_t, the condition
variable. We create this variable with the pthread_cond_init() function and destroy it
when we’re done with it with the pthread_cond_destroy() function.
But how’s this all work? Let’s look at the outline of what the child thread will do:
If you didn’t skim that too hard (it’s OK—I’m not offended), you might notice something
weird: how can the main thread hold the mutex lock and signal the child if the child has to hold
the mutex lock to wait for the signal? They can’t both hold the lock!
And indeed, they don’t! There’s some behind-the-scenes magic with condition variables: when
you pthread_cond_wait(), it releases the mutex that you specify, and the thread goes to
sleep. And when someone signals that thread to wake up, it reacquires the lock as if nothing
had happened.
106
e.g., in this case, if not all the numbers have yet been entered). Here’s the deal: this condition
should be in a while loop, not an if statement. Why?
• The main thread will set up the mutex and condition variable and launch the child
thread.
• Then it will, in an infinite loop, get numbers as input from the console.
• It will also acquire the mutex to store the inputted number into a global array.
• When the array has 5 numbers in it, the main thread will signal the child thread that it’s
time to wake up and do its work.
• Then the main thread will unlock the mutex and go back to reading the next number
from the console.
And here’s the code! Give it some study so you can see where all the above pieces are being
handled:
Example 2
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define VALUE_COUNT_MAX 5
int value[VALUE_COUNT_MAX]; // Shared global
int value_count = 0; // Shared global, too
pthread_mutex_t value_mtx; // Mutex around value
pthread_cond_t value_cnd; // Condition variable on value
void *run(void *arg)
{
(void)arg;
107
for (;;) {
pthread_mutex_lock(&value_mtx); // <-- GRAB THE MUTEX
And here’s some sample output (individual numbers on lines are my input):
108
Output:
It’s a common use of condition variables in producer-consumer situations like this. If we didn’t
have a way to put the child thread to sleep while it waited for some condition to be met, it
would be forced to poll, which is a big waste of CPU.
109
Lab# 12
Open-Ended Lab
An open-ended lab is where students can develop their own experiments instead of merely
following the guidelines from a lab manual or elsewhere. To make this stage open-ended, the
teacher may give the students questions with a purpose and not the procedure. The students
would then have to develop their own logic to back the theory or fulfill the purpose. It will
make the students think critically and out of the box. The students here have to devise their
own strategies and back them up with explanations, theories, and logical justification. Making
labs, open-ended pushes students to think for themselves and think harder.
110
References
The following resources were used in the development of this manual.
1. https://www.geeksforgeeks.org
2. https://www.geeksforgeeks.org/condition-wait-signal-multi-threading/
3. https://beej.us/guide/bgc/html/split/multithreading.html
4. operating systems: three easy pieces - university of wisconsin–madison
111