Note: You'll often hear references to "the kernel". The kernel refers to the lowest level of the operating system --- the lowest level software buffer between applications and the hardware. So the kernel is just the core of the Unix operating system.In other words, a system call represents a request, by your program to the kernel, for some action or information. Our first system call is asking the kernel "do you have the time?".
System calls are function calls, and the system call to
request the time uses the function time. It's
prototype is
time_t time(time_t *tloc);You can try
man -s2 time for more info. If you
do, you'll notice that, right up at the top, you're told
that this is a system call! So it's not part of the
standard C library, it is a function that is implemented as
part of the Unix operating system. What time
does with a non-zero argument is outisde of the scope of
this discussion. What's important is that time(0)
returns the time ... as a great big number. What does that
number mean? It is the time in seconds
since "the Epoch": 00:00:00 UTC, January 1, 1970.
The "UTC" is "Coordinated Universal Time". The type used to
store this value is time_t. So what's that?
You can actually figure that out by searching for it in the
header file that defines time_t.
bash$ grep time_t /usr/include/sys/time.h | head -1
typedef long time_t; /* time of day in seconds */
What this tells you, is that type "time_t" is just
another name for "long", which on our machines
is just another name for int.
| p1.c | Compiling & running |
#include <stdio.h>
#include <time.h>
int main()
{
int t = time(0);
printf("%i\n",t);
return 0;
}
|
bash$ gcc -o p1 p1.c bash$ ./p1 1265134163 |
So why is time a system call? Why is the kernel involved? Because to get the time we need to consult one of the system resources, the clock, and we never use system resources directly; only through the kernel.
time returns a number like
1265134163, it has indeed given us the time. That's just not
such useful information. We'd like to convert it to something
intelligeble. There are routines for converting time
representations, but they are not system calls, since
conversion does not require accessing any system resources.
They are handled by standard C library calls. The functions
we'll look at are:
#include <time.h>
char *ctime(const time_t *clock);
struct tm *localtime(const time_t *clock);
char *asctime(const struct tm *tm);
The first of these takes the time as a time_t
and returns the time as a nicely formatted, human-readable
string. Note, however, that it asks for a pointer to the
time_t object. That's going to impact how we call this function!
| p2.c | Compiling & running |
#include <stdio.h>
#include <time.h>
int main()
{
time_t t = time(0);
printf("%i\n",t);
char *s = ctime(&t);
printf("%s",s);
return 0;
}
|
bash$ gcc -o p2 p2.c bash$ ./p2 1265135684 Tue Feb 2 13:34:44 2010 |
When you're using system calls and standard library calls that return anything more complicated that in/float/char, you need to make sure you understand who is responsible for the memory that results are stored in. If you don't, bad things happen. Consider the following program, which reports how long it takes you to type the alphabet.
| p3.c (this version is bad!) | Compiling & running |
#include <stdio.h>
#include <time.h>
int main()
{
time_t t0 = time(0);
printf("Type the alphabet: ");
char alpha[32];
int success = 0;
while(!success && scanf("%s",alpha) == 1)
success = (0 == strcmp(alpha,"abcdefghijklmnopqrstuvwxyz"));
time_t t1 = time(0);
printf("\nStarted at %s\nFinished at %s\n",ctime(&t0),ctime(&t1));
printf("It took you %i seconds\n",t1-t0);
return 0;
}
|
bash$ gcc -o p3 p3.c bash$ ./p3 Type the alphabet: abcdefghijklmnopqrstuvwxyz Started at Tue Feb 2 13:44:43 2010 Finished at Tue Feb 2 13:44:43 2010 It took you 9 seconds |
So, if it took me 9 seconds, why are the two date/times
identical? Well, we need to understand who's responsible
for the char array returned
by ctime. It turns out that there's a single
static array that ctime always writes its
results in ... in fact the char* returned
by ctime is always the same. So the second
call overwrites the first call! Thus, if we want to save
the string returned by ctime for later use, we
must copy it into another buffer, where it won't get
overwritten. There are two ways to do this: allocate our
own buffer and use strcpy to copy the string
returned by ctime into it, or use
the strdup function, which allocates the buffer
for us. Compare these three functions:
| function | example | who's in charge of memory |
ctime |
char* s = ctime(&t); ... | ctime is responsible for both
allocating and deallocating the buffer pointed to
by s. However, the buffer may get overwritten! |
strcpy | char* tmp = ctime(&t); char* s = (char*)malloc((strlen(tmp)+1)*sizeof(char)); strcpy(s,tmp); ... free(s); | We are responsible for both allocating and
deallocating the buffer pointed to by s. |
strdup | char* s = strdup(ctime(&t)); ... free(s); | strdup is responsible for
allocating the buffer pointed to by s, but
we are responsible for deallocating it. |
localtime. Here's its prototype:
struct tm *localtime(const time_t *clock);Hopefully we understand the argument: it takes (a pointer to) the time in seconds since the Epoch. But what is the result? It returns a pointer to a
struct tm object.
What's that? Well if you look at the man page for localtime
you'll eventually find the following:
The tm structure contains the following members:
int tm_sec; /* seconds after the minute [0, 60] */
int tm_min; /* minutes after the hour [0, 59] */
int tm_hour; /* hour since midnight [0, 23] */
int tm_mday; /* day of the month [1, 31] */
int tm_mon; /* months since January [0, 11] */
int tm_year; /* years since 1900 */
int tm_wday; /* days since Sunday [0, 6] */
int tm_yday; /* days since January 1 [0, 365] */
int tm_isdst; /* flag for daylight savings time */
So let's print out the date as month/day/year.
| p4.c | Compiling & running |
#include <stdio.h>
#include <time.h>
int main()
{
time_t t0 = time(0);
struct tm* pt = localtime(&t0);
printf("%i/%i/%i\n", 1 + pt->tm_mon, pt->tm_mday, 1900 + pt->tm_year);
return 0;
}
|
bash$ gcc -o p4 p4.c bash$ ./p4 2/2/2010 |
Question: since localtime is returning a
pointer to a struct tm, who's responsible for
allocating and deallocating that struct? This is actually
just like ctime. It's localtime's
job to do both, and any information in there is in danger of
being overwritten. If you want your own copy, you can do
this:
struct tm mytm = *localtime(&t);This says set
mytm equal to the object pointed
to localtime's result. The semantics of
setting structs equal is that you get memberwise
assignment. In this case, where all members
are ints, that works perfectly.
It's critially important that you understand types of struct's and pointers to structs and accessing struct fields.
mktime function, which is the
inverse of the localtime function. It too is a C
standard library function:
time_t mktime(struct tm *timeptr);So we create a
struct tm object, fill its fields
appropriately, and pass mktime a pointer to it.
| p5.c | Compiling & running |
#include <stdio.h>
#include <time.h>
int main()
{
time_t t0 = time(0);
struct tm gd;
gd.tm_sec = 0;
gd.tm_min = 0;
gd.tm_hour = 12;
gd.tm_mday = 25;
gd.tm_mon = 4; /* Remember: months since January - [0, 11] */
gd.tm_year = 2012-1900;
gd.tm_isdst = 1;
time_t t1 = mktime(&gd);
int r = t1 - t0;
printf("%i seconds 'til graduation!\n",r);
printf("that's %i hours, %i minutes and %i seconds\n",
r/3600,(r%3600)/60,r%60);
return 0;
}
|
bash$ gcc -o p5 p5.c bash$ ./p5 72821752 seconds 'til graduation! that's 20228 hours, 15 minutes and 52 seconds |
Now, how could you use this to print out the number of seconds 'til graduation every time you logged in?