door 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