Introduction-To-Unix-Programming-1
Introduction-To-Unix-Programming-1
CC BY-SA
The texts and images contained in this publication are subject –except where indicated to
the contrary– to an Attribution-ShareAlike license (BY-SA) v.3.0 Creative Commons
license. This work can be modified, reproduced, distributed and publicly disseminated as
long as the author and the source are quoted (Fundació per a la Universitat Oberta de
Catalunya), and as long as the derived work is subject to the same license as the original
work. The full terms of the license can be viewed at
https://creativecommons.org/licenses/by-sa/3.0/legalcode
Index
Index ............................................................................................................................................. 2
1 Introduction .......................................................................................................................... 4
1.1 The Unix manual ................................................................................................. 4
1.2 Control of errors................................................................................................... 5
2 The management of processes.............................................................................................. 6
2.1 The processes ....................................................................................................... 6
2.1.1 Process identifier (PID)........................................................................................ 6
2.1.2 Parent process ID ................................................................................................. 6
2.1.3 Process group ID.................................................................................................. 6
2.1.4 User identifier (UID)............................................................................................ 7
2.1.5 User group identifier (GID) ................................................................................. 7
2.1.6 Status of the process............................................................................................. 8
2.2 The creation of new processes ..................................................................................... 8
2.3 The transformation of a process (exec) ..................................................................... 11
2.4 Calls to the processes management system................................................................ 13
2.4.1 fork..................................................................................................................... 14
2.4.2 exec .................................................................................................................... 15
2.4.3 exit ..................................................................................................................... 17
2.4.4 wait .................................................................................................................... 18
2.4.5 getpid ................................................................................................................. 19
2.4.6 getppid ............................................................................................................... 20
2.4.7 getpgrp ............................................................................................................... 21
2.4.8 setpgid ................................................................................................................ 22
2.5 Examples of process management calls ..................................................................... 23
2.5.1 Example 1 .......................................................................................................... 23
2.5.2 Example 2 .......................................................................................................... 25
3 Input/output ........................................................................................................................ 26
3.1 Input/output and the file system ................................................................................. 26
3.2 The independence of devices and the input/output redirection ....................... 28
3.3 The open file table and the read/write pointer .................................................... 31
3.4 Calls to the input/output system ........................................................................ 33
3.4.1 creat.................................................................................................................... 34
3.4.2 open.................................................................................................................... 36
3.4.3 close ................................................................................................................... 38
3.4.4 read .................................................................................................................... 39
3.4.5 write ................................................................................................................... 41
3.4.6 lseek ................................................................................................................... 43
3.4.7 dup ..................................................................................................................... 44
3.4.8 unlink ................................................................................................................. 45
3.5 Examples of input/output calls........................................................................... 46
3.5.1 Example 1 .......................................................................................................... 46
3.5.2 Example 2 .......................................................................................................... 47
3.5.3 Example 3 .......................................................................................................... 48
3.5.4 Example 4 .......................................................................................................... 49
3.5.5 Example 5 .......................................................................................................... 50
4 Communication and synchronisation between processes .................................................. 51
4.1 Signals................................................................................................................ 51
4.2 Pipes ................................................................................................................... 53
4.3 Calls to the communication and synchronisation system between processes ... 56
4.3.1 pipe .................................................................................................................... 57
4.3.2 signal .................................................................................................................. 58
4.3.3 kill ...................................................................................................................... 59
4.3.4 alarm .................................................................................................................. 61
4.3.5 pause .................................................................................................................. 62
4.4 Examples of communication and synchronisation calls between processes ..... 63
4.4.1 Example 1 .......................................................................................................... 63
4.4.2 Example 2 .......................................................................................................... 64
5 Bibliography ....................................................................................................................... 66
1 Introduction
This document will introduce the basic concepts of programming with Unix system calls.
However, the document does not pretend to be a complete catalogue of all the system
calls and their use, but it wants to present the most significant functions of the Unix
environment related to process management, input/output and communication between
processes. The final objective is to offer the basic tools to understand the most
characteristic features of Unix so you can start developing applications on this
environment.
The document is structured in four chapters. This first chapter is an introduction that
presents, in general, some concepts that will help us to understand the fundamentals
of Unix programming. The other three chapters deal with three fundamental areas of
the operating system: process management (chapter 2), input/output (chapter 3) and
the communication and synchronisation between processes (chapter 4).
The structure of chapters 2, 3 and 4 is very similar. In each of them, the concepts related
to the topic discussed are introduced first, then the fundamental system calls are
presented and it ends with a set of examples that will help clarify all the issues that have
been treated.
1. Syntax: presents the C-language syntax of each system call and describes the
necessary definition files (include).
2. Description: introduces in detail how system calls work and the actions
performed.
3. Parameters: explains the parameters used and the possible values one by one.
4. Returned value: describes the possible returned values and their meaning.
5. Errors: presents the most significant errors that each system call can produce.
Unix offers a user manual structured in 8 sections where all its characteristics are
described. This manual is available in the operating system itself (man pages) and
can be accessed through the order man of the command-line interpreter. The sections
cover the following topics:
The description of each concept of the manual is accompanied by the identifier of the
section to which it belongs. For example, the command Is that allows us to list the
contents of a directory will appear as ls (1) since it is a command from the command-
line interpreter and, therefore, it will be described in section 1. All system calls will be
explained in section 2 and the references will indicate it as follows: fork (2).
The Unix online manual is a very important help tool in the use of the operating
system and, specifically, for the in-depth knowledge of system calls.
Most Unix system calls return an integer value at the end of the execution. A 0 or
positive value indicates a correct end of the call. A negative value (-1) indicates an
error in its execution.
Each system call can produce different and varied errors. To be able to know the error
that occurred, each process has a global variable called errno that describes the error
that occurred after each system call. In order to properly control the behavior of a
process, which uses system calls, it is completely necessary to track the execution of
each call in detail. For this reason, it is entirely advisable to verify the correct end of the
calls and, otherwise, to detect the error that has occurred.
#include <errno.h>
int p[2];
...
The previous code includes the file errno.h, where the global variable errno required to
identify the error that occurred is described. The process executes the system call
pipe which will return the value -1 in case of error. If an error occurs, the code will
write the type of error (variable errno) and will end the execution of the process (system
call exit).
Process management is the fundamental tool that allows for the creation and
termination of new processes within the operating system. In Unix, there is a hierarchy
of processes headed by an initial process from which the rest of the system processes
are generated. This process (the process init) has ID 1 and will play a very important
role throughout the life of the system as we will see.
Unix processes have a set of attributes that must be known in order to manage them
correctly, of which the following should be highlighted:
The process ID is a unique positive integer that is associated to each process at the
time of its creation and that is maintained until its termination. This ID will allow us to
manage the process throughout its life and track its execution.
Unix processes maintain the hierarchical relationship of the system through the parent
process ID. This ID allows us to know who created the process.
The group ID indicates the process group to which the process belongs. A process
group is a grouping of processes that facilitates the joint management of some functions
such as sending signals (system call signal). The group's leading process is the one
that defines the value of the group ID, which will be the same as its process identifier
(PID). Process groups exist while any process of the group still exists. A new process
keeps the parent process group as long as it does not change groups using the system
call setpgid.
Every process belongs to the user who created it and every Unix user has a unique ID
that represents it. The user ID will be assigned to the process at the time of its creation
and will grant it a set of rights over system resources.
The real user ID is never modified throughout the life of the process and corresponds
to the identifier of the user who created the process.
The effective user ID is used by the system to verify the rights of the process over
different system resources. At the time of creating a process, its effective user ID
matches the real user ID, but the effective user ID can be modified in a controlled
manner throughout the execution of the process. Modifying the effective user ID
provides an important tool for the restricted access of user processes to protected
system resources.
The change of the effective user ID can be caused by the execution (system call exec)
of an executable file that has the bit called setUID (set-user-ID) active. If the bit set-
user-ID is active, the effective user ID of the process that executed (exec) the file, will
take as a value the ID of the owner of the executable file. The real user ID is not
modified.
A clear example of the use of setUID can be found in the command passwd. This
command allows any user to modify his or her system access password. This password
is stored in a file (/etc/passwd) that is owned by the user root or super-user and that
only this user can modify. The executable file with the command passwd also belongs
to the user root and has the bit setUID active. Therefore, any process that executes this
command will change its effective user ID which will take as a value the ID of the user
root and, therefore, it will be able to access the password file (/etc/passwd) and
modify it.
Every process belongs to the user group of the user who created it. The user group ID
will be assigned to the process at the time of its creation and will grant it a set of rights
over system resources.
The real group ID is never modified throughout the life of the process and corresponds
to the ID of the user group of the user who created the process (the owner of the
executable file).
The effective group ID is used by the system to verify the rights of the process over
different system resources. At the time of creating a process, its effective user group ID
matches the real user group ID, but the effective ID of the user group can be modified
in a controlled manner throughout the process. Modifying the effective ID of the user
group offers an important tool for the restricted access of user processes to protected
system resources.
The change of the effective ID of the user group can be caused by the execution
(system call exec) of an executable file that has the bit called setGID (set-group-ID)
active. If the bit set-group-ID is active, the effective ID of the user group of the process
that executed (exec) the file will take as a value the user group ID of the owner of the
executable file. The real user group ID will not be modified.
In Unix, it is worth highlighting a particular state that some processes have after its
termination: the state zombie. This state corresponds to a process that cannot be
executed again because it has already ended but is still present in the system because
it has not been able to free all its resources. The reason for the existence of the state
zombie is related with the fact that the Unix process hierarchy originates in the creation
of the processes and continues until its disappearance.
Each process is the child of its parent process who is responsible for freeing resources
from its child processes at the time of its end. To free the processes and remove them
completely from the system, the parent process must be synchronised (call wait) with
the end (exit) of its child processes. In this synchronisation process, the child process
informs the parent of the cause of its termination while freeing all its resources and
disappearing from the system. If a process ends without this parent-child
synchronisation, the process goes to the zombie state and will remain in this state
until it can release the pending resources. If the parent process disappears without
synchronisation (wait) with its child processes, the child processes will be adopted by
the process init (process 1) and this first-born process will free them from the zombie
state and they will disappear from the system.
The creation of new processes is one of the most important actions which allows new
actions to be carried out by different users. Each new process will be executed
concurrently with other processes, and it will have to share the resources (processor,
memory, ...) of the system. Unlike other operating systems, the creation of processes
in Unix is done in a very simple way, using a system call named fork that does not use
any parameter. The result of the execution of the call fork is the creation of a new
process, child of the creator process which will run concurrently with the rest of the
processes of the system, which inherits many characteristics from its parent. It should
be noted, for example, that the new process has the same code, the same data, the
same stack and the same program counter value as the creator process. This does not
mean that it shares code and data, but rather that a new process is created where the
code and data of the process that created it is copied. Ther e is only one piece of data
that is different: the value returned by the call fork.
Indeed, the fork call returns twice, first in the process that has invoked it and second, in
the new process that, as already mentioned, has the same code as the creator process
and, therefore, it will begin its execution just after the function fork has ended. In the
parent process, the call fork returns the child process ID and in the child process it
returns the value 0. It is thanks to the different return values that we can distinguish
between parent and child and, therefore, the behavior of both processes can be
modified. Below, a small program that creates a new process can be found:
main()
{
int process, st, pid;
char s[80];
case 0:
default:
The evolution of both processes is shown in the following diagram (figure 1):
Figure 1
Start
exit
wait
Synchronisation of child and parent deaths
exit
In the previous code, the possible values returned by the call fork can be clearly
distinguished:
- Value -1: represent an error and that the new process could not be created.
- Value 0: is the value returned to the new process (child process).
- Value > 0: is the value returned to the creator process (pid of the new process).
If the call fork has not produced any error, a new process will appear that will have
replicated (not shared) the same code and data as the creator process and with the
same program counter value. Therefore, it will begin its execution at the point where
the creator process has created it, that is, just after returning from the call fork.
Therefore, the new process will begin its execution with case 0 of the switch, it will write
a message and it will end (exit):
case 0:
In contrast, the parent, after having created the child, will continue its execution with
case default of the switch, will write the ID of the child returned by the call fork (variable
process), will wait for the end of the child and it will end (exit):
default:
The exit of the parent and child processes can occur in any order since the execution
is concurrent. Either the parent or the child may write first:
or
As we have seen in the previous section, the creation of Unix processes is limited to
creating a new process identical to the parent process. But actually, when a new
process is created we usually want it to do things different from those done by its
creator. For this reason, it is necessary to have a function that allows us to completely
change a certain process.
The system call exec allows us to change the entire code, data and stack of a process
and to load new code and new data stored in an executable file. This call is the one that
really allows us to execute programs after a new process h a s b e e n c r e a t e d and i t
will usually be used immediately after the call fork. Once the call exec has ended,
the code of the process that has invoked it will have completely disappeared and,
therefore, the call will never return, that is, the commands of the program that may exist
after the call will never be executed.
The code shown below creates a new process and, immediately afterwards, the child
process executes (system call execlp) the executable file Is. Therefore, the new
process no longer maintains the same code or data as the creator process, but has
completely transformed its code and data according to the content of the executable
file Is. For this reason, the process will never execute the commands after the call,
except if exec causes an error and, therefore, the change of image cannot be
performed. This mutation only affects the data and the code, but the process remains
the same with the same identifier (pid) and the same user IDs, unless the executable
file had the bit setUID active or the bit setGID, as we have previously explained.
main()
{
int st;
char s[80];
switch (fork())
{
case -1:
case 0:
default:
wait(&st);
exit(0);
}
}
2.4.1 fork
Syntax
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
Description
The call fork has no parameters, and its function is to create a new process. The new
process (child process) is an exact copy of the creator process (parent process). The
child process inherits the entire environment of the parent process. Among all the
inherited attributes the following must be highlighted:
The child process differentiates from the parent process in the following aspects:
Returned value
If the fork is executed correctly, it will return the value 0 to the child process and the
child process ID (PID) to the parent process. In case of error, the call will return the
value - 1 to the parent process, no process will be created and the variable errno will
indicate the error that occurred.
Errors
• EAGAIN: the maximum number of possible processes per user has been
exceeded or the total amount of available system memory is insufficient to
duplicate the process.
• ENOMEM: there is not enough swap space.
2.4.2 exec
The system call exec is offered with different syntaxes that facilitate its use according
to the input parameters: execl, execv, execle, execve, execlp, execvp.
Syntax
#include <unistd.h>
int execl(char *path, char *arg0, ..., char *argn, char * /*NULL*/);
int execle(char *path, char *arg0, ..., char *argn, char * /*NULL*/, char *envp[]);
int execlp(char *file, char *arg0, ..., char *argn, char * /*NULL*/);
Description
Each of the functions of the exec family replaces the image (code and data) of the
process that invokes it with a new image. The new image is constructed from an
executable file that is passed as a parameter. No value is returned if the call is executed
correctly, since the image of the process that invokes it is overwritten by the new
image.
The open channels (file descriptors) of the process that invokes the call remain open
after the image change, except those that have the flag close-on-exec active.
The signals defined in the process that invokes the call with default action or that must
be ignored, remain the same after the image change. The signals programmed with
some function in the process that invokes the call, change its programming to the
default action after the image change, since the code of the functions with which they
had been programmed disappears after the call exec.
If the bit set-user-ID is active, the effective user ID of the process with the new image
will take as a value the ID of the owner of the image file. Equally, if the bit set-group-ID
is active, the effective group ID of the process with the new image will take as a value
the group ID of the image file. Real user and group IDs will be maintained.
The shared memory segments of the process that invokes the call will be unassigned
from the new image.
The new image of the process will keep, among others, the following attributes:
Parameters
• path: points to the full name that identifies the new image file.
• file: is used to build the full name that identifies the new image file. If the
parameter file contains a slash ('/'), then it is used as the full name of the new
image file. Otherwise, the full path of the file name is obtained by searching
the directories included in the environment variable PATH.
• The parameters represented by arg()... are pointers to character strings. These
parameters are the list of arguments that will be passed to the new image. The
list ends with a null pointer. The parameter arg0 will indicate the name that will
be associated with the process initiated by the function exec.
• argv: is a pointer to a table of character strings. The last entry in the table must
be a null pointer. The items of the table are the list of arguments that will be
passed to the new image. The value of the entry argv[0] will indicate the name
that will be associated with the process initiated by the function exec.
• envp: is a pointer to a table of character strings. The last entry in the table must
be a null pointer. The values of this parameter constitute the environment for the
new image.
Returned value
The call will return the value -1 in the event of an error in its execution and the
variable errno will indicate the error that occurred. No value is returned if the call is
executed correctly, since the image of the process that invokes it is overwritten by
the new image.
Errors
2.4.3 exit
Syntax
#include <stdlib.h>
Description
The function of the call exit is to terminate the process that invokes it with the following
consequences:
Parameters
The parameter status stores, in its 8 less significant bits (bits 0377), the value that the child
process will pass to its parent when the parent process executes the call wait.
Returned value
The call exit never returns to the process that invokes it.
Errors
2.4.4 wait
Syntax
#include <sys/types.h>
#include <sys/wait.h>
Description
The call wait blocks the process that invokes it until the information on the end status
of any of its children is available, or until a signal that has a programmed function or
that causes the process to end arrives. If the information on the end status of any of its
children is available before invoking the call, wait will return immediately.
If wait ends because the end status of a child is available, then it will return the ID of
the child which has ended.
If a process ends without having waited (wait) for the end of its children, the identifier
of the parent process of all its children will take the value 1. That is, its child processes
are inherited by the initialisation process (init) which becomes its parent.
Parameters
The parameter stat_loc is a pointer where the status of the child process is stored.
If the call has returned because the end status of any child was available, then the
status of the ended child process will be stored at the address pointed at by stat_loc as
follows:
• If the child process ends by the execution of the call exit, the eight less
significant bits of the variable pointed at by stat_loc will have a value of 0
and the eight most significant bits will contain the value of the eight less
significant bits of the argument passed to the call exit.
• If the child process ends due to a signal, the eight most significant bits of the
variable pointed at by stat_loc will be cleared and the eight less significant
bits will contain the number of the signal that caused the process to end.
Returned value
If wait returns due to the end of a child process, then the call returns the ID of the
process that has ended. Otherwise, the value -1 will be returned and the variable
errno will indicate the error that occurred.
Errors
2.4.5 getpid
Syntax
#include <unistd.h>
pid_t getpid(void);
Description
The call getpid has no parameters and returns the ID of the process that invokes it.
Returned value
The call will return the ID of the process that invokes it. There will be no case that causes
an error.
Errors
2.4.6 getppid
Syntax
#include <unistd.h>
pid_t getppid(void);
Description
The call getppid has no parameters and returns the ID of the parent process that
invokes it.
Returned value
The call will return the ID of the parent process that invokes it. There will be no case
that causes an error.
Errors
2.4.7 getpgrp
Syntax
#include <unistd.h>
pid_t getpgrp(void);
Description
The call getpgrp has no parameters and returns the process group ID of the process that
invokes it.
Returned value
The call will return the process group ID of the process that invokes it. There will be
no case that causes an error.
Errors
2.4.8 setpgid
Syntax
#include <sys/types.h>
#include <unistd.h>
Description
The call setpgid assigns the value pgid to the process group ID of the process with ID pid.
If pgid equals pid, the process with ID pid will become the leader of the group. Otherwise,
the process with ID pid identifier will become a member of an existing group.
If pid equals 0, the process ID of the process that made the call is used as pid.
If pgid equals 0, the process with ID pid will become the leader of the process group.
Parameters
• pid: is the ID of the process to which we want to change its process group ID.
• pgid: is the value of the process group ID we want to assign to the process.
Returned value
If the call is executed correctly, it will return the value 0. Otherwise, it will return the
value -1 and the variable errno will indicate the error that occurred.
Errors
• EACCES: the pid corresponds to the identifier of a child in the process that
invokes the call and that has executed a system call exec.
• EINVAL: the parameter pgid has a negative value or greater than the maximum
allowed.
• ESRCH: the parameter pid does not match the process ID that invokes the call
nor the ID of any of its children.
This example shows the creation of a new process (fork) and the synchronisation of the
parent process with the end of the child process (exit and wait). The new process will
be executed concurrently with the parent process.
The child process, meanwhile, will perform the following actions concurrently with the
parent process:
The writing b) of the parent and the writing a) and b) of the child can appear in any order
depending on the concurrent execution of both processes:
or
or
In contrast, the writing d) of the parent will always appear at the end since the parent,
before writing, waits for the end of the child. In the last writing, the parent will return the
ID of the child which has died, and which has been returned by the system call wait.
Therefore, the last writing will be:
#include <errno.h>
main()
{
int st, pid;
char s[80];
switch (fork())
{
case -1:
error("Fork");
case 0:
default:
2.5.2 Example 2
This example shows the creation of a new process (fork), the change of image (exec)
and the synchronisation of the parent with the end of the child (exit and wait). The
child process executes (exec) the command ls –l /usr/bin and ends (exit). The
parent process waits for the end of its child (wait) and ends (exit).
#include <errno.h>
main()
{
int st;
switch (fork())
{
case -1:
error("Fork");
case 0:
error("Executing ls");
default:
/* Parent process */
/* Waits for the child to end */
wait(&st);
exit(0);
}
}
3 Input/output
This chapter will describe the general structure of the file system, the different file types,
the independence of devices and the redirection of the input/output, as well as the main
calls for managing inputs and outputs.
Unix's input/output and its file system are closely related either by the set of uniform
and identical system calls to access any device or file, as well as by the existence of
several file types that include the devices themselves. Every Unix device is recognised
on the system as a file of type device, which can be used with the same system calls
(open, close, read, write, ...) used for regular files that contain user information.
File types
The Unix file system includes several file types that represent both conventional files,
which contain user information, as well as input/output devices and other system
resources. The most prominent file types in Unix are the:
- Directory: is a file that contains references to other files and is the fundamental
element of the Unix file hierarchy. Within directory files there are entries that
match the number of an inode (the item that describes each file) with the name
(link) that is given to the file in that directory.
- Regular file: is a file that contains general information without any specific
structure.
- Device: is a special file that represents each of the devices in the system. There
are two types of devices: character devices (for example terminals) and block
devices (for example disks).
- Soft link: is a file that contains the name of another file that it represents, so that
accessing the soft link is like accessing the file it links to.
- Named pipes: are devices for communicating between processes.
A feature of the Unix file system is the possibility that files may have more than one
name (hard links). There may even be files that do not have a name and are accessible
and usable by the processes that have opened them. Thanks to this feature, a file
can be made visible and accessible from various points in the file system. And this is
possible due to the separation existing between the information of the file ( inode) and
its name.
Inodes are structures that contain all the information of a file except its name, or names
of the file, and its data, which are stored in the data blocks of the file system. Inside the
inode we can find, among other, the following information:
Inodes are numbered from 1 to the maximum number of inodes of a given file system.
The file name is stored inside the directories. Each entry in a directory is an association
between an inode number and a name. In this way, we can have as many names as
we want of each file. We just need to associate the same inode number (which
represents the file) with different names within the desired directories.
Entry of a directory
The number of names (hard links) of a file will be stored in the corresponding inode and
it will allow the operating system to detect when a file no longer has a name, and
therefore when a file can be removed from the system. However, the fact that a file
does not have a name is not a sufficient condition for its inode and the information it
contains to disappear. To be able to completely delete a file we need, in addition to
having no name, that there is no process that is using it (file open). Therefore, in Unix
it is possible that a process has an open file, that it deletes its last name and that it
continues working with it without any other process being able to access it, since it does
not have any visible name in the file system. This way of working is common among
programs that use temporal files during execution. In this case, the file disappears when
it is closed by the last process that had it open (see example 2 in this chapter).
Unix uses the word link to refer to the names of a file and distinguishes between hard
links (which are the ones that have just been explained) that are accounted for within
the inode of each file and soft links (symbolic links). Soft links are a special type
of file used to access another file from anywhere in the file system. Soft links contain
the name of the file they point to and have a set of characteristics that differentiate them
from hard links:
1. Soft links are files, on the contrary, hard links are nothing more than an entry
in a directory that associates a name with an inode number.
2. Soft links are not known by the file they represent. Unlike hard links, which
are counted within the inode of each file, soft links are created and destroyed
without the file which was pointed at knowing it. For this reason, it is possible
that there may be soft links that point to non-existent files, because the files
pointed at might have disappeared from the system after the creation of the
soft link.
3. Soft links can be placed in a file system different from the one of the file they
represent. On the contrary, hard links (couples “inode - name” within the
directories) must all be located within the same file system, to avoid
ambiguities with the inode numbers matching between different file systems.
Unix's virtual devices (or channels) called file descriptors are grouped into a separate table
for each process. Each virtual device is identified with a value in the table, so that the
first virtual device will be the channel 0 and so on. Read and write Unix system calls use
exclusively virtual devices and, in this way, they are independent of the real devices or files
that are associated with each file descriptor. To associate virtual devices with a given
device or file, the call open is used. To release a certain device, the system call used is
close. Therefore, before we can use a certain device or file, we will need to open it in order
to associate the virtual device with the file or real device. Once we have associated the
virtual device, we will be able to write or read on this virtual device. Once we have finished
using the real device or file, we will be able to release it together with the virtual channel
using the call close.
The following page shows the code for a process that opens an existing file and reads
all its contents character by character. After the reading process is complete, the
channel is closed and the resources are released. It is necessary to note that the call
open returns the value of the first free virtual device in the p r o c e s s channel t a bl e
which, in this case, will be channel 3 as explained below.
In the example on the next page, you can see that the function error writes the error
messages through the standard error output channel (virtual device 2 - stderr). Instead,
the contents of the file are written character by character through the standard output
channel (virtual device 1 - stdout).
All processes executed from the Unix command-line interpreter have the channels 0, 1
and 2 open, since new processes inherit from its parents a channel table with the same
channels open as the creator process. The command-line interpreter has the standard
channels open and, therefore, all its children will also have them open and associated
with the same devices at the time of its creation. Assuming this fact, it could be affirmed
that the call open in the example would return the channel 3 (the first free entry in the
process channel table) if the command-line interpreter is the one that creates that
process.
write(2, m, strlen(m));
write(2, "\n", 1);
write(2, strerror(errno), strlen(strerror(errno));
exit(1);
}
main()
{
int fd, n;
char c;
if (n<0)
error("Read file");
close (fd);
}
Let us assume that the previous example is stored in an executable file named
testprogram and we want to execute it with the standard output redirected to another
file called output.dat. From the command-line interpreter, the following line should be
invoked:
The symbol > represents the redirection of the standard output of the process to the desired
file. To make this redirection from the program, it could be made with the following code:
main()
{
int st;
char s[80];
switch (fork())
{
case -1:
error("Fork error");
case 0:
close (1);
error("Executing testprogram");
default:
wait(&st);
exit(0);
}
}
In the previous program, to redirect the standard output of the new process, the following is
done:
1. The standard output channel (file descriptor 1) is closed so that it is free and,
therefore, it can be assigned at the time of making a call open.
2. With the call open the file where we want to redirect the standard output is
created. This call will search for the first free channel (virtual device) of the
channel table of the process. Channel 1 has been previously closed and,
therefore, it will be the first free channel. From that moment on, the standard
output channel will be associated with the file output.dat and everything written
through the standard output of the process (file descriptor 1), will be written on
this file.
3. Once the standard output of the new process is redirected, the system call exec
is invoked with the file testprogram as a parameter and the process will change
its code to the code defined in the file. The call exec generates no modifications
in the channel table of the process and, therefore, the process will read the entire
file Datafile.dat and will write it character by character through its standard
output that, in this case, is redirected to the file output.dat.
The previous section explained how to redirect the input/output of a process thanks to
the existence of virtual devices and a set of homogeneous calls. It has also been seen
that the table of channels or virtual devices is inherited between parents and children,
so that the channels opened in the parent process are also open and pointing to the
same devices in the child process.
Each channel is associated with a device or file through an entry in the so-called open
file table. This table is global for the whole system and, therefore, is the s a m e f o r
all processes. This table of open files stores, among other things, the read/write pointer
of open files.
The read/write pointer indicates where the next reading or writing will be done on the
corresponding file. At the time of opening a file, the read/write pointer is placed, by
default, at the beginning of the file. As we read or write to a file, the read/write pointer
automatically moves forward as many positions as bytes have been read or wr itten.
The read/write pointer can also be modified with a system call specific for this purpose
(lseek).
Each time a file is opened or created, in addition to occupying an entry in the channel s
table (file descriptors) of the corresponding process, a new entry of the open file table
that is associated with the open channel is also opened. Inside the open file table entry,
the read/write pointer placed at the beginning of the open file will be saved, unless
another option is indicated.
In process creation, the new process not only inherits the contents of the channel table
from the parent process, but its open channels also point to the same entries in the
open file table as the parent. Therefore, parent and child will share the same read/write
pointer of the open files that the child inherited at the time of its creation.
In the following examples, 4 and 5, we can see two programs that show the difference
between sharing an already open file by a parent and child process (example 4), or
opening twice and independently the same file by a parent and child (example 5).
In example 4 (figure 2), the parent process opens a file before the child is created
(channel source). Therefore, when the new process is created, both processes share
the same read/write pointer.
Figure 2
Parent process Child process
open open
... channel table channel table ...
fork fork
source source
read/write pointer
In the figure 2 we can see the status of the channel table of both processes and the
status of the open file table after the creation of the child process. The child process
inherits the same channel table as the parent and each channel inherited points to the
same shared entry in the open file table. For this reason, read/write pointers for
inherited files are shared by parent and child.
In example 5 (figure 3), the parent process opens a file (channel source) after having
created the child process. The child process also opens the same file. The result is that
both processes access the same file with independent read/write pointers:
Figure 3
Parent process Child process
read/write pointer
(child)
read/write pointer
(parent)
In the figure 3 we can see the status of the channel table of both processes and the
status of the open file table, after the creation of the child process and after both
processes have opened the same file independently. In this case, the parent and child
process have an independent entry in the open file table and therefore they do not share
the read/write pointer in the access to the file.
This section presents the following calls related to input/output and the file system:
3.4.1 creat
Syntax
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
Description
The call creat creates a new regular file or rewrites an existing file.
If the file exists, its length becomes 0 and its owner is not modified. If the file does not
exist, the owner ID of the new file will be the effective user ID of the process that invokes
the call, and the group ID of the file will be the effective group ID of the process.
The values of the bits corresponding to access permissions will be those indicated in
the parameter mode and w i l l b e modified according to the creation mask (umask): a
bitwise AND will be made with the complemented creation mask and, therefore, all bits
with value 1, in the process creation mask, will take the value 0 in the permission mask.
If the call ends successfully, a write-only channel (file descriptor) is returned with the
read/write pointer placed at the beginning of the file and the file remains open for writing,
although the parameter mode does not allow it.
Parameters
Returned value
If the call ends successfully, a non-negative integer value corresponding to the first free
available channel (file descriptor) of the process is returned. Otherwise, the negative
value -1 is returned, no file is created nor modified and the variable errno will indicate
the error that occurred.
Errors
• EACCES: the process does not have permission to search in one of the
directory components of the file name, the file does not exist and the directory
where the new file should be created does not allow writing or the file exists, but
we do not have write permissions.
• EDQUOT: the file cannot be created because the user does not have more
space quota of disk blocks or does not have a higher quota of inodes in the
file system.
• EFAULT: the parameter path points to an illegal address.
• EINTR: a signal has arrived during the execution of the call creat.
• EISDIR: there is a directory with the same name as the file to be created.
• EMFILE: the process has too many files open.
• ENOENT: some directory component of the parameter path does not exist or
the parameter is empty.
• ENOTDIR: some component of the prefix of the parameter path is not a directory.
3.4.2 open
Syntax
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
Description
The call open opens a file by establishing a connection between the file (path) and a
channel (file descriptor) of the process.
A new entry is created in the open file table that represents the open file , and the new
channel (file descriptor) points to this entry.
If the call ends successfully, a channel (file descriptor) with the read/write pointer placed
at the beginning of the file is returned. The access mode o f t h e fi l e is set according
to the value of the parameter oflag. The parameter mode is only used if the parameter
oflag includes the value O_CREAT.
The value of the parameter oflag is constructed with a bitwise OR of all the values
included as parameters.
Parameters
Returned value
If the call ends successfully, a non-negative integer value corresponding to the first free
available channel (file descriptor) from the process is returned. Otherwise, the negative
value -1 is returned, no file is created nor modified and the variable errno will indicate
the error that occurred.
Errors
• EACCES: the process does not have permission to search in one of the
directory components of the file name, or the file exists and the permissions
specified by oflag are not allowed, or the file does not exist and writing to the
directory where it should be created is not allowed, or O_TRUNC has been
specified and we do not have write permissions.
• EDQUOT: the file does not exist, O_CREAT has been specified and cannot be
created because the user does not have more space quota of disk blocs or does
not have a higher quota of inodes in the file system.
• EEXIST: the values O_CREAT and O_EXCL have been included and the file
already exists.
• EFAULT: the parameter path points to an illegal address.
• EINTR: a signal has arrived during the execution of the call open.
• EISDIR: the file is a directory and we are trying to access it with O_WRONLY or
O_RDWR.
• EMFILE: the process has too many files open.
• ENOENT: the value O_CREAT has not been included and the file does not exist,
or the value O_CREAT has been included but some directory component of the
parameter path does not exist or the parameter is empty.
• ENOTDIR: some component of the prefix of the parameter path is not a directory.
3.4.3 close
Syntax
#include <unistd.h>
Description
The call close closes the channel (file descriptor) indicated in the parameter fildes.
Closing the channel means making it available so that it can be subsequently assigned
by other calls such as open.
Once all the channels associated with a pipe are closed, the data that may still be in
the pipe are removed. Once all the channels associated with a file are closed, if the
number of links of the file is zero, the space occupied by the file in the file system is
released and the file will no longer be accessible again.
When all the channels associated with an entry i n the o pe n fi l e t able are closed,
this entry will be released.
Parameters
Returned value
If the call ends correctly, it returns the value 0. Otherwise, it will return the value -1 and
the variable errno will indicate the error that occurred.
Errors
3.4.4 read
Syntax
#include <unistd.h>
Description
The call read reads from a file. The call read tries to read the number of bytes specified
in the parameter nbyte of the file associated with the open channel (file descriptor)
fildes. The bytes read are stored in the buffer pointed at by the parameter buf.
If the parameter nbyte is 0, the call will return the value 0 and it will have no other effect.
In the files where the read/write pointer can be placed (lseek), for example in regular
files, the call read will start reading at the position indicated by the offset of the
read/write pointer associated with the channel fildes. At the end of reading, the offset
of the read/write pointer of the file will increase the number of bytes that have been read.
In the files where we cannot place the read/write pointer (e.g., terminals), the call read
will always read from the current position. In this case, the offset of the read/write pointer
associated with the file will be undefined.
If we try to read from the end-of-file or beyond the end of the file, no reading will occur.
• If there is no process that has the pipe open for writing, the call read
will return the value 0 to indicate end-of-file.
• If any process has the pipe open for writing and the flag O_NDELAY of the pipe
is active (open), the call read will return the value 0.
• If any process has the pipe open for writing and the flag O_NONBLOCK of the
pipe is active (open), the call read will return the value -1 and the value of the
variable errno will be EAGAIN.
• If the flags O_NDELAY and O_NONBLOCK of the pipe are not active (open),
the call read is blocked until any process writes data to it or until all the
processes that have it open for writing close it.
Reading a file associated with a terminal that does not have data available will have the
following effects:
• If the flag O_NDELAY of the terminal is active (open), the call read will
return the value 0.
• If the flag O_NONBLOCK of the terminal is active (open), the call read will
return the value -1 and the value of the variable errno will be EAGAIN.
• If the flags O_NDELAY and O_NONBLOCK of the terminal are not active
(open), the call read will be blocked until some data is available.
The call read reads previously written data. However, if any part of a file previous to the
end-of-file that has never been written is read, read will return bytes with value 0. This
situation can happen when situating (lseek) the read/write pointer beyond the end of
the file and some information is written to it. The space situated in between will return,
when it is read, bytes with value 0 until something is explicitly written in it.
If the call ends successfully and the parameter nbyte is greater than 0, then the number
of bytes read will be returned. This number will never be bigger than the value of the
parameter nbyte, but it can be smaller if there are not enough bytes available to be read
or the call has been interrupted by a signal.
If the call read is interrupted by a signal before it has read anything, the value -1 will be
returned and the variable errno will indicate the error that occurred.
If the call read is interrupted by a signal after having read something, it will return the
number of bytes read.
Parameters
• fildes: open channel associated with the file we want to read from.
• buf: pointer to a memory space where the bytes read will be stored.
• nbyte: number of bytes that we want to read.
Returned value
If the call read ends correctly it will return a non-negative value that will indicate the
number of bytes actually read from the file associated with the channel (file descriptor)
fildes. This number will never be bigger than the value of the parameter nbytes.
Otherwise, the value -1 will be returned and the variable errno will indicate the error
that occurred.
Errors
• EBADF: the parameter fildes is not an open channel (file descriptor) valid
for reading.
• EFAULT: the parameter buf points to an illegal address.
• EINTR: a signal has arrived during the execution of the call read and there was no
reading.
• EIO: a physical device input/output error occurred.
3.4.5 write
Syntax
#include <unistd.h>
Description
The call write writes to a file. The call write tries to write the number of bytes specified
in the parameter nbyte, which are stored in the buffer pointed at by the parameter buf,
to the file associated with the open channel (file descriptor) fildes.
If the parameter nbyte is 0, the call will return the value 0 and it will have no other effect.
In the files where the read/write pointer can be placed (lseek), for example in regular
files, the call write will start writing at the position indicated by the offset of the read/write
pointer associated with the channel fildes. At the end of writing, the offset of the
read/write pointer of the file will increase the number of bytes that have been written.
In files where s i t u a t i n g read/write pointers is not allowed (e.g., terminals), the call
write will always write to the current position. In this case, the offset of the read/write
pointer associated with the file will be undefined.
If the flag O_APPEND (open) is active, the read/write pointer will be placed at the end
of the file before each writing.
If the number of bytes specified in the parameter nbyte cannot be written, because there
is no space available or some limit indicated by the system has been exceeded, the call
write will only write as many bytes as possible and it will return this number.
If the call write is interrupted by a signal before it has written anything, the value -1 will
be returned and the variable errno will indicate the error that occurred.
If the call write is interrupted by a signal after it has written something, the number of
bytes written will be returned.
The correct execution of the call write on a regular file will have the following
consequences:
• If a read of the positions of the file modified by the call write is made, it will
return the data written by the call write in these positions.
• If we write over previously written positions, the values in the last write will
override the existing values.
Writing to a pipe has the same consequences as writing to regular files except for the
following exceptions:
• There is no read/write pointer associated with the pipe and, therefore, all
the writings are added at the end of the pipe.
• The writing to a pipe is indivisible and, therefore, it guarantees that the data of
any other writing made by other processes to the same pipe, cannot be mixed
with the data written by the call write.
• If the flags O_NDELAY and O_NONBLOCK of the pipe are not active (open),
the call write can block the process (for example if the pipe is full), but at the end
of the writing it will have written all the bytes indicated and, therefore, it will return
the nbyte.
• If the flags O_NDELAY and O_NONBLOCK of the pipe are active (open), the
call write will never block the process. If the call can write the bytes indicated,
it will return nbyte. Otherwise, if the flag O_NONBLOCK is active, it will return
the value -1 and the value of the variable errno will be EAGAIN, or if the flag
O_NDELAY is active, it will return the value 0.
Parameters
• fildes: open channel associated with the file which we want to write to.
• buf: pointer to a memory space where the bytes that we want to write are stored.
• nbyte: number of bytes we want to write.
Returned Value
If the call write ends correctly it will return a non-negative value that will indicate the
number of bytes actually written to the file associated with the channel (file descriptor)
fildes. This number will never be higher than value of the parameter nbytes. Otherwise,
the value -1 will be returned and the variable errno will indicate the error that occurred.
Errors
• EBADF: the parameter fildes is not an open channel (file descriptor) valid
for writing.
• EDQUOT: the disk block quota of the user has been exceeded.
• EFAULT: the parameter buf points to an illegal address.
• EFBIG: there is an attempt to write to a file that exceeds the maximum file
size allowed in the process or that exceeds the maximum file size of the
system.
• EINTR: a signal has arrived during the execution of the call write and there has
been no writing.
• EPIPE: a try to write to a pipe that is no longer open for reading by any
process is made. A signal SIGPIPE will be sent to the process when this
error is generated.
3.4.6 lseek
Syntax
#include <sys/types.h>
#include <unistd.h>
Description
The call lseek places, at a certain position, the read/write pointer of the file specified
by the open channel (file descriptor) fildes. The final position of the pointer will depend
on the value of the parameters offset and whence as follows:
• If the value of whence is SEEK_SET, the pointer is placed at the position of the
value of offset.
• If the value of whence is SEEK_CUR, the pointer is placed in the position
resulting from adding the value offset with the current pointer position.
• If the value of whence is SEEK_END, the pointer is placed in the position
resulting from adding the value offset at the end of the file.
The call lseek allows us to place the read/write pointer beyond the end of the file. If
some writing is performed in this position, the readings made in the positions where
there is no data will return the zero value until something different is written.
Parameters
Returned value
If the call ends successfully, it will return the resulting offset, measured in bytes, of the
read/write pointer with respect to the beginning of the file. Otherwise, the negative value
-1 will be returned, the offset will not be modified and the variable errno will indicate the
error that occurred.
Errors
3.4.7 dup
Syntax
#include <unistd.h>
Description
The call dup returns a new channel (file descriptor) that shares the following
characteristics with the channel indicated in the fildes:
Parameters
Returned value
If the call ends successfully, a non-negative integer value corresponding to the first free
available channel (file descriptor) from the process will be returned. Otherwise, the
negative value -1 will be returned and the variable errno will indicate the error that
occurred.
Errors
3.4.8 unlink
Syntax
#include <unistd.h>
Description
The call unlink removes a name (link) from a file and reduces the name counter (links)
of the referenced file. If the parameter path represents a symbolic link, the call deletes
the symbolic link but does not affect the file or directory pointed at by the link.
If the link counter of a file reaches zero and no process has the file open, then, the
space occupied by the file is freed and the file is no longer accessible. If any process
has the file open when the last name (link) is deleted, then the contents of the file will
not be deleted until all processes have closed the file.
Parameters
path points to the full name of the entry (link) that we want to delete.
Returned value
If the call ends correctly, the value 0 will be returned. Otherwise, the value -1 will be
returned and the variable errno will indicate the error that occurred.
Errors
This example copies, character by character, a source file into another destination file.
File names are passed as parameters of the program. The program opens the source
file (open), creates the destination file (open) and reads (read), character by character,
from the source file and writes (write), character by character, into the destination file.
write(2, m, strlen(m));
write(2, "\n", 1);
write(2, strerror(errno), strlen(strerror(errno));
exit(1);
}
if (argc != 3)
error("Wrong number of arguments");
if (n<0)
error("Reading from the source file");
close (source);
close (dest);
}
3.5.2 Example 2
This example shows the creation of a temporary file that does not appear in the file
system while it is in use. To call it the creat system call is used and immediately
afterwards the file name is deleted (unlink). From that moment on, the file still exists but
without access by any other process. When the process closes the open channel
associated with the file (close), the file physically disappears from the file system.
#include <fcntl.h>
#include <errno.h>
write(2, m, strlen(m));
write(2, "\n", 1);
write(2, strerror(errno), strlen(strerror(errno));
exit(1);
}
main()
{
int fildes;
if (unlink("temp.dat") < 0)
error("Error deleting the name (link) of the file");
/* ... */
close(fildes);
exit(0);
}
3.5.3 Example 3
This example writes the contents of a file backwards, that is, the first character in a file
(source) becomes the last character in another file (dest). It opens the source file
(source) passed as a first parameter (open) and creates the destination file (dest)
passed as a second parameter (open). It reads the source file (read) in reverse order
(lseek) and writes the data read (write) in the destination file.
write(2, m, strlen(m));
write(2, "\n", 1);
write(2, strerror(errno), strlen(strerror(errno));
exit(1);
}
if (argc != 3)
error("Insufficient arguments");
if ((source = open(argv[1], O_RDONLY)) < 0)
error("Open the source file");
if ((dest = open(argv[2], O_WRONLY|O_TRUNC|O_CREAT, 0600)) < 0)
error("Create the destination file");
close(source);
close(dest);
}
3.5.4 Example 4
This example shows the sharing of the read/write pointer between a parent process
and its child process that has inherited the channel table (file descriptors) from the
parent. The parent process opens the file (open) before creating the child process,
creates (fork) the child process that inherits the channel table and, therefore, shares
the read/write pointer associated with the channel of the file. Both processes read from
the file and write through the standard output until the end of the file is reached. The
output will show the file only once thanks to the sharing of the read/write pointer.
write(2, m, strlen(m));
write(2, "\n", 1);
write(2, strerror(errno), strlen(strerror(errno)));
exit(1);
}
switch (fork())
{
case -1: /* In case of error the process ends */
error("Fork");
3.5.5 Example 5
If we take the same code as in example 4 but we move the opening (open) of the file
and we position it after the creation (fork) of the child process, then both the parent
process and the child process will write the complete file and, therefore, the content will
appear twice. In this program, the child has not inherited the open channel of the file
and, therefore, each process creates its own channel with its own entry in the file table
and with independent read/write pointers for each process.
write(2, m, strlen(m));
write(2, "\n", 1);
write(2, strerror(errno), strlen(strerror(errno)));
exit(1);
}
switch (fork())
{
case -1: /* In case of error the process ends */
error("Fork");
4.1 Signals
Signals are asynchronous signals that can be sent to the processes for the following
reasons:
Each process can program its response to each of the signals. The action to be taken
upon receiving a signal will depend on this programming and the following actions can
be taken:
- Default action (SIG_DFL): in most signals the default action will be the
termination of the process that receives the signal. This action is taken if the
process has not inherited any other programming, nor it has programmed the
signal.
- Ignoring the signal (SIG_IGN): in this case the signal will not reach the process
and, therefore, no action will be taken.
- Programming a service routine of the signal (signal handler): at the time of
receiving the signal the execution of the process will be interrupted, and the
service routine programmed will be executed. Once the execution of the service
routine is finished, the process will be returned to the point where it had been
interrupted. This way of acting is similar to the handling of interruptions. Some
Unix versions reprogram the signals to the default action once a signal is served.
For this reason, it is advisable to reprogram the signal within the service routine.
Some signals cannot be reprogramed, and upon receipt, the default action is always
taken: for example, the signal SIGKILL (always kills the process) and the signal
SIGSTOP (always stops the process). An exception is the signal SIGCHLD which is
ignored by default.
The programming of the signals is inherited (after a fork) from parents to children. If a
process makes a change of image (system call exec) only the signals programmed to
be ignored (SIG_IGN) or to take the default action (SIG_DFL) will be maintained.
In contrast, the signals that have been programmed with a service routine (signal
handler) will pass to the default programming, since with the new image the
programmed service routine will disappear. The programming of the signals is done
with the system call of the same name (signal).
Below, the execution of a process that receives a signal programmed (with the system
call signal) with a service routine is shown. At the time the signal arrives, it interrupts
the execution of the code of the process and the service routine is executed. Once the
service routine ends, the execution of the process returns to the point where it had
previously been interrupted:
Figure 4
A signal arrives
All signals from 1 to 15 cause, by default, the termination of the process. It should be
noted that the signal SIGKILL cannot be programmed nor ignored and it is used to
permanently kill processes.
Signals from 17 to 22 will be offered only if the job control is defined in the version of
the system. By default, SIGCHLD is ignored, on the contrary, SICONT causes the
continuation of a stopped process and the rest (SIGSTOP, SIGTSTP, SIGTTIN,
SIGTTOU) cause the process that receives them to stop.
4.2 Pipes
Pipes are logical devices that allow for the communication of processes with a kinship
relationship between them, that is, between processes that share this device by
inheritance. Pipes are one-way communication channels and, when created (system
call pipe), return two file descriptors inside the channel table of the process, one for
reading and one for writing:
int p[2];
...
...
In the previous code, the process creates a pipe which is accessible through channels
(file descriptors) p[0] and p[1]. The channel p[0] is for reading and the channel p[1] is
for writing.
To be able to use the pipe to communicate with another process, both processes must
share it in their respective channel tables. For this reason, it is necessary that the
process that created the pipe creates a new process that inherits the channels of the
pipe, so that it can communicate with the creator process.
The figure 5 shows two processes (parent and child) that communicate through a pipe.
The parent process creates the pipe (system call pipe) and right away it creates the
child process (fork).
After the creation of the pipe, two new entries appear in the parent process channel table,
p[0] and p[1], which correspond to the read and write channels of the pipe. For now, however,
the pipe can only be used by the process that created it, since the pipe only exists in its channel
table. After the creation of the new process, the child process has a channel table identical
to that of the parent and, therefore, shares both access channels to the pipe. From that moment
on, parent and child will be able to communicate through the same pipe. In the example
presented, the child writes a message to the pipe through its write channel in pipe p[1]. This
message is read by the parent through its read channel of pipe p[0].
In order to be able to take advantage of all the characteristics of the pipes, as explained below,
processes should close the channels they do not use before starting the communication. In the
example presented, the parent process will only use the read channel of pipe (p[0]) and,
therefore, it will close its write channel (p[1]). On its part, the child process will only use the write
channel of pipe (p[1]) and, therefore, it will close its read channel (p[0]).
Figure 5
fork() fork()
p[0] pipe p[1] Starting point of the child process
read(p[0], ...) write(p[1], ...)
p[0] p[0]
p[1] p[1]
The code presented below shows the actions that both parent and child will do in order to
communicate through a pipe according to the figure 5.
#include <erno.h>
void error(char*m)
write(2, m, strlen(m));
write(2, "\n", 1);
write(2, strerror(errno), strlen(strerror(errno));
exit(1);
}
main()
{
int p[2];
int st;
char buff[20];
switch (fork())
{
case -1: error("Fork 1");
close(p[0]);
write(p[1],"Hello\n", 5);
close(p[1]);
exit(0);
close(p[1]);
wait(&st);
exit(0);
}
Reading a pipe causes the blocking of the process that attempts to read until the pipe
has the total bytes to be read. Likewise, the writing of a pipe writes the message and
returns immediately unless the pipe is full. In this case, the process that is trying to write
will be blocked until there is enough space in the pipe to write the entire message. This
is the usual behaviour of pipes in reading and writing, as long as there is a process that
has some read channel and some write channel open on the pipe. Otherwise, the
behavior is quite different:
- If there is no process that has a write channel open from a pipe, that is, if it is no
longer possible for anyone to write anything in the pipe, then the reading of the
pipe will never block the process and will immediately return with the number of
r e a d bytes available in the pipe. And if the pipe is empty the reading will
return 0 bytes read.
- If there is no process that has a read channel open from a pipe, that is, if it is no
longer possible for anyone to read anything from the pipe, then an attempt to
write on the pipe will cause the process to receive a signal SIGPIPE and that
nothing is written in the pipe. The signal SIGPIPE will notify, to the process that
is trying to write, that it is no longer possible for any process to read from the
pipe.
This special behavior of the pipes that do not have both channels (read and write
channel) allows for a detailed control of the end of the communication between the
processes involved. For this reason, it is very important to always close all the channels
that are not used in the pipes. Otherwise, the end of any of the processes, which are
communicated with the pipe, would not be detected by the other process and would
cause a permanent blockage of the process, either in the attempt to read from an empty
pipe or in the attempt to write to a full pipe.
This section presents the following calls related to the communication and synchronisation
between processes:
• pipe: creates a communication pipe and returns the read and write
channels (file descriptors).
• signal: programs the action that will be taken upon arrival a determined signal.
• kill: sends a signal to a process or process group.
• alarm: programs the process clock to send a signal SIGALRM after a
certain amount of time.
• pause: blocks the process until a signal arrives.
4.3.1 pipe
Syntax
#include <unistd.h>
Description
The call pipe creates a mechanism for communicating between processes called pipe
and returns two open channels (file descriptors) corresponding to the read channel on
the pipe (fildes[0]) and to the write channel on the pipe (fildes[1]).
Reading the pipe through the channel fildes[0]] accesses the written data in the pipe
through the channel fildes[1] following a FIFO (first-in-first-out) structure.
Parameters
fildes is a table where the call will return both access channels (file descriptors) to the pipe.
Returned value
If the call ends correctly, it will return the value 0. Otherwise, the value -1 will be
returned, and the variable errno will indicate the error that occurred.
Errors
• EMFILE: the number of open channels exceeds the maximum allowed in the
process.
• ENFILE: there is no space for a new entry in the file table.
4.3.2 signal
Syntax
#include <signal.h>
Description
This call allows to program the action that the process will perform when it receives a
signal. Signals can be programmed in three different ways:
• SIG_DFL: the default action defined for the signal specified in the parameter
sig will be performed.
• SIG_IGN: the reception of the signal specified in the parameter sig will be ignored.
• The address of the service routine (signal handler): every time a signal
of the type defined in the parameter sig arrives, the specified service routine will be
executed.
The system guarantees that if more than one signal of the same type is sent to a
process, the process will receive at least one of these signals. However, the reception
of each of the signals sent is not guaranteed.
Parameters
• sig: indicates the signal that we want to program and its value cannot be
SIGKILL nor SIGSTOP.
• disp: indicates the programming of the signal which can be SIG_DFL,
SIG_IGN or the address of the service routine of the signal (signal handler).
Returned value
If the call signal ends correctly, it will return the previous programming of the
corresponding signal. Otherwise, it will return SIG_ERR and the variable errno will
indicate the error that occurred.
Errors
• EINTR: a signal has arrived during the execution of the call signal.
• EINVAL: the value of the parameter sig is not a valid value of signal or equals
SIGKILL or SIGSTOP.
4.3.3 kill
Syntax
#include <sys/types.h>
#include <signal.h>
Description
The call kill sends a signal to a process or process group. The process or process group
is specified in the parameter pid and the signal sent is specified in the parameter sig. If
the value of the parameter sig is 0 (null signal), then the call kill verifies the validity of
the ID indicated in the pid but no signal is sent.
The real or effective user ID of the process that sends the signal must match the real
ID of the process that receives the signal, except if the process that sends the signal
belongs to the super-user.
If the parameter pid is greater than 0, the signal will be sent to the process that has
this ID.
If the parameter pid is negative and different from -1, the signal will be sent to all
processes that belong to the group with ID equal to the absolute value of pid and for
which we have permission to send a signal.
If the value of the parameter pid is 0, the signal will be sent to all processes that belong
to the same group as the process that sends the signal.
If the v a l u e o f t h e parameter pid is -1 and the effective user of the process that
sends the signal is not super-user, then the signal will be sent to all processes that have
a real user ID equal to the effective user ID of the process that sends the signal.
If the value of the parameter pid is -1 and the effective user of the process that sends the
signal is super-user, then the signal will be sent to all processes
The system guarantees that if more than one signal of the same type is sent to a
process, the process will receive at least one of these signals. However, the reception
of each of the signals sent is not guaranteed.
Parameters
Returned value
If the call ends correctly, the value 0 will be returned. Otherwise, the value -1 will be
returned, no signal will be sent and the variable errno will indicate the error that
occurred.
Errors
• EINVAL: the value of the parameter sig is not a valid value of signal.
• EPERM: if we do not have permissions to send a signal to a certain process.
• ESRCH: no process or process group exists with the ID indicated in the
parameter pid.
4.3.4 alarm
Syntax
#include <unistd.h>
Description
The call alarm programs the clock of the process that invokes it so that it sends to the
process itself a signal SIGALRM after a certain number of seconds specified in the
parameter sec.
The requests made with the call alarm by the same process do not accumulate, but a
new call overrides the previous one.
If the value of the parameter sec is 0, any alarm request previously made is cancelled.
The call fork cancels in the child the programming of the alarm that the parent process
may have. On the contrary, the call exec maintains the alarm that was programmed
before its invocation.
Parameters
Returned value
The call alarm returns the time remaining for the next alarm in the process.
Errors
4.3.5 pause
Syntax
#include <unistd.h>
int pause(void);
Description
The call pause suspends the execution of the process that invoked it until the process
receives a signal that has not been programmed to be ignored.
If the signal causes the process to end, then the call pause no longer returns.
Returned value
The call pause always returns error with the value –1 and the variable errno will
indicate the error that occurred.
Errors
• EINTR: a signal has arrived during the execution of the call pause.
This example shows the programming (signal) of an alarm that is sent to the process
every 3 seconds. The process is blocked (pause) and waits for the alarm to arrive.
#include <signal.h>
#define TIME 3
signal(SIGALRM, routine_alarm);
alarm(TIME);
}
main()
{
/* Defines the programming of the signal SIGALRM) */
signal(SIGALRM, routine_alarm);
alarm(TIME);
while(1)
{
/* Blocks the process until the signal arrives */
pause();
}
}
4.4.2 Example 2
This example shows the creation (fork) of two child processes that communicate with
a pipe. The parent process creates the pipe and i m m e d i a t e l y a f t e r w a r d s
creates (fork) the first child process. This first child process inherits the pipe of the
parent, redirects (close and dup) its standard output towards the pipe and closes (close)
the channels that the pipe does not use. Next, it executes (exec) the executable file ls.
The parent creates (fork) the second child process. The second child process inherits
the pipe of the parent, redirects (close and dup) its standard input towards the pipe,
closes (close) the unused channels of the pipe and executes (exec) the executable file
grep. The parent closes (close) the channels of the pipe that it does not use, waits (wait)
for the end of both children and ends (exit). The execution of both children is equivalent
to the command: ls -l /urs/bin | grep cat
#include <errno.h>
write(2, m, strlen(m));
write(2, "\n", 1);
write(2, strerror(errno), strlen(strerror(errno));
exit(1);
}
main()
{
int p[2];
int st1, st2;
switch (fork())
{
case -1: error("Fork 1");
close(1); dup(p[1]);
close(p[0]); close(p[1]);
switch (fork())
{
case -1: error("Fork 2");
close(0); dup(p[0]);
close(p[0]); close(p[1]);
close(p[0]); close(p[1]);
wait(&st1);
wait(&st2);
exit(0);
}
If any of the processes did not close the channels of the pipe that it does not use, the
end of the processes would not be correct. For example, if the parent does not close
both channels of the pipe before waiting for the end of the children, it will cause the
process grep to be indefinitely blocked reading (read) from its standard input once the
process ls has ended. Consequently, the parent process will not end either because it
will be blocked waiting (wait) for the end of its second child.
5 Bibliography
• Unix online manual: command man.