Jednou z hlavních úloh kernelu je rízení HW pripojeného k pocítaci. Nutnou soucástí je komunikace kernelu s jednotlivými zarízeními. Protoze jsou procesory vetšinou rádove rychlejší nez hardware, se kterým komunikují, není pro kernel ideální vyslat zádost a cekat na odpoved od potencionálne pomalého hardware. Místo toho musí být kernel pripraven pokracovat ve své cinnosti a se zarízením komunikovat pouze pokud je pripraveno. Jedním rešením tohoto problému je polling. Kernel v pravidelných intervalech kontroluje stav hardware v systému a podle toho reaguje. To zpusobuje zvýšení rezijních nároku, protoze polling vzniká periodicky bez ohledu na to, zda je zarízení vubec aktivní.
Prerušení dovolují hardware komunikovat s procesorem. Napr. pokud píšete na klávesnici, ovladac klávesnice (hardwarové zarízení, které rídí klávesnici) vyvolá prerušení, aby upozornil operacní systém na stisknutí klávesy. Prerušení jsou speciální elektrické signály, které posílá HW zarízení procesoru. Procesor dostane prerušení a signalizuje operacnímu systému, ze muze zpracovat nová data. Hardwarové zarízení generují výjimky asynchronne s ohledem na hodiny procesoru – muze se vyskytnout kdykoli. Kernel tedy muze být prerušen kdykoli behem prerušení procesoru.
Prerušení je fyzicky tvorené elektronickým signálem, vzniká v zarízení a smeruje prímo na vstupní piny radice prerušení. Radic prerušení pak vyšle signál procesoru. Procesor detekuje tento signál, preruší soucasný beh a obslouzí prerušení. Procesor pak muze upozornit operacní systém a ten vhodne obslouzí toto prerušení.
Hodnoty prerušení se nazývají IRQ (interrupt request lines). Typicky jsou to císla, napr. na PC je IRQ 0 prerušení casovace a IRQ 1 je prerušení z klávesnice. Ne všechna císla jsou takto striktne definována. Napr. prerušení, která se týkají zarízení na PCI sbernici, jsou pridelována dynamicky. Jiné architektury (ne PC) mají podobná dynamická prirazení pro hodnoty prerušení. Dulezité je, ze urcité prerušení se týká jistého zarízení (príp. Nekolika zarízení) a kernel to ví.
Výjimky se casto povazují za soucást prerušení. Na rozdíl od prerušení jsou ale synchronní (s ohledem na hodiny procesoru). Obcas se jim proto ríká synchronní prerušení. Výjimky vznikají v procesoru behem zpracování instrukcí, bud jako reakce na chybu programátora (delení nulou) nebo na nezvyklou situaci, kterou musí vyrešit kernel (výpadek stránky). Protoze mnoho architektur procesoru obsluhuje výjimky podobným zpusobem jako prerušení, infrastruktura kernelu pro jejich obsluhu je taktéz podobná. Vetšina vecí, které reknu o prerušeních (asynchronní prerušení vytvorené HW) se proto týká i výjimek (synchronní prerušení generované samotným procesorem).
Funkce, kterou kernel spustí jako odpoved na urcité vyvolané prerušení, se nazývá handler prerušení nebo interrupt service routine (ISR). Kazdé zarízení, které generuje prerušení, má své handlery prerušení. Napr. jedna funkce obsluhuje prerušení ze systémového casovace a jiná funkce obsluhuje prerušení generované klávesnicí. Handler prerušení pro zarízení je soucástí jeho ovladace (kód, který rídí dané zarízení).
V linuxu jsou handlery prerušení normální
C-ckovské funkce. Musí se shodovat
v daném
prototypu, který kernelu umozní s tímto kódem
pracovat ne jako s normální funkcí, ale jako s
handlerem prerušení. Handlery prerušení
se od normálních funkcí liší tím,
ze handlery jsou volány po urcitém prerušení
a ze bezí ve speciálním kontextu (kontext
prerušení), o kterém ješte budu mluvit.
Protoze se prerušení muze vyskytnout kdykoli, handler prerušení musí být spustitelný v jakémkoli okamziku. Mel probehnout rychle, aby se znovu spustil prerušený beh programu.
Handler by mel prinejmenším potvrdit hardwaru, ze obsluhuje jeho prerušení. Vetšinou mají handlery dost "práce". Napr. handler prerušení pro sítové zarízení musí zkopírovat sítové pakety z hardwaru do pameti, zpracovat je a predat je príslušné aplikaci.
Tyto dva cíle – ze handler prerušení by mel být vykonán rychle, ale zároven musí vykonat mnoho práce – jsou v kontrastu. Kvuli tomu se zpracování prerušení delí na dve cásti, tzv. poloviny. Samotný handler prerušení tvorí horní polovina – ta se spustí hned po prijetí prerušení a predstavuje jenom práci, která je casove kritická, jako je napr. poslání potvrzení o prijetí prerušení nebo reset hardware. To, co se muze vykonat pozdeji je zahrnuto v dolní polovine. Dolní polovina bezí pozdeji ve vhodnejším case, kdyz jsou všechna prerušení povolena. Na tento kód je kladen další pozadavek – nesmí být uspán, protoze se vykonává v kontextu prerušení a proto nemuze být preplánován.
Predtím existovaly jenom bottom halves (BH), které nevyuzívaly výhody multiprocesoru. Nedávno se však uz podarilo toto omezení odstranit.
Soubor include/linux/interrupt.h obsahuje ruzné BH. Nezálezí na tom, kolik procesoru váš pocítac má, nikdy však nesmí bezet dve ruzné BH najednou. Velmi dulezitým BH je timer BH (include/linux/timer.h): muzete ho zaregistrovat tak, aby v urcité periode byla volána vaše funkce (omezené delky).
Softirq jsou plne SMP (symmetric multiprocessing) verze bottom halves. Mohou bezet na tolika procesorech, kolik je jich potreba. To znamená, ze všechny races ve sdílené pameti musí rešit pouzíváním vlastních zámku. Na zjištení, která softirqs jsou povolena, se pouzívá bitmaska, takze tech 32 dostupných softirqs by nemelo být tak lehce spotrebováno.
Tasklets (include/linux/interrupt.h) se podobají softirqs, az na to, ze se registrují dynamicky (znamená to, ze jich muzete mít tolik, kolik chcete). Garantují, ze kazdý tasklet muze bezet kdykoli, ale jenom na jednom CPU. Ruzné tasklety ale mohou bezet simultánne (na rozdíl od ruzných BH).
Upozornení: Jméno tasklet je trochu zavádející, nemá totiz nic spolecného s tasks.
Muzete zjistit, zda jste v softirq (nebo bottom half, prípadne v taskletu): makro in_softirq() (include/asm/softirq.h).
Pokud bottom half sdílí data s user contextem, máte dva problémy. První je ten, ze aktuální user context muze být prerušen bottom halfem a druhý, ze jiný procesor by mohl vstoupit do kritické sekce. Tady se pouzívá spin_lock_bh() (include/linux/spinlock.h). Tato funkce zakáze na tomto procesoru bottom halves a pak aktivuje zámek. Funkce spin_ulock_bh() delá opak.
Toto funguje bez problému i pro UP (Uni-Processor): spin lock se nepouzije a makro se zmení jen na local_bh_disable() (include/asm/softirq.h), které zakáze beh bottom half.
Tento prípad je stejný jako predcházející, protoze local_bh_disable() zakáze i všechna softirqs a tasklety na tom procesoru. Ve skutecnosti by se mela volat funkce local_softirq_disable(), ale jméno zustalo zachováno z historických duvodu. Podobne, v dokonalém svete by se tady spin_lock_bh() jmenovalo spin_lock_softirq().
Nekdy by mohl bottom half chtít sdílet data s jinou bottom half .
Víme, ze bottom half nemuze bezet najednou na dvou ruzných procesorech, proto se nemusíme bát toho, ze by byl bottom half spušten dvakrát, dokonce ani na SMP.
Protoze v jednom okamziku bezí jenom jeden bottom half, nemusíme si delat starosti s race conditions s ostatními bottom halves. Veci se ale mohou zmenit, pokud nekdo zmení tento bottom half na tasklet. Pokud se chcete zajistit, aby to fungovalo vzdy, predstavte si, ze to je tasklet a pouzijte navíc zámky.
Nekdy mohou tasklety chtít sdílet data z jiným taskletem nebo s bottom half.
Tasklet nikdy nebezí na dvou ruzných procesorech najednou, takze kód nemusí být ani na SMP reentrantní.
Pokud jiný tasklet (nebo bottom half, napr. timer) chce sdílet data s vaším taskletem, oba dva musíte pouzívat funkce spin_lock() a spin_unlock(). Funkce spin_lock_bh() je zbytecná, protoze na procesoru, kde bezí tasklet, ted nic jiného nemuze bezet.
Casto chtejí softirqs sdílet data se softirq, taskletem nebo bottom half.
To samé softirq muze bezet na ruzných procesorech. Muzete proto pouzít meziprocesorové pole. Pokud jste zašli tak daleko, ze pouzíváte softirq, pravdepodobne vám zálezí na výkonu, takze zajistíte komplexnost. Pro sdílená data budete potrebovat funkce spin_lock() a spin_unlock().
Ruzná softirq
Pro sdílená data pouzívejte funkce spin_lock() a spin_unlock(), az uz jde o timer, bottom half, tasklet nebo jiné softirq.
Ovladace mohou zaregistrovat handler prerušení a umoznit danému prerušení, aby bylo zpracováno funkcí
int request_irq (unsigned int irq,
irqreturn_t (*handler) (int, void *, struct pt_regs *),
unsigned long irqflags,
const char *devname,
void *dev_id)
První parametr, irq, specifikuje císlo prerušení, které nastalo. Nekterá zarízení, napr. systémový casovac nebo klávesnice v PC, mají hodnotu prerušení danou uz od výrobce a nelze ji zmenit. Pro vetšinu ostatních zarízení se hodnota zjištuje nebo dynamicky urcuje.
Druhý parametr, handler, je ukazatel na handler tohoto prerušení. Tato funkce se vyvolá kdykoli je operacnímu systému doruceno toto prerušení. Všimnete si prototypu funkce – má tri parametry a vrací hodnotu irqreturn_t, pozdeji se o ní ješte zmíním.
Tretí parametr, irqflags, muze být 0 nebo bitmaska pro jeden nebo víc z následujících príznaku:
SA_INTERRUPT: Tento príznak ríká, zda je daný handler prerušení rychlý. V minulosti se handlery delily na pomalé a rychlé. Rychlé musely být vykonány ve velmi krátkém case, protoze se predpokládalo, ze se budou volat casto. Dnes je tam jenom jeden rozdíl: behem rychlého handleru prerušení jsou všechna prerušení na lokálním procesoru zakázána. To umozní zvýšit rychlost behu handleru, protoze nehrozí zastavení kvuli ostatním prerušením. Implicitne je tento flag vypnut, jedním z duvodu je prerušení casovace.
SA_SAMPLE_RANDOM: Tento príznak ríká, zda prerušení, generované tímto zarízením, bude zahrnuto v kernelím entropy pool. Kernelí entropy pool poskytuje náhodná císla, odvozená z ruzných náhodných události. Pokud je tento príznak nastaven, casování prerušení z tohoto zarízení bude vlozeno do entropy pool. Tento príznak nesmí byt nastaven, pokud zarízení produkuje prerušení v predpovídatelném case (timer) nebo jej lze narušit zvencí (sítové zarízení). Na druhou stranu, vetšina zarízení produkuje prerušení v nepredvídatelném case, takze je to dobrý zdroj entropie.
SA_SHIRQ: Tento príznak specifikuje, zda muze být císlo IRQ sdíleno mezi více handlery prerušení. Pokud jo, kazdý handler registrovaný s tímto císlem musí mít zapnutý tento príznak, v opacném musí mít kazdý handler zaregistrovanou jinou hodnotu.
Ctvrtý parametr, devname, je ASCII reprezentace zarízení, které toto prerušení posílá. Napr. tato hodnota pro prerušení z klávesnice na PC je "keyboard". Tato textová jména pouzívá /proc/irq a /proc/interrupts pro komunikaci s uzivatelem.
Pátý parametr, dev_id, se pouzívá zejména u sdílených hodnot prerušení. Pokud je handler prerušení uvolnen, dev_id poskytne jednoznacné cookie, které predstavuje urcitý handler (s jistou IRQ hodnotou). Bez tohoto parametru by bylo pro kernel nemozné zjistit, který handler má odstranit (je víc handleru se stejným císlem). Muzete mu dát hodnotu NULL, pokud císla nejsou sdílena, ale pokud sdílena jsou, musí tady být jednoznacné cookie. U kazdého vyvolání handleru se mu pak predá i tento parametr.
V prípade úspechu vrátí request_irq() nulu. Nenulová hodnota predstavuje chybu – handler nebyl registrován. Nejcastejší chyba je –EBUSY, která znamená, ze císlo IRQ je práve zpracováváno (a není specifikováno SA_SHIRQ).
Funkce request_irq() muze být uspána a proto nemuze být volána z kontextu prerušení. Beznou chybou je predpoklad, ze request_irq() muze být voláno v kontextu, kde není bezpecné spát. U registrace je v souboru /proc/irq vytvoren záznam odpovídající prerušení. Funkce proc_mkdir() vytvárí nové záznamy procfs. Tato funkce volá proc_create(), která nastaví tyto záznamy. No práve tady se pouzívá funkce kmalloc(), která muze být uspána.
Zádost o IRQ císlo a instalace handleru do ovladace muze vypadat napr. takhle:
if(request_irq(irqn, my_interrupt, SA_SHIRQ, "my_device", dev))
{
printk(KERN_ERR "my_device: cannot register IRQ %d\n", irqn);
return –EIO;
}
V tomto príkladu je irqn dané císlo IRQ, my_interrupt je handler, císlo muze být sdíleno, zarízení se jmenuje "my_device" a jako dev_id má hodnotu dev. V prípade, ze se registrace nepovede, vytiskne se chybová hláška a funkce se ukoncí. Pokud funkce vrátí nulu, handler byl úspešne nainstalován. Je dulezité inicializovat hardware pred samotnou registrací handleru.
Pro uvolnení handleru prerušení slouzí funkce
void free_irq(unsigned int irq, void *dev_id);
Pokud císlo tohoto prerušení není sdíleno, funkce odstraní handler a zruší toto císlo. Pokud sdíleno je, odstraní se handler identifikovaný císlem dev_id a IRQ císlo se zruší jen v prípade, ze jde o poslední handler s tímto císlem.
Tato funkce musí být volána z kontextu procesoru.
Typická deklarace handleru prerušení vypadá asi takhle (musí se shodovat s argumentem funkce request_irq):
static irqreturn_t intr_handler(int irq, void *dev_id, struct pt_regs *regs);
První parametr, irq, je císelná hodnota IRQ handleru. Krome tisku logu to uz dnes není moc uzitecné. Az do kernelu 2.0 tam dev_id parametr nebyl, takze irq bylo pouzito pro rozlišení mezi ruznými zarízeními, které pouzívají stejný ovladac, a proto i stejný hanler prerušení (napr. pocítac s více radici pevných disku).
Druhý parametr, dev_id, je normální ukazatel na stejné dev_id, které bylo u registrace handleru argumentem request_irq().
Poslední parametr, regs, je ukazatel na strukturu, obsahující registry procesoru a jejich stavy pred obsluhou prerušení. Jsou pouzívany jenom zrídka.
Návratová hodnota handleru prerušení má speciální typ irqreturn_t. Handler muze vrátit dve speciální hodnoty – IRQ_NONE a IRQ_HANDLED. První se vrací, kdyz handler detekuje prerušení, které ale nevyvolalo "jeho" zarízení, v opacném prípade vrátí IRQ_HANDLED.
Handlery prerušení v linuxu nemusí být reentrantní. Behem vykonávání kódu handleru daného prerušení je císlo tohoto prerušení zakázané na všech procesorech a tím zabranuje zpracování prerušení se stejným císlem. Ostatní prerušení jsou normálne povolena.
Sdílený handler je registrován a vykonáván podobne jako nesdílený. Tri hlavní rozdíly jsou:
* príznak SA_SHIRQ musí být nastaven jako argument funkce request_irq()
* parametr dev_id jednoznacný pro kazdý registrovaný handler. Uzitecnou volbou je dát dev_id strukturu zarízení. Tento parametr nemuze mít hodnotu NULL
* handler prerušení musí být schopný rozlišovat, zda toto zarízení práve vygenerovalo prerušení. Proto je nutná podpora hardware i príslušného handleru. Pokud hardware neposkytuje tuto moznost, handler prerušení nemuze zádným zpusobem zjistit, zda prerušení zpusobilo toto zarízení nebo nejaké jiné se stejnou hodnotou prerušení.
Pokud nejaké zarízení nebo handler nesplnuje nekteré z výše uvedených podmínek, hodnota prerušení nemuze být sdílena. Volání funkce request_irq() s parametrem SA_SHIRQ uspeje, pokud tato hodnota ješte nebyla pouzita nebo všechny ostatní handlery, které sdílejí tuto hodnotu, mají taky definovaný príznak SA_SHIRQ.
Pokud kernel dostane prerušení, zavolá postupne všechny handlery registrované pro hodnotu tohoto prerušení. Proto je dulezitá vlastnost, aby handler poznal, zda je to prerušení generované príslušným zarízením a v prípade, ze není, se rychle ukoncil. Hardware zarízení musí mít nejaký registr stavu (nebo podobný mechanizmus), který muze handler zkontrolovat. Vetšina zarízení tuto vlastnost má.
Podívejme se na skutecný handler z ovladace RTC (real-time clock), který muzeme najít v drivers/char/rtc.c. RTC je na mnohých strojech, vcetne PC. Je to zarízení, oddelené od systémového casovace, které se pouzívá na nastavení systémového casu, poskytuje alarm nebo periodický casovac. Nastavení systémového casu je vetšinou zápis do speciálního registru. Alarm a periodický casovac bývá implementovát pres prerušení. Funguje totak, ze kdyz je posláno prerušení, alarm nebo casovac se vypne.
Kdyz se RTC natáhne do pameti, vyvolá se funkce rtc_init(), která inicializuje ovladac. Jedna z její povinností je zaregistrovat handler prerušení:
if (request_irq(RTC_IRQ, rtc_interrupt, SA_INTERRUPT, "rtc", NULL)
{
printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq);
return –EIO;
}
Vidíme, ze hodnota prerušení je ulozena v RTC_IRQ. Je to define preprocesoru, který specifikuje prerušení RTC pro danou architekturu. Napr. na PC má prerušení RTC vzdy hodnotu IRQ 8. Druhý parametr je náš handler prerušení, rtc_interrupt, behem jeho vykonávání jsou ostatní prerušení zakázána (flag SA_INTERRUPT). Ze ctvrtého parametru muzeme vycíst, ze jméno ovladace je "rtc". Protoze toto zarízení nemuze sdílet hodnotu prerušení, parametr dev_id má hodnotu NULL.
Samotný handler vypadá takhle:
static irqreturn_t rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
/*
* Muze to být prerušení alarmu, prerušení oznacující konec update
* nebo periodické prerušení. Stav si ulozíme v dolním byte a pocet
* prerušení dorucených od posledního ctení do zbytku rtc_irq_data.
*/
spin_lock (&rtc_lock);
rtc_irq_data += 0x100;
rtc_irq_data &= ~0xff;
rtc_irq_data |= (CMOS_READ (RTC_INTR_FLAGS) & 0xF0);
if (rtc_status & RTC_TIMER_ON)
mod_timer (&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);
spin unlock (&rtc_lock);
spin_lock (&rtc_task_lock);
if (rtc_callback)
rtc_calback->func(rtc_callback->private_data);
spin_unlock(&rtc_task_lock);
wake_up_interruptible (&rtc_wait);
kill_fasync (&rtc_async_queue, SIGIO, POLL_IN);
return IRQ_HANDLED;
}
Tato funkce se volá vzdy pri prerušení RTC. Všimnete si volání spin lock: první pár zabezpecí, ze rtc_irq_data v tom case nebude prístupná dalšímu procesoru a druhý pár delá to samé s rtc_callback.
rtc_irq_data ukládá informace o RTC a obnovuje se po kazdém prerušení, aby mohla ulozit stav prerušení. Pokud je RTC periodický casovac nastaven, updatuje se pres mod_timer().
Poslední cást kódu, uzavrená ve druhém páru spin locku, vykoná predem nastavené callbacky, pokud takové jsou. Ovladac RTC povoluje registraci callbacku, které se mají vykonat u kazdého RTC prerušení.
Funkce vrací IRQ_HANDLED, címz oznamuje, ze registrace probehla úspešne.
Pokud bezí handler prerušení nebo spodní polovina, kernel je v kontextu prerušení. Kontext procesu je rezim, ve kterém je kernel, kdyz vykonává nejaký proces (napr. system call, nebo bezí kernelové vlákno). V kontextu procesu makro current ukazuje na asociovanou úlohu. Proces je spojen s kernelem v kontextu procesu, takze kontext procesu muze být upsán nebo jinak vyvolat scheduler.
Na druhou stranu, kontext prerušení není spojen s procesem. Makro current není významné (i kdyz ukazuje na prerušený proces). V kontextu prerušení nemuze a nesmí dojít ke spánku, proto nelze pouzít nekteré funkce v handleru prerušení.
Kontext prerušení je casove kritický, protoze handler prerušil cizí kód. Proto by mel být rychlý a jednoduchý.
Handler prerušení nedostane svuj vlastní zásobník. Místo toho sdílí zásobník procesu, který byl prerušen. Pokud práve nebezí zádný proces, pouzije zásobník vlákna idle task.
Asi není prekvapením, ze implementace obsluzného systému prerušení v linuxu hodne závisí na architekture. Implementace závisí na procesoru, typu pouzitého radice prerušení a návrhu architektury jako takové.
Obrázek ukazuje, jakou cestou se prerušení dostane od hardware do kernelu:
Zarízení
informuje o prerušení tím, ze pošle
sbernicí elektrický signál radici prerušení.
Pokud je hodnota prerušení povolena (muze být
zakázána), radic pošle prerušení
procesoru. Ve vetšine architektur na to má procesor
speciální pin. Pokud nejsou prerušení
zakázány v procesoru, procesor okamzite prestane se
zpracováním kódu, pozastaví systém
prerušení a skocí na preddefinované místo
v pameti a vykoná kód, který je tam ulozený.
Preddefinovaný bod je nastaven kernelem a je to vstupní
bod pro handlery prerušení.
Pro kazdou hodnotu prerušení má kernel jednoznacné predem známé místo. Kernel zná IRQ císlo pricházejícího prerušení. Vstupní bod jenom ulozí tuto hodnotu a zálohuje hodnoty v registrech (které se týkají zpracovávané úlohy) na zásobníku. Potom kernel zavolá funkci do_IRQ(). Od tohoto místa je vetšina kódu psaná v C, ale je to stále závislé na architekture.
do_IRQ je definovaný jako unsigned int do_IRQ(struct pt_regs regs).
Protoze C umístuje argumenty funkce na vrchol zásobníku, struktura pt_regs obsahuje vstupné hodnoty registru, které uz byly predtím ulozeny assemblerem. Ulozena byla i hodnota prerušení, takze do_IRQ() muze tyto hodnoty získat. x86 kód je
int irq =regs.orig_eax & 0xff;
Poté, co je vypoctena hodnota prerušení, do_IRQ() pošle potvrzení o prijetí prerušení. Na PC jsou tyto operace obslouzeny operací: mask_and_ack_8259A(), kterou volá do_IRQ().
Dále se do_IRQ() ujistí, ze je handler registrovaný pro tuto hodnotu prerušení, hodnota je povolena a není práve zpracovávána. Pokud je vše v porádku, zavolá hardware_IRQ_event() a tím spustí handler prerušení. Na x86 handle_IRQ_event() vypadá takhle:
int handle_IRQ_events (unsigned int irq, struct pt_regs *regs, struct irqaction *action)
{
if(!(action->flags & SA_INTERRUPT))
local_irq_enable();
do
{
status |= action->flags;
action->handler(irq, action->dev_id, regs);
action = action->next;
} while (action);
if (status & SA_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();
return status;
}
Protoze procesor zakáze všechna prerušení, jsou znovu povoleny krome prípadu, kdy byl pouzit argument SA_INTERRUPT. SA_INTERRUPT ríká, ze handler musí být spušten se zakázanými prerušeními. Dále, kazdý handler je vykonán ve smycce. Pokud hodnota tohoto prerušení není sdílena, smycka bude ukoncena po první iteraci, jinak se pustí všechny handlery. Pokud byl behem registrace specifikován SA_SAMPLE_RANDOM, zavolá se funkce add_interrupt_randomness(). Tato funkce pouzije casování prerušení na vytvorení entropie pro generátor náhodných císel. Na konci jsou prerušení znovu zakázány (do_IRQ() je potrebuje mít zakázané) a funkce koncí. Znovu v do_IRQ() funkce uvolní pamet a vrátí se do vstupního bodu, odkud skocí na ret_from_initr().
Funkce ret_from_initr() je podobne jako vstupní kód psaná v assembleru. Zkontroluje, zda nastane preplánování. Pokud jo a kernel se vrací do user-space, zavolá se schedule(). Pokud se kernel vrací do kernel-space, schedule() se zavolá jenom kdyz je preempt_count rovný 0 (jinak není bezpecné privlastnit si kernel). Po schedule(), nebo pokud tato funkce není vubec volána, jsou registry obnoveny do stavu pred prerušením a kernel pokracuje od místa, kde byl prerušen.
Na x86 jsou inicializacní assemblerovské
funkce ulozeny v souboru arch/i386/kernel/entry.S
a C-ckové funkce jsou v arch/i386/kernel/irq.c.
U ostatních podporovaných architektur je to
podobné.
Procf je virtuální filesystem, který existuje jen v pameti kernelu a je vetšinou mountnutý jako /proc. Zápis nebo ctení souboru v procfs volá funkce kernelu, které jsou podobné ctení a zápisu do skutecného souboru. Príkladem je soubor /proc/interrupts, který obsahuje statistiky spojené s prerušeními systému. Tady je príklad výstupu pro jednoprocesorové PC:
CPU0
0: 3602371 XT-PIC timer
1: 3048 XT-PIC i8042
2: 0 XT-PIC cascade
4: 2689466 XT-PIC uhci-hcd, eth0
5: 0 XT-PIC EMU10K1
12: 85077 XT-PIC uhci-hcd
15: 24571 XT-PIC aic7xxx
NMI: 0
LOC: 3602236
ERR: 0
První rádek predstavuje hodnoty prerušení. V tomto systému existují prerušení s císly 0-2, 4, 5, 12 a 15. Císla, která nejsou zobrazena, nemají zádný registrovaný handler. Druhý sloupec ukazuje, kolikrát dané prerušení nastalo. Normálne obsahuje kazdý procesor jeden takovýhle sloupec. Jak vidíme, prerušení casovace nastalo 3 602 371 krát, ale napr. zvuková karta (EMU10K1) nemela ani jedno prerušení (to znamená, ze od bootu pocítace nebyla pouzita). Tretí sloupec radic prerušení, který toto prerušení obslouzí. XT-PIC je v PC standardní programovatelný radic prerušení. Na systémech s I/O APIC bude mít vetšina prerušení v tomto sloupci napsáno IO-APIC-level nebo IO-APIC-edge. Poslední sloupec urcuje zarízení, asociované s tímto prerušením. Tento název je argument devname z funkce request_irq(). Pokud je prerušení sdílené (napr. tady je to císlo 4), je tam seznam všech príslušejících zarízení.
Pro zajímavost, kód procfs je normálne ve fs/proc. Funkce, která poskytuje /proc/interrupts je závislá na architekture a jmenuje se show_interrupts().
Linux kernel implementuje nekolik interface pro manipulaci stavu prerušení. Tyto rozhraní umoznují programátorovi zrušit systém prerušení pro aktuální procesor nebo zakázat prerušení na celém stroji. Tyto rutiny velmi závisí na architekture a muzeme je najít v <asm/system.h> a <asm/irq.h>.
Následující tabulka obsahuje kompletní seznam interface:
funkce |
popis |
local_irq_disable() |
zakáze dorucování lokálních prerušení |
local_irq_enable() |
povolí dorucování lokálních prerušení |
local_irq_save(unsigned long flags) |
ulozit aktuální stav dorucování prerušení a pak to zakázat |
local_irq_restore(unsigned long flags) |
obnoví stav dorucování lokálních prerušení |
disable_irq(unsigned int irq) |
zakáze danou hodnotu prerušení a ujistí se, ze zádný handler s touto hodnotou se nevykonává |
disable_irq_nosync(unsigned int irq) |
zakáze danou hodnotu prerušení |
enable_irq(unsigned int irq) |
povolí danou hodnotu prerušení |
irqs_disabled() |
vrátí nenulovou hodnotu, pokud je dorucování lokálních prerušení povoleno, jinak vrátí nulu |
in_interrupt() |
vrátí nenulovou hodnotu, pokud je v kontextu prerušení a nulu, pokud je v kontextu procesu |
in_irq() |
vrátí nenulovou hodnotu, pokud se v této chvíli vykonává nejaký handler prerušení, jinak vrátí nulu |
Duvody, proc kontrolovat prerušení, se týkají vlastní synchronizace. Zákazem prerušení lze zarucit to, ze handler prerušení si neprivlastní procesor behem vykonávání vašeho kódu. Zruší se tím taky preempce kernelu.
Linux podporuje multiprocesory, takze kód kernelu musí mít prostredky na uzamykání sdílených dat pred jinými procesory. Tyto zámky jsou vetšinou spojeny se zakazováním lokálních prerušení.
Lokální zákaz prerušení pro aktuální procesor a jeho povolení:
local_irq_disable();
local_irq_enable();
Tyto funkce jsou vetšinou implementované jako jediné asm operace (jsou samozrejme závislé na architekture). Funkce local_irq_disable() je nebezpecná, pokud prerušení uz byla zakázána pred jejím voláním.
U zakazování a povolování prerušení je dulezité zapamatovat si puvodní stav a pak tento stav obnovit. Na to slouzí funkce:
unsigned lond flags;
local_irq_save(flags);
local_irq_restore(flags);
Tyto funkce jsou implementovány jako makra, takze parametr flags je predávaný hodnotou. Tento parametr obsahuje data, závislé na dané architekture, které obsahují stav systému prerušení. Protoze prinejmenším jedna podporovaná architektura zahrnuje informaci o zásobníku do hodnoty (SPARC), promenná flags nesmí být argumentem jiné funkce. Jinak receno, musí zustat ve stejném rámci zásobníku. Proto se volání save a restore musí nacházet v té samé funkci.
Obe funkce mohou být volány i v kontextu prerušení i v kontextu procesu.
Kernel pred casem poskytoval metodu na zakázání prerušení na všech procesorech v systému. Pokud tuto metodu chtel volat další procesor, musel pockat az do chvíle, kdy byly puvodní prerušení povoleny. Tato funkce se jmenovala cli() a odpovídající povolení se jmenovalo sli(). Tyto funkce však byly v 2.5 odstraneny a synchronizace prerušení ted musí pouzívat kombinaci lokálního rízení prerušení a spin locku.
Odstranení globálního cli() má mnoho výhod. Nutí programátory pouzívat v ovladacích skutecné zámky, které jsou v speciálním pouzití rychlejší nez globální zámek (cli). Taky byly odstraneny spousty kódu a ve výsledku jsou prerušení jednodušší a snazší na pochopení.
V predešlé kapitole jsme se naucili zakázat dorucování všech prerušení pro jeden procesor. Nekdy je uzitecné zakázat jenom urcitou hodnotu prerušení pro celý systém. Tomu se ríká maskovaní prerušení. Linux nabízí ctyri funkce pro manipulaci s urcitým prerušením:
void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq);
void enable_irq(unsigned int irq);
void synchronize_irq(unsigned int irq);
První dve funkce zakázou urcitou hodnotou prerušení v ovladaci prerušení. To znamená, ze zakázou dorucení prerušení s touto hodnotou všem procesorum systému. Funkce disable_irq() se neukoncí, dokud bude existovat nejaký handler, který práve bezí. Takze volání této funkce zarucí nejenom to, ze zádné další prerušení s daným císlem nebude doruceno, ale i to, ze všechny dobíhající handlery prerušení budou ukonceny. Funkce disable_irq_nosync() tuhle vlastnost nemá.
Funkce synchronize_irq() pocká pred svým ukoncením na konec handleru, který se týká dané hodnoty prerušení.
Tyto funkce na sobe závisí. Pro jakékoli volání funkce disable_irq() nebo disable_irq_nosync() pro jistou hodnotu prerušení je potrebné zavolat príslušnou funkci enable_irq(). Az poslední volání enable_irq() skutecne povolí prerušení. Napr. pokud voláme funkci disable_irq() dvakrát, prerušení s danou hodnotou se povolí az po druhém zavolání enable_irq().
Všechny tri funkce mohou být volány v kontextu prerušení nebo procesu a nemuzou být uspány. V kontextu prerušení budte opatrní, urcite nechcete napr. povolit prerušení s hodnotou behem vašeho zpracování prerušení s touto hodnotou (tato hodnota je totiz vymaskovaná).
Je celkem nepríjemné, kdyz nekdo zakáze prerušení s nejakou sdílenou hodnotou, protoze se to pak týká všech príslušných zarízení. Proto novejší ovladace nepouzívají tento interface. PCI zarízení musí podporovat sdílení hodnoty prerušení z jejich specifikace, proto nesmí tyto funkce vubec pouzívat.
Vetšinou se hodí znát stav systému prerušení (napr. zda jsou prerušení povoleny nebo zakázány nebo zda je procesor v kontextu prerušení).
Makro irqs_disabled(), definované v <asm/system.h>, vrací nenulovou hodnotu, pokud je systém prerušení vypnut. Jinak vrací nulu.
Dve makra definované v <asm/hardirq.h> poskytují interface pro kontrolu aktuálního kontextu kernelu. Jsou to:
in_interrupt();
in_irq();
První z nich je uzitecnejší - vrací nenulovou hodnotu, pokud je kernel v kontextu prerušení, tj. pokud se zpracovává nejaké prerušení, at v dolní nebo horní polovine. Makro in_irq() vrací nenulovou hodnotu jenom pokud bezí handler nejakého prerušení.
Mnohem casteji potrebujete zkontrolovat, zda jste v kontextu procesu. To znamená, ze se chcete ujistit, ze nejste v kontextu prerušení. Jsou totiz funkce, které lze volat jenom v kontextu procesu, napr. sleep. Pokud funkce in_interrupt() vrátí 0, kernel je v kontextu procesu.