Skip to content
Snippets Groups Projects
d04_c.rst 104.63 KiB

Sormet syvemmälle C:hen

ITKA2030 Käyttöjärjestelmien ja pilvipalveluiden perusteet -opintojakson Demo 4.

(Aiemmin ITKA203 Käyttöjärjestelmät -kurssin Demo 4 vuosina 2018-2024)

"Lähespikaintro C-kielellä ohjelmointiin"

Paavo Nieminen, paavo.j.nieminen@jyu.fi

Jyväskylän yliopiston Informaatioteknologian tiedekunta.

STATUS: Vuosina 2018-2025 tämä on tarkoitettu ennakkolukemistoksi ja esitehtäväksi noin 2 tunnin mittaiseen yhteisharjoitteeseen, jota on tehty tilanteesta ja vuodesta riippuen mikroluokissa tai etänä Zoomilla. Keväällä 2025 tehdään etänä Zoomin kautta. Itsenäistä palautustehtävää ei tarvitse palauttaa, mikäli pääsee osallistumaan ryhmätyöistuntoon. Sen palautuslaatikko avautuu ryhmäistuntojen jälkeen, jotta ei tule ylimääräistä tarkastustyötä.

Vaihtoehtoisesti voi tehdä tämänkin tehtävän itsenäisesti, jolloin voi joutua pähkäilemään itsekseen hiukan enemmän kuin ryhmän ja opettajan aktiivisella avustuksella.

Deadline-perusteisesti suorittavien opiskelijoiden tulisi tutustua tähän tarkoin ja hyvissä ajoin ennen lähi-istuntoa, jotta yhdessä tekemisestä tulee mielekästä. Kaikki vähintäänkin tietäisivät, millaisista asioista suurin piirtein puhutaan. Päivämäärät ja yksityiskohdat tiedotetaan toteutuskohtaisesti vuosittain.

Contents

Mistä tässä harjoitteessa on kyse

Tämä harjoite perustuu demoissa 1-3 esiteltyihin perustaitoihin, jotka oletetaan tunnetuksi ja joita harjoitellaan lisää samalla kun tehdään uutta. Teksti on pieni suomenkielinen selviytymisopas C-kieleen sillä tasolla, jota käyttöjärjestelmäkurssin loppuosan seuraaminen vaatii. Se yrittää kattaa C-kielestä kaiken tarvittavan tämän kurssin sisällön seuraamiseksi. Ilmoita, jos jotakin tarpeellista tuntuu puuttuvan. Mallia on katsottu esimerkiksi seuraavista maailmalta löytyvistä tutoriaaleista, joita asiasta kiinnostuneet voivat lisäksi selata omatoimisesti omalla ajallaan (linkkien toimivuus tarkistettu viimeksi 19.4.2023):

Vaikka tämä yrittää olla minimaalinen, on luettavaa, kokeiltavaa ja hahmotettavaa aika paljon. Pakollista palautettavaa on loppujen lopuksi hyvin vähän. Edelleen tekemisen ja oppimisen määrä on jokaisen omalla vastuulla. Pyri käyttämään kurssiin tarkoitettu viikkotuntimäärä tehokkaasti.. Tätä voi kertailla myös myöhemmin, kun kurssin loppupuoliskossa katsotaan käyttöjärjestelmän toimintojen käyttöä pienten C-koodiesimerkkien kautta ja todellisia toteutusratkaisuja Linux-lähdekoodia silmäilemällä.

Aiempien havaintojen perusteella rakenteisen ohjelmoinnin edellyttämä imperatiivisten algoritmien kehittelykyky ei monellakaan ole vielä ehtinyt hahmottua asiaan johdattelevan Ohjelmointi 1 -kurssin aikana. Tämä on luonnollista, koska ohjelmoinnin oppiminen on pitkä ja ikuisesti jatkuva prosessi. On siis otettava huomioon, että tämän materiaalin läpikäynti voi kestää opiskelijasta riippuen tunnista (läpiluku, asian toteaminen jo aiemmin opituksi) useisiin päiviin (ohjelmoinnin perusasioiden kertaaminen, käsitteiden oppiminen, kattavamman oppikirjallisuuden etsiminen ja lukeminen, avun kysymyminen ohjaustilaisuuksissa, vertaistukikanavalla tai kavereiden kanssa tmv.).

Hyvänä puolena toivottavasti Käyttöjärjestelmät -sisällön lopputulema tältä osin olisi myös opiskelijoiden ohjelmointitaidollisten tasoerojen pieneneminen ennen tätä seuraavia jatkokursseja. Vähintään kaikki saavat mahdollisuuden nähdä ohjelman rakentamisen periaatteet ruohonjuuritasolla, käyttöjärjestelmän ja laiteläheisen kielen päällä.

Harjoituksen tavoitteita:

  • Osaat siirtää aiemmin oppimiasi ohjelmoinnin perusrakenteita olioperustaisesta lohkorakenteisesta kielestä (esimerkiksi C# / Java) C-kieleen, erityisesti:

    • muuttujat ja niiden tyypit
    • ehtolauseet
    • silmukat eli toistorakenteet
    • aliohjelmakutsu (vastaa jotakuinkin luokkametodin kutsua; instanssimetodeja ei C:ssä olekaan olemassa).
  • Tiedät, mitä ovat taulukot C-kielessä.

  • Tiedät, mitä ovat merkkijonot C-kielessä.

  • Tiedät, mitä ovat järjestetyt datakokoelmat eli "tietueet" eli "struktuurit" eli "oliomaiset rakenteet" C-kielessä.

  • Tiedät, mitä ovat osoittimet ja miten niitä käytetään.

  • Ymmärrät näkökulmaeron: C on puhtaasti imperatiivinen, rakenteinen kieli ja lisäksi laitteistoläheinen, eli toisin sanoen:

    • Olioita ei samassa mielessä ole kuin C#:ssa, Javassa tai C++:ssa (siis ei instanssimetodeja, ei perintää, ei sisäisen tilan piilotusta, ei rajapintoja samassa mielessä, ei poikkeuksia, ei roskienkeruuta, eikä muitakaan elämää helpottavia abstraktioita... ellet tee tai ota käyttöön aliohjelmakirjastoa, johon olisi toteutettu näitä hienouksia).
    • Datakokoelmien rakenteet ja niitä käsittelevät aliohjelmat ovat erillisiä kokonaisuuksia eivätkä olioluokiksi paketoituja.
  • Ymmärrät erot ja samankaltaisuudet aliohjelmakutsussa (luokkametodi / staattinen metodi) ja instanssimetodikutsussa (normaali olioinstanssin tilaa hyödyntävä metodi)

Keväällä 2015 määriteltyjen osaamistavoitteiden osalta tämän ja aiemmat demot tehtyään opiskelija erityisesti:

  • osaa kääntää valmiita C-ohjelmia ja linkittää objektitiedostoja staattisesti komentoriviltä GNU-työkaluilla ja suorittaa näin syntyneen ohjelman itsenäisenä prosessina sekä tekstipohjaisen debuggerin kautta [ydin/arvos1]
  • osaa selittää vaiheet ja mahdolliset ongelmatilanteet suoritettavan ohjelmatiedoston lataamisessa, dynaamisessa linkittämisessä ja käynnistämisessä [ydin/arvos2]
  • osaa lukea, ymmärtää ja muokata lyhyitä (n. 50 riviä kommenttirivien lisäksi) C-kielisiä ohjelmia [edist/arvos3]
  • osaa esittää strukturoidun tietorakenteen sisällön tietokoneen muistissa sijaitsevien peräkkäisten muistipaikkojen sisältöinä, joilla on yksilölliset osoitteet; erityisesti pystyy toteuttamaan objekti-/tietorakenneviitteen muistiosoitteena. Vastaavasti osaa löytää muistissa olevasta datasta yksinkertaisen strukturoidun tietorakenteen komponenttien sijainnin ja sisällön, kun tietorakenne, alkuosoite ja muistin sisältö osoitteen ympäriltä on annettu. [ydin/arvos2]

Rajaus 1: Tavoitteena on, että tämän kurssin nykyiset esimerkkiohjelmat olisivat ISO-standardin C99 mukaista C-kieltä. Jos huomaat poikkeamia standardiin nähden, ilmoita niistä. Kieli on C eikä siis missään tapauksessa olio-ohjelmointiin soveltuva C++, joka on erillinen, paljon laajempi, kieli. Käytämme C-kielen vanhaa standardia C99, koska uusin POSIX (tilanne vuonna 2023) on yhä kiinnitetty siihen. Olettaa voisi, että koska C:stä on jo olemassa uudempia standardeja, tulevaisuudessa myös POSIX siirtyy viittaamaan johonkin uudempaan standardiin. Uusin C-kieli vuonna 2023 on C17 ja mahdollisesti lähivuosina tulee C23. Standardointiprosessi yleensä pyrkii laajentamaan, ei kaventamaan, määritelmiä, joten C99:llä tehtyjen ohjelmien pääsääntöisesti pitäisi toimia muuttamattomina myös uudempien kääntäjien kanssa. Sen sijaan vuoden 1999 jälkeen kehitettyjä uusia kieli- tai kirjasto-ominaisuuksia ei periaatteessa saisi pedantisti C99:n mukaiseksi tehdyssä ohjelmassa käyttää, vaikka ne niin näppäriä olisivatkin!

Rajaus 2: Kaikkia ominaisuuksia C-kielestä ei voida käydä läpi näin lyhyessä prujussa. Oikea ymmärrys vaatii C:tä varten tehdyn oppikirjan lukemista ja muutaman oikean ohjelman tekemistä C:llä. Sen sijaan tässä koetetaan vain nopealla hands-on -kokemuksella saada hahmottumaan, miten lyhyitä ja irrallisia C-koodin pätkiä ymmärretään tai tehdään. Kautta linjan C-ohjelmoinnin rooli kurssilla on demonstroida tietokonelaitteiston ja käyttöjärjestelmän toimintaperiaatteita konkreettisella tasolla. Esitieto-oletus on olio-ohjelmoinnin perustaito esimerkiksi C# tai Java-kielellä, joihin "oliotonta ohjelmointia" ja C-kieltä tässä kautta linjan vertaillaan.

Rajaus 3: Tässä vedetään pari mutkaa suoraksi todelliseen C-kielen määrittelyyn nähden. Kuten muissakin kurssin asioissa, on syytä muistaa, että tämä on johdantotyyppistä kurssimateriaalia, jossa jätetään asioita sanomatta yksinkertaisuuden nimissä. Oikea totuus löytyy spesifikaatioista - tämän demon osalta C99 -standardista ja POSIXista. Draft-versio C99:n speksistä on saatavissa tuolta: http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf (linkin toimivuus tarkistettu viimeksi 19.4.2023)

Pari juttua:

  • Jos ohjelmaan tuli ikuinen silmukka tai muuta jumia, pystyt luultavasti lopettamaan sen painamalla Ctrl-c; katso ettei jää ikuisia silmukoita turhaan pyörimään monen käyttäjän palvelinkoneelle. Kurssilla käsitellään hyvin pian käyttöjärjestelmän palvelu "signaalit", johon mm. Ctrl-c -painallus liittyy.
  • Päätavoite on ymmärtäminen, joten tähtää siihen ja käytä tarvittava aika ja energia. Erillisiä, omia muistiinpanoja voi olla hyvä tehdä edelleenkin.

Seuraavaksi syvennytään joihinkin yksityiskohtiin, jotka ovat osin samanlaisia ja osin erilaisia C-kielessä kuin nykyiseltä Ohjelmointi 1 -kurssilta tutussa oliokielessä.

C-kielisen ohjelman yleisrakenne, kääntäminen ja linkittäminen

Ennen kuin päästään käsiksi itse C-kieleen, on katsottava aihetta kauempaa, eli tyypillisen C-kielisen ohjelman ilmenemisestä kokoelmana lähdekooditiedostoja sekä vaiheita, joiden kautta C-kielisestä ohjelmasta saadaan rakennettua (engl. build / make) suoritettava ohjelmatiedosto (engl. executable, "binary"). Nyt tässäkin avataan kuorikomentojen avulla konepelti asioista, joita tyypillinen ohjelmakehitysympäristö eli IDE tekee. POSIX määrittelee yhteensopivan käyttöjärjestelmän vapaaehtoisena lisäosiona yksinkertaisen tekstipohjaisen "ohjelmakehitysympäristön". Vastaavat vaiheet tapahtuvat napin painalluksella IDE:ssä, mutta ohjelman tekijän yleissivistykseen kuuluu tietää, mitä IDE oikeastaan tekee. Ilman tätä tietoa toimintaan kuuluisi "mystisiä vaiheita", joissa ilmenevien virheiden jäljittäminen olisi mystiikan takia vaikeaa. Otetaan nyt pois kaikki se mystisyys tekemällä käännöskomennot sellaisina kuin ne IDEn napistakin voisivat tulla!

Lähdekoodien ja moduulien organisointi

C-ohjelman perusyksikkö on yksittäinen lähdekooditiedosto (source file), jonka nimen on sovittu päättyvän merkkeihin .c eli piste ja pieni cee-kirjain. Toinen nimi tälle on käännösyksikkö (compilation unit). Lähdekooditiedostossa voi olla yksi tai useampia aliohjelmia tai pelkkää vakiodataa. Ei ole mitään yleistä standardia ohjaamassa, mitä yhteen tiedostoon laitetaan: Siellä voi olla vaikkapa kokonainen 100 000 rivin ohjelma kaikkine määrityksineen ja aliohjelmineen, tai siellä voi olla yksi 30 rivin aliohjelma, tai siellä voi olla vain rimpsu merkkijonoja, joita halutaan käyttää datana. Suosituksena voi maalaisjärjellä ajatella, että yhdessä tiedostossa saisi mieluiten olla hallittavissa oleva määrä yhteen tiettyyn toimintokokonaisuuteen liittyviä asioita. Jos tiedosto alkaa jossain vaiheessa kasvaa liian isoksi, se voi olla hyvä jakaa jollain järkiperusteella pienempiin osiin.

Yleensä C-ohjelmissa on myös .h-päätteisiksi sovittuja otsikkotiedostoja (header file). Otsikkotiedostoissa julkaistaan tietorakenteiden määrittelyt ja sovelluksen yhteiseen käyttöön tarkoitettujen, .c-tiedostoissa toteutettujen, aliohjelmien kutsurajapinnat (eli kunkin aliohjelman "otsikkorivi"). Otsikkotiedostoihin laitetaan siis C-kielellä kirjoitettujen ohjelmamoduulien tai kirjastojen julkiset rajapinnat, joten ne ovat paljolti samassa roolissa kuin uudemmissa oliokielissä luokkakirjastojen julkiset luokat julkisine metodeineen. Otsikkotiedostoistakaan ei ole standardia ohjaamassa, mitä yhteen tiedostoon tulee suhteessa .c -päätteisiin lähdekoodeihin. Maalaisjärjellä ajatellen voisi olla fiksua, että esimerkiksi yhtä .c -lähdekooditiedostoa vastaisi samalla alkuosalla nimetty .h -tiedosto, jossa julkaistaan julkiseksi tarkoitetut tyypit ja aliohjelmat juuri kyseisestä .c -koodista. Tarpeen mukaan joko .c tai .h -tiedostot voi olla järkevää nimetä eri tavoin tai jakaa hienojakoisemmin kuin vastinkappaleensa.

Laajemman sovelluksen C-lähdekoodi koostuu yhdestä tai useammasta .c -ohjelmasta ja .h -otsikkotiedostosta.

C:ssä ei ole mitään kiinnitettyä hakemistorakennetta ohjelman osioille, kunhan kääntäjä tietää, mistä sen pitää etsiä .h ja .c -tiedostoja. Maalaisjärjellä ajatellen ohjelma voi olla fiksua jakaa alihakemistoihin sitten, kun sen tarvittavien lähdekoodi- ja otsikkotiedostojen määrä kasvaa niin isoksi, että kokonaisuutta on vaikea hallita yhdessä hakemistossa, tai jos ryhmittely alihakemistoihin on muusta syystä tarpeen esimerkiksi toiminnallisten kokonaisuuksien suhteen. Esimerkiksi Linuxin lähdekoodin miljoonat koodirivit jakautuvat käyttöjärjestelmän tyypillisten tehtäväkokonaisuuksien mukaisiin alihakemistoihin (muistinhallinta omassaan, tiedostojen hallinta omassaan, laiteajurit omassaan ja niin edelleen).

Valmiiden kirjastojen otsikkotiedostot kuten stdlib.h ovat usein hakemistossa /usr/include (voit huvikseen listata hakemiston jalavassa tai halavassa, tietenkin). Tiedostoissa on tosiaan vain otsikot eli aliohjelmien ja tietorakenteiden esittelyt sekä erinäisten vakioiden määrittelyjä. Varsinaiset toteutukset eli valmiiksi käännetyt aliohjelmakirjastot ovat usein hakemistossa nimeltä /lib (peruskirjastot) ja /usr/lib (tarpeen mukaan kyseiseen tietokoneyksilöön asennetut lisäkirjastot sovellusohjelmien kääntämistä ja ajamista varten). 64-bittisessä esimerkkijärjestelmässämme vastaavat hakemistot ovat /lib64 ja /usr/lib64. Kääntäjä voidaan ohjata etsimään kirjastoja ja otsikoita muualtakin kuin oletuspaikoista antamalla sille tietyt komentoriviargumentit, jotka selviävät kääntäjän manuaalista; sijainteja ei ole sinänsä mitenkään ennalta sovittu.

C-ohjelman sijoittelussa tiedostoihin ja hakemistoihin on siis aika rajaton vapaus. Ohjelmoijalla on kaikki vastuu siitä, että rakenne on selkeä: hyvä ja ylläpidettävä koodi luultavasti on sellainen, joka on jaettu toiminnallisuuden mukaisesti järkevänkokoisiin, samankaltaisia asioita tekeviä tai käsitteleviä aliohjelmia sisältäviin .c-tiedostoihin ja vastaavasti .h-tiedostoihin, jotka julkaisevat .c-tiedostojen rajapinnat muiden moduulien käytettäväksi.

Esimerkiksi Linux-käyttöjärjestelmän ydin https://www.kernel.org/ on käyttökelpoisuudellaan jonkinlaista tunnettuutta kerännyt C-ohjelmisto, jossa on useita miljoonia lähdekoodirivejä. Sen organisoinnista ja muista käytänteistä mallia ottamalla ei välttämättä mene kovin pahasti pieleen, ainakaan jos tarkoitus on ylläpitää ja kehittää usean miljoonan rivin ohjelmistoa useiden satojen löyhästi toisensa tuntevien ohjelmoijien yhteisönä...