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
Enter the password to open this PDF file:
File name:
-
File size:
-
Title:
-
Author:
-
Subject:
-
Keywords:
-
Creation Date:
-
Modification Date:
-
Creator:
-
PDF Producer:
-
PDF Version:
-
Page Count:
-
Preparing document for printing…
0%
Σχόλια 0
Συνδεθείτε για να κοινοποιήσετε σχόλιο