fprintf and fscanf.
These are not system calls! These are C standard
library calls. Clearly, the implementations of these function
calls must make system calls to get the I/O done, but the
functions themselves are regular ol' C library functions.
We need to understand a bit about how C library functions are
implemented. In particular, we need to understand
I/O buffering.
Insert analogy here for why this makes I/O faster! Figure 3.5 in the book shows how dramatic an effect buffering can have. Remember, this buffering is done by the C standard library routines, not by the kernel!
I/O associated with a given file stream (FILE*)
may be unbuffered, line buffered, or
fully buffered. The kind of buffering you get
depends on how the program is actually called. For example,
if foo is called like this
./foo < tempits input will be fully buffered, but its output will be line buffered. Within a program, you can change the buffering mode with
setbuf
and setvbuf.
void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int type, size_t size);
By default, input and output are line buffered.
If you want to "flush" an output buffer, i.e. have
everything stored in it actually sent to its destination,
you can call fflush (try man
fflush). In a line buffering context, of course, you
can just write a newline!
exec, about which we
will have much to say in the coming weeks, whose job it is
to start processes running. So a C program's process (in
fact any process) is born when another process
calls exec. When the process starts, a C
start-up routine (which you as a regular ol' programmer do
not write) is executed. It is responsible for some
initialization and then for calling main,
which of course is where you as a programmer start.
return from main,exit (try
man -s3C exit for info), or
_exit and
_Exit.
The different ways to terminate a process have different implications for exit handlers, the facility that allows for automatic "cleanup" when processes termninate.
main() ---
the C language provides a mechanism by which we can be sure
that some actions are taken before program exits
... regardless of whether the program exits by returning from
main or by calling exit.
What you do is provide a function that the C's runtime
system will call before actually exiting from
exit. (No, that's no typo.) The lingo is that
the function is an "exit handler", and you need to
"register" the exit handler in order to tell C's runtime
system to call it before exit exits. You
register a function as an exit handler using the
atexit function in stdlib. This is a funny
function, in that its argument is a function
pointer. In fact, when you define a function
foo, the name "foo" refers to a
function pointer. It has a type, but a type that looks very
different from most, because it musdt specify the return
type as well as the number and types of the function's
arguments.
Let's look at atexit's prototype:
int atexit(void (*func)(void));The return type is
int, that's clear, but what
does the parameter "void (*func)(void)" tell us?
It says that the parameter func is a function
that takes no arguments and returns nothing. So, here's a
simple function that fits the bill:
void foo() { fprintf(stderr,"I'm dying ...\n");
This function takes no arguments and returns nothing, so you
can pass foo as an argument to
atexit. For example:
#include <stdlib.h>
#include <stdio.h>
int main()
{
atexit(foo);
return 0;
}
If I run this program, I get I'm dying ... as
output. Since the function exit calls the exit
handlers, can you see why it'd be bad to call
exit from within an exit handler like foo?
One question that arises is this: if I register several handlers, in what order do they get called when the program terminates? They are guaranteed to be called in the reverse of the order in which they were registered.
| p2.c | Compiling & running |
#include <stdio.h>
#include <stdlib.h>
void foo() { fprintf(stderr,"foo says bye.\n"); }
void bar() { fprintf(stderr,"bar says bye.\n"); }
int main()
{
atexit(foo);
atexit(bar);
sleep(2);
return 0;
}
|
bash$ gcc -o p2 p2.c bash$ ./p2 bar says bye. foo says bye. |
_exit (and _Exit)exit is that it is listed
both as a system call and a C standard library routine.
However, it is truly a C standard library routine since it
deals with C sdio buffers.
In fact, exit makes a system call to
_exit to finally terminate a process.
(_Exit is functionally
equivalent). _exit also
does cleanup, but what it cleans up is related to the kernel's
bookkeeping related to the process, not the C library's
bookkeeping. We haven't learned much about the kernel yet, so
what _exit does do is something we'll talk about
later. Intstead we focus on what it doesn't do: it doesn't
call exit handlers, and it doesn't flush stdio buffers!
| p3.c | Compiling & running |
#include <stdio.h>
#include <stdlib.h>
void foo() { fprintf(stderr,"foo says bye.\n"); }
void bar() { fprintf(stderr,"bar says bye.\n"); }
int main(int argc, char **argv)
{
atexit(foo);
atexit(bar);
sleep(2);
fprintf(stdout,"Oops ... forgot a newline!");
if (argc > 1 && strcmp(argv[1],"exit") == 0)
exit(0);
if (argc > 1 && strcmp(argv[1],"_exit") == 0)
_exit(0);
if (argc > 1 && strcmp(argv[1],"_Exit") == 0)
_Exit(0);
return 0;
}
|
bash$ gcc -o p3 p3.c bash$ ./p2 bar says bye. foo says bye. Oops ... forgot a newline!bash$ bash$ ./p2 exit bar says bye. foo says bye. Oops ... forgot a newline!bash$ bash$ ./p2 _exit bash$ bash$ ./p2 _Exit bash$ |