Introduction to System Calls - CSIS, BITS, Pilani

brickcompetitiveΛογισμικό & κατασκευή λογ/κού

14 Δεκ 2013 (πριν από 3 χρόνια και 3 μήνες)

124 εμφανίσεις

An Introduction to

Unix Operating System

Introduction:

Operating System
(OS)

is a software which
provides an interface between user of a computer and the
computer hardware.
This interface is provided in terms of managing execution of programs, memory
mana
gement, file management, I/O and network management etc.

There are many operating systems in
to

existence. For example Windows, Unix,

MS
-
DOS, Solaris etc.

There are different types of OS
s based on
type of task it has to perform.

For example, single user OS,

multi
-
user OS, multi
-
tasking OS, real time OS
etc.

Unix OS is a multi
-
user, multi
-
tasking operating system which can facilitate

various services as
mentioned above.

Unix is developed in C programming language at

AT&T Bell Laboratories

during
1970s.

It is widely used by academicians and industry people.
Unix is the best operating system for
enhancing the theoretical concepts of

Operating System.

Architecture of Unix Operating System:

The high level architecture of Unix system is shown in figure 1. I
t shows hardware at the center. Next is
the system kernel or just
kernel
. Kernel is a software that controls the hardware resources of the computer
and provide an environment to run a program. It is relatively small and resides at the core of the
environme
nt.

An interface to the kernel is a
layer of software called the

system calls
.

These system calls

instruct the
kernel to do various operations for the calling program and exchange data between the kernel and the
program.

Several standard commands and also user executable can exist in the next layer. For example,
vi

and
ed

can interact with the kernel by invoking a well defined set of system calls. Also an executable
a.out

can invoke different system calls to interact with

kernel.

Other application programs can be build on
top of this layer.

In brief, the set of system calls and the internal algorithms that implement them form the
body of the kernel.



Figure 1.

Detailed Block Diagram of Kernel:



Figure
2
.

The above
figure shows various sub
-
modules of Unix Operating System and their relationship with each
other. File System sub
-
module on the left and process control sub
-
module on the right are the two major
components of the kernel. The above diagram shows the logical

view of the kernel. It shows three levels:
user, kernel and the hardware. System calls (explain in detail later) and library interface serves as a border
between kernel and the user.

The file sub
-
system manages files, allocating file space, administrating

free space, controlling access to
files and retrieving data
for user. Processes interact with the file sub
-
system via a specific set of system
calls such as open, close, read, write etc.

The kernel can read and write directly to and from the disk for all

file system accesses but s
ystem
response time and through
put will be very slow due to slow disk transfer rate.

The kernel therefore
attempts to minimize the frequency of disk access by keeping a pool of internal data buffers, called as
buffer cache
, which

contains the data in recently used disk blocks
.
This mechanism of file data access is
called buffering mechanism.
This buffering mechanism
regulates data flow between the kernel and
secondary storage devices.
It

interacts with the I/O device drivers (kern
el modules that control operation
of peripheral devices) to initiate data transfer to and from the kernel.

The position of the buffer cache
module in the kernel architecture is between the file sub
-
system and the I/O device drivers.

When a read operation is initiated, the kernel
attempts to read from the buffer cache. if the data is already
available in the cache, kernel does not have to get it from the disk, and if it is not in buffer cache, kernel
reads it from the cache. Similarly,

while writing data to the disk, kernel writes it to the buffer cache if
space is available in buffer cache in order to minimize frequency of disk accesses. Later the data is
transferred to the disk. There are many algorithms for managing the buffer cache
and disk transfer.

The process control sub module shown on the right side of the block diagram is responsible for process
synchronization, inter
-
process communication, memory management and process scheduling. The file
sub
-
system and the process control su
bsy
s
tem interact when loading a file into memory before execution.
The process subsystem reads executable files from the memory before executing them.

Memory management module controls the allocation of memory. If at any time system does not have
enough ma
in memory for all processes, the kernel moves them from main memory to secondary memory
so that all processes get a fair chance to execute. Two popular policies for memory management are
swapping and demand paging.

In order to execute a process, it should
be assigned to the CPU. In multiprogramming environment, CPU
Scheduling module allocates the CPU to processes for execution. It schedules the processes in the system
so that they get a fair share of CPU. There are various CPU scheduling algorithms.

Finally

hardware control is responsible for handling interrupts and for communication with the machine.
Devices such as disks or terminals may interrupt the CPU while a process is executing. Kernel services
the interrupt
and resumes the execution of interrupted p
rocess.

Introduction to System Calls
:



One of the principal functions of an operating system is to act as a bridge between a user
and the underlying machine hardware. In other words, the operating system provides a level of abstraction
between the hardware and the end user. When a user program
wants to access the hardware (say some file
“xyz.txt” that is present in a hard
-
disk of that computer), the program will not directly access the disk.
Instead the program sends a request to the OS to fetch contents. The OS fetches the disk contents on
beha
lf of the user program.



Clearly there should be some mechanism by means of which the user program can send
requests to the OS and for the OS to send data
\
results back to the user program. That mechanism is called
System Calls.

Need for System Calls
:



In general, whenever a user program wants to perform some hardware
-
related or some other
privileged task (eg: installing new software), it does not have the authority to do it directly and hence it
requests the OS to do the task. Now, why should not the us
er program itself be given the authority to
perform these tasks directly? The OS intervention in form of a system call is needed primarily for two
reasons:

1.

Protection
: A user process when given direct access to perform privileged operations, may
unintentio
nally
\
maliciously corrupt other user’s data or the system data. The OS does the
policing job to make sure that every user’s data and resources are safe from corruption by other
users

2.

Abstraction
: A user program does not have the burden of knowing how to i
nteract with the
hardware. It can just say that a content of some file, “xyz.txt” is needed. The actual placing of the
file in physical memory is done by the OS.

How do System Calls work?


Every instruction in a user program can be classified as 2 types:

1.

N
ormal Instructions : add,

sub etc

2.

Privileged Instructions: access to protected memory etc

Accordingly, there are 2 modes of operation: the kernel mode and the user mode. While normal
instructions can run in both modes, the privileged instructions can run
only in kernel mode.
Whenever a system call is issued from the program (to perform an IO or any other task) it is
always a privileged instruction and hence runs in a kernel mode.


Consider a program that writes some contents to a file, the following would
be the approximate
sequence of operations:

1.

Read the name of a file to be edited from the user (USER MODE)

2.

Pass this file name to kernel using a system call (KERNEL MODE)

3.

Kernel checks to see if the file exists in the system and informs the user program usi
ng return
value of the system call(KERNEL MODE)

4.

Pass the data to be written to the file using a system call (USER MODE)

5.

Write data in the disk portion where that file resides and inform user if successfully written
using return value of the system call (KE
RNEL MODE)


Note that whenever a system call occurs, control switches from user mode to kernel mode.
Then some operation is performed in kernel mode and then control again switches back from
kernel mode to user mode. This switch between modes is also calle
d context
-
switches and
every system call needs 2 context switches. A procedure or function call on the other hand,
just branches the program to execute a different set of instructions. Hence normal procedure
calls are typically much cheaper than a system c
all.

How are System Calls normally used?



In C programming, when working with files we usually use functions (available in stdio.h
library) like fopen(),fread() etc. These functions in
-
turn call these system calls to actually access the files.
Usua
lly, programmers do not directly use system calls. They access some library functions which wrap
the system calls.

User Perspective:

Login Details:

When one logs in to a Unix system by entering login name and password, system looks for valid login
name in

its password file, usually located at /etc/passwd. This password file is composed of seven colon
separated fields: login name, encrypted password, numeric user id, numeric group id, a comment field,
home directory and shell program.

Shell:

After login the

system starts by displaying some system information messages and then one can type
commands to the shell program. Some systems have GUI where shell program can be initiated in a
separate window.

A shell is a command line interpreter that reads user input
and executes commands.
Input to a shell may be from terminal or from a file.

System knows which shell to execute from the final
field in the password file.

Figure 2 shows different shells used in Unix system.





Figure 2. Common shells used on Unix syste
ms

Files and Directories:

Unix file system is an hierarchical arrangement of directories and files.
Everything starts in the directory
called
root
whose name is the single character /.

A directory

is a file that contains
filename
s

along with its attributes such as type of file (file or directory),
size of file, owner of the file etc.

Filename cannot have two characters: / and NULL character.

Two file
names are automatically created when a new directory is created. those are . (curr
ent directory name) and
.. (parent directory name).

A filename can have up to 255 characters.

Pathname: A sequence of one or more filenames, separated by slashes (/) and optionally starting with a
slash forma a
pathname
.
Pathname that begins with slash is
called an
absolute path

where as those which
are not starting with slash are called as
relative paths
.
Relative pathnames refers to files relative to current
directory.

For example, the relative pathname
doc/memo/jeo

refers to the file or directory jeo

in the
directory memo, in the directory doc, which must bea directory within the current working directory.

The
pathname /usr/lib/lint is an absolute pathname that refers to the file or directory lint in the directory lib, in
the directory lib, which is i
n the root directory.

Home Directory:

The current working directory is set to home directory as soon as one logs in the system.

Home directory
is obtained from the entry in the password file.


File Descriptors


A file descriptor is very simply a number wh
ich is a reference to a file. Every user program that
needs to access a file acquires some handle
\
reference to that file called the file descriptor. When a user
program opens a file, it obtains a file descriptor. These file descriptor numbers can vary from

0 to some
N. The actual value of N is UNIX version dependant but on most versions it is 1024. The first three file
descriptors need not be explicitly created. Those three are already open and have pre
-
defined meanings as
follows:


● File Descriptor 0


St
andard Input (STDIN_FILENO)

● File Descriptor 1


Standard Output (STDOUT_FILENO)

● File Descriptor 2


Standard Error Output (STDERR_FILENO)


By default all of these file descriptors refer to the terminal or console (Recall according to UNIX, a
terminal i
s also a file). The names (like STDIN_FILENO) are more convenient to use than their
corresponding numbers (like 0). Usually the standard input and output is the console.

Process Basics

Program Vs Process


A program is just a file containing instructions
and data. These instructions while in execution constitute a
process. A running instance of a program is called a process. For example, if you open two windows of
internet explorer, the same internet explorer program is executed twice. There is a single pr
ogram but
there are two running instances of the same program, hence two processes.


More formally, a process is an execution environment that consists of instructions, user
-
data and system
-
data segments as well as resources acquired at runtime.

Process
-
I
D

Each process in a UNIX system is identified by its unique process id, referred to as pid. Process IDs are
usually 16
-
bit numbers that are assigned sequentially by UNIX as new processes are created. Every
process also has a parent process (except the spec
ial init process and Zombie Processes).Thus, you can
think of the processes on a UNIX system as arranged in a tree, with the init process at its root. The parent
process ID, or ppid, is simply the process ID of the process’s parent. When referring to proce
ss IDs in a C
or C++ program, always use the pid_t typedef, which is defined in<sys/types.h>. A program can obtain
the process ID of the process it’s running in with the getpid() system call, and it can obtain the process ID
of its parent process with the
getppid() system call. The program below prints its process ID and its
parent’s process ID:


#include <stdio.h>

#include <unistd.h>

int main()

{

printf("
\
nProcess id: %d",getpid());

printf("
\
nParent Process id: %d",getppid());

return 0;

}

Run the above pro
gram in two terminal windows. The output in each window would be different. If new
windows are opened, you will find that each time process
-
id’s are printed. This is because each
invocation is a new process.


Run the program twice in the same window. Thi
s time you with every execution, you will find that only
the current process
-
id changes while the parent process
-
id (which is the process
-
id of the terminal shell)
remains the same.

Viewing Active Processes


The ps command displays the processes that are
running on your system. By default, invoking ps displays
the processes controlled by the terminal or terminal window in which ps is invoked. For example:





ps


PID TTY TIME CMD

21693 pts/8 00:00:00 bash

21694 pts/8 00:00:00 ps


This invocation of ps

shows two processes. The first, bash, is the shell running on this terminal. The
second is the running instance of the ps program itself. The first column, labeled PID, displays the
process ID of each.

Context of a Process


Many processes may be running i
n a UNIX system. But memory/processor
-
capacity may be limited and
we may not be able to hold all processes in main memory at the same time. Hence the system needs to
replace one process from main memory and introduce another process in its place. The repla
cement of
one process with another is called context
-
switching.


Assume process A executes for some time and is then replaced with process B before it fully completes
its execution. After sometime, we may again need to run process A. In that case, process
A would be
brought back to main memory. When process A comes back to main memory, we need to ensure that
process A starts exactly at the same point where it had stopped execution. To be able to do that, some
information about process A needs to be stored i
n the system before we replace it. This information is
called the “context” of a process.


The context of a process includes all the information that the OS needs to restart a process after a context
switch. Typically, this includes PC, stack, registers,
executable code etc



Creating Processes


exec() and fork()


The fork() system call creates a new process which is an exact clone of an existing process. Recall that the
context of each process contains the program instructions and data. Hence when we
clone a process from
an existing process using the fork() call, the new process also would contain the same program
instructions and data. Hence the newly created process executes the same program as the old (parent)
process.


The exec() system call on the

other hand, reinitializes an existing process with some other designated
program. exec() does NOT create a new process. It merely flushes the current context of a program and
loads a new context (new program).


It is evident that exec() and fork() when u
sed individually have very limited use. When used as a pair,
these system calls can be powerful (explained in the next section).


exec() call is the only way to execute programs in UNIX. In fact, the kernel boots itself using the exec()
call. And fork()
is the only way to create new processes in UNIX.


Concept of T
hreads:

Generally, a process has only one thread of control i.e. one set of instructions executing at a time. Some
problems are easier to solve when more than one thread of control can operate
on different parts of the
problem.

Also, multiple threads of control can exploit the parallelism pos
sible on multiprocessor systems
and can give high throughput.

All the threads within a process share the same address space, file descriptors, stacks and pr
ocess
-
related
attributes.

Since all the thread under one process access same memory, they need to synchronize access to
shared data among themselves to avoid inconsistency.

Threads are identified by thread IDs local to a
process.

Error Handling
:

When an er
ror occur in any Unix System function, a ne
gative value is often returned and t
he integer
errno

is usually set to a value that gives additional information.

For example, the
open

function returns either a
non
-
negative file descriptor if all is OK or
-
1 if
an error occurs. An error from open has about 15 possible
values such as file doesn't exist, permission problem, and so on. Some functions use a convention other
than returning a negative value.

The file <errno.h> defines the symbol errno and constants for

each value that errno can assume. Each of
these constants begin with the character E. For example, if errno is equal to the constant EACCES, this
indicates a permission problem.

Two functions are defined by the C standard to help in printing error message
s.

1.

#include<string.h>

char *strerror(int errnum);




2.

#include<stdio.h>

void perror(const char *msg);


The first function maps errnum, which is typically the errno value, into an error message string and

returns pointer to the string. The second perror()
function produces an error message on the standard

error, based on the current value of errno, and returns.

The following program shows the use of these two

error functions.


#include<stdio.h>

#include<string.h>

#include<errno.h>


int main()

{


fprintf(std
err,"EACCES: %s
\
n",strerror(EACCES));


errno = ENOENT;


perror(argv[0]);


exit(0);

}


output:

$./a.out

EACCES: Permission denied

./a.out: No

such file or directory

Program Execution Process
,
M
emory Layout of a C Program,
User Stack and Kernel Stack:






Figure 3.





Figure 4.

Kernel plays a vital role while a program is executed by the processor. Processor can execute an
executable file. Complier compiles the program and creates an executable. Then this executable file is
loaded into the main memory before execution. Loading of

an executable file is done by the kernel with
the help of exec (one of the process control system call) system call. This loaded process consist of three
parts called
regions
: text, data and stack.

The text and data sections correspond to the text/code an
d data
sections of the executable file. And the stack region is automatically created and its size is dynamically
adjusted by the kernel at run time.

The stack consists of logical stack frames that are pushed when calling
a function and popped when returni
ng. A special register called the stack pointer indicates the current
stack depth.

A stack frame contains the parameters to a function, its local variables and the data necessary
to recover the previous stack frame

that include the value of program counter

and the stack pointer at the
time of function call.


Since a process executes in two modes, kernel and user, it uses a separate stack for each mode. The user
stack contains the arguments, local variables and other data for functions executing in user mode
. Figure 3
above shows user and kernel stack for the "copy" program in figure 4.

The process stratup procedures
calls the main function with two parameters, pushing frame 1 onto the user stack. Frame 1 contains space
for local variables. Main function then

invokes copy function with two input arguments,
old

and
new
.
Upon this call to copy function a frame number 2 is pushed onto the stack along with its input arguments
and local variable
count
.
Finally the process invokes a
write

system call. Each system call has an entry

point in a system call library. This system call library is encoded in assembly language and contains a
special
trap

instruction which when executed causes an interrupt that results in hardware switch to kernel
m
ode.

When process switches to kernel mode, it executes kernel and uses kernel stack.

The kernel stack
contains the stack frames for functions executing in kernel mode. The function and data entries on the
kernel stack refer to functions and data in the ker
nel and not in the user program
, but its construction is
the same as that of the user stack.
The kernel stack is null whe
n process executes in user mode. The
function names in each kernel stack frame corresponds to the algorithms used in write system calls
. Exact
names are not mentioned here and not also not require
d for this
explanation
.

Unix File System Design and Programming

Contents
:

1. Brief introduction of Unix File System Design aspects

2. Syntax and usage of file related system calls

3. Programming
examples using file system calls


1
. Design aspects of Unix File System


Unix file system is organized as a tree with a single root node called
root

(denoted as "/"), every
non
-
leaf node of the file system structure is a directory of files, and files at the leaf nodes of the tree are
regular files, directories or special device files.



Programs in the Unix system have no knowledge of the internal for
mat in which the kernel stores
the file data. It treats the data as an unformatted stream of bytes.

Thus, the syntax of accessing the data in
a file is defined by the system and is identical for all programs, but the semantics of the da
ta are imposed
by th
e program. F
or example,

the text formatting program expects to find "new line" characters at the end
of each line of text, and the system accounting program expects to find fixed length records
.

Both
programs use the same system services to access the data

in the file as a byte stream but internally they
parse the stream into their own formats.

1.1 Internal Representation of a Unix file

inode:

A file is internally represented by an
inode
. It contains description of the disk layout of the file
data and other information such as the file owner, access permissions and access time. The term
inode

is a
contraction of the term
index node
.
When a process access a file by name, the kernel parses
the file name
in the path, checks the permissions to search in to the directories in the path and eventually retrieves the
inode

for the file.

Whenever a process creates a new file, the kernel assigns it an unused
inode
.
Inodes are

stored in the file syste
m but kernel brings them in main memory in
to an in
-
core inode table when
manipulating files.

User File Descriptor Table and File Table:

Kernel maintains two more data structures
along with inode table
for file handling. They are
user file
descriptor table

and
file table
.
When a process creates or open a file, kernel allocates an entry from each
table corresponding to the file's inode.

User file descriptor table is created for each process while file table
is a global kernel structure and common to all the
processes in the system.

Entries in each of the three
tables maintains the state of the file.

User file descriptor table
:

It identifies all open files of a particular process. Kernel returns a file
descriptor for the
open

and
creat

system calls, which is
an index into the user file descriptor table.

This
table is implemented as a vector in which each entry contains the flag descriptor flag and pointer to a file
table entry.

When executing

read
and
write

system calls, the kernel uses the file descriptor to

access the user file
descriptor table, follows pointers to the file table and inode table entries.

From inode it finds the data in
the file.

Figure 1. shows the relationship between the three tables.





Figure 5
. Abstract view of file data structue

File Table:
As mentioned earlier, kernel maintains a global data structure known as file table for all open
files. Each file table entry contains

(1) The file status flags for the file such as read, write, append
, crea
te, truncate and many more.

(2) The current file offset

(3) A pointer to the
v
-
node table entry for the file

Each open file or device has a v
-
node structure that contains information about the type of file and
pointers to functions that operate on the file. It also contains the i
-
node information for the file. This
information is read from the disk when the file
is opened in order to get all pertinent information about
the file.

Note:

Some Unix versions have inode structure instead of a layer of v
-
node structure. v
-
node structure is
an additional layer of abstraction implemented in some Unix version.

Refer Chapter

3 of "Advanced
Programming in Unix Environment" by

Richard Stevens
EndOfNote


Figure
6
.

File Data Structure

File System Structure:

Unix divided physical disks into l
ogical disks called partitions.
Each partition is a
standalone file system.

Each partition is divided into blocks:



Boot block: A

boot block is located in the first few sectors of a file system. The boot block
contains the initial bootstrap program used to load the operating system.



Super block: A

super block describes the state o
f the file system: the total size of the partition, the
block size, pointers to a list of free blocks, the inode number of the root directory etc.



a linear array of inodes (short for “index nodes”). There is a one to one mapping of files to inodes
and vice

versa.



D
ata blocks
: These

blocks contain

the actual contents of files






Figure 7.


















Basic IO System Calls


Creating, Opening and Closing files


The

Open System Call



The signature of the open system call is given below.



int open(

const char *path, /*pathname*/

int flags, /*flags*/

mode_t perms /* permissions (creation) */

);

/* Returns file descriptor or
-
1 on error */

open() system call opens an existing file or can be used to create a new file and then open
it, if the file does n
ot exist. Once the file is opened the file descriptor to that file is returned. This
file descriptor would be used in the subsequent read or write to that file.


Opening an existing file

A new file specified by “path” variable in the system call will be
opened. The “flags” can
take any of the following values:


Usually the “perms” argument is omitted when we are trying to open a existing file. In
that case, the open() call would take only two arguments as follows:

int fd;

fd=open("input.txt",O_RDWR);


The open() call usually fails if the path is given wrong or if there is some problem with
the access rights. The open() call (and almost all system calls) returns
-
1 if some error occurred. If
the file is successfully opened, lowest numbered file descripto
r that is available is returned.



Creating a new file

If the file does not exist, open() creates the file, if the flag contains the O_CREAT flag.
When more than one flag is used, the flags are usually ORed. O_CREAT flag can be combined
with any other flag

but combining it with flags like O_RDONLY makes no sense. A newly
created file is usually not opened in a read only mode. The third argument in the call, “perms” is
useful only when the file created. It has no effect if the file already exists. Understand
ing the
“perms” argument requires a short review of file permission concepts.


File Permissions

The file permission bits are laid out in 3 groups: rwx rwx rwx where,

r
-

read permission, w
-

write permission, x
-
execute permission

From left to right the
permission groups are:

owner permissions
-

u

group permissions


g

all others permissions


o


Example:

The encoding: 111 110 100 implies that the owner(also referred as user) has read, write and
execute permissions. The group has read and write permissio
ns. And others have only read
permission. The same maybe expressed in octal notation as 764.


In modern UNIX systems, symbols are frequently used than these octal representations. The
symbol representations are version dependant. But the general notation
is S_I
pwww

where

p

is
the permission and
www

is for whom (USR,GRP or OTH) . The octal value 755 can be
symbolically represented as:

S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH


Now the open() call would look like,


int fd;

fd=open("input.txt",

O_WRONLY|O_CREAT|O_TRUNC,

S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);




The above call creates a fresh file with no data in it. If the file exists, the data is deleted
completely(O_TRUNC). If the file does not exist, it is cr
eated as fresh(O_CREAT).Since the file is
fresh(newly created) it’s opened in a write
-
only mode(O_WRONLY). And the permissions are set to be
755 as described above.


The combination of O_WRONLY|O_CREAT|O_TRUNC is, in fact, so common, that a dedicated
syste
m call exists just for that combination


the creat() system call.

The Creat System Call


When open() call is used for opening an existing file, it needs only the “path” and the “flags”
argument. On the other hand, the creat() call requires only the “path”

and the “perms” argument. The
flags always have the same constant value in the creat() call
-


O_WRONLY|O_CREAT|O_TRUNC

int creat (

const char *path,

mode_t perms

);

/* Returns file descriptor or
-
1 on error */


In modern UNIX systems, creat
() can be done away with and we could just we use the open() call
with the appropriate flags. creat() existed in legacy UNIX systems where open() call had only two
arguments.

The Close System Call


The close() call does nothing except to make the file desc
riptor available for re
-
use. It does not
flush any kernel buffers or perform any other clean
-
up task. In fact, if the file descriptor is not needed
again (for re
-
use) close() need not be called at all. But still it’s a good programming practice to free up
kernel structures when you are done using them.


int close(

int fd; /*file descriptor */

);

/* Returns 0 on success and
-
1 on error */


Solved Exercise


Show the kernel data

structures (file descriptor table, file table, inode table) after the following
c
ommands has executed.

Process A:

fd= open(“/etc/passwd”,O_RDONLY);

fd2= open(“local”,O_WRONLY);

fd3= open(“/etc/passwd”,O_RDWR);


Process B:


fd1= open(“/etc/passwd”,O_RDONLY);

fd2= open(“private”,O_RDONLY);




Reading and Writing to Files


write

System Call



#include<sys/types.h>

#include<sys/uio.h>

# include<unistd.h>

ssize_t write(


int

fd,


/* open file descriptor on which to write*/


const void
*buf,

/*data to write*/


ssize_t

nbytes

/*amount to write*/

);

/* Returns the number of bytes wri
tten or
-
1 on error */


write()

writes
nbytes

pointed to by
buf

to the open file represented by
fd
. The write starts at the
current position in the file offset and after the write, the file offset is incremented by the number
of bytes written. If the O_APP
END flag had been set while opening the file, the file offset
(position in the file from where the write starts) is automatically set to the end of the file prior to
the write.


The
write

call does not actually write the contents to the disk. It just tran
sfers data to an
intermediate place called buffer cache and returns. At some later point (which can be ANY
arbitrary time), the kernel transfers the data from the buffer cache to the actual place in disk
where the file resides. The user process may never b
e able to find out when the data was actually
transferred to the disk. This delayed writing accounts for the speeds up the
write

call. This is
because read or write to buffer caches are very fast compared to read or write to disk. This
technique is referr
ed to as
delayed writing
.


However delayed writing does not come without disadvantages:


1. Perhaps the first problem with the write() call is that the user process would never know when
the write actually occurred. There is some uncertainty about the phys
ical write to the disk.


2. Processes cannot be informed of write errors. When the kernel writes data to the disk, it may
encounter a write error. But that write error cannot always to be communicated to the user
process that initiated the write. This is
because the process could have even finished it’s execution
by the time the kernel encounters the error.


3. The order of physical writes cannot be controlled. There is no guarantee that if write() calls are
issued in some order, the kernel will follow the

same order while writing contents to disk from
the buffer cache.


There are ways to get around this problem, by forcing synchronized writes (Will be covered as a
part of the sync() system call , later in this labsheet)

Usage:

charbuf[512];

ssize_tnbytes
=100;

int

bytes_written;

bytes_written=write(STDOUT_FILENO,buf,nbytes);


If the

write

call had been successful,
bytes_written

should be (usually) equal to
nbytes
.

read

System Call




#include<sys/types.h>

#include<sys/uio.h>

#
include<unistd.h>

ssize_t
read
(


int

fd,
/* open file descriptor from which to read*/


void *buf,


/*address to receive the read data*/


ssize_t

nbytes

/*amount to read*/

);

/* Returns the number of bytes read or
-
1 on error */





read

call reads
nbytes

pointed to by
buf

from the open file represented by
fd
. The read
starts at the current position in the file offset and then the file offset is incremented by the number
of bytes read. The read and write calls’ signature are exactly similar but for one (obvious) thing


the

buffer cannot be
“const”

while reading. A common mistake is to pass on an uninitialized
pointer in place of
buf
which could lead to unpredictable results or a memory access violation.
The kernel tries to speed up the read process by employing read
-
ahead a
nd caching techniques.

A Complete IO Program: Using open(),creat(),read(),write() and close()


/* This program reads contents from an existing file "some.txt"

and
copies the contents int
o another newly created file "new
.txt"
*/


# include<stdio.h>

#
include<fcntl.h>

# include<unistd.h>

# include<string.h>

# include<sys/types.h>

# include<sys/uio.h>



int main(int

argc,char **argv){


int read_bytes
,n,fd,fd1;


char

buf[128];


char *pathname="some.txt";




fd=open(pathname,O_RDONLY);

/* In the
open call permissions








are not mandatory */


if(fd==
-
1)

{



printf("Error opening %s
\
n",pathname);



return 1;


}



while(1){



read_bytes
=read(fd,buf,sizeof(buf));



if(!read_bytes
)




break;





if(read_bytes
==
-
1)

{




printf("Error
reading file %s
\
n",pathname);




return 2;



}







fd1=creat("new
.txt",777);
/* The permissions in
creat

ca
ll can be set either
u
sing

octal or symbolic
representation*
/



n=
write(fd1,buf,read_bytes
);



if(n==
-
1)

{




printf("Error writing to
stdout
\
n");




return 3;



}


}


close(fd);


close(fd1);


return 0;

}

Seeking in a File

A file offset is just a position in a file that marks where the next read or write should occur. The
lseek()

system call just sets the file offset for use by the next
r
ead(), write()

or
lseek().
There is no actual
I/O performed.


# include <unistd.h>

off_t

lseek(


int

fd,


/* file descriptor */


off_t offset
,
/* How many bytes to move? */


int whence


/* From which point to move? */

);

/* Returns a new file offset
or
-
1 on error */


The combination of the arguments
offset

and

whence

determine how the seek is
performed on the file. The argument
offset

can take positive as well as negative values.

The values that the argument
whence

can take include:



The
resulting file offset can have any non
-
negative value, even greater than the size of the file. If
greater, the next write stretches the length of the file, effectively filling it with bytes of zeros.

Most of the seeks done by the kernel are implicit rather

than explicit calls. When open() is called,
the kernel seeks to the first byte. When read or write is called, the kernel seeks to increment the offset by
the number of bytes read or written. When a file is opened with O_APPEND, a seek to the end of file
p
recedes each write.


/* This program prints a file backwards one line at a time */


# include<stdio.h>

# include<fcntl.h>

# include<unistd.h>

# include<errno.h>

# include<string.h>

# include<sys/types.h>

# include<sys/uio.h>




int main(int argc,char **arg
v){



int read_bytes
,n,fd,fd1,i;


int cnt;



char buf[512];


char c;


char *pathname="back.txt";



fd=open(pathname,O_RDONLY);



if(fd==
-
1){



printf("Error opening %s
\
n",pathname);



return 1;


}




cnt=sizeof(buf)
-
1;


buf[cnt]='
\
0';


lseek
(fd,1,SEEK_END);

/* Offset is just beyond the EOF */





d
o
{



n=lseek(fd,
-
2,SEEK_CUR);

/* Why is the offset
-
2









from current? */



read_bytes
=read(fd,&c,1);



buf[
--
cnt]=c;







if(cnt <= 0)

{




printf("File too big for buffer
\
n");




return 2;



}





if(!read_bytes
){




printf("read_bytes is zero%
s
\
n",strerror(errno));




return 3;



}






if(read_bytes
==
-
1){




printf("Error reading file %s
\
n",pathname);




return 4;



}






if(buf[cnt] == '
\
n'){




for(i=cnt;buf[i]!='
\
0';i++)






printf("%
c",buf[i]);




cnt=sizeof(buf)
-
1;



}


}while(n>0);





printf("
\
n");


for(i=cnt;buf[i]!='
\
0';i++)



printf("%c",buf[i]);


printf("
\
n");


close(fd);



return 0;

}

Duplicating File Descriptors

UNIX provides the capability to have one open file descriptor available as two (or more) separate
file descriptors:
dup()

call. It is also possible to take an open file descriptor and cause it to be available on
a specific file unit number (that is already

not in use):
dup2()

call.

dup

System Call



# include<unistd.h>

int dup(int

oldfd);

/* file descriptor to duplicate */

/* Returns new file descriptor or
-
1 on error */




dup()

duplicates an existing file descriptor, returning a new file descriptor that is open to the same
file. The call fails if the argument is bad (
oldfd

is not an existing file descriptor) or if no file descriptors
are available (to return).

In case of
dup()
,
the returned file descriptor when successful is the lowest unused file number
available in current process. The
dup()

call provides the programmer the flexibility to use more than one
file descriptor on the same file, without having to worry about synchron
izing file offsets every time. The
following program demonstrates the same:


/* Using two open calls on the same file */


# include<string.h>

# include<stdio.h>

# include<fcntl.h>


int main(int argc,char **argv){

int fd1,fd2;


fd1=open("xyz.txt",O_RDWR);

f
d2=open("xyz.txt",O_RDWR);


printf("fd1 file offset before write: %d
\
n",lseek(fd1,0,1));

write(fd1,"hello",strlen("hello"));

printf("fd2 file offset before write: %d
\
n",lseek(fd2,0,1));

write(fd2,"world",strlen("world"));


return 0;

}


The output for the
above program would be:


> fd1 file offset before write: 0

> fd2 file offset before write: 0


> cat xyz.txt

> world


Note that the actual data “hello world” is not written successfully into the file because update of the file
offset by
fd1

is not reflected by
fd2
.This is because
fd1

and
fd2

each have a separate file description (and
hence different copy of offset values).


/* Using open followed by a dup call */


int main(int argc,char **argv){

int fd1,fd2;


fd1=open("xyz.txt",O_RDWR);

fd2=
dup(fd1);


printf("fd1 file offset before write: %d
\
n",lseek(fd1,0,1));

write(fd1,"hello",strlen("hello"));

printf("fd2 file offset before write: %d
\
n",lseek(fd2,0,1));

write(fd2,"world",strlen("world"));


return 0;

}


This time the output would be:


>
fd1 file offset before write: 0

> fd2 file offset before write: 5


> cat xyz.txt

> helloworld


When a
dup()

call is used, the two file descriptors (
fd1

and

fd2
) share the same file description and hence
update by one is reflected in the other.

dup2

System

Call


# include<unistd.h>

int dup2(


int

oldfd,
/* file descriptor to duplicate */


int

newfd);
/* file descriptor to use */

/* Returns new file descriptor or
-
1 on error */



dup2()

causes
newfd

to refer to the same file as
oldfd
. If
newfd

refers to an already open file, the
open file is closed first.

The file descriptor returned by
dup2()

has the following in common with
oldfd
:



Same open file



Same file pointer (that is, both file descriptors share one file pointer)



Same access mode (read,

write or read/write)



Same file status flags (refer to stat() system call)


dup2()

is frequently used to re
-
direct file input
\
output. That is to reset the standard file input or output.
The file descriptor STDIN_FILENO (standard input) does not always hav
e to be the terminal (console).It
can be made to refer to any other file (or device). The redirection is made simpler because
dup2()

automatically takes care of closing the old file descriptor.

Saving and Destroying Files





We already know that the
write()

call does not immediately write to the disk but instead only
writes to the buffer cache. One of the ways to making sure that the write actually happens to the disk is
using the sync() system call.

sync

and
fsync
System Calls



# include<unistd.h>

void sync(void);


sync() is used to force critical data to be written on to the disk media immediately. sync() tells the
kernel to flush the buffer cache. Kernel flushes the buffer cache at the earliest possible after the sync()
call is issued. sync() is h
eavy handed in the sense that all the data in the buffer cache, written by so many
different processes to so many different files get copied to the disk. Usually the sync() command is
executed before the UNIX system is shutdown or a removable device is unm
ounted.


sync()

is (understandably) restricted in shared UNIX environments. A frequent call to
sync()

would affect other users too much. Because every time sync() is called, the entire contents of the buffer
cache has to be copied into the disk and this
is a very time consuming operation. A solution to this
problem is to use the fsync() call.


# include<unistd.h>

i
nt

fsync(int

fd);

/* Returns 0 on error or
-
1 on error */


This function accepts a file descriptor as an argument and all cached changes only f
or that file is
written out on the disk.

Truncating files

The

open

and
create

calls when used with the appropriate flags can be used to truncate a file.
However if a file needs to be truncated after it’s opened, the following calls could be used.

truncate

and
ftruncate
System Calls

truncate

call does not work on a open file. It truncates a file to a specified length without actually
opening a file.
ftruncate

call works with open files.


# include<unistd.h>

int truncate(


const char *path,
/* Pathname */



off_t length);

/* New Length */


int

ftruncate(


int

filedes,

/* File Descriptor */


off_t length);

/* New Length */


Oddly enough, the truncate() and ftruncate
() calls can also be used to expand the file, if the new length is
greater than the original length of the file. The expanded portion of the file would be filled with bytes of
zero.

Usage:

i
nt

x,y;

i
nt

fd
=open
(
“./pqr/lmn/file”,O_RDWR);


x= truncate(
“./abc
/xyz/filen
ame

,0);

y=

ftruncate(fd,10);


Directories in UNI
X

UNIX systems implement directories also as “regular files”, except that there is a special
distinguishing bit set in their inode that differentiates the directory from regular files. The kernel
d
oes not permit writing on that bit.

Directories render the hierarchical file structure to UNIX. They are instrumental in mapping a file
name to an inode number. Recall that an inode number is a unique number associated with every
file. The inode number ind
exes to a data structure called inode that holds information about a file.

A directory can be formally defined as a regular file whose data is a sequence of entries, each
consisting of an inode number and the name of the file contained in that directory. E
very
directory contains the file names “.” and “..” whose inode numbers are those of the directory and
the parent directory. In UNIX systems, the file system hierarchy starts with the “root” directory
and hence the kernel loads the inode values of “.” and
“..” in the “root” directory during file
system initialization.

Special system calls can manipulate directories but the kernel reserves the exclusive right to write
to a directory so as to ensure the correct structure of a directory.

Search Paths and Curre
nt Working Directory

Assume you have created an executable file called “ls” that resides in your current working
directory (which is by default your “home” directory). Also note that another executable file
called “ls” would exist in your system (most like
ly in the path “/bin/ls”). Now when you type
“ls”, which of the two executables would get invoked?


The answer depends on the value of the PATH variable.

Whenever UNIX encounters an executable it searches for it in systematic way


using the PATH
variable.

The PATH variable is one of the constituents of a collection of variables called
environment variables (More about environment variables later). A typical value of the PATH
variable would like below:


/usr/bin:/etc:/usr/local/bin/:usr/ccs/bin:/home/myname
/bin:.



PATH contains the fully qualified pathnames of important directories separated by colons. This
means that when UNIX encounters an executable it would first search in


/usr/bin
”.
If not
found then it would look in


/etc

and then in


/usr/local/bin

and so on. If the
executable is not located even after traversing all the paths listed in the PATH variable then an
error message is flashed.


To see the actual value of PATH variable in your machine, type

> echo $PATH


getcwd

System Call


To get the cu
rrent working directory in UNIX, we use the “pwd” command. The system call
behind the “pwd” command is the getcwd() call.


# include<unistd.h>

char *getcwd(

char *buf, /* Returned pathname */

size_t bufsize /* sizeof buf */ ???

);

/* Returns pointer to 'bu
f' on success and NULL on

error */


The getcwd() call copies the current working directory path into ‘buf’. Note that ‘buf’ cannot be
null. The second argument denotes the size of the buffer. Ideally the size of the buffer should be
the maximum length of a

path. To be able to find the maximum path length in a UNIX system,
we use the pathconf() call. The call below finds the maximum path length starting from the “root”


long max= pathconf(“/”,_PC_PATH_MAX);


A program that uses the getcwd() would look like
below:


# include<stdio.h>

# include<unistd.h>

int main(void) {

long max;

char *buf;


max= pathconf(“/”,_PC_PATH_MAX);

buf=(char*)malloc(max);

getcwd(buf,max);

printf(“%s
\
n”,buf);


return 0;

}

chdir

System Call

The command to change directory in UNIX is the cd command. The system call behind the cd
command is the chdir system call.

# include<unistd.h>

int chdir(

const char *path

); /* Returns zero on success and
-
1

on error */


This system call changes the current working directory to that specified in “path”. The “path”
argument can be a relative path or the absolute path. Whatever inode that path leads to (if it’s a
directory inode) becomes the current working directory.

Makin
g and Removing Directories

mkdir
System Call



# include<sys/stat
.h>

int mkdir
(

const char *path,

/* Pathname */

mode_t perms


/* Permissions */


);


/* Returns 0 on success and
-
1 on error */





The semantics of this call is very similar to open() call. The mkdir() call automatically creates the
“.” and “..” links.

rmdir
System Call



# include<unistd
.h>

int rmdir
(

const char *path


/* Pathname */


);


/* Returns 0 on success and
-
1 on error

*/

Every directory in UNIX is nested inside another directory (except for the root directory). When
removing a directory, the link in the parent directory that leads to the directory to be deleted will
be removed. On removing the link, the link count of t
he corresponding inode( the deleted
directory) reduces by one. If the link count becomes zero, the file system will discard that inode.



Strictly, removing a directory only means removing a link to the directory.


A restriction on removing directories is

that the directory to be removed has to be empty. If it is
not empty, you have to remove the files in directory, other directories (and in turn the contents of
those directories) and so on. In fact, the command “rm

r”, recursively deletes a directory, as

described above.

Opening, Reading and Closing Directories


Given that directories are also regular files, it would be natural for open() and read() calls to work
on directories as well. In fact, in FreeBSD and Solaris systems, trying to open and read directories
using the standard read() and open() calls, would gi
ve some difficult
-
to
-
read output . But these
generic calls do not work on modern linux systems. There are standardized system calls
specifically for directory manipulations and they are described below.


opendir and closedir

System Calls


# include<dirent
.h>

DIR* opendir
(


const char* path

/* directory pathname */


);

/* Returns a DIR pointer or NULL on error */


The opendir() call returns a pointer to a directory stream ( much like how the standard C function
fopen() would return a pointer to a FILE t
ype). This DIR pointer is used as an argument in other
directory manipulation functions. The returned pointer points to the first entry in the directory.


# include<dirent
.h>

int closedir
(


DIR *dirp

/*DIR pointer from opendir */


);

/* Returns a 0 on
success or
-
1 on error */


closedir() is just an explicit way of informing the kernel that you are done with the use of DIR.


readdir

System Call and
dirent

structure


#include <dirent.h>

struct dirent *readdir(




DIR *dirp

/* DIR Pointer from opendir */

);

/* Returns structure or NULL on EOF or error */


readdir () returns a pointer to the dirent structure which contains a i
-
number and name as shown
below


struct dirent {


ino_t d_ino;

/* i
-
number */


char d_name[];

/* name */

};


The i
-
number and name re
turned would correspond to single entry. Hence to read all entries in a
directory, we would call readdir in a loop until EOF is reached.

Program that mimics the “ls” command



# include<dirent.h>

# include<stdio.h>




int main(){

struct dirent *direntp;

DIR *dirp;




dirp=
opendir
("."); /* Open the current directory */

while((direntp =
readdir
(dirp)) != NULL){




printf("%s
\
n",direntp
-
>d_name);

}

closedir
(dirp);

return 0;

}

Note that the output of the program is not exactly the same as that of “ls” comman
d. The output
of the program is not sorted. In fact, there is no control over the order in which entries are
actually stored or read from the directory.

Scanning a directory



Scanning a directory is looking into the contents of the directory. A more sophi
sticated function
to perform directory scans is called the scandir() call.

scandir
System Call





#include

<dirent.h>


int

scandir(



const

char

*
dir
,



/* Directory to be scanned */



struct

dirent

***
namelist
,


int

(*
select
)(const

struct

dirent

*),


int

(*
compar
)(const

struct

dirent

**,

const

struct

d
irent

**)



);


The scandir() function scans the directory
dir
, calling
filter()

on each directory entry. Entries for
which
filter()

returns nonzero are stored in strings allocated via malloc(). These strings are sorted
using the comparison function
compar()
, and then collected in array
namelist

which is allocated via
malloc(). If
filter()

is NULL, all entries are selected.


One of the

commonly used
compar()

functions is the inbuilt
alphasort()

function, given below


int

alphasort(const

struct

dirent

**
a
,

const

struct

dirent

**
b
);



The following example clarifies the above explanation.

Program to print the contents of a direc
tory in reverse sorted order



#include <dirent.h>

#include <stdio.h>

int main(void){

struct dirent **namelist;

int n;


n = scandir(".", &namelist, NULL, alphasort);

if (n < 0)




printf("scandir error
\
n");

else {




while(n
--
) {


printf("%s
\
n", namelist[n]
-
>d_name);


}




}


return 0;



}

In this program, the
filter()

function is not used. Hence all entries in the current directory (“.”) would be
returned. Each of these entries would be sorted alphabetically (using
alphasort()

function) and allotted to
namelist
. The while loop just prints the contents of the
namelist
.

Program to print only the c program files in a directory



#include <dirent.h>


#include <stdio.h>


#include <string.h>



int filter(struct dirent *entry);



int main(void){


struct dirent **namelist;


int n;




n = scandir(".", &namelist, filter , alphasort);


if (n < 0)


printf("scandir error");


else {


while(n
--
) {


printf("%s
\
n", namelist[n]
-
>d_name);


}


}


return 0;


}



int filter(struct dirent *entry)


{


char *name=entry
-
>d_name;


if(name[strlen(name)
-
1]=='c' && name[strlen(name)
-
2]=='.')



return 1;


else



return 0;


}









Programming Solutions


Program 1:

Program

to open an

existing file using O_RDONLY flag


#include<stdio.h>

#include<unistd.h>

#include<fcntl.h>

#include<string.h>

#include<stdlib.h>


int main(int argc,char *argv[])

{


int fd1,fd2;


ssize_t nbytes;


char buf[1024];


fd1 = open(argv[1],O_RDONLY,0777
);//if file

exist then only it opens otherwise not


if(fd1 ==
-
1)



printf("file not opened
\
n");


else



printf("file opened with fd = %d
\
n",fd1);


close(fd1);

}


Output:

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ ./a.out
/home/mayuri/OS_2012/FileSystemC
alls/lseek.c

file opened with fd = 3



Program 2: Program to open a file with different flags


#include<stdio.h>

#include<unistd.h>

#include<fcntl.h>

#include<string.h>

#include<stdlib.h>


int main(int argc,char *argv[])

{


int fd1,fd2;


ssize_t nbytes;


char buf[1024];



fd1 = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC|O_EXCL,0777);//if file
exist it gives error message, it will not create becoz of O_EXCL flag


//fd1 = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0777);//even if file
exist it will erase the content a
nd again create a new file with that name



if(fd1 !=
-
1)



printf("file is opened successfully with file descriptor =
%d
\
n",fd1);


else



printf("file not opened
\
n");





close(fd1);

}


Output:

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ ./
a.out new1.txt

file is opened successfully with file descriptor = 3

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ gcc open2.c

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ ./a.out new1.txt

file not opened


Program 3: Program to open a
file with truncate flag


#include<stdio.h>

#include<unistd.h>

#include<fcntl.h>

#include<string.h>

#include<stdlib.h>


int main(int argc,char *argv[])

{


int fd1,fd2;


ssize_t nbytes;


char buf[1024];



fd1 = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC|O_EXCL,07
77);//if file
exist it gives error message, it will not create becoz of O_EXCL flag





if(fd1 !=
-
1)



printf("file is created and opened successfully with file
descriptor = %d
\
n",fd1);


else{



printf("file already exist so it is not created,now open it
\
n");



fd1 = open(argv[1],O_RDWR,0777);



printf("file opened successfully with fd = %d
\
n",fd1);


}


close(fd1);

}


mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ gcc open3.c

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ ./a.out new
file2.txt

file already exist so it is not created,now open it

file opened successfully with fd = 3

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ ./a.out new2.txt

file is created and opened successfully with file descriptor = 3


Program 4:
Program which uses open and write system calls


#include<stdio.h>

#include<unistd.h>

#include<fcntl.h>

#include<string.h>

#include<stdlib.h>


int main(int argc,char*argv[])

{



int fd,nbytes;


char buf[30]="Operating Systems ",buf1[16]="GALVIN ";


fd = ope
n(argv[1],O_RDWR|O_APPEND,0777);


if(fd ==
-
1){



printf("file not opened!......
\
n");



exit(0);


}


nbytes = write(fd,buf,16);


if(nbytes ==
-
1)



printf("write error!.....
\
n");


else



printf("no of bytes written = %d
\
n",nbytes);


nbytes

= write(1,buf1,8);


if(nbytes ==
-
1)



printf("write error!.....
\
n");


else



printf("
\
nno of bytes written = %d
\
n",nbytes);


close(fd);

}


mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ ./a.out new2.txt

no of bytes written = 16

GALVIN

no of by
tes written = 8

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ ./a.out new2.txt

no of bytes written = 16

GALVIN

no of bytes written = 8

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ cat new2.txt

Operating SystemsOperating Systems



Pr
ogram 5:
Program which uses open, read and write system calls


#include<stdio.h>

#include<unistd.h>

#include<fcntl.h>

#include<string.h>

#include<stdlib.h>


int main(int argc,char *argv[])

{


int fd1,fd2;


ssize_t nbytes;


char buf
[1024],buf1[100]="OPERATING SYSTEMS";


/*fd1 = creat(argv[1],0777);


if(fd1 !=
-
1)



printf("file is created successfully with file descriptor =
%d
\
n",fd1);


else



printf("file is not created
\
n");


close(fd1);*/


//fd1 = open(argv[1],O_RDONLY,0777);


//fd
1 = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC|O_EXCL,0777);


//fd1 = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0777);


fd1 = open(argv[1],O_RDWR|O_CREAT|O_TRUNC|O_EXCL,0777);




if(fd1 !=
-
1)



printf("file is opened successfully with file descriptor =
%d
\
n",fd1);


else{



printf("file already exist ,just open it = %d
\
n",fd1);



fd1 = open(argv[1],O_RDWR,0777);



printf("file opened successfully with fd = %d
\
n",fd1);



}


close(fd1);


//fd2 = open(argv[2],O_CREAT|O_RDWR|O_APPEND|O_EXCL,0777);


fd2 = open(argv
[2],O_RDWR|O_APPEND,0777);


if(fd2 !=
-
1)



printf("file is opened successfully with file descriptor =
%d
\
n",fd1);


else{



printf("file not opened = %d
\
n",fd1);






}




nbytes = read(fd2,buf,1024);


printf("number of bytes after read = %d
\
n",nbytes);


if(nbytes ==
-
1)


{



printf("file read error
\
n");



exit(0);


}


puts(buf);


nbytes = write(fd2,buf1,20);


printf("number of bytes after write = %d
\
n",nbytes);


if(nbytes ==
-
1)


{



printf("file write error
\
n");



exit(0);


}


puts(buf1);


close(fd2);

}


Output:





Program 6:

Program which uses lseek system call

#include<stdio.h>

#include<unistd.h>

#include<fcntl.h>

#include<string.h>

#include<stdlib.h>


int main(int argc,char *argv[])

{


int fd1,fd2;


ssize_t nbytes;


int offset;


char buf[1024];


fd1

= open(argv[1],O_RDONLY,0777);//if file exist then only it opens
otherwise not


if(fd1 ==
-
1)



printf("file not opened
\
n");


else



printf("file opened with fd = %d
\
n",fd1);


offset=lseek(fd1,5,SEEK_SET);


printf("curr offset SEEK_SET= %d
\
n",offset);


if( offset ==
-
1){



printf("seek ERROR!....
\
n");



exit(0);



}


nbytes = read(fd1,buf,2);


if(nbytes ==
-
1){



printf("read error!....
\
n");



exit(0);



}


puts(buf);


offset = lseek(fd1,5,SEEK_CUR);


printf("curr offset SEEK CUR = %d
\
n",offset);


if( of
fset ==
-
1){



printf("seek ERROR!....
\
n");



exit(0);



}


nbytes = read(fd1,buf,2);


if(nbytes ==
-
1){



printf("read error!....
\
n");



exit(0);



}


puts(buf);


offset = lseek(fd1,
-
5,SEEK_END);


printf("curr offset SEEK END = %d
\
n",offset);


if( offset=
=
-
1){



printf("seek ERROR!....
\
n");



exit(0);



}


printf("curr offset = %d
\
n",offset);


nbytes = read(fd1,buf,2);


if(nbytes ==
-
1){



printf("read error!....
\
n");



exit(0);



}


printf("curr offset = %d
\
n",offset);


puts(buf);


close(fd1);

}


Output:

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ ./a.out f1.txt

file opened with fd = 3

curr offset SEEK_SET= 5

67

curr offset SEEK CUR = 12

DE

curr offset SEEK END = 13

curr offset = 13





Program 7: Program to show use of dup system call


#include<stdio.h>

#include<unistd.h>

#include<fcntl.h>

#include<string.h>

#include<stdlib.h>


int main(int argc,char *argv[])

{


int fd1,fd2,fd3,fd4,nbytes;


char buf1[100],buf2[100];


fd1 = open(argv[1],O_RDONLY,0777);


fd3 = open(argv[1],O_RDONLY,0777);


if(fd1 != 1)



printf("fd1=%d
\
n",fd1);


else{



printf("file not opened");



exit(0);


}


if(fd3 != 1)



printf("fd3=%d
\
n",fd3);


else{



printf("file not opened");



exit(0);


}


fd2 = dup(fd1);


fd4 = dup2(fd1,5);


nbytes = read(fd1,buf1,5);


if(nbytes

==
-
1){



printf("read error
\
n");



exit(0);




}



else{



printf("fd1,nbytes=%d
\
n",nbytes);



printf("%s
\
n",buf1);


}


nbytes = read(fd4,buf2,5);


if(nbytes ==
-
1){



printf("read error
\
n");



exit(0);




}



else{



printf("fd4,nbytes=%d
\
n",nbytes);



printf("%s
\
n",buf2);


}





close(fd1);


close(fd2);

}






Output:

//mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ cat f1.txt

//123456789ABCDEFGH


/*mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ ./a.out f1.txt

fd1=3

fd3=4

fd1,nbytes=5

12345

fd4,nbytes=5

6789A

*/


curr offset = 13

EF

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ cat f1.txt

123456789ABCDEFGH



Program 8: Program which opens a file in append mode, creates a file descriptor using dup system
call and
uses original and duplicate fd for writing


#include<stdio.h>

#include<unistd.h>

#include<fcntl.h>

#include<string.h>

#include<stdlib.h>


int main(int argc,char *argv[])

{


int fd1,fd2,fd3,fd4,nbytes;


char buf1[100],buf2[100];


fd1 = open(argv
[1],O_CREAT|O_RDWR|O_APPEND,0777);


if(fd1 ==
-
1)


{



printf("error OPEN
\
n");



exit(0);


}


else



printf("file opened with fd1=%d
\
n",fd1);


fd2=dup(fd1);


fd3=dup2(fd1,6);



printf("fd2=%d,fd3=%d
\
n",fd2,fd3);


nbytes = write(fd1,"hello",5);



if(nbyte
s ==
-
1)


{


printf("write error
\
n");


exit(0);


}


else



printf("nbytes = %d
\
n",nbytes);



nbytes = write(fd2," Mr. ",5);


if(nbytes ==
-
1)


{


printf("write error
\
n");


exit(0);


}


else



printf("nbytes = %d
\
n",nbytes);



nbytes

= write(fd3,"GALVIN ",7);


if(nbytes ==
-
1)


{


printf("write error
\
n");


exit(0);


}


else



printf("nbytes = %d
\
n",nbytes);



}


Output:

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ gcc dup1.c

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/
FileSystemCalls$ ./a.out newfile4.txt

file opened with fd1=3

fd2=4,fd3=6

nbytes = 5

nbytes = 5

nbytes = 7

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ cat newfile4.txt

hello Mr. GALVIN


Program 9: Program to make, open and read files and
directories in a directory


#include<stdio.h>

#include<unistd.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<dirent.h>


int main(int argc,char *argv[])

{


int md,rd,cld;


DIR *ds;


struct dirent *dir;


//*********** make directory ********


md = mk
dir(argv[1],0777);


if(md == 0)



printf("%s directory is created
\
n",argv[1]);


else



printf("%s directory is not created
\
n",argv[1]);


//**************open directory code below **************


printf("open the directory
\
n");


ds = opendir(argv[3]);


if(
ds == NULL)



printf("directory %s is not opened
\
n",argv[3]);


else



printf("ds is opened %s
\
n",argv[3]);


//*************read directory contents ******************




printf("list of files and directories
\
n");


while((dir = readdir(ds)) != NULL){





pri
ntf("%s
\
n",dir
-
>d_name);


}


//*********close Directory code below****************


if((cld = closedir(ds)) == 0)



printf("%s is successfully closed
\
n",argv[3]);


else



printf("%s is not successfully closed
\
n",argv[3]);


//*********remove directory code
below ************


rd = rmdir(argv[2]);


if(rd == 0)



printf("%s directory is removed
\
n",argv[2]);


else



printf("%s directory is not removed
\
n",argv[2]);



}


Output:

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ ./a.out
/home/mayuri/OS_2012/
myDirs /home/mayuri/OS_2012/FileSystemCalls/dir1
/home/mayuri/rtsched_algos/

/home/mayuri/OS_2012/myDirs directory is not created

open the directory

ds is opened /home/mayuri/rtsched_algos/

list of files and directories

.

..

PDS_Algo_Aprd_04_9_12

PDS_Algo_09_9_12

/home/mayuri/rtsched_algos/ is successfully closed

/home/mayuri/OS_2012/FileSystemCalls/dir1 directory is removed

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$


Program 10: Program to get current working directory, change direct
ory
, open and read that
directory


#include<stdio.h>

#include<unistd.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<dirent.h>

#include<stdlib.h>


int main(int argc,char *argv[])

{


long max;


char *buf;


DIR *ds;


struct dirent *dir;


max =
pathconf("/", _PC_PATH_MAX);


buf=(char*) malloc(max);


getcwd(buf,max);


printf("%s
\
n", buf);


int cd = chdir(argv[1]);


if(cd ==
-
1)



printf("can not change the directory
\
n");


else printf("return of cd=%d
\
n",cd);


ds = opendir(argv[1]);


if(ds == NULL)



printf("directory %s is not opened
\
n",argv[1]);


else



printf("ds is opened %s
\
n",argv[1]);


//*************read directory contents ******************




printf("list of files and directories
\
n");


while((dir = readdir(ds)) != NULL){





printf("%s
\
n",
dir
-
>d_name);


}


return (0);

}


Output:

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ gcc chwdir.c

mayuri@mayuri
-
Latitude
-
E5520:~/OS_2012/FileSystemCalls$ ./a.out
/home/mayuri/OS_2012/FileSystemCalls/dir1

/home/mayuri/OS_2012/FileSystemCalls

return of cd=0

ds is opened /home/mayuri/OS_2012/FileSystemCalls/dir1

list of files and directories

.

..

f.txt


**************************


























Programming Exercise on File and Directory related System Calls