door Ismael Ripoll Over de auteur: Behaalde een graad aan de Polytechnische Universiteit van Valencia in 1996. Professor in operating systemen op het departement van DISCA. Onderzoeksgebieden omvatten onder andere real-time scheduling en operating systemen. Linux gebruiker vanaf 1994. Hobbies: met de rugzak door de Pyreneën, skiën en hobby electronica. Inhoud: |
Kort:
In dit tweede artikel over RT-Linux zal ik proberen wat meer in te gaan op de praktijk. Voordat we hieraan beginnen echter, zal ik eerst een kakelvers real-time operating systeem bespreken, genaamd Linux KURT.
Het meest karakteristiek aan KURT is zijn scheduling mechanisme. Er is besloten voor een cyclische scheduler. Een dergelijk type scheduler gebruikt altijd een tabel, plan genaamd, die alle verdeel-acties bevat: Moment van activeren, uit te voeren taak, duur van de taak, etc. De tabel wordt aangemaakt in de ontwerpfase. Tijdens executie bestaat het werk eruit om eenvoudig de tabel uit te lezen en de instructies hieruit op te volgen. Aan het einde van de tabel aangekomen springt de scheduler terug naar het begin en het ritueel kan opnieuw beginnen -- vandaar de term "cyclisch". Een dergelijke scheduler heeft vele voordelen:
RT-Linux applicaties gaan verder. Hiermee kunnen we de besturing van de PC overnemen (Ik zeg hier PC en niet computer omdat er op dit moment nog geen implementatie van RT-Linux is voor andere platformen) net als bij MS-DOS. Tijdens het uitvoeren van een real-time taak is het mogelijk om alle PC-poorten te benaderen, interrupt-handlers te installeren en deze tijdelijk uit te zetten. Met andere woorden, we kunnen het systeem "crashen" alsof we onder Windows zitten. Deze mogelijkheid is zeer aantrekkelijk voor diegenen die er lol in scheppen om elektronische apparaatjes aan de computer te koppelen.
Modules zijn "delen van het operating systeem" die in een operationeel
systeem kunnen worden ingebracht en er weer uitgehaald. Wanneer een
programma, dat bestaat uit diverse codebestanden, wordt gecompileerd dan wordt eerst van
ieder bestand een objectfile ".o" gemaakt, waarna
deze objecten aan elkaar worden geknoopt (linken) en alle verwijzingen worden
opgelost om één executeerbaar bestand te maken (executable).
Laten we even aannemen dat de objectfile waarin main()
staat direct uit kan
worden gevoerd en dat het operating systeem daarbij in staat is om dit in geheugen te laden,
tezamen met de andere benodigde objectfiles, waarbij verwijzingen worden opgelost daar waar
dat nodig is. Welnu, dat kan de kernel ook met zichzelf doen. Tijdens opstart wordt alleen het
executeerbare bestand vmlinux ingeladen, waarin alleen de onmisbare
basisfunctionaliteit aanwezig is. Later, in de operationele toestand, kan het modules inladen en
verwijderen al naar gelang de behoefte.
Het gebruik van modules is optioneel in Linux, deze optie moet mee worden gecompileerd in de kernel. In alle distributies die ik ken is dat al gebeurd.
Het is zelfs mogelijk om nieuwe modules aan te maken en direct in te laden zonder dat het systeem een herstart nodig heeft of een nieuwe compilatie.
Als een module eenmaal is geladen, is het een onderdeel van de kernel geworden, daarom:
example1.c
#define MODULE #include <linux/module.h> #include <linux/cons.h> static int output=1; int init_module(void) { printk("Output= %d\n",output); return 0; } void cleanup_module(void){ printk("Adiós, Bye, Chao, Ovuar, \n"); }
# gcc -I /usr/src/linux/include/linux -O2 -Wall -D__KERNEL__ -c example1.cDe optie -c geeft aan gcc door dat hij moet stoppen na het genereren van de objectfile en ook niet moet linken. Het resultaat hiervan is het bestand example1.o.
De kernel kent geen standard output, waardoor we de functie
printf()
niet kunnen gebruiken. In plaats daarvan heeft de kernel een functie
genaamd printk()
die bijna identiek is behalve dan dat de uitvoer naar een ring
wordt gestuurd. Al dit soort uitvoer verdwijnt in deze buffer. Dit zijn feitelijk de boodschappen die
we zien als het systeem op wordt gestart. Op ieder gewenst moment kunnen we de inhoud van
de buffer bekijken met het commando dmesg of door direct in het bestand
/proc/kmsg te kijken.
Merk op dat er geen functie main()
is en in plaats daarvan de functie
init_module()
, zonder parameters. cleanup_module()
is de
laatste functie die aan wordt geroepen voordat de module wordt verwijderd. Het laden van de
module kan met het commando insmod.
# insmod example1.oOp dit moment hebben we de module example1 geïnstalleerd en de functie
init_module()
uitgevoerd. Zie voor het resultaat:
# dmesg | tail -1 Output= 1Het commando lsmod geeft een overzicht van de modulen die op dat moment geladen zijn.
# lsmod Module Pages Used by: example1 1 0 sb 6 1 uart401 2 [sb] 1 sound 16 [sb uart401] 0 (autoclean)Als laatste gebruiken we nu rmmod om de module te verwijderen.
# rmmod example1 # dmesg | tail -2 Output= 1 Adiós, Bye, Chao, Orvua,De uitvoer van dmesg laat zien dat de functie
cleanup_module()
is uitgevoerd.
We hoeven nu alleen te weten hoe we parameters aan een module doorgeven. Niets is minder makkelijk. We kunnen globale variabelen een waarde geven door parameters door te geven aan insmod. Bijvoorbeeld:
# insmod example1.o output=4 # dmesg | tail -3 Output= 1 Adíos, Bye, Chao, Orvua, Output= 4We weten nu alle relevante dingen van modules, laten we verder gaan met RT-Linux.
Er zijn twee manieren om RT-Linux te gebruiken:
example2.c
#define MODULE #include <linux/module.h> #include <linux/kernel.h> #include <linux/version.h> #include <linux/rt_sched.h> RT_TASK task; void fun(int computo) { int loop,x,limit; limit = 10; while(1){ for (loop=0; loop<computo; loop++) for (x=1; x<limit; x++); rt_task_wait(); } } int init_module(void) { RTIME now = rt_get_time(); rt_task_init(&task,fun, 50 , 3000, 1); rt_task_make_periodic(&task, now+(RTIME)(RT_TICKS_PER_SEC*4000)/1000000, (RTIME)(RT_TICKS_PER_SEC * 100)/1000000); return 0; } void cleanup_module(void){ rt_task_delete(&task); }
# gcc -I /usr/src/linux/include/linux -O2 -Wall -D__KERNEL__ -D__RT__ -c example2.cOmdat het een module betreft is de ingangsfunctie
init_module()
. De eerste
actie is het inlezen van de tijd en dit opslaan in een lokale variabele; de functie
rt_get_time()
geeft het aantal RT_TICKS_PER_SEC
terug,
verstreken sinds het opstarten van het systeem (in de huidige implementatie is
RT_TICKS_PER_SEC
gelijk aan 1.193.180, wat een resolutie oplevert van
0,838 microseconden). Met behulp van rt_task_init()
wordt de structuur
"task" geïnitialiseerd maar de taak wordt nog niet opgestart.
Het programma van deze taak is fun()
, die mee wordt gegeven als tweede
parameter. De volgende parameter is een waarde die door wordt gegeven aan de nieuwe taak
wanneer deze start. Merk op dat fun()
een int
variabele
verwacht. De volgende parameter geeft de grootte van de stack voor deze taak;
omdat iedere taak zelfstandig uit wordt gevoerd heeft iedere taak een éigen stack. De
laatste parameter is de prioriteit; in dit geval, met slechts één taak op het systeem,
kunnen we een willekeurige waarde opgeven.
rt_task_make_periodic()
maakt de taak periodiek met twee tijdswaarden als
parameters. De eerste geeft de absolute tijd aan waarop de taak moet worden gestart. De
tweede waarde geeft de periode aan waarop hij vervolgens bij herhaling wordt gestart.
De real-time taak (de functie fun()
) is een oneindige lus met slechts twee
acties: een lus die alleen maar tijd verdoet en dan rt_task_wait()
aanroept.
rt_task_wait()
is een routine die de geactiveerde taak laat pauzeren tot de
volgende activering, op welk moment hij door zal gaan met de instructie die op
rt_task_wait()
volgt. De lezer moet zich realiseren dat een periodieke taak niet
steeds bij het begin start maar dat de taak steeds zichzelf moet stoppen (na de gedane arbeid)
en moet wachten op het volgende startsignaal. Met dit mechanisme kan men dus een taak
maken die zichzelf alleen maar initialiseert bij de eerste ronde.
Voor het uitvoeren van example2 moeten we eerst de module
rt_prio_sched installeren omdat ons programma de functies
rt_task_make_periodic(), rt_task_delete()
en rt_task_init()
nodig heeft. De functie rt_get_time()
zit niet in de module maar in de Linux
kernel. We hoeven hem daarom niet te installeren voor gebruik.
# modprobe rt_prio_sched # insmod ./example2.oOmdat rt_prio_sched een systeemmodule is, is hij aangemaakt tijdens compilatie van de Linux kernel en staat het bestand dus in de directory /var/modules/2.0.33. We gebruiken hier het commando modprobe omdat dit makkelijker is met het inladen van modules (hij zoekt naar de modules op de divers standaard plaatsen) (zie modprobe(1)).
Als alles goed is gegaan zien we met lsmod dat beide modules zijn geladen.
En dan hebben we nu dus een real-time programma lopen. Valt je iets op? Bij een trage
processor zal het de lezer reeds zijn opgevallen dat Linux nu trager reageert dan normaal. Je
kunt proberen het aantal omlopen van de lus in fun()
te vergroten door andere
waarden voor de derde parameter van rt_task_init()
te gebruiken. Het is aan te
bevelen om hierbij het programma ico te draaien om te zien hoeveel minder
processortijd er over is want dat is het effect van het real-time programma op Linux, het lijkt
alleen maar alsof de processor traag is geworden. Linux gelooft als het ware dat de processen
meer tijd nodig hebben om hun werk te doen. Als de rekentijd van de taak groter wordt dan 100
microseconden dan zal Linux "hangen" omdat Linux een achtergrondtaak is en de
real-time taak gebruikt dan 100% van de processortijd. Eigenlijk "hangt" Linux niet
echt, het krijgt alleen de processor niet.
Met FIFOs kan er dus worden gecommuniceerd tussen taken of met gewone taken.
Gezien vanuit een gewoon proces is een FIFO een speciaal, karakter-georiënteerd, bestand. Meestal onder de naam /dev/rtf0, /dev/rtf1 enz. Deze bestanden zijn normaal gesproken niet aanwezig op Linux dus moeten ze als volgt worden aangemaakt:
# for i in 0 1 2 3; do mknod /dev/rtf$i c 63 $i; doneAls er nog meer FIFOs nodig zijn kunnen deze eenvoudig extra worden aangemaakt. Het bestand vormt het interface met een afhandelingprogramma van het operating systeem. Dit programma moet er dan wél in zitten, anders is het bestand niets waard. Sterker nog; voor ieder van dit soort bestanden geldt dat het openen ervan een foutmelding oplevert als er geen afhandelingprogramma achter zit.
Gezien vanuit een real-time taak worden de FIFOs gebruikt via bepaalde functieaanroepen:
rt_create(unsigned int fifo, int size)
: maakt een FIFO aan met buffergrootte
size
. Vanaf dat moment, en tot aan de verwijdering, kan het apparaat
/dev/rtf[fifo] worden benaderd.
rt_destroy(unsigned int fifo)
: de betreffende FIFO wordt verwijderd en het geheugen
teruggegeven.
rt_fifo_put(fifo, char *buf, int count)
: probeert count
bytes vanuit de buffer
buf
weg te schrijven. De functie retourneert -1
als er niet
genoeg ruimte is in de FIFO buffer.
rt_fifo_get(fifo, char *buf, count)
: probeert count
bytes te lezen uit de
FIFO. Als er niet voldoende data aanwezig is wordt er -1
geretourneerd.
example3.c
#define MODULE #include <linux/module.h> #include <linux/rt_sched.h> #include <linux/rtf.h> #include <asm/io.h> RT_TASK task; static int filter(int x){ static int oldx; int ret; if (x & 0x80) { x = 382 - x; } ret = x > oldx; oldx = x; return ret; } void fun(int dummy) { char data; char temp; while (1) { if (rtf_get(0, &data, 1) > 0) { data = filter(data); temp = inb(0x61); temp &= 0xfd; temp |= (data & 1) << 1; outb(temp,0x61); } rt_task_wait(); } } int init_module(void){ rtf_create(0, 4000); /* enable counter 2 */ outb_p(inb_p(0x61)|3, 0x61); /* to ensure that the output of the counter is 1 */ outb_p(0xb0, 0x43); outb_p(3, 0x42); outb_p(00, 0x42); rt_task_init(&task, fun, 0 , 3000, 1); rt_task_make_periodic(&task, (RTIME)rt_get_time()+(RTIME)1000LL, (RTIME)(RT_TICKS_PER_SEC / 8192LL)); return 0; } void cleanup_module(void){ rt_task_delete(&task); rtf_destroy(0); }
Er wordt een periodieke, real-time taak gecreëerd met een frequentie van 8192 Hz. Deze taak leest bytes in vanuit FIFO 0 en stuurt dit door naar de luidspreker van de PC. Als we nu een geluidsbestand in ".au" formaat kopiëren naar /dev/rtf0 dan kunnen we er naar luisteren. Het moge duidelijk zijn dat dit niet om aan te horen is omdat de hardware van de PC slechts één bit heeft voor modulatie van het signaal. De testing/sound directory heeft een bestand linux.au dat kan worden gebruikt voor het testen.
Om dit te compileren en uit te voeren:
# gcc -I /usr/src/linux/include/linux -O2 -Wall -D__KERNEL__ -D__RT__ -c example3.c # modprobe rt_fifo_new # modprobe rt_prio_sched # insmod example3.o # cat linux.au > /dev/rtf0Merk op dat het cat commando kan worden gebruikt voor het schrijven naar welk bestand dan ook, dus ook naar device files. We kunnen ook het commando cp gebruiken.
Om te kunnen ervaren hoe het real-time gedrag van invloed is op de kwaliteit, hoeven we alleen een programma te maken wat hetzelfde doet in een gewoon Linux proces.
example4.c
#include <unistd.h> #include <asm/io.h> #include <time.h> static int filter(int x){ static int oldx; int ret; if (x & 0x80) x = 382 - x; ret = x > oldx; oldx = x; return ret; } espera(int x){ int v; for (v=0; v<x; v++); } void fun() { char data; char temp; while (1) { if (read(0, &data, 1) > 0) { data = filter(data); temp = inb(0x61); temp &= 0xfd; temp |= (data & 1) << 1; outb(temp,0x61); } espera(3000); } } int main(void){ unsigned char dummy,x; ioperm(0x42, 0x3,1); ioperm(0x61, 0x1,1); dummy= inb(0x61);espera(10); outb(dummy|3, 0x61); outb(0xb0, 0x43);espera(10); outb(3, 0x42);espera(10); outb(00, 0x42); fun(); }
# gcc -O2 example4.c -o example4en om het uit te voeren:
# cat linux.au | example4Om poorten vanuit een normaal programma te kunnen benaderen moeten we permissie krijgen vanuit het operating systeem. Dit is een normale (en noodzakelijke) veiligheidsmaatregel om te voorkomen dat ieder programma bijvoorbeeld direct naar schijf gaat schrijven. De aanroep
ioperm()
vraagt het operating systeem toestemming voor het benaderen van
bepaalde in- en uitvoer adressen. Alleen programma's met root permissie
zullen toestemming krijgen. Een ander interessant detail is de manier waarop de frequentie van
8192 Hz voor het moduleren van het geluid wordt gegenereerd. Hoewel er een systeemaanroep
is met de naam nanodelay()
is de resolutie hiervan slechts enkele
milliseconden, waardoor we gebruik moeten maken van een temporele klok met wachtlus. De
wachtlus is dusdanig ingesteld dat het min of meer werkt op een 100 MHz Pentium.
Ik stel nu voor om example4 te testen tegelijk met het uitvoeren van het ico programma. Hoe hoor je het nu? Hoe is dit ten opzichte van de real-time versie? Heeft real-time hiermee zijn nut bewezen?
|
Site onderhouden door het LinuxFocus editors team
© Ismael Ripoll LinuxFocus 2000 |
Translation information:
|
2000-03-27, generated by lfparser version 1.3