Ծրագրավորողը և օպերացիոն համակարգը։ Մաս IV

Նախորդ երեք մասերում մենք մանրամասն ուսումնասիրեցինք մեկ տող ծրագիր։ Ֆայլ բացել/ստեղծելու հարցումը, անցնելով օպերացիոն համակարգի տարբեր կոմպոնենտներով, հասնելով արտաքին հիշասարքին, հետ եկավ կանչող պրոցեսին՝ ֆայլային նկարագրիչի համարի տեսքով։ Այդ գործընթացի բարդությունը կարող է թևաթափ անել ընթերցողին, սակայն պետք է հասկանալ, որ սա այս պահին մարդկության ստեղծած լավագույն լուծումն է։ Միևնույն ժամանակ պետք է հիշել, որ այն կարող է լավացվել հենց քո մտքերի շնորհիվ։

Անցնենք երկրորդ տողի վերլուծությանը։ Հիշեցնեմ, որ մենք դիտարկում ենք հետևյալ ծրագրային կոդի հատվածը՝

int fd = open(“/home/me/file1”, O_RDONLY);

read(fd, buf, 10);

Երկրորդ տողում կանչվում է POSIX ստանդարտի կողմից տրամադրվող read() ֆունկցիան, որը տրված ֆայլային նկարագրիչի համարով որոշվող ֆայլից կարդում է տրված քանակով բայթեր և լցնում տրված հիշողության հասցեում։ Այս ֆունկցիան իրականացվում է օպերացիոն համակարգի ինտերֆեյսում և հասանելի է դառնում պրոցեսին՝ համակարգային գրադարանային ֆունկցիայի միջոցով (այս մասին առավել մանրամասն խոսել ենք առաջին մասում)։

Քանի որ այս ֆունկցիան ևս վերաբերում է ֆայլային համակարգին, ապա այն ևս փոխանցվում է վիրտուալ ֆայլային համակարգի կոմպոնենտին՝ մշակվելու համար։ Առաջին հերթին հարկավոր է համոզվել, որ ֆունկցիային փոխանցված արգումենտները կոռռեկտ են։ Մասնավորապես՝ ֆայլային նկարագրիչի համարի ճիշտ լինելը։ Այդ նպատակով ստուգվում է, թե արդյոք կանչող պրոցեսի ֆայլային նկարագրիչների աղյուսակում կա այդպիսի համարով նկարագրիչ։ Եթե այդպիսին չի գտնվում, ապա կանչող պրոցեսին վերադարձվում է -1 արժեք և errno փոփոխականին վերագրվում է այս իրավիճակը նկարագրող համար (սխալ հանդիսացող իրավիճակներից յուրաքանչյուրի համար գոյություն ունեն նախապես մշակված համարներ)։

Մեր օրինակում բերված ծրագրում այդպիսի խնդիր չկա։ Անցնելով բուն ֆայլից կարդալու գործին՝ վիրտուալ ֆայլային համակարգը պետք է հարցումը փոխանցի կոնկրետ այն ֆայլային համակարգի կոմպոնենտին, որը պատասխանատու է հենց այդ ֆայլի հետ իրականացվող գործողությունների համար։ Այս դեպքում պետք է կանչել ֆայլից կարդացող ֆունկցիան, որի ցուցիչը կարելի է գտնել համապատասխան կառուցվածքի (struct) մեջ։ Այն ստեղծվում, արժեվորում և փոխանցվում է վիրտուալ ֆայլային համակարգին՝ յուրաքանչյուր ֆայլային համակարգի կոմպոնենտի կողմից։

Ֆայլից կարդալու համար հարկավոր է գտնել այն բլոքի համարը, որտեղ պահվում է ֆայլի բովանդակությունը։ Այդ բլոքների համարները պահվում են ֆայլի i-node — ում, որը գտնելու համար էլ պետք է իմանալ դրա համարը։ i-node — ի համարը պահվում է ֆայլային նկարագրիչում, որի համարը փոխանցվել է կարդացող ֆունկցիային։ Խճանկարը հավաքվեց։ Սխեմատիկ պարզ է, թե ինչ է պետք անել, բայց նշված կետերից յուրաքանչյուրի իրականացման համար կարող է կարիք լինել դիմելու արտաքին հիշասարքին՝ i-node-ը, բլոքը կամ մեկ այլ տեղեկություն կարդալու համար։ Նման յուրաքանչյուր քայլը լի է ստորջրյա քարերով, որոնց մասին մանրամասն խոսացել ենք հոդվածի առաջին երկու մասերում։

Ֆայլի i-node-ը հատուկ կառուցվածք ունի, որում պահվում է տեղեկություն ֆայլի ատրիբուտների և նրա պարունակության մասին։ Մասնավորապես պահվում են ֆայլի առաջին մի քանի բլոքների համարները։ Սակայն երբեմն ֆայլերը կարող են լինել ավելի մեծ և մի քանի բլոքը կարող է բավարար չլինել ֆայլի ողջ բովանդակությունը պահելու համար։ Այդ դեպքում պետք է կարդալ այն բլոքը, որի համարը վերջինն է i-node-ում։ Այդ բլոքը հանդիսանում է դեպի հրաշալիքների աշխարհը տանող դռան բանալին։ Իսկ ավելի կոնկրետ այն հանդիսանում է մի ծառի արմատը, որի տերևներում այն բլոքներն են, որտեղ պահվում է ֆայլի պարունակությունը։ Ծառը կարող է իրականացվել տարբեր եղանակներով, որոնց թվում կան B, B+ և B* ծառեր։ Այդ ծառերի մասին մանրամասն ինֆորմացիա կարելի է ստանալ տվյալների կառուցվածքների և ալգորիթմների դասընթացներից։

Մեր օրինակում պետք է կարդալ ֆայլի առաջին 10 բայթերը։ Դրանք, ակնհայտորեն, գտնվում են առաջին բլոքում, որի հասցեն գտնվում է անմիջականորեն i-node-ում։ Երբ արտաքին հիշասարքից կարդացված տվյալները հայտնվում են ֆայլային համակարգի հիշողության մեջ, կարելի է անցնել տվյալները պրոցեսին փոխանցելու գործին։ Պետք է հաշվի առնենք, որ ցուցիչը, որը փոխանցվել է ֆայլից կարդացող ֆունկցիային, հանդիսանում է օգտագործողի տիրույթի (user space) ցուցիչ։ Օպերացիոն համակարգի միջուկն, իհարկե, իրավունք ունի գրելու այդ հիշողության մեջ, սակայն դեռ պետք է գտնել նրա իրական գտնվելու վայրը։ Այսինքն պետք է օգտվել կանչող պրոցեսի վիրտուալ հիշողության էջային աղյուսակից, որպեսզի պարզենք, թե օպերատիվ հիշողության որ հասցեին է այն համապատասխանում։ Այդ խնդիրը լուծվում է միջուկի կողմից տրամադրվող հատուկ ֆունկցիաների միջոցով։

Ուշադիր ընթերցողը կարող է նկատել, որ ֆայլից տվյալներ կարդացող ֆունկցիայի աշխատանքի նկարագրության մեջ օգտագործեցինք «գրել» բառը։ Բանն այն է, որ այդ ֆունկցիան կարդում է ֆայլից և գրում է օպերատիվ հիշողության մեջ։ Այդ հանդիսանում է կարդացող ֆունկցիա, երբ մենք նրան կանչում ենք որպես օգտագործող։ Սակայն հենց այդ ֆունկցիան գրող ծրագրավորողի տեսակետից այն գրում է։ Այս պարադոքսը շատ պարզ ցույց է տալիս, թե որքան բարդ է համակարգային ծրագրավորողի կյանքը։ Նրա համար նույնիսկ կարդալն ու գրելն այդքան միարժեք գործողություններ չեն։

Բարեհաջող ավարտի դեպքում read() ֆունկցիան վերադարձնում է բայթերի քանակը, որն այն կարդացել է ֆայլից։ Ինչպես նաև ֆայլի ներքին ցուցիչը առաջ է տեղափոխվում (այս դեպքում 10 բայթով)։

Այս օրինակն իհարկե ամբողջությամբ չի բացահայտում օպերացիոն համակարգում տեղի ունեցող գործընթացները, սակայն այն ծածկեց բավական լայն մի մասը։ Դեռևս բաց մնացին հիշողության և պրոցեսների կառավարումը, բազմախնդրայնությունը և էլի մի շարք կոմպոնենտներ և հնարավորություններ, որոնք առկա են օպերացիոն համակարգերում։

Սպասենք նոր զարգացումների․․․