Teorija
Slojevi U/I
Poziv read() prolazi kroz nekoliko slojeva, od kojih svaki skriva detalje sloja ispod sebe. Stallings U/I funkciju dijeli na logical I/O (logički U/I: uređaj kao logički resurs, operacije poput otvori, čitaj, zatvori), device I/O (U/I uređaja: zahtjev se pretvara u niz naredbi uređaju i u međuspremnike) i scheduling and control (raspoređivanje i upravljanje: red zahtjeva, interrupts (prekidi), stanje uređaja). Tanenbaum isti put opisuje slojevima softvera. Simulator ih crta kao korisnički proces → nezavisni U/I sloj → upravljački program → kontroler uređaja → uređaj.
Preslikavanje slojeva
| Stallings | Tanenbaum & Bos | Sloj u simulatoru |
| korisnički procesi | korisnički U/I softver | korisnički procesi |
| logički U/I | device-independent software (o uređaju neovisan softver) | logički U/I (nezavisni U/I sloj) |
| U/I uređaja | device driver (upravljački program) | upravljački program: zahtjev R n |
| raspoređivanje i upravljanje | upravljački program i interrupt handler (obrađivač prekida) | red zahtjeva, gornja i donja polovica |
| hardver | hardver | kontroler uređaja i disk |
I/O layers
A read() call passes through several layers, each of which hides the details of the layer below it. Stallings divides the I/O function into logical I/O (the device as a logical resource, with operations such as open, read and close), device I/O (the request is converted into a sequence of commands to the device, and the data is buffered) and scheduling and control (the request queue, interrupts, device status). Tanenbaum describes the same path as layers of software. The simulator draws them as user process → device-independent I/O layer → device driver → device controller → device.
Mapping of the layers
| Stallings | Tanenbaum & Bos | Layer in the simulator |
| user processes | user-level I/O software | user processes |
| logical I/O | device-independent software | logical I/O (device-independent layer) |
| device I/O | device driver | device driver: request R n |
| scheduling and control | device driver and interrupt handler | request queue, top and bottom halves |
| hardware | hardware | device controller and disk |
Upravljački program i kontroler
Device driver je jedini dio jezgre koji zna kako radi određeni uređaj. Ostatku jezgre nudi jednolično sučelje (čitaj blok, piši blok), a uređajem upravlja preko registara device controller (kontroler uređaja): upisuje naredbu u command register (registar naredbe), iz status register (statusni registar) čita je li uređaj zauzet ili gotov, a podatke prenosi kroz data register (registar podataka). Prijenos bloka u memoriju ovdje se ne prikazuje: kontroler javlja jedan prekid po bloku, kao DMA na stranici Tehnike U/I. Dok kontroler radi, procesor je slobodan: proces koji čeka na podatke je Blocked (blokiran), a ostali procesi dijele procesor kružnim raspoređivanjem s quantum (kvant), vidi Procesi i Raspoređivanje.
Device driver and controller
The device driver is the only part of the kernel that knows how a particular device works. To the rest of the kernel it offers a uniform interface (read block, write block), and it controls the device through the registers of the device controller: it writes a command to the command register, reads from the status register whether the device is busy or done, and moves data through the data register. The transfer of the block to memory is not shown here: the controller raises one interrupt per block, as DMA does on the I/O techniques page. While the controller works, the processor is free: the process waiting for the data is Blocked, and the other processes share the processor in round robin with a quantum; see Processes and Scheduling.
Red zahtjeva uređaja
Kontroler poslužuje jedan zahtjev odjednom. Zahtjevi koji stignu dok je zauzet čekaju u request queue (red zahtjeva) upravljačkog programa, a kad kontroler javi kraj, obrada prekida pokreće sljedeći. Ovdje je red FIFO, pa zahtjevi čekaju redom dolaska; zato latency (latencija) čitanja, od ulaska u read() do povratka, raste s duljinom reda. Stvarni upravljački program diska red preslaguje prema položaju glave (SSTF, SCAN i drugi), vidi Raspoređivanje diska.
Device request queue
The controller serves one request at a time. Requests that arrive while it is busy wait in the driver's request queue, and when the controller signals completion, the interrupt handling starts the next one. Here the queue is FIFO, so requests wait in order of arrival; that is why the latency of a read, from entering read() to the return, grows with the length of the queue. A real disk driver reorders the queue by the position of the head (SSTF, SCAN and others); see Disk scheduling.
Gornja i donja polovica
Kad kontroler završi, podiže prekid (ovdje IRQ2 = Disk, kao na stranici Prekidi). Interrupt service routine (ISR, rutina za obradu prekida) radi dok su prekidi te linije maskirani, pa mora biti kratka. Zato se dijeli na top half (gornja polovica), koja odmah potvrdi prekid, preuzme gotov zahtjev, pokrene sljedeći i zakaže ostatak posla, i bottom half (donja polovica), koja kasnije, s omogućenim prekidima, dovrši posao: provjeri podatke, oslobodi zahtjev i probudi proces koji čeka. Nazivi su iz Linuxa (mehanizmi softirq, tasklet i workqueue); Windows sličnu ulogu daje mehanizmu deferred procedure call (DPC, odgođeni poziv procedure). U simulatoru gornja polovica ima prednost pred donjom, a donja pred procesima: kad je prekidnog posla mnogo, spreman proces čeka (scenarij „Gornja i donja polovica”).
Top half and bottom half
When the controller finishes, it raises an interrupt (here IRQ2 = Disk, as on the Interrupts page). The interrupt service routine (ISR) runs while interrupts on that line are masked, so it must be short. It is therefore split into a top half, which at once acknowledges the interrupt, takes the finished request, starts the next one and schedules the rest of the work, and a bottom half, which later, with interrupts enabled, finishes the work: it checks the data, releases the request and wakes up the waiting process. The names come from Linux (the softirq, tasklet and workqueue mechanisms); Windows gives a similar role to the deferred procedure call (DPC). In the simulator the top half has priority over the bottom half, and the bottom half over the processes: when there is a lot of interrupt work, a ready process waits (the “Top and bottom halves” scenario).
Blokirajući, neblokirajući i asinkroni U/I
Kod blocking I/O (blokirajući U/I) proces u read() čeka dok podaci ne stignu i ne troši procesor, pa ga mogu koristiti drugi procesi. Kod non-blocking I/O (neblokirajući U/I, zastavica O_NONBLOCK) read() odmah vraća pogrešku EAGAIN ako podaci nisu spremni; proces radi nešto drugo i poziv ponavlja, a polling (prozivanje) troši procesor i kvant. Umjesto slijepog ponavljanja program može čekati na više opisnika odjednom (poll, epoll). Kod asynchronous I/O (asinkroni U/I; aio, io_uring) proces zada zahtjev i kasnije dobije obavijest o završetku. Napomena: na Linuxu O_NONBLOCK ne djeluje na obične datoteke na disku (čitanje uvijek čeka), nego na cijevi, utičnice i terminale; ovdje je model didaktički. Isti poziv s gledišta sistemskih poziva prikazuje Sistemski pozivi, put od imena datoteke do bloka Datotečni sustav, a cijeli put jednog čitanja Put jednog poziva.
Blocking, non-blocking and asynchronous I/O
With blocking I/O the process waits in read() until the data arrives and does not use the processor, so other processes can use it. With non-blocking I/O (the O_NONBLOCK flag) read() returns the error EAGAIN at once if the data is not ready; the process does something else and repeats the call, and polling uses up processor time and the quantum. Instead of repeating the call blindly, a program can wait on several descriptors at once (poll, epoll). With asynchronous I/O (aio, io_uring) the process issues a request and is notified later that it has completed. Note: on Linux O_NONBLOCK has no effect on regular files on disk (a read always waits), only on pipes, sockets and terminals; the model here is for teaching. The same call from the point of view of system calls is shown on the System calls page, the path from a file name to a block on the File system page, and the whole path of one read on the Journey of a call page.
Literatura: Stallings, Operating Systems: Internals and Design Principles, 9. izd., pogl. 11, odjeljak 11.3 Operating System Design Issues (logička struktura U/I funkcije) i pogl. 1, odjeljak 1.4 Interrupts; alternativno Tanenbaum & Bos, Modern Operating Systems, pogl. 5 (5.2 načela U/I softvera, 5.3 slojevi U/I softvera). Gornja i donja polovica: Corbet, Rubini i Kroah-Hartman, Linux Device Drivers, 3. izd., pogl. 10.Reading: Stallings, Operating Systems: Internals and Design Principles, 9th ed., ch. 11, sec. 11.3 Operating System Design Issues (logical structure of the I/O function) and ch. 1, sec. 1.4 Interrupts; alternatively Tanenbaum & Bos, Modern Operating Systems, ch. 5 (5.2 principles of I/O software, 5.3 I/O software layers). Top and bottom halves: Corbet, Rubini and Kroah-Hartman, Linux Device Drivers, 3rd ed., ch. 10.