1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
|
<!--startcut ==============================================-->
<!-- *** BEGIN HTML header *** -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML><HEAD>
<title>Linux Through an Oscilloscope LG #95</title>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#0000AF"
ALINK="#FF0000">
<!-- *** END HTML header *** -->
<!-- *** BEGIN navbar *** -->
<A HREF="millson.html"><< Prev</A> | <A HREF="index.html">TOC</A> | <A HREF="../index.html">Front Page</A> | <A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue95/pramode.html">Talkback</A> | <A HREF="../faq/index.html">FAQ</A> | <A HREF="rondina.html">Next >></A>
<!-- *** END navbar *** -->
<!--endcut ============================================================-->
<TABLE BORDER><TR><TD WIDTH="200">
<A HREF="http://www.linuxgazette.com/">
<IMG ALT="LINUX GAZETTE" SRC="../gx/2002/lglogo_200x41.png"
WIDTH="200" HEIGHT="41" border="0"></A>
<BR CLEAR="all">
<SMALL>...<I>making Linux just a little more fun!</I></SMALL>
</TD><TD WIDTH="380">
<CENTER>
<BIG><BIG><STRONG><FONT COLOR="maroon">Linux Through an Oscilloscope</FONT></STRONG></BIG></BIG>
<BR>
<STRONG>By <A HREF="../authors/pramode.html">Pramode C.E</A></STRONG>
</CENTER>
</TD></TR>
</TABLE>
<P>
<!-- END header -->
<html>
<head>
<title>Linux Through an Oscilloscope</title>
</head>
<body>
<h2>Introduction</h2>
<p>
It was some time since I had wired up a few
circuits and watched them on my old 20MHz
Oscilloscope. I thought it might be interesting
to observe how the complex, dynamic nature of
a multitasking operating system influences the
working of timing sensitive code by viewing
signals generated by such programs
on the scope. This article
describes a few experiments which I did, first
with a `normal' 2.4.18 kernel and then with a
kernel patched with `real time extensions'
provided by the RTAI project. The reader is assumed
to have some background in simple kernel programming.
<h2>Experimental setup</h2>
<p>
I converted an old Cyrix CPU based system which was lying
around unused to my `embedded linux' experimentation platform.
The motherboard was taken out of the cabinet - HDD, monitor,
keyboard etc were removed - only the Ethernet card with a boot
ROM remained - together with an ISA protoboard. This machine boots
from a full fledged Linux system situated just a few feet away.
This way, I can conduct hardware experiments without worrying
too much about damaging expensive hardware. I have the option of
booting either a plain 2.4.18 kernel or an RTAI patched one.
<h2>Simple waveform generation</h2>
<p>
Here is a little user space program which, when executed as the superuser,
generates a waveform on the parallel port output pins - I can view this
on the scope.
<pre>
#include <asm/io.h>
#define ON 100000
#define OFF ON*10
delay(unsigned int i)
{
while(i--);
}
main()
{
iopl(3);
while(1) {
outb(0xff, 0x378);
delay(ON);
outb(0x0, 0x378);
delay(OFF);
}
}
</pre>
The working of the program is simple. Parallel port pins 2 to 9 act as output
pins - they can be accessed through an i/o port whose address is 0x378. You write
0xff to 0x378, you are turning on (ie, putting about 5V) on all these pins, you
write 0x0 and your are turning off the voltage on these pins. The program has to be
compiled with the -O2 option and executed as super user (if the outb is to work, the
iopl call, which is concerned with setting some privilege levels, should work. For
iopl to work, you have to be the superuser).
<p>
On my system, I observe a waveform with an on time of about 2.5 to 2.7ms with
my scope set at 1ms/division. The result will
surely vary depending on the speed of your processor.
<h2>Why simple things are not so simple</h2>
<p>
Anybody who has done a basic course in microprocessors will know how
to generate `delays' by writing loops. That's exactly what we have
done here - absolute kids stuff.
<p>
Just being curious, I log on to another console and run the `yes'
command, which generates a continuous stream of the character `y'
on the screen. I watch the scope and see that my nice looking signal
has gone haywire. The ON and OFF periods have been so lengthened that
what I see is mostly a continuos line which keeps on jumping from 0V
to 5V.
<p>
I do another experiment. I `flood ping' (the ping command with the -f
option) the sytem from a faster machine - again, I notice that the signal
on the scope gets wildly disturbed.
<p>
The reason behind this behaviour is not at all difficult to see. My
program is now contesting with another one for CPU cycles. In between
executing the delay loop, control can switch to the other program, thereby
lengthening the delay perceived by the first program. Flood pinging
results in lots of activity within the OS kernel, this too has a
detrimental effect on the timing of my program.
<p>
The solution to the problem is simple - just don't disturb the program
which generates the waveform. Let it have full control of the CPU. Then
the question is why have a complex multitasking OS at all? Let's see.
<p>
I call the program which generates the signal a `realtime' program.
Let's visualize the program as a `task' whose job is to `toggle' the
parallel port pins at specified intervals. If the generated waveform
is used to control a physical appliance like, say, a servo motor (the
rotation of the servo is controlled by the length of the `on period'
of a pulse whose total on+off period is somewhere around 20ms. When the
ON period varies from 1ms to 2ms, the servo rotates by about 180 degree),
variation in pulse length can have dramatic effects. My Futaba S2003
servo swings wildly when it is controlled by a program like the one
above, if it is perturbed by some other process. A real time program has
timing deadlines which it HAS to meet, for correct operation. The
classical solution to designing control applications has been to use
dedicated microcontrollers and digital signal processors. But with PC
hardware becoming so cheap, a very wide range of applications are cropping
up where we require the ability to run programs with
sensitive timing requirements correctly, and, at the same time, also
do things like communicate over the network, visualize data with
graphical interfaces, log data on to secondary storage etc, jobs
where timing deadlines are not an issue, so called `non-realtime' jobs.
<p>
If it is possible to modify the Linux kernel in some way so that
the timing constraints imposed on some tasks (which are created and executed
in some special manner) are always met, even under the prescence of
other `non-realtime' tasks, then we have an ideal situation. We will
see a bit later in this article that not one, but many such solutions
are available.
<h2>Sleeping Vs Looping</h2>
<p>
Besides the fact that the timing of the program depends a lot on
other activities going on in the system, we are burning up CPU
cycles by executing a tight loop (also, on a complex microprocessor
like the Pentium, it is difficult to compute delays by counting
instructions). Why not let the program sleep?
<p>
By using functions like `nanosleep', we instruct the Operating System
to put our process to sleep, to be woken up at a specified time. But,
here again, there is a possibility that our process does not wake up
and execute at the desired time because the Operating System was too
busy executing some action in kernel mode (say, processing TCP/IP packets,
or doing disk I/O) or another process got scheduled just before the
kernel woke up our process.
<h2>Doing it in kernel space</h2>
<p>
What if we implement our signal generation code as a kernel space
module?
<pre>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/param.h>
#include <asm/uaccess.h>
#include <asm/io.h>
static char *name = "foo";
static int major;
#define ON 100000
#define OFF ON*10
void delay(unsigned int i)
{
while(i--);
}
static int
foo_read(struct file* filp, char *buf, size_t count, loff_t *f_pos)
{
while(1) {
outb(0xff, 0x378);
delay(ON);
outb(0x0, 0x378);
delay(OFF);
}
return 0;
}
static struct file_operations fops = {
read: foo_read,
};
int init_module(void)
{
major = register_chrdev(0, name, &fops);
printk("Registered, got major = %d\n", major);
return 0;
}
void cleanup_module(void)
{
printk("Cleaning up...\n");
unregister_chrdev(major, name);
}
</pre>
Executing an infinte loop in the kernel has disastrous consequences - as far
as user processes are concerned. No user process would be able to execute
until control comes out of kernel mode (this is the way the OS is designed).
What we would like to have is a situation where realtime as well as nonrealtime
processes coexist.
<p>
Although user space processes now can't disturb our program, it is still
possible to generate interrupts on the network card by flood pinging. As
interrupts are serviced even when kernel code is executing, the waveform
displayed on the scope starts jumping around as usual.
<p>
It is possible to go to sleep within the kernel - this prevents the system
from getting locked up - but then it does not solve our problem of peaceful
coexistence of realtime as well as non realtime code.
<h2>Enter Real Time Linux</h2>
<p>
What if we slide in a `nano kernel' between Linux and our hardware? This
kernel would be in control of both Linux as well as a set of `real time
tasks'. Linux will be treated as a very low priority task which will be
executed only when no other higher priority `real time' tasks are
executing. The control of interrupts would be in the hands of this
specialized kernel - requests by Linux to disable interrupts will be
treated in such a way that interrupts don't really get disabled - only
Linux won't be able to see those interrupts - the real time tasks will
still be able to execute their interrupt handlers without too much delay.
<p>
This novel concept, introduced by Dr.Victor Yodaiken, lead to the birth of
RTLinux. Many other universities and research instituitions have attempted
their own implementations - one of the most promising (and completely
non proprietary) being RTAI, developed by researchers at Dipartimento
di Ingegneria Aerospaziale - Politecnico di Milano (DIAPM).
<h2>Getting and Installing RTAI</h2>
<p>
RTAI can be obtained from <a href="http://www.aero.polimi.it/~rtai">here</a>. There are two
major components:
<ul>
<li>An HAL (hardware abstraction layer) patch to the Linux kernel.
<li>A set of modules for performing scheduling, interprocess communication,
synchronization etc.
</ul>
Before patching and installing the new kernel, the instructions given in
the README.INSTALL file should be read carefully (especially those regarding
certain kernel configuration options. "Set version information on
loadable modules" should be disabled. You are most probably using a uni
processor system - so don't forget to disable SMP support (maybe, disable
power management also)). Once you reboot with the new kernel, you can
compile the main RTAI modules and examples. Before running any programs,
you will need to load the three modules - rtai.o, rtai_fifos.o and
rtai_sched.o.
<h2>Generating waveforms with RTAI tasks</h2>
<p>
Let's look at an RTAI program which creates a waveform on the parallel
port output pins:
<pre>
#include <linux/module.h>
#include <rtai.h>
#include <rtai_sched.h>
#define LPT1_BASE 0x378
#define STACK_SIZE 4096
#define TIMERTICKS 1000000 /* 1 milli second */
static RT_TASK my_task;
static void fun(int t)
{
unsigned char c = 0x0;
while(1) {
outb(c, LPT1_BASE);
c = ~c;
rt_task_wait_period();
}
}
int init_module(void)
{
RTIME tick_period, now;
rt_set_periodic_mode();
rt_task_init(&my_task, fun, 0, STACK_SIZE, 0, 0, 0);
tick_period = start_rt_timer(nano2count(TIMERTICKS));
now = rt_get_time();
rt_task_make_periodic(&my_task, now + tick_period, 2*tick_period);
return 0;
}
void cleanup_module(void)
{
stop_rt_timer();
rt_busy_sleep(10000000);
rt_task_delete(&my_task);
}
</pre>
<p>
Let's look at the general idea before we examine specific
details. First, we need a `task' to do anything useful. The `task'
is simply a C function. The structure of most of our tasks would
be something like this - perform some action, sleep for some time,
perform some action again, repeat. One way to sleep is to call
`rt_task_wait_period' - the question is how long do we sleep? We
sleep for a certain fixed `period', which will be a multiple of
a base `tick'. The system 8254 timer can be programmed to generate
interrupts at a rate of say 1KHz (ie, 1000 times a second). The RTAI
scheduler takes scheduling decisions at each tick - if we set the
period of our task to be `2 ticks' and if the interval between each
tick is 1ms, then the scheduler will wake up our task after 2ms.
<p>
We start with `init_module'. We first configure the timer as a
`periodic timer' (another mode is available). The `rt_task_init'
function accepts the address of an object of type RT_TASK, the
address of our function and a stack size, besides some other
values. Some kind of `initialization' is performed and information
is stored in the object of type RT_TASK which can be later used
for identifying this particular task.
<p>
Our TICK_PERIOD is 1000000 nano seconds (1 milli second). The
nano2count function converts this time into internal `count
units'. The timer is started with a tick period equal to 1ms
(which is what the `start_rt_timer' function does).
<p>
What remains is to start our task and set its period (remeber,
the period is used by rt_task_wait_period to set the time at
which the task is to be awakened). We set the period to 2 ticks
and instruct the scheduler to start it at the next tick itself.
<p>
The body of our task is very simple - it simply writes a value
to the parallel port output pins, complements the variable which
stores that value and waits for the next period (which will be
2ms). After waking up, it performs the same sequence. Again and
again and again... The end result is we observe a waveform on
the scope whose on time is 2ms and off time also is 2ms.
<p>
I observed the waveform first on an unloaded system. I then
resorted to flood pinging the system. The waveform on the
scope remained steady. The promise that RTAI gives us is that
it will always run Linux as a very low priority task - Linux will
execute only when no real time tasks are to be serviced. A
real time task waking up will result in control getting transferred
to it immediately (of course, there are delays involved in
preempting whatever is being done now, activating the real time
scheduler and transferring control back to the task which just
woke up - these delays also need not be constant). That is
why we are able to observe a fairly steady signal even under
load.
<p>
Here is a code segment which demonstrates the use of a
function - `rt_sleep':
<pre>
#define LPT1_BASE 0x378
#define STACK_SIZE 4096
#define TIMERTICKS 1000000 /* 1 milli second */
#define ON_TIME 3000000 /* 3 milli seconds */
#define OFF_TIME 1000000 /* 1 milli second */
static RT_TASK my_task;
RTIME on_time, off_time;
static void fun(int t)
{
while(1) {
outb(0xff, LPT1_BASE);
rt_sleep(on_time);
outb(0x0, LPT1_BASE);
rt_sleep(off_time);
}
}
int init_module(void)
{
RTIME tick_period, now;
rt_set_periodic_mode();
rt_task_init(&my_task, fun, 0, STACK_SIZE, 0, 0, 0);
tick_period = start_rt_timer(nano2count(TIMERTICKS));
on_time = nano2count(ON_TIME);
off_time = nano2count(OFF_TIME);
now = rt_get_time();
rt_task_make_periodic(&my_task, now + tick_period, 2*tick_period);
return 0;
}
</pre>
The basic tick period is 1ms. Our on and off times are
integral multiples of this period (3ms and 1ms each).
An invocation of `rt_sleep(on_time)' will put the task
to sleep - it gets woken up after 3 tick periods. It does
some action and again goes to sleep for one tick period.
<h2>Using FIFO's to communicate between real time
and non real time tasks</h2>
<p>
It may be required to transmit data from a user space
non realtime program to an RTAI task (and back). This
is very easily done with the use of fifo's. For example,
an RTAI task may be generating a PWM (pulse width modulated)
signal and you may have to control the width from user
space.
The RTAI
installation creates several device files under /dev/
going by the name rtf0, rtf1 etc. The user program
identifies each fifo by its name while the RTAI task
does it with numbers 0, 1, 2 etc.
<pre>
#include <linux/module.h>
#include <linux/errno.h>
#include <rtai.h>
#include <rtai_sched.h>
#include <rtai_fifos.h>
#define STACK_SIZE 4096
#define COMMAND_FIFO 0
#define FIFO_SIZE 1024
int fifo_handler(unsigned int fifo)
{
char buf[100];
int r;
r = rtf_get(COMMAND_FIFO, buf, sizeof(buf)-1);
if (r <= 0) return r;
rt_printk("handler called for fifo %d, get = %d\n", fifo, r);
buf[r] = 0;
rt_printk("data = %s\n", buf);
return 0;
}
int init_module(void)
{
/* Create fifo, set handler */
rtf_create(COMMAND_FIFO, FIFO_SIZE);
rtf_create_handler(COMMAND_FIFO, fifo_handler);
return 0;
}
void cleanup_module(void)
{
printk("cleaning up...\n");
}
</pre>
In `init_module', we create a fifo and set `fifo_handler' as
a function to be invoked when somebody writes to the fifo. The
`rtf_get' function reads data from the fifo. After compiling and
loading the module, if we do something like:
<pre>
echo hello > /dev/rtf0
</pre>
we will see the handler getting invoked and reading data from the
fifo.
<h2>Further Reading</h2>
<p>
If you are interested in general real time programming
issues, you should start with the excellent <a href="http://people.mech.kuleuven.ac.be/~bruyninc/rthowto/">Real Time and Embedded Guide</a> written
by <b>Herman Bruyninckx</b>. RTAI programming is explained in
detail in the <b>RTAI manual</b> and <b>RTAI programming guide</b>
available for download from the project home page.
<h2>Conclusion</h2>
<p>
An Operating System which provides support for deterministic execution
of tasks with stringent timing requirements is just one part of
the realtime system design landscape. After playing with RTAI for a few
days, I realized that this (realtime design) is something which can't
be done as a hobby by a novice like me - you have to invest a lot of
time, effort and patience in understanding your system thoroughly (hardware
as well as software) and using the tools well. But then, that shouldn't
stop you from experimenting and having a little bit of fun!
</body>
</html>
<!-- *** BEGIN author bio *** -->
<P>
<P>
<!-- *** BEGIN bio *** -->
<P>
<img ALIGN="LEFT" ALT="[BIO]" SRC="../gx/2002/note.png">
<em>
I am an instructor working for IC Software in Kerala, India. I would have loved
becoming an organic chemist, but I do the second best thing possible, which is
play with Linux and teach programming!
</em>
<br CLEAR="all">
<!-- *** END bio *** -->
<!-- *** END author bio *** -->
<!-- *** BEGIN copyright *** -->
<hr>
<CENTER><SMALL><STRONG>
Copyright © 2003, Pramode C.E.
Copying license <A HREF="../copying.html">http://www.linuxgazette.com/copying.html</A><BR>
Published in Issue 95 of <i>Linux Gazette</i>, October 2003
</STRONG></SMALL></CENTER>
<!-- *** END copyright *** -->
<HR>
<!--startcut ==========================================================-->
<CENTER>
<!-- *** BEGIN navbar *** -->
<A HREF="millson.html"><< Prev</A> | <A HREF="index.html">TOC</A> | <A HREF="../index.html">Front Page</A> | <A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue95/pramode.html">Talkback</A> | <A HREF="../faq/index.html">FAQ</A> | <A HREF="rondina.html">Next >></A>
<!-- *** END navbar *** -->
</CENTER>
</BODY></HTML>
<!--endcut ============================================================-->
|