0% found this document useful (0 votes)
1 views67 pages

Introduction-To-Unix-Programming-1

This document serves as an introduction to Unix programming, focusing on key concepts such as process management, input/output, and inter-process communication. It outlines the structure of Unix system calls, their syntax, parameters, and error handling, while providing examples for clarity. The content is organized into chapters that detail the fundamental aspects of Unix programming, aiming to equip readers with the necessary tools to develop applications in the Unix environment.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views67 pages

Introduction-To-Unix-Programming-1

This document serves as an introduction to Unix programming, focusing on key concepts such as process management, input/output, and inter-process communication. It outlines the structure of Unix system calls, their syntax, parameters, and error handling, while providing examples for clarity. The content is organized into chapters that detail the fundamental aspects of Unix programming, aiming to equip readers with the necessary tools to develop applications in the Unix environment.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 67

lOMoARcPSD|33746887

Introduction to UNIX programming-1

Sistemas operativos (Universitat Oberta de Catalunya)

Escanea para abrir en Studocu

Studocu no está patrocinado ni avalado por ningún colegio o universidad.


Descargado por Inanna Ari ([email protected])
lOMoARcPSD|33746887

Introduction to Unix programming

CC BY-SA

First edition: September 2021


© of this edition, Fundació Universitat Oberta de Catalunya (FUOC)
Av. Tibidabo, 39-43, 08035 Barcelona
Authorship: Miquel Nicolau i Vila
Production: FUOC

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

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 2

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

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 3

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

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 4

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.

The presentation of each system call is structured in five sections:

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.

1.1 The Unix manual

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:

Section 1: user commands available from the command-line interpreter (shell).


Section 2: system calls.
Section 3: library functions (library) of the C language.
.S ection 4: devices.
Section 5: file formats.
.S ection 6: games.
Section 7: environments, tables, and macros.
Section 8: system maintenance.

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).

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 5

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.

Some examples of commands:

- $ man man (we will get help on the manual itself).


- $ man ls (we will get help on the command Is).
- $ man 2 write (we will get help on the system call write, section 2 has been
specified - with the parameter 2 - to distinguish it from the shell's command write (1).

1.2 Control of errors

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.

The general programming structure of any system call would be as follows:

#include <errno.h>

int p[2];

...

if (pipe(p) < 0){

/* Writes the error through the standard error channel (channel 2) */

write(2, "Error pipe\n", strlen("Error pipe\n"));


write(2, strerror(errno), strlen(strerror(errno)));
exit(1);
}

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).

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 6

2 The management of processes


This chapter will describe the fundamental characteristics of processes, their attributes,
how to create and terminate them, and also the basic calls for their management.

2.1 The processes

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:

- Process identifier (PID).


- Parent process ID.
- Process group ID.
- User identifier (UID).
- User group identifier (GID).
- Status of the process.

2.1.1 Process identifier (PID)

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.

2.1.2 Parent process ID

Unix processes maintain the hierarchical relationship of the system through the parent
process ID. This ID allows us to know who created the process.

2.1.3 Process group ID

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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 7

2.1.4 User identifier (UID)

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.

Every process will have two user IDs:

- the real user ID.


- the effective user ID.

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.

2.1.5 User group identifier (GID)

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.

Every process will have two identifiers of the user group:

- the real group ID.


- the effective group ID.

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).

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 8

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.

2.1.6 State of the process

As in any operating system, processes can be in different states: executing, blocked,


ready, zombie, etc.

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.

It is important to consider this characteristic of the processes and avoid, whenever


possible, that there is no process left in the zombie state since it would uselessly take
system resources. For this reason, the processes will expect with the system call wait
the termination of its children and, in this way, will make the processes that have already
been completed disappear from the system.

2.2 The creation of new processes

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

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 9

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

switch (process = fork())


{
case -1:

/* In case of error the process ends */

sprintf(s, "Fork error\n");


write(2, s, strlen(s));
exit(1);

case 0:

/* Child process - Write and end */

sprintf(s, "Hello, I am the child\n");


write(1, s, strlen(s));
exit(0);

default:

/* Parent process - Wait for child end and write */

sprintf(s, "I am the parent with child: %d\n", process);


write(1, s, strlen(s));
pid = wait(&st);
exit(0);
}
}

The evolution of both processes is shown in the following diagram (figure 1):

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 10

Figure 1

Start

0 – child process > 0 – parent process


fork

Child code Parent code


I am the child I am the parent with child: ...

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:

/* Child process - Write and end */

sprintf(s, "Hello, I am the child\n");


write(1, s, strlen(s));
exit(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:

/* Parent process - Wait for child end and write */

sprintf(s, "I am the parent with child: %d\n",process);


write(1, s, strlen(s));
pid = wait(&st);
exit(0);

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:

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 11

I am the parent with child: ...


Hello, I am the child

or

Hello, I am the child


I am the parent with child: ...

It is important to emphasise the importance of synchronising with the disappearance


of children (wait). As mentioned above, if the creator process does not wait for the end
of its children, the processes that terminate cannot free all the resources and remain in
a zombie state. If in the example above the creator process had ended without
executing the system call wait, the child process would have been in a zombie state
until the creator process had terminated and the process init would have adopted the
new process and released its resources.

Other important aspects of inheritance between processes related to input/output and


communication between processes will be discussed in the next sections.

2.3 The transformation of a process (exec)

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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 12

main()
{
int st;
char s[80];

switch (fork())
{
case -1:

/* In case of error the process ends */

sprintf(s, "Fork error\n");


write(2, s, strlen(s));
exit(1);

case 0:

/* Child process - Will execute ls */


/* Loads the code of ls */

execlp("ls", "ls", (char *)0);

/* If here, there has been an error and it ends */

sprintf(s, "Exec error\n");


write(2, s, strlen(s));
exit(1);

default:

/* Parent process - We wait for child end and end */

wait(&st);
exit(0);
}
}

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 13

2.4 Calls to the processes management system

This section presents the following calls related to process management:

• fork: creates a new process.


• exec: replaces the image of the process with a new image.
• exit: ends a process.
• wait: waits for the end of a child process.
• getpid: gets the process ID.
• getppid: gets the parent process ID.
• getpgrp: gets the process group ID of the process.
• setpgid: changes the process group ID of a process.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 14

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:

• User and group IDs, both real and effective.


• Open channels (file descriptors).
• Programming of the signals.
• Shared memory segments.
• Bit of set-user-ID.
• Bit of set-group-ID.
• Process group ID.
• Current working directory.
• Root directory.
• File creation mask (umask).

The child process differentiates from the parent process in the following aspects:

• The child's process ID is different from the parent ID.


• The child process has a different parent process ID (it is the process ID of the
process that created it).
• The child process has its own copy of the channels (file descriptors) of the
parent. Each channel of the child shares with the parent's channel the same
pointer to the file (read/write pointer).
• The set of pending signals is empty.
• No asynchronous input/output operation is inherited.

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

The call fork will fail if:

• 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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 15

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 execv(char *path, char *argv[]);

int execle(char *path, char *arg0, ..., char *argn, char * /*NULL*/, char *envp[]);

int execve(char *path, char *argv[], char * envp[]);

int execlp(char *file, char *arg0, ..., char *argn, char * /*NULL*/);

int execvp(char *file, char *argv[]);

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:

• The process ID (PID).


• The parent process ID.
• The process group ID.
• The real user ID and real group ID.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 16

• The current working directory.


• The root directory.
• The file creation mask (umask).
• The pending signals.

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

The call exec will fail, among other reasons, if:

• EACCES: we do not have permission to search in one of the directories that


appear in the full name of the image file, or the file with the new image is not a
regular file, or the file with the new image cannot be executed.
• EAGAIN: the amount of system memory available at the time of reading the
image file is insufficient.
• EFAULT: any of the parameters point to an illegal address.
• EINTR: a signal has arrived during the execution of the call exec.
• ELOOP: too many symbolic links have been found during the translation of the
parameter path or file.
• ENAMETOOLONG: the length of the parameter path or file exceeds the allowed
size (PATH_MAX).
• ENOENT: any component of the parameter path or file does not exist or is empty.
• ENOMEM: the new image of the process requires more memory than is allowed.
• ENOTDIR: any component of the prefix of the parameter path or file is not a
directory.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 17

2.4.3 exit

Syntax

#include <stdlib.h>

void exit(int status);

Description

The function of the call exit is to terminate the process that invokes it with the following
consequences:

• All the channels (file descriptors) of the process are closed.


• If the parent process is executing the call wait, it is notified of the termination
of the child and the value of the eight less significant bits of the parameter
status are passed to it. If the parent process is not executing the call wait,
the status of the child (value of the parameter status) will be passed to the
parent when it executes the call wait.
• If the parent process is not executing the call wait when the process invokes
the call exit, then, the process that invokes exit turns into a zombie process.
A zombie process is an inactive process that only occupies one entry of the
process table, and it will be completely deleted when its parent process
executes the call wait.
• A signal SIGCHLD is sent to its parent.
• All the memory assigned to the process is released.
• Shared memory segments are unassigned.

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

There are no errors defined.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 18

2.4.4 wait

Syntax

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

pid_t wait(int *stat_loc);

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

The call wait will fail if:

• ECHILD: the process invoking the call has no child alive.


• EINTR: the call has been interrupted by a signal.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 19

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

There are no errors defined.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 20

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

There are no errors defined.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 21

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

There are no errors defined.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 22

2.4.8 setpgid

Syntax

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

int setpgid(pid_t pid, pid_t pgid);

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

The call setpgid will fail, among other reasons, if:

• 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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 23

2.5 Examples of process management calls


2.5.1 Example 1

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 parent process will perform the following actions:

a) Creating the child process (fork).


b) Writing its process ID (write and getpid).
c) Waiting for the end of the child process (wait).
d) Writing the ID of the child which has died (write).
e) Ending (exit).

The child process, meanwhile, will perform the following actions concurrently with the
parent process:

a) Writing its process ID (write and getpid).


b) Writing its parent's process ID (write and Getppid).
c) Ending (exit).

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:

Hello, I am the child: ... {output a) of the child}


My parent is: ... {output b) of the child}
Hello, I am the parent: ... {output b) of the parent}

or

Hello, I am the parent: ... {output b) of the parent}


Hello, I am the child: ... {output a) of the child}
My parent is: ... {output b) of the child}

or

Hello, I am the child: ... {output a) of the child}


Hello, I am the parent: ... {output b) of the parent}
My parent is: ... {output b) of the child}

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:

The finalised child is: ... {output d) of the parent}

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 24

#include <errno.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 st, pid;
char s[80];

switch (fork())
{
case -1:

/* In case of error the process ends */

error("Fork");

case 0:

/* Child process - Write and end */

sprintf(s, "Hello, I am the child: %d\n", getpid());


write(1, s, strlen(s));
sprintf(s, "My parent is: %d\n", getppid());
write(1, s, strlen(s));
exit(0);

default:

/* Parent process – Write and wait for child end */

sprintf(s), "Hello, I am the parent: %d\n",


getpid()); write(1, s, strlen(s));
pid = wait(&st);

/* Write and end */

sprintf(s), "The finalised child is: %d\n", pid);


write(1, s, strlen(s));
exit(0);
}
}

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 25

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>

void error(char *m)


{
write(2, m, strlen(m));
write(2, "\n", 1);
write(2, strerror(errno), strlen(strerror(errno)));
exit(1);
}

main()
{
int st;

switch (fork())
{
case -1:

/* In case of error the process ends */

error("Fork");

case 0:

/* Child process - Will execute ls */


/* Loads the code from ls */
/* Receives as parameters -l and /usr/bin */

execlp("ls", "ls", "-l", "/usr/bin", (char *)0);

/* If here, execlp has failed */

error("Executing ls");

default:

/* Parent process */
/* Waits for the child to end */

wait(&st);
exit(0);
}
}

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 26

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.

3.1 Input/output and the file system

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.

File names (hard links and soft links)

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:

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 27

- File owner ID.


- File user group ID.
- File size.
- Creation date, date of the last access, and date of the last modification.
- File type.
- Access permissions for the user, group, and other users.
- Number of names (hard links) of the file.
- Pointers to data blocks.

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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 28

3.2 The independence of devices and the input/output


redirection

To guarantee t h a t a p p l i c a t i o n s a r e device independent it is necessary to have


a set of homogeneous input/output calls and virtual devices (channels) that can be
associated with any real or file device. Unix offers a set of identical calls to handle
input/output regardless of the device or file being used. These calls use virtual devices
that can be associated with any real device or file.

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.

By convention, Unix considers that channels 0, 1 and 2 will be used as standard


input/output channels:

- 0: standard input (stdin).


- 1: standard output (stdout).
- 2: standard error output (stderr).

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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 29

#include <fcntl.h> /* definitions O_RDONLY, O_WRONLY, ... */


#include <errno.h>

void error(char *m)


{

/* Writes the errors through the standard error channel (channel 2) */

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 ((fd = open("Datafile.dat", O_RDONLY)) < 0)


error("File open");

/* Reads from the file while there is information available */


/* and writes the content through the standard output */

while ((n=read(fd, &c, 1)) > 0)


write(1, &c, 1);

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:

$ testprogram > output.dat

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:

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 30

#include <fcntl.h> /* definicions O_RDONLY, O_WRONLY, ... */


#include <errno.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 st;
char s[80];

switch (fork())
{
case -1:

/* In case of error the process ends */

error("Fork error");

case 0:

/* The standard output channel is closed */

close (1);

/* Creation of the file to be assigned to the device */


/* virtual 1 (stdout) which is the first free one */

if (open("output.dat," O_WRONLY|O_CREAT, 0600) < 0)


error("File open");

/* From this moment on, the stdout of the new */


/* process is already redirected to file */

/* Child process - Execute testprogram */

execlp("testprogram", "testprogram", (char *)0);

/* If here, execlp failed */

error("Executing testprogram");

default:

/* Parent process - Wait for child end and finish */

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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 31

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.

3.3 The open file table and the read/write pointer

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).

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 32

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

Open file table

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

fork channel table channel table fork


... ...
open open
source source
Open file table

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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 33

3.4 Calls to the input/output system

This section presents the following calls related to input/output and the file system:

• creat: creates a new file.


• open: opens a file.
• close: closes a channel (file descriptor).
• read: reads from a file.
• write: writes to a file.
• lseek: places the read/write pointer.
• dup: duplicates an open channel.
• unlink: deletes an entry (link) from a directory.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 34

3.4.1 creat

Syntax

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

int creat(const char *path, mode_t mode);

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.

This call is equivalent to:

open(path, O_WRONLY | O_CREAT | O_TRUNC, mode)

Parameters

• path: points to the full file name.


• mode: represents a bitmask that describes the permissions with which the file
will be created once modified according to the value of the creation mask
(umask).

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

The call creat will fail, among other reasons, if:

• 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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 35

• 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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 36

3.4.2 open

Syntax

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

int open(const char *path, int oflag, /* mode_t mode */...);

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

• path points to the full name of a file.


• oflag will indicate the access mode of the open file and it can take,
among others, the following values:
o It must always have only one of these three values:
■ O_RDONLY: file open in read-only mode.
■ O_WRONLY: file open in write-only mode.
■ O_RDWR: file open in read and write mode.
o Any combination of the following values can be used:
■ O_APPEND: the read/write pointer will be placed at the end of
the file.
■ O_CREAT: creates the file if it does not exist. If this value is
used, the parameter mode must be added to the call.
■ O_EXCL: if the values O_CREAT and O_EXCL are included, the call
open will fail if the file already exists.
■ O_NONBLOCK or O_NDELAY: if any of these two values is included,
the subsequent read and write processes will not cause a block. If
both values are present, O_NONBLOCK has preference.
■ O_TRUNC: if the file exists, it is a regular file and it can be open with
O_RDWR or O_WRONLY, then its length becomes 0. The result of
using O_TRUNC with O_RDONLY is not defined.
• mode represents a bitmask that describes the permissions with which the file
will open, once modified according to the value of the creation mask (umask).

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 37

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

The call open will fail, among other reasons, if:

• 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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 38

3.4.3 close

Syntax

#include <unistd.h>

int close(int fildes);

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

fildes indicates the channel (file descriptor) that we want to close.

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

The call close will fail, among other reasons, if:

• EBADF: the parameter fildes is not a valid channel (file descriptor).


• EINTR: a signal has arrived during the execution of the call close.
• EIO: an input/output error occurred while reading or writing to the file system.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 39

3.4.4 read

Syntax

#include <unistd.h>

ssize_t read(int fildes, void *buf, size_t nbyte);

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.

Reading an empty pipe will produce the following effects:

• 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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 40

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

The call read will fail, among other reasons, if:

• 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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 41

3.4.5 write

Syntax

#include <unistd.h>

ssize_t write(int fildes, const void *buf, size_t nbyte);

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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 42

• 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

The call write will fail, among other reasons, if:

• 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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 43

3.4.6 lseek

Syntax

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

off_t lseek(int fildes, off_t offset, int whence);

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

• fildes: indicates an open channel (file descriptor) of a file.


• offset: indicates the relative offset to be applied to the read/write pointer.
• whence: specifies the position from which the offset will be added.

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

The call lseek will fail, among other reasons, if:

• EBADF: the parameter fildes is not an open channel (file descriptor).


• ENVAL: the parameter whence is not SEEK_SET, SEEK_CUR nor SEEK_END.
• ESPIPE: the parameter fildes is associated with a pipe.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 44

3.4.7 dup

Syntax

#include <unistd.h>

int dup(int fildes);

Description

The call dup returns a new channel (file descriptor) that shares the following
characteristics with the channel indicated in the fildes:

• Same open file or pipe.


• Same read/write pointer, that is, both channels will share a single pointer.
• Same access mode (read, write or read/write).

Parameters

fildes indicates the channel (file descriptor) that we want to duplicate.

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

The call dup will fail, among other reasons, if:

• EBADF: the parameter fildes is not a valid channel (file descriptor).


• EINTR: a signal has arrived during the execution of the call dup.
• EMFILE: the process has too many files open.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 45

3.4.8 unlink

Syntax

#include <unistd.h>

int unlink(const char *path);

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

The call unlink will fail, among other reasons, if:

• EACCES: searching is not allowed on any of the directory components of the


path, writing to the directory containing the name (link) to be deleted is not
allowed, or the user is not the owner of the directory containing the name (link)
nor is the owner of the file.
• EFAULT: the parameter path points to an illegal address.
• EINTR: a signal has arrived during the execution of the call unlink.
• ENOENT: the file name does not exist (link) or the parameter path is empty.
• ENOTDIR: some component of the prefix of the parameter path is not a directory.
• EROFS: the entry that we want to delete is part of a read-only file system.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 46

3.5 Examples of input/output calls


3.5.1 Example 1

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.

#include <fcntl.h> /* definitions O_RDONLY, O_WRONLY, ... */


#include <errno.h>

void error(char *m)


{

/* Writes errors through the standard error channel (channel 2) */

write(2, m, strlen(m));
write(2, "\n", 1);
write(2, strerror(errno), strlen(strerror(errno));
exit(1);
}

main(int argc, char *argv[])


{
int source, dest, n;
char c;

if (argc != 3)
error("Wrong number of arguments");

if ((source = open(argv[1], O_RDONLY)) < 0)


error("Opening of the source file");

/* If the destination file exists, overwrites it (O_TRUNC) */


/* If the destination file does not exist, it is created (O_CREAT, 0600) */

if ((dest = open(argv[2], O_WRONLY| O_TRUNC| O_CREAT, 0600)) < 0)


error("Creation of the destination file");

/* Reads from the source file while there is information */


/* Writes to the destination file all that has been read */

while ((n=read(source, &c, 1)) > 0)


if (write(dest, &c, 1) < 0)
error("Writing to the destination file");

if (n<0)
error("Reading from the source file");

close (source);
close (dest);
}

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 47

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>

void error(char *m)


{

/* Writes errors through the standard error channel (channel 2) */

write(2, m, strlen(m));
write(2, "\n", 1);
write(2, strerror(errno), strlen(strerror(errno));
exit(1);
}

main()
{
int fildes;

/* The temporary file.dat is created with permissions for the owner */

if ((fildes = creat("temp.dat", 0700)) < 0)


error("File creation error");

/* The name (link) of the file creat is deleted */

if (unlink("temp.dat") < 0)
error("Error deleting the name (link) of the file");

/* From now on, the file is only */


/* accessible through the fildes channel of this process */

/* ... */

/* Once the fildes channel is closed, the file disappears */

close(fildes);
exit(0);
}

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 48

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.

#include <fcntl.h> /* definitions O_RDONLY, O_WRONLY, ... */


#include <unistd.h> /* definitions of SEEK_SET, SEEK_CUR, SEEK_END */
#include <errno.h>

void error(char *m)


{

/* Writes errors through the standard error channel (channel 2) */

write(2, m, strlen(m));
write(2, "\n", 1);
write(2, strerror(errno), strlen(strerror(errno));
exit(1);
}

main(int argc, char*argv[])


{
int source, dest;
long index;
char c;

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");

/* The source file is read from the end to the start */

/* The file size is calculated with the return of the */


/* call lseek that positions the pointer at the end of the file */

if ((index = lseek(source, 0, SEEK_END)) < 0)


error("Calculate file size with lseek");
index--;
while (index >= 0) {

if (lseek(source, index, SEEK_SET) < 0)


error("Position with lseek");
if (read(source, &c, 1) < 0)
error("Reading source file");
if (write(dest, &c, 1) < 0)
error("Writing destination file");
index--;

close(source);
close(dest);
}

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 49

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.

#include <fcntl.h> /* definitions O_RDONLY, O_WRONLY, ... */


#include <errno.h>

void error(char *m)


{

/* Writes the errors through the standard error channel (channel 2) */

write(2, m, strlen(m));
write(2, "\n", 1);
write(2, strerror(errno), strlen(strerror(errno)));
exit(1);
}

main(int argc, char*argv[])


{
int source, n, st;
char c;

if (argc != 2) error("Insufficient arguments");


if ((source = open(argv[1], O_RDONLY)) < 0)
error("Open the source file");

switch (fork())
{
case -1: /* In case of error the process ends */
error("Fork");

case 0: /* Child process – reads from the file and writes */


while ((n=read(source, &c, 1)) > 0)
if (write(1, &c, 1) < 0)
error("Write standard output");
if (n<0)
error("Read the source file");
close(source);
exit(0);

default: /* Parent process – reads from the file and writes */


while ((n=read(source, &c, 1)) > 0)
if (write(1, &c, 1) < 0)
error("Write standard output");
if (n<0)
error("Read the source file");
close(source);
wait(&st);
exit(0);
}
}

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 50

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.

#include <fcntl.h> /* definitions O_RDONLY, O_WRONLY, ... */


#include <errno.h>

void error(char *m)


{

/* Writes the errors through the standard error channel (channel 2) */

write(2, m, strlen(m));
write(2, "\n", 1);
write(2, strerror(errno), strlen(strerror(errno)));
exit(1);
}

main(int argc, char*argv[])


{
int source, n, st;
char c;

if (argc != 2) error("Insufficient arguments");

switch (fork())
{
case -1: /* In case of error the process ends */
error("Fork");

case 0: /* Child process – opens, reads and writes */


if ((source = open(argv[1], O_RDONLY)) < 0)
error("Open the source file");
while ((n=read(source, &c, 1)) > 0)
if (write(1, &c, 1) < 0)
error("Write standard output");
if (n<0)
error("Read source file");
close(source);
exit(0);

default: /* Parent process – open, read and write */


if ((source = open(argv[1], O_RDONLY)) < 0)
error("Open the source file");
while ((n=read(source, &c, 1)) > 0)
if (write(1, &c, 1) < 0)
error("Write standard output");
if (n<0)
error("Read source file");
close(source);
wait(&st);
exit(0);
}
}

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 51

4 Communication and synchronisation


between processes
All the operating systems that allow for the concurrent execution of processes must
offer communication and synchronisation tools. Unix offers several communication and
synchronisation mechanisms, and in this chapter, we will present two of them: signals
and pipes. Signals are signals that allow us to notify a specific event, but that do not
include content other than the signal itself. Pipes, on the contrary, allow us to send
messages with any type of content between processes that maintain a kinship
relationship (parent/child).

4.1 Signals

Signals are asynchronous signals that can be sent to the processes for the following
reasons:

- An error in the execution of the process: for example, executing an illegal


instruction (signal SIGILL), trying to access an invalid memory address (signal
SIGSEGV), ...
- An operating system signal about any resource related to the process: indication
of a change in the status of a child (signal SIGCHLD), warning of writing to a
pipe that has no readers (signal SIGPIPE), ...
- A signal from another process or the same process for some event that the
process considers appropriate: alarm signal of the process itself (signal
SIGALRM), a sign of an event defined by the process itself or set of processes
(signal SIGUSR1), ... This explicit submission of signals between processes is
performed through the system call kill.

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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 52

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

The process programs the signal


signal (...)
Service routine

A signal arrives

Table of signals according to the POSIX standard:

Signal Meaning of the signal Number


SIGABRT Abnormal termination of a process. 6
SIGALRM Signal produced by the alarm of a process. 14
SIGFPE Arithmetic exception. 8
SIGHUP A cut in the communication with the line (terminal, modem, ...). 1
SIGILL Illegal instruction. 4
SIGINT Interruption generated from the terminal (key combination). 2
SIGQUIT Termination generated from the terminal (key combination). 3
SIGKILL Kill the process (cannot be programmed nor ignored). 9
SIGPIPE Writing to a pipe without readers. 13
SIGSEGV Invalid access to memory. 11
SIGTERM Termination signal of a process. 15
SIGUSR1 Signal 1 defined by the user. 10
SIGUSR2 Signal 2 defined by the user. 12
SIGCHLD The state of a child has changed. 17
SIGCONT Continuation signal if the process is stopped. 18
SIGSTOP Stop signal (cannot be programmed nor ignored). 19
SIGTSTP Stop signal generated from the terminal (key combination). 20
SIGTTIN Background process trying to read from the terminal. 21
SIGTTOU Background process trying to write to the terminal. 22

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 53

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

...

/* A pipe is created and both channels are received: */


/* one for reading and one for writing (p[0] and p[1]) */

if (pipe(p) < 0) error("Create pipe");

...

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]).

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 54

Figure 5

parent process child process


The parent process
creates the pipe
pipe (p)
pipe (p)

The process creates a new process

fork() fork()
p[0] pipe p[1] Starting point of the child process
read(p[0], ...) write(p[1], ...)

Channel table Channel table

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)

/* Writes the errors through the standard error channel (channel 2) */

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

/* A pipe is created and both channels are received: */


/* the read and write channels (p[0] and p[1]) */

if (pipe(p) < 0) error("Create pipe");

switch (fork())
{
case -1: error("Fork 1");

case 0: /* Child process */

/* Closes the unused channel of the pipe */

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 55

close(p[0]);

/* Writes a message in the pipe, closes it, and ends */

write(p[1],"Hello\n", 5);
close(p[1]);
exit(0);

default: /* Parent process */


break;
}

/* The parent closes the unused channel of the pipe */

close(p[1]);

/* Reads the message of the pipe and writes it through stdout */

read(p[0], buff, 5);


write(1, buff, 5);

/* Waits for the child to end and ends its execution */

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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 56

4.3 Calls to the communication and synchronisation system


between processes

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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 57

4.3.1 pipe

Syntax

#include <unistd.h>

int pipe(int fildes[2]);

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

The call pipe will fail if:

• 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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 58

4.3.2 signal

Syntax

#include <signal.h>

void (*signal (int sig, void (*disp)(int)))(int);

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

The call signal will fail if:

• 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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 59

4.3.3 kill

Syntax

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

int kill(pid_t pid, int sig);

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

• pid: indicates the ID of the process or process group to which we want to


send the signal.
• sig: indicates the signal that we want to send.

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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 60

Errors

The call kill will fail if:

• 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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 61

4.3.4 alarm

Syntax

#include <unistd.h>

unsigned int alarm(unsigned int sec);

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

sec: indicates the number of seconds a f t e r w hich a signal SIGALRM w i l l b e


s e n t t o the process that invoked the call.

Returned value

The call alarm returns the time remaining for the next alarm in the process.

Errors

There are no errors defined.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 62

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

The call pause will fail if:

• EINTR: a signal has arrived during the execution of the call pause.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 63

4.4 Examples of communication and synchronisation calls


between processes
4.4.1 Example 1

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

/* SIGALARM handling routine */

void routine_alarm(int foo)


{
char s[20];

/* Redefines the programming of the signal SIGLARM */

signal(SIGALRM, routine_alarm);

/* Writes the message every time the alarm arrives */

write(1, "Alarm 3 seconds\n", strlen("Alarm 3 seconds\n"));

/* Requests a signal SIGLARM in TIME seconds */

alarm(TIME);
}

main()
{
/* Defines the programming of the signal SIGALRM) */

signal(SIGALRM, routine_alarm);

/* Request a SIGLARM in TIME seconds */

alarm(TIME);

/* Infinite loop waiting for the alarm*/

while(1)
{
/* Blocks the process until the signal arrives */

pause();
}
}

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 64

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>

void error(char *m)


{
/* Writes the errors through the standard error channel (channel 2) */

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;

/* A pipe is created and both channels are received: */


/* the read and write channels (p[0] and p[1]) */

if (pipe(p) < 0) error("Create pipe");

switch (fork())
{
case -1: error("Fork 1");

case 0: /* Child 1 – Redirect, close and execute */

/* Redirect standard output to the pipe */

close(1); dup(p[1]);

/* Close unused pipe channels */

close(p[0]); close(p[1]);

/* Load the code of ls with two parameters*/

execlp("ls", "ls", "-l", "/usr/bin", (char *)0);


error("Execute ls");

default: /* Parent process */


break;
}

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 65

switch (fork())
{
case -1: error("Fork 2");

case 0: /* Children 2 – Redirects, closes and executes */

/* Redirect standard input to the pipe */

close(0); dup(p[0]);

/* Close unused pipe channels */

close(p[0]); close(p[1]);

/* Load the code of grep with a parameter */

execlp("grep", "grep", "cat", (char *)0);


error("Execute grep");

default: /* Parent process */


break;
}

/* The parent closes the channels it does not use */

close(p[0]); close(p[1]);

/* Wait for both children to end */

wait(&st1);
wait(&st2);

/* Ends its execution */

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.

Descargado por Inanna Ari ([email protected])


lOMoARcPSD|33746887

Introduction to Unix programming 66

5 Bibliography
• Unix online manual: command man.

• Kernighan, Brian W. i Pike, Rob. El entorno de programación UNIX.


Prentice- Hall Hispanoamericana. Mexico, 1987.

• Robbins, Kay A. and Robbins, Steven. UNIX programación práctica.


Prentice-Hall Hispanoamericana. Mexico, 1997.

• Stevens W.Richard. Advanced Programming in the Unix Environment.


Reading, Mass.: Addison-Wesley, 2001.

Descargado por Inanna Ari ([email protected])

You might also like