CS 330 System Calls for I/0


Highlights of This Lab:


Introduction to System Calls

System calls are commands that are executed by the operating system. "System calls are the only way to access kernel facilities such as file system, multitasking mechanisms and the interprocess communication primitives."(Rochkind's book, Advanced Unix Programming)

Here is a short list of System Calls that we may be using in your labs:


System Calls versus Library Routines

The tricky thing about system calls is that they look very much like a library routine (or a regular function) that you have already been using (for instance, printf). The only way to tell which is a library routine and which is a system call is to remember which is which.

Another way to obtain information about the system call is to refer to Section 2 of the man pages. For instance, to find more about the "read" system call you could type:

% man -S 2 read

By contrast, on our current version of Linux, if you try:

% man read 

Section 1 of the library will be displayed (indicated by the 1 in parenthesis).

Section 3 contains library routines. By issuing a:

% man -S 3 fread 
you will learn more about the "fread" library routine.

Some library functions have embedded system calls. For instance, the library routines scanf and printf make use of the system calls read and write. The relationship of library functions and system calls is shown in the below diagram (taken from John Shapley Gray's Interprocess Communications in UNIX)


"The arrows in the diagram indicate possible paths of communication. As shown, executable programs may make use of system calls directly to request the kernel to perform a specific function. Or, the executable program may invoke a library function which in turn may perform system calls." (page 4 and 5, Interprocess Communications in UNIX).


Using System Calls for File I/O

The main systems calls that will be needed for this lab are

Each of these will be covered in their own subsection

open():

   #include <sys/types.h>
   #include <sys/stat.h>
   #include <fcntl.h>
   int open (const char *path, int flags [, mode_t mode ]);

path is a string that contains the name and location of the file to be opened.

The mode of the file to be opened is determined by the oflag variable. It must have one of the following values:
O_RDONLY    Read only mode
O_WRONLY    Write only mode
O_RDWR      Read/Write mode
and may have one or more options bitwise OR-ed on. A few examples of options include:
O_APPEND    If the file exists, append to it.
O_CREAT     If the file does not exist create it.
O_EXCL      (Only with O_CREAT.) If the file already exists, 
              open fails and returns an error.
O_TRUNC     If the file exists and is being opened for writing, 
              truncate it to length 0.

Upon success, open returns a file descriptor. If the operation fails, a -1 is returned. Use the 'man -S 2 open' command for more information on this system call.

The convenience system call creat(const char *path, mode_t mode) is equivalent to open(path, O_WRONLY|O_CREAT|O_TRUNC, mode). Use the 'man -S 2 creat' command for more information on this command.

When you use the O_CREAT flag you are required to provide the mode argument to set access permissions for the new file. The permissions you supply will be AND-ed against the complement of the user's umask. For example:

outFile=open("myfile",O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); 
In the above example, the permissions set on "myfile" will be read and write by the user (S_IRUSR | S_IWUSR). For more details on these modes or permissions see man -S 2 open.

close():

   #include < unistd.h>
   int close(int fildes); 

close() closes the file indicated by the file descriptor fildes.

The operating system will free any resources allocated to the file during its operation. Use the 'man -S 2 close' command for more information on this system call.

read():

   #include < unistd.h>
   ssize_t read(int fildes, void *buf, size_t nbyte);

read() attempts to read nbyte bytes from the file associated with fildes into the buffer pointed to by buf.

If nbyte is zero, read returns zero and has no other results. On success a non-negative integer is returned indicating the number of bytes actually read. Otherwise, a -1 is returned. Use the 'man -S 2 read' command for more information on this system call.

write():

   #include < unistd.h>
   ssize_t write(int fildes, const void *buf, size_t nbyte);

write() attempts to write nbyte bytes from the buffer pointed to by buf to the file associated with fildes.

If nbyte is zero and the file is a regular file, write returns zero and has no other results. On success, write returns the number of bytes actually written. Otherwise, it returns -1. Use the command 'man -S 2 write' for more information on this system call.

The following is a sample program reads a file and displays its contents on the screen.

//Modified from page 7 of Interprocess Communication in Unix by John
//Shapely Gray 
//Usage: a.out filename
//Displays the contents of filename
#include <cstdio> #include <unistd.h> #include <cstdlib> #include <sys/types.h> //needed for open #include <sys/stat.h> //needed for open #include <fcntl.h> //needed for open using namespace std; int main (int argc, char *argv[]) { int inFile; int n_char=0; char buffer[10]; inFile=open(argv[1],O_RDONLY); if (inFile==-1) { exit(1); } //Use the read system call to obtain 10 characters from inFile while( (n_char=read(inFile, buffer, 10))!=0) { //Display the characters read n_char=write(1,buffer,n_char); } return 0; }  

You will notice that the first argument to a read/write system call is an integer value indicating the file descriptor. When a program executes, the operating system will automatically open three file descriptors:

A write to file descriptor 1 (as in the above code) will be writing to your terminal.

stat():

   #include < sys/types.h>
   #include < sys/stat.h>
   int stat(const char *path, struct stat *buf) 

stat() obtains information about the named file, and puts them in the stat structure.

This command comes in handy if you want to emulate the ls command. All the information that you need to know about a file is given in the stat structure.

The stat structure is defined as:

mode_t st_mode; /* protection */
ino_t st_ino; /* this file's number */
dev_t st_dev; /* device file resides on */
dev_t st_rdev; /* device identifier (special files only) */
nlink_t st_nlink; /* number of hard links to the file*/
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
off_t st_size; /* file size in bytes */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last data modification */
time_t st_ctime; /* time of last file status change*/
blksize_t st_blksize; /* preferred I/O block size */
blkcnt_t st_blocks; /* number 512 byte blocks allocated */


Use the 'man -S 2 stat' command for more information on this system call.

fstat():

  #include < sys/types.h>
  #include < sys/stat.h>
  int fstat(int fildes, struct stat *buf)

fstat() is like stat(), but works on a file that is specified by the fildes file descriptor. Use the 'man -S 2 fstat' command for more information on the fstat system call.

Example

   struct stat statBuf;
int err, FD; FD = open("openclose.in", O_WRONLY | O_CREAT, S_IREAD | S_IWRITE); if(FD == -1) /* open failed? */ exit(-1);
err = fstat(FD, &statBuf); if(err == -1) /* fstat failed? */ exit(-1);
printf("The number of blocks = %d\n", statBuf.st_blocks);

perror()

In most cases, if a system call or library function fails, it returns a value of -1 and assigns a value to an external variable called errno. This value indicates where the actual problem occurred.

It is a good programming habit to examine the return value from a system call or library function to determine if something went wrong. If there was a failure, the program should do something such as display a short error message and exit (terminate) the program. The library function perror can be used to produce an error message.

The following is an example of using perror to provide some error checking:

//Modified from page 7 of Interprocess Communication in Unix by John
//Shapely Gray

//checking errno and using perror


#include <cstdio>
#include <unistd.h>
#include <cstdlib>
#include <errno.h>      //must use for perror


using namespace std;

//extern int errno;  //in linux, don't seem to need this


int main (int argc, char *argv[])
{
        int n_char=0;
	char buffer[10];

        //Initially n_char is set to 0 -- errno is 0 by default
        printf("n_char = %d \t errno = %d\n", n_char, errno);

        //Display a prompt to stdout
        n_char=write(1, "Enter a word  ", 14);

        //Use the read system call to obtain 10 characters from stdin
        n_char=read(0, buffer, 10);
        printf("\nn_char = %d \t errno = %d\n", n_char, errno);

        //If read has failed
        if (n_char==-1)
        {
                perror(argv[0]);
                exit (1);
        }

        //Display the characters read
        n_char=write(1,buffer,n_char);

        return 0;
}

This is what the program will do if you run it and type in the word "hello":

% a.out
n_char = 0       errno = 0
Enter a word  hello

n_char = 6       errno = 0
hello

If you want it to display a error message, change the file number for the read system call to 3 (a file number that we haven't opened)

% a.out
n_char = 0 errno = 0
Enter a word
n_char = -1 errno = 9
a.out: Bad file descriptor

Two things to note about this are:

  1. perror(argv[0]) will print out a.out followed by a colon ":". If we had perror("error when opening"), it would print "error when opening" followed by a colon ":". We can also say perror(NULL) and it will only display the description of the error.
  2. You will notice that the errno is 9, this is equivalent to a symbolic constant EBADF defined in a <errno.h> file. The error message associated with error code 9 is "Bad file descriptor".

    To see a list of these errno and descriptions on our current version of linux, try:

    more /usr/include/asm-generic/errno*

References