Some Operating System Tutorials
Some Operating System Tutorials
Hardware Clocks
There are three hardware clocks which the Linux kernel makes use of:
1. The Real Time Clock (RTC) exists in all PCs and operates independently of the CPU and all
other chips. It has its own power supply (a battery) and therefore continues to tick even when
the computer is shut off. The RTC is read once by the kernel during system initialization to
determine the current time and date. After this, the RTC is no longer used by the kernel.
2. The Time Stamp Counter (TSC) exists as a 64-bit register in Pentium (and later) Intel 80x86
processors. This register is incremented once every cycle. Thus, the faster the CPU (in terms
of megahertz), the faster this clock ticks.
3. The Programmable Interval Timer (PIT) is a device that can be programmed by the kernel to
issue a timer interrupt after a specified length of time. It is this clock that is used to implement
process scheduling.
Open a bash shell window and type sudo apt install linux-source.
This will download a tar file containing the source for Ubuntu.
The file is saved in the usr/src folder.
You will need to extract the file to view the contents.
After extracting the source code files, navigate to the directory /linuxsource- 5.10.11. This
is the base directory for the source code files for the Linux operating system.
Go into the directory kernel/time.
Look for a file called time.c and view the contents of this file.
Search within time.c for the function sys_gettimeofday().
Browse the code of the sys_gettimeofday() to get an idea of how it works. You are not
expected to understand it thoroughly.
Different computer hardware will require different machine instructions to access the hardware clock.
The implementation of sys_gettimeofday() can be found in the function do_gettimeofday()in the
file /linux-source- 5.10.11/kernel/time/timekeeping.c.
Go to the directory /linux-source- 5.10.11/kernel/time
Locate and view the file timekeeping.c
Search within timekeeping.c for the function do_gettimeofday().
Browse the code of the do_gettimeofday() to get an idea of how it works. You are not
expected to understand it thoroughly.
The first activity is urgent and is performed by the interrupt handler directly, with interrupts disabled.
The other activities are executed in the so-called “bottom halves” known as TIMER_BH and
TQUEUE_BH
Linux terminology: A bottom half is a low-priority function, usually related to interrupt handling, that is
executed when the kernel finds it convenient, i.e. after the kernel has performed all urgent and critical
instructions related to the interrupt handler.
do_timer() increments jiffies and marks TIMER_BH for execution. When timer_bh()(the bottom
half TIMER_BH) runs, it updates timers by calling update_process_times().
For the system time, the timer bottom half uses the current value of jiffies to compute the current time.
It stores the value in struct timeval xtime, where the value can be read by other kernel functions,
such as sys_gettimeofday().
Follow the sequence of calls described above starting with timer_interrupt().
They are actually countdown timers, that is, they can be initialized to some value and then used to
reflect the passage of time by counting down toward zero.
When the timer reaches zero, it raises a signal to notify another part of the system that the counter
has reached zero. Then it resets the value and begins counting down again. These timers use the
kernel time to keep track of the following three intervals of time relevant to every process. (note: each
type of interval is represented by a defined constant).
ITIMER_REAL: Reflects the passage of real time and is implemented using the it_real_value
and it_real_incr fields.
ITIMER_VIRTUAL: Reflects the passage of virtual time. This time is incremented only when the
corresponding process is executing. It is implemented using the it_virt_value and
it_virt_incr fields.
ITIMER_PROF: Reflects the passage of time during which the process is active (virtual time)
plus the time that the kernel is doing work on behalf of the corresponding process (for
example, reading a timer). It is implemented using the it_prof_value and it_prof_incr
fields.
Each timer can be initialized with the setitimer() system call, and can be read using the
getitimer() call. The signatures of these two functions are respectively:
In this structure, it_interval stores the value that will be used to reset the timer when it expires.
it_value contains the current value of the timer. Both of these variables are of type struct
timeval , whose definition you have already seen in the exercise above with gettimeofday().
The which parameter takes one of the following defined constants to indicate which timer is being
referenced.
ITIMER_REAL
ITIMER_VIRTUAL
ITIMER_PROF
The next exercise will illustrate how these functions work.
Create a C file called mytimer.c .
Include the header file sys/time.h .
Declare a variable v of type struct itimerval .
Set the it_interval field of v to 10 seconds and 0 microseconds.
Set the it_value field of v to 10 seconds and 0 microseconds.
Set up a real interval timer by calling setitimer(). Simply use NULL as the last parameter.
Write a for loop with 1000 iterations.
Within the for loop do the following:
o Write another for loop to waste a bit of time by executing the null instruction 9999999
times.
o Get the current value of the real timer that you set up by calling getitimer(). Use a
pointer to v as the second parameter.
o Using printf(), print the second and microsecond values of the current state of the
timer.
Compile and run your program mytimer.c
Signals
UNIX systems define a fixed set of signals that can be raised by one process, causing another
process to be interrupted.
The second process can catch the signal by executing a “handler" or if it does not specify a handler, it
will execute some default action such as terminating or ignoring the signal.
Thus, signals are a means for one process to notify another that some event has happened, and for
the receiving process to take some appropriate action.
Types of Signals
There are numerous pre-defined signals. They are defined in a source code file called signals.h
o Using Konqueror, navigate to the directory /usr/src/linux/include/asm.
o Search for the file signals.h and browse it.
o There are two special signals called SIGUSR1 and SIGUSR2, which are reserved for user-
defined
purposes.
o Locate the definition of the SIGUSR1 and SIGUSR2 signals in signals.h.
Raising a Signal
A signal can be “raised" (i.e. created) by the operating system, or by a user process.
A user process can raise a signal by using the C language kill() function. Do not be thrown off by
the name “kill". This function does not necessarily kill the process to which the signal is being sent.
The kill() function takes two parameters, the first is the process id of the receiving process and the
second is the defined constant representing the signal to be raised. For example:
kill(process pid, SIGUSR1);
will send the SIGUSR1 signal to the process with process id equal to the integer process pid.
Handling a Signal
The receiving process can handle a signal in two steps:
1. Write a signal handler function.
2. Register the signal handler function to deal with a particular type of signal
The signal handler function must have the same signature as the following sample:
void signal handler(int signal number){…}
Registration of the signal handler function is done via the signal() function. The first argument to
signal() is a defined constant representing the type of signal that the handler function will deal with,
and the second argument is a pointer to the signal handler function. A pointer to a function is given
simply by the name of the function.
Example
The following invocation of signal() registers the function signal handler() to handle signals of type
SIGUSR1.
signal(SIGUSR1, signal handler);