MYF

[RA] Ch11 Interacting With the User and System

Reading Assignment: All of Programming Chapter 11 Interacting With the User and System

The Operating System

The program asks the operating system(OS)-low-level software responsible for managing all of the resources on the system for all programs-to access hardware on its behalf. The program makes this request via a system call-a special type of function call that transfers control from the program into the operating system.

While your C code can make system calls directly, it is more common to use functions in the C library. The C library functions then make system calls as they need. The OS then interacts with the hardware appropriately.

Difference between system call and library call:

  • System call transfers control into the OS, requesting it to perform a task
  • Library call calls a function found in a library, such as the C standard library.

Errors from System Calls

System calls can fail in a variety of ways. Whenever these system calls fail in C, they set a global variable called errno, which stands for “error number”. The C library has a function perror which prints a descriptive error message based on the current value of errno.

Note that since errno is global, you must take care not to call anything that might change it before you test it or call perror. For example,

1
2
3
4
5
6
// Broken Code
int x = someSystemCall();
if(x != 0) {
printf("someSystemCall() failed!\n"); //may change errno
perror("The error was: ");
}

Here, printf might change errno(it makes system calls), so we may not have a correct description of the error from perror.

Command Line Arguments

One form of input that our programs can take is command line arguments. We can write our programs so that they can examine their command line arguments. To do so, we must write main with a slightly different signature:

1
2
3
int main(int argc, char **argv){

}

The first, int argc is the count of how many command line arguments were passed in. The second is an array of strings which contains the arguments that were passed in. The 0th element of argv contains the name of the program, as it was invoked on the command line. We can see this behavior in a toy example:

1
2
3
4
5
6
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char **argv){
printf("Hello, my name is %s\n", arg[0];
return EXIT_SUCCESS;
}

Whatever other arguments were passed to our program appear in argv[1] through argv[argc-1]. The arguments appear in the array in the order that they were written on the command line.

Complex Option Processing

Some programs, however, perform more complex option processing, which means they take various flags and options. getopt function is part of the C library, and parses the command line arguments, accounting for such considerations as options which have arguments, and short and long name versions.

The Environment Pointer

While less commonly, main can potentially take a third argument char ** envp, which is a pointer to an array of strings containing the values of environment variables. If you do so, the elements of the array are strings of the form variable=value(e.g.PATH=/bin:/usr/bin). You can also access the environment variables with the functions getenv, setenv and unsetenv. See their man pages for details.

Process Creation

When the command shell wants to run another program, it makes a couple of system calls. First, it makes a call to create a new process(fork). This new process(which is an identical copy of the original, distinguishable only by the return value of the fork system call) then makes another system call(execve) to replace its running program with the requested program. The execve system call takes an argument specifying the file with the binary to run, a second argument specifying the values to paa the new programm as argv, and a third argument specifying the values to pass the new program as argv, and a third argument specifying the values to pass for envp.

Files

Another form of interaction with the outside world is accessing files.

Opening a File

We can open a file and get the stream associated with that file. The stream is a sequence of data that can be read or written, which can be represented in chars. Typically, we can open a file with the fopen function, which has the following prototype: FILE* fopen(const char *filename, const char *mode);

In this function,

  • The filename must be a null terminated string. It can be either absolute path or a path relative to the current working directory of the program.
  • The mode is the mode of the file, it can be used for reading or writing, whether to create the file if it does not exist.

The actual struct of FILE is a complex one, we should not attempt to use the information details, it can be system dependent. We should use the abstraction to think about what a program will do.

Real FILE struct do not contain the name of the file, but a file descriptor

If the file you requested may not exist or you may not have the permissions required to access it.

Mode Read and/or write Does not exist Truncate Position
r read only fails no beginning
r+ read/write fails no beginning
w write only created yes beginning
w+ read/write created yes beginning
a writing created no end
a+ rea/write created no End

Reading a File

Once we have our file open, we can use fgetc, fgets, or fread to read input from file.

When using fgetc, you should write in this way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argv, char **argv){
if (argv != 2) { /* omitted */ }
FILE *f = fopen(argv[1], "r");
if (f == NULL) { /* ommitted */ }
int c;
int letters = 0;
while ( (c = fgetc(f) != EOF) {
if(isalpha(c)){
letters++;
}
}
printf("%s has %d letters in it\n", argv[1], letters);
return EXIT_SUCCESS;
}

The fgets function is useful when you want to read one line(with a maximum length) at a time. This function has the following prototype:

1
char *fgets(char *str, int size, FILE *stream);

fgets function requires the size of the string, we can examine whether there is a \n in the line to judge whether it reads the entire line.

1
2
3
4
5
6
7
8
9
FILE *f = fopen(argv[i], "r");
if(f == NULL) { /* ... */ }
char line[LINE_SIZE];
while(fgets(line, LINE_SIZE, f) != NULL) {
if(strchr(line, '\n') == NULL) {
printf("Line is too long\n");
}
// do something
}

When reading non-textual data from a file like image, video, sound. The specific size of integer used for each piece of data is part of the file format specification. In this case, the most appropriate function is fread, the prototype is :

1
size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);

The fread function returns how many items were successfully read. As with, fgets, you should employ feof and/or ferror to obtain more information if fread returns fewer items than you requested.

fscanf and sscanf are also helpful. There is introduction in man pages.

Writing files

One option to write something in files is fprintf function. This function behaves the same as the familiar printf function, except that it takes an additional argument, which is a FILE * specifying where to write the output. fputc can be used to write a single character at a time, or fputs to write a string without any format conversions. For non-textual data, the best choice is fwrite, which has the prototype:

1
size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE* stream);

Closing files

After finished with a file, fclose function should be applied:

1
int fclose(FILE *stream);

The type of return value is int, indicating the success or failure of the operation.

Other Interactions

Sometimes programs interact with the rest of the system or outside world in ways other than reading from/wirting to files.

When transferring data across the network, the program obtains a file descriptor for a socket-the logical abstraction over which data is transferred-via the socket system call.

Another form of interaction that looks like a file is a pipe. A pipe is a one way communication channel between two processes. One process writes data into one “end” of the pipe, and the other process reads from the pipe, obtaining the data that was written in by the first process.

Another way is through device special files. These files are typically found in the /dev directory. For example, /dev/random provides access to the secure pseudo-random number generator provided by the kernel. The /dev/urandom device can be used instead to generate numbers with the same algorithm, but without regard to if there is sufficient entropy available for security-sensitive purposes.