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 | // Broken Code |
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 | 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 |
|
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 char
s. 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 | int main(int argv, char **argv){ |
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 | FILE *f = fopen(argv[i], "r"); |
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.