მოგზაურობა ლინუქსის ბირთვში

დავით მაჭახელიძე

ბირთვის სამყაროში შესავალი

ბირთვის შიგნით განვითარება ძალიან განსხვავდება ყოველდღიური მომხმარებლის სივრცის პროგრამირებისგან. პირველ რიგში, თქვენ უნდა გაუშვათ ბევრი ნაცნობი კომფორტი. სტანდარტული C ბიბლიოთეკა აკრძალულია, თუმცა ბირთვი თავის lib/ დირექტორიაში გთავაზობთ libc-ის ზოგიერთი ფუნქციის გამარტივებულ ვარიანტებს. თქვენ არ შეგიძლიათ დაეყრდნოთ ყველა იმ სათაურს, რომელსაც ჩვეულებრივ შეიცავთ მომხმარებლის სივრცეში და უნდა იცოდეთ, რომ ბირთვს აქვს GNU C გაფართოებების გამოყენების საკუთარი კონვენციები.

არანაკლებ მნიშვნელოვანია: მეხსიერების დაცვა არ არის იგივე, რაც მომხმარებლის სივრცეში. მომხმარებლის სივრცეში, თუ შემთხვევით მიმართავთ მაჩვენებელს, რომელიც არ გეკუთვნით, დიდი ალბათობით გამოიწვევთ სეგმენტაციის შეცდომას და თქვენი პროცესი შეიძლება ავარიულად დასრულდეს. ბირთვში მეხსიერებაზე ცუდმა წვდომამ შეიძლება გამოიწვიოს ფატალური “oops” შეტყობინება, რამაც შეიძლება დააზიანოს მთელი სისტემა. თქვენ ასევე არ შეგიძლიათ შემთხვევით შეასრულოთ მცურავი წერტილის ოპერაციები ბირთვში – ყოველ შემთხვევაში, არა ჩვეულებრივი გზით – რადგან ბირთვი თავს არიდებს მცურავი წერტილის ერთეულის გამოყენებას ნაგულისხმევად შესრულებისა და სირთულის მიზეზების გამო.

შემდეგ არის სტეკი. იმის ნაცვლად, რომ (შედარებით) დიდი, მზარდი სტეკი, რომელსაც შეჩვეული ხართ, ბირთვი უზრუნველყოფს მცირე, ფიქსირებული ზომის სტეკს (ხშირად მხოლოდ რამდენიმე გვერდს, მაგალითად, 8 კბ ბევრ 64-ბიტიან სისტემაზე). სტეკის გადმოდინება შეიძლება ჩუმად მოხდეს, ამიტომ თქვენ უნდა იყოთ დისციპლინირებული ლოკალური ცვლადების მიმართ. და მიუხედავად იმისა, რომ თანხვედრა შეშფოთების საგანია ნებისმიერი თანამედროვე პროგრამული პროექტისთვის, ბირთვი სრულიად სხვა დონეზეა. შეფერხებები შეიძლება მოხდეს ნებისმიერ დროს, წინასწარი ამოღება შეიძლება მოხდეს მოულოდნელ ადგილებში და სიმეტრიული მრავალპროცესირება (SMP) ნიშნავს, რომ მრავალ CPU-ს შეუძლია ერთდროულად გაუშვას თქვენი ბირთვის კოდი. ეს სრულიად განსხვავებული თამაშია!

პროცესები: მეტი ვიდრე უბრალოდ „ამოცანები“

ერთ-ერთი პირველი დიდი სიურპრიზი ჩემთვის იყო იმის გაცნობიერება, რომ Linux-ში პროცესები და თრედები არ არის ჭეშმარიტად განსხვავებული ერთეულები ქუდის ქვეშ. ორივე წარმოდგენილია ერთი და იგივე ფუნდამენტური მონაცემთა სტრუქტურით, task_struct. როდესაც იყენებთ clone()fork() ან pthread_create() მომხმარებლის სივრცეში, თქვენ რეალურად უბრალოდ ქმნით ვარიაციებს იმავე “ამოცანის” თემაზე. არგუმენტები, რომლებსაც გადასცემთ clone()-ს, არსებითად წყვეტს, რომელი რესურსები – როგორიცაა მეხსიერება, ფაილების დესკრიპტორები ან სიგნალის დამმუშავებლები – გაზიარებულია და რომელი დუბლირებულია.

მშვენიერი ხრიკი, რომელსაც Linux იყენებს შვილობილი პროცესის გამოჩეკვის შემდეგ, არის ბავშვს პირველ რიგში გაშვების უფლება. თუ ბავშვი უწოდებს exec()-ს, ის მყისიერად ცვლის თავის მისამართების სივრცეს ახალი პროგრამით, რაც copy-on-write ოპტიმიზაციას ძალიან ეფექტურს ხდის. თუ მშობელს უფლება მიეცათ, გაეშვათ და პირველ რიგში დაეწერა მეხსიერებაში, ბირთვს მოუწევდა გვერდების ზედმეტი ასლების შექმნა, რაც შეიძლება უსარგებლო აღმოჩნდეს, თუ ბავშვი მალე გამოიძახებს exec()-ს.

მიმდინარე ამოცანაზე წვდომისთვის, ბირთვის კოდი, როგორც წესი, იყენებს მაკროს სახელად current. მისი განხორციელება არქიტექტურაზეა დამოკიდებული. ზოგიერთ არქიტექტურაზე current მდებარეობს კონკრეტულ რეესტრში, ხოლო სხვებზე (როგორიცაა x86) მასზე წვდომა ხდება სტეკის ქვედა ნაწილში.

და ბოლოს, პროცესის შეწყვეტა არ ნიშნავს, რომ ის მაშინვე ქრება. შეწყვეტილი ამოცანა ხდება “ზომბი” მანამ, სანამ მისი მშობელი არ გამოიძახებს wait()-ს (ან ეკვივალენტს) მისი გასვლის კოდის წასაკითხად. თუ მშობელი ავარიულად მთავრდება, მისი შვილები “ხელახლა მშობლდებიან” init-ზე (PID 1) და საბოლოოდ init ასუფთავებს მათ. ასე რომ, თუ ოდესმე შეამჩნევთ “ზომბი” პროცესებს გარშემო, ეს ზუსტად ის არის, რაც ხდება.

განრიგი: და სამართლიანობა ყველასთვის… თუ არა

განრიგი ბირთვის ერთ-ერთი მთავარი სამუშაოა: გადაწყვიტოს რომელი პროცესი (ან თრედი) გაეშვას შემდეგი. Linux იყენებს განრიგის კლასებს სხვადასხვა პოლიტიკის სამართავად, ხოლო სრულიად სამართლიანი განრიგი (CFS) არის ძირითადი ნორმალური, არარეალური დროის ამოცანებისთვის. უმაღლესი დონის განრიგის ფუნქცია, schedule(), ამოწმებს განრიგის თითოეულ კლასს პრიორიტეტის მიხედვით. როგორც კი ის იპოვის კლასს გაშვებადი პროცესით, ის განრიგის გადაწყვეტილებებს გადასცემს ამ კლასს.

CFS მიზნად ისახავს სამართლიანობას: ის ცდილობს თითოეულ გაშვებად პროცესს მიანიჭოს CPU-ს პროპორცია, რომელსაც ხელმძღვანელობს nice მნიშვნელობები (დიაპაზონი -20 მაღალი პრიორიტეტისთვის +19 დაბალი პრიორიტეტისთვის). CFS-ის პირობებში არ არსებობს ფიქსირებული დროის მონაკვეთი. ამის ნაცვლად, განრიგი თვალყურს ადევნებს ვირტუალურ გაშვებას (vruntime), რომელიც უფრო სწრაფად გროვდება დაბალი პრიორიტეტის ამოცანებისთვის და უფრო ნელა მაღალი პრიორიტეტის ამოცანებისთვის. მონაცემთა სტრუქტურები, როგორიცაა წითელ-შავი ხე, ეხმარება თვალყური ადევნოს, რომელი პროცესი უნდა გაეშვას შემდეგი (ის, რომელსაც აქვს ყველაზე პატარა vruntime).

რეალურ დროში ამოცანებისთვის (პრიორიტეტებით 0-დან 99-მდე), განრიგი მათ პრიორიტეტს ანიჭებს ნორმალურ “მშვენიერი მნიშვნელობის” ამოცანებზე. Linux ასევე მხარს უჭერს ბირთვის წინასწარ ამოღებას, რაც იმას ნიშნავს, რომ ბირთვის რეჟიმის კოდიც კი შეიძლება იძულებით შეწყდეს უფრო მაღალი პრიორიტეტის ამოცანის გასაშვებად, სანამ არ ჩატარდება სპინლოკები ან სხვა არაპრეემპტირებადი რეგიონები. ეს ყველაფერი ბირთვის სწრაფვის ნაწილია ლატენტურობის შესამცირებლად და სისტემის რეაგირების უნარის შესანარჩუნებლად.

ბირთვში ზარების გაკეთება: სისტემური ზარები

სისტემური ზარები ქმნიან საზღვარს მომხმარებლის სივრცესა და ბირთვის სივრცეს შორის. მიუხედავად იმისა, რომ მათ “ფუნქციებს” ვუწოდებთ, სისტემური ზარები რეალურად განსაკუთრებული შესვლის წერტილებია, რომლებიც გამოწვეულია არქიტექტურის სპეციფიკური ინსტრუქციებით, როგორიცაა syscall ან int 0x80 x86-ზე. ბირთვში შესვლის შემდეგ, syscall დამმუშავებელი მიმართავს თქვენ სწორ ფუნქციას სისტემური ზარის ცხრილის მეშვეობით. თითოეულ არქიტექტურას აქვს საკუთარი ცხრილი, ასე რომ, თუ დაამატებთ ახალ სისტემურ ზარს, თქვენ უნდა დაამატოთ ჩანაწერი ყველა არქიტექტურისთვის, რომლის მხარდაჭერაც გსურთ.

იმის გამო, რომ მომხმარებლის სივრცეს შეუძლია გადასცეს მავნე ან დეფექტური მაჩვენებლები, სისტემური ზარები გულდასმით უნდა ვალიდირებდეს მათ პარამეტრებს – აქედან მოდის ფუნქციები, როგორიცაა copy_from_user() და copy_to_user(), რომლებიც უსაფრთხოდ გადააქვთ მონაცემებს მომხმარებლის სივრცესა და ბირთვის სივრცეს შორის. ამ ფუნქციებს შეუძლიათ დაბლოკვა და ისინი დააბრუნებენ შეცდომებს, თუ მეხსიერებაზე წვდომა არასწორია.

პრაქტიკაში, თანამედროვე ბირთვის დეველოპერები იშვიათად ამატებენ ახალ სისტემურ ზარებს, თუ ეს აბსოლუტურად აუცილებელი არ არის. უფრო ხშირად, თქვენ გამოავლენთ ფუნქციონირებას მოწყობილობის ფაილების ან sysfs ინტერფეისის საშუალებით, რაც მომხმარებლის პროგრამებს საშუალებას მისცემს ურთიერთქმედონ წაკითხვის/ჩაწერის ოპერაციების ან სპეციალიზებული სისტემის ფაილების მეშვეობით.

ბირთვის მონაცემთა სტრუქტურები: სიები, ხეები და სხვა

არ არის საჭირო ბორბლის ხელახლა გამოგონება: Linux ბირთვი მოიცავს ჩაშენებული მონაცემთა სტრუქტურების ჯგუფს. მაგალითად, დაკავშირებული სიის განხორციელება არის წრიული ორმაგად დაკავშირებული სია, რომლის ჩაშენებაც შეგიძლიათ პირდაპირ თქვენს საკუთარ სტრუქტურებში. არსებობს მაკროებისა და დამხმარე ფუნქციების ნაკრები ამ სიების დასამატებლად, ამოსაღებად და გამეორებისთვის ყოველგვარი აურზაურის გარეშე. თქვენ შეგიძლიათ შექმნათ სტეკები, რიგები ან სხვა შაბლონები მათ თავზე.

რუკებისთვის, ბირთვი გთავაზობთ სპეციალიზებულ სტრუქტურებს, რომლებიც ხშირად ძირითადია მომხმარებლის ID-ების ან სხვა ინტეგრალური ID-ების მიხედვით, როგორც წესი, წითელ-შავი ხეების გამოყენებით ქუდის ქვეშ. ასევე არის kfifo ინტერფეისი რგოლის ბუფერებისთვის (კლასიკური FIFO რიგის მიდგომა) და მაკროები თქვენი საკუთარი სპეციალიზებული სტრუქტურების შესაქმნელად მათ გარშემო. საერთო დიზაინი არის მინიმალისტური, მაგრამ მძლავრი და ეს ყველაფერი იქ არის, რათა თავიდან აიცილოთ საკუთარი კოდის დაწერა ამ საერთო შაბლონებისთვის.

შეფერხებები და შეფერხებების დამმუშავებლები

შეფერხებები არის ის, თუ როგორ აცნობებენ ტექნიკის მოწყობილობები CPU-სა და ბირთვს, რომ რაღაცას ყურადღება სჭირდება. თითოეულ შეფერხებას აქვს უნიკალური ნომერი და შესაბამისი შეფერხების სერვისის რუტინა (ISR) რეგისტრირებულია ბირთვში. როდესაც შეფერხება მოხდება, CPU აჩერებს ყველაფერს, რასაც აკეთებს, გადადის ISR-ზე და (იდეალურად) ბრუნდება მას შემდეგ, რაც კოდი შეასრულებს მინიმალურ აუცილებელ სამუშაოს.

რატომ მხოლოდ მინიმუმი? იმიტომ, რომ სანამ ISR-ის “ზედა ნახევარში” ხართ, შეფერხებები ამ ხაზზე გამორთულია და მთლიანი სისტემის შესრულება შეიძლება გაუარესდეს, თუ იქ გაჩერდებით. მძიმე აწევა ხდება “ქვედა ნახევარში”, რომელსაც ბირთვი ახორციელებს ისეთი მექანიზმების გამოყენებით, როგორიცაა taskletssoftirqs ან workqueues. ეს გადადებული დამუშავების მოდელი ეხმარება შეინარჩუნოს შეფერხების დაბალი ლატენტურობა.

Linux-ში შეფერხების თითოეული დამმუშავებელი აბრუნებს ან IRQ_HANDLED-ს დასადასტურებლად “დიახ, ეს ჩემთვის იყო” ან IRQ_NONE-ს, თუ ის რეალურად არ იყო გენერირებული ამ კონკრეტული მოწყობილობის მიერ. ბევრ თანამედროვე სისტემაში, მრავალი მოწყობილობა იზიარებს შეფერხებებს, ამიტომ გჭირდებათ სწრაფი გზა იმის დასადგენად, ხართ თუ არა განკუთვნილი მიმღები.

ქვედა ნახევრები და სამუშაოს გადადება

იმისათვის, რომ თავიდან აიცილოთ ზედმეტი დრო შეფერხებებით გამორთული, Linux გადადებს სამუშაოს დიდ ნაწილს “ქვედა ნახევრებზე”. ეს ქვედა ნახევრები მუშაობს ნაკლები შეზღუდვებით და უფრო ხელსაყრელ მომენტებში. ბირთვი გთავაზობთ ამისათვის სამ ძირითად საშუალებას: Softirqs (სტატიკურად გამოყოფილი, შეუძლია მუშაობა მრავალ CPU-ზე), Tasklets (აშენებულია softirqs-ის თავზე, დინამიურად იქმნება, არ შეუძლია ორი ერთნაირი ტიპის მუშაობა ორ CPU-ზე ერთდროულად) და Workqueues (მუშაობს პროცესის კონტექსტში, შეუძლია დაიძინოს/დაბლოკოს, თითოეულ CPU-ს აქვს საკუთარი ბირთვის მუშაკის თრედი).

თუ თქვენს გადადებულ რუტინას სჭირდება ძილი (მაგ., რესურსის მოლოდინში), თქვენ უნდა გამოიყენოთ სამუშაო რიგი. წინააღმდეგ შემთხვევაში, შეგიძლიათ დაიცვათ დავალებებით ან softirqs შესრულებისთვის. დრაივერების ავტორების უმეტესობა მთავრდება დავალებების ან სამუშაო რიგების გამოყენებით, რადგან softirqs მოითხოვს უფრო რთულ თანხვედრის დამუშავებას და უნდა დარეგისტრირდეს სტატიკურად.

ბირთვის სინქრონიზაცია: უსაფრთხოდ დარჩენის ხელოვნება

თანხვედრის პრობლემები ყველგან არის ბირთვში. თქვენ გაქვთ შეფერხებები, ქვედა ნახევრები, ბირთვის წინასწარი ამოღება, SMP, ძილი და სხვა. ამ გარემოში გადარჩენისთვის, თქვენ უნდა დაეყრდნოთ სხვადასხვა სინქრონიზაციის პრიმიტივებსა და დიზაინის შაბლონებს, რომლებიც უზრუნველყოფენ მონაცემთა უსაფრთხო წვდომას.

ყველაზე ძირითად დონეზე, ბირთვი უზრუნველყოფს ატომურ ოპერაციებს (atomic_tatomic64_t) და ბიტიან ოპერაციებს, რომლებიც მუშაობენ ატომურად. შემდეგ არის საკეტები:

  • Spinlocks: დაკავებული ლოდინის საკეტები, რომლებიც შეიძლება გამოყენებულ იქნას შეფერხების კონტექსტში (რადგან ისინი არ ბლოკავენ). თუ თქვენ მათ იყენებთ შეფერხების დამმუშავებელში, თქვენ ასევე უნდა გამორთოთ შეფერხებები საკეტის დაჭერისას.
  • მკითხველ-ჩამწერი სპინლოკები: მრავალ მკითხველს უშვებს შიგნით, მაგრამ მხოლოდ ერთ ჩამწერს ერთდროულად. თუმცა მკითხველებს შეუძლიათ ჩამწერის შიმშილი.
  • სემაფორები: მრიცხველი სემაფორები, რომლებიც საშუალებას გაძლევთ დაბლოკოთ, თუ რესურსი მიუწვდომელია. ერთი “ტოკენისთვის” ისინი მოქმედებენ როგორც მუტექსი.
  • მუტექსები: სემაფორების მსგავსი, მაგრამ მხოლოდ თრედს, რომელმაც ჩაკეტა, შეუძლია მისი განბლოკვა. განმეორებადი ჩაკეტვა დაუშვებელია.
  • დასრულებები: მსუბუქი სიგნალები, რომლებიც ერთ თრედს შეუძლია გამოიყენოს მეორისთვის იმის შესატყობინებლად, რომ “ჰეი, მე დავასრულე ჩემი საქმე”.

ასევე არსებობს მოწინავე კონსტრუქციები, როგორიცაა თანმიმდევრობის საკეტები, რომლებიც უპირატესობას ანიჭებენ ჩამწერებს. და თუ თქვენ გჭირდებათ შეფერხებებთან, ქვედა ნახევრებთან ან ბირთვის წინასწარ ამოღებასთან კოორდინაცია, შეგიძლიათ გამორთოთ ისინი ლოკალიზებული გზით თქვენი კრიტიკული სექციების დასაცავად. უმჯობესია ვივარაუდოთ, რომ ყველაფერი ერთდროულად ხდება, რადგან ბირთვის სამყაროში ხშირად ასეა.

ტაიმერები და დროის აღრიცხვა

ბირთვის კიდევ ერთი მაგია არის ის, თუ როგორ ხდება დროის თვალყურის დევნება და გამოყენება. აპარატურის პლატფორმა უზრუნველყოფს სისტემის ტაიმერს, რომელიც იკვრება სიხშირით, რომელიც წარმოდგენილია HZ მუდმივით ბირთვში. თითოეული “ტიკი” იწვევს ტაიმერის შეფერხებას და საპასუხოდ, ბირთვი განაახლებს “jiffies” მრიცხველს (გლობალური ცვლადი, რომელიც შეიცავს ჩატვირთვის შემდეგ ტიკების რაოდენობას), ინარჩუნებს სისტემის მუშაობის დროს, განაახლებს დატვირთვის საშუალოს და ა.შ.

ბირთვში ტაიმერები არის პერიოდული ან დინამიური. დინამიური ტაიმერები საშუალებას გაძლევთ დაგეგმოთ ფუნქცია გარკვეული დაყოვნების შემდეგ გასაშვებად. როდესაც დინამიური ტაიმერი იწურება, ბირთვის ტაიმერის softirq აწარმოებს შესაბამის უკუკავშირს ქვედა ნახევრის კონტექსტში. თუ ოდესმე დაგჭირდებათ ტაიმერის გაუქმება, დარწმუნდით, რომ იყენებთ გაუქმების ფუნქციების “სინქრონულ” ვერსიებს – როგორიცაა del_timer_sync() – თუ არსებობს რაიმე შესაძლებლობა, რომ ტაიმერი მუშაობს სხვა CPU-ზე.

მოკლე დაყოვნებისთვის, სადაც ძილი შეუძლებელია (როგორიცაა მიკროწამის მასშტაბის ლოდინი დრაივერის კოდში), შეგიძლიათ გამოიძახოთ udelay()ndelay() ან mdelay(). წინააღმდეგ შემთხვევაში, თუ თქვენ შეგიძლიათ დათმოთ CPU, schedule_timeout() თქვენი მეგობარია.

მეხსიერების მართვა: გვერდები და განაწილებები

ბირთვის შიგნით, ფიზიკური მეხსიერება იყოფა გვერდებად. თითოეულ გვერდზე ჩვეულებრივ თვალყურს ადევნებს struct page, რომელიც აღწერს ვინ ფლობს მას (მომხმარებლის პროცესები, ბირთვის განაწილებები და ა.შ.). გვერდები დაჯგუფებულია ზონებად, როგორიცაა ZONE_DMAZONE_NORMAL და ZONE_HIGHMEM. თითოეულ არქიტექტურას შეიძლება ჰქონდეს განსხვავებული რაოდენობის ზონები.

თუ გჭირდებათ ფიზიკურად მიმდებარე მეხსიერება ზომით 2^order გვერდები, შეგიძლიათ გამოიყენოთ alloc_pages(). ყველაზე მცირე განაწილებისთვის თქვენ გამოიყენებთ kmalloc(), რომელიც აბრუნებს ფიზიკურად მიმდებარე მეხსიერების ნაწილს. ასევე არის vmalloc() განაწილებისთვის, რომლებიც საჭიროა მხოლოდ ვირტუალურ სივრცეში მიმდებარე იყოს (შესრულების დანაკარგით), მაგრამ არა ფიზიკურად მიმდებარე.

მთელი ქვესისტემა სახელწოდებით “slab allocator” (ან SLUB, თქვენი ბირთვის ვერსიის მიხედვით) მართავს ქეშების შექმნას ხშირად გამოყოფილი ობიექტებისთვის. ეს ხელს უწყობს მეხსიერების ეფექტურად გადამუშავებას. თქვენ შეგიძლიათ შექმნათ თქვენი საკუთარი მორგებული ქეშები სტრუქტურებისთვის, რომლებსაც ხშირად ანაწილებთ თქვენს დრაივერში ან ქვესისტემაში.

ბირთვი ასევე გთავაზობთ გამოყოფილ თითო CPU ცვლადებსა და API-ებს, რათა შეგეძლოთ მონაცემების შენახვა ცალკე თითოეულ CPU-ზე მისი ჩაკეტვის გარეშე. ამ ცვლადებზე წვდომა სიფრთხილით მოითხოვს: ჩვეულებრივ, ცუდი იდეაა სხვა CPU-ს თითო CPU ცვლადის წაკითხვა ჩაკეტვის გარეშე.

ვირტუალური ფაილური სისტემა (VFS)

თუ ოდესმე გამოგიყენებიათ ფაილების შეყვანა/გამოტანა Linux-ში (და ვის არა?), თქვენ ირიბად ურთიერთობდით VFS-თან. ის წარმოადგენს ერთიან, ერთგვაროვან ინტერფეისს ყველა ფაილური სისტემისთვის – ext4, XFS, NFS, თქვენ დაასახელეთ. ქუდის ქვეშ, VFS იყენებს საერთო ობიექტის ტიპების კომპლექტს, როგორიცაა superblockinodedentry და file სტრუქტურები. თითოეულ ობიექტს აქვს ფუნქციის მაჩვენებლების ასოცირებული ცხრილი – მეთოდები წაკითხვისთვის, წერისთვის, ჩანაწერების მოსაძებნად და ა.შ.

inode ინახავს ფაილის მეტამონაცემებს (მფლობელობა, ნებართვები, დროის ნიშნები და ა.შ.). dentry წარმოადგენს ფაილის ან დირექტორიის კომპონენტის დირექტორიის ჩანაწერს ბილიკში. ბირთვი ქეშებს ამ ჩანაწერებს dcache-ში ბილიკების ძიების დასაჩქარებლად. ამასობაში, superblock ობიექტი ინახავს ინფორმაციას მთელი დამონტაჟებული ფაილური სისტემის შესახებ და file ობიექტი წარმოადგენს ღია ფაილს კონკრეტული წვდომის რეჟიმებით. გენიოსი აქ არის ის, რომ როდესაც თქვენ ხსნით ფაილს, თქვენ ნამდვილად არ გჭირდებათ იცოდეთ, არის თუ არა ის ext4 დანაყოფზე, ქსელის გაზიარებაზე თუ ramdisk-ზე. ეს ყველაფერი უბრალოდ მუშაობს, VFS ფენის წყალობით.

ბლოკის შეყვანის/გამოტანის ფენა

საწყობი მოწყობილობები, როგორც წესი, განიხილება როგორც ბლოკის მოწყობილობები Linux-ში, რაც იმას ნიშნავს, რომ ბირთვს შეუძლია მათი წაკითხვა ან ჩაწერა ფიქსირებული ზომის სექტორების ჯერადებში. ამის ზემოთ არის ფაილური სისტემის ფენა, რომელიც ეხება ბლოკებს (სექტორების ჯერადებს) სისტემის გვერდის მაქსიმალურ ზომამდე.

ძველი ბირთვები იყენებდნენ “ბუფერული სათაურის” სტრუქტურას მეხსიერებაში თითოეული ბლოკის წარმოსადგენად. თანამედროვე ბირთვები ხშირად ეყრდნობიან bio სტრუქტურებს, რომლებიც იძლევა მიმოფანტულ-შეკრების შეყვანას/გამოტანას (ანუ არამიმწყვდილი გვერდები, რომლებიც ქმნიან ერთ ოპერაციას). როდესაც თქვენ იწყებთ წაკითხვას ან ჩაწერას, ბირთვი აშენებს bio-ს, აყენებს გვერდების, ოფსეტების და სიგრძეების სიას და შემდეგ აყენებს მოთხოვნას ბლოკის მოწყობილობის მოთხოვნის რიგში. სპეციალიზებული შეყვანის/გამოტანის განრიგი შემდეგ აერთიანებს და ახარისხებს ამ მოთხოვნებს დისკის ძიების შესამცირებლად – გადამწყვეტი მბრუნავი დისკებისთვის. SSD-ებზე, NOOP ან ვადის გასვლის განრიგი შეიძლება საკმარისი იყოს, რადგან “შემთხვევითი წვდომისთვის” მცირე ჯარიმაა.

ბირთვის გამართვა

ბირთვის კოდის გამართვა შეიძლება დამაშინებელი იყოს. საბედნიეროდ, არსებობს printk() ფუნქცია, რომელიც მომხმარებლის სივრცეში printf()-ის მსგავსია, მაგრამ საიმედოდ მუშაობს ბირთვის თითქმის ნებისმიერ კონტექსტში. თუ რამე ნამდვილად არასწორად წავა, შეიძლება დაინახოთ “oops” ან ბირთვის სრული პანიკა. “oops” არის შეცდომა, საიდანაც ბირთვი ცდილობს აღდგენას; პანიკა ნიშნავს, რომ ბირთვი საერთოდ ვერ აღდგება.

ზოგჯერ oops ან პანიკის შეტყობინება ბეჭდავს ნედლ მისამართებს. ინსტრუმენტებს, როგორიცაა ksymoops (ან CONFIG_KALLSYMS_ALL-ის ჩართვა), შეუძლიათ გაშიფრონ ეს მისამართები ფუნქციის სახელებად და ხაზების ნომრებად. ბირთვი ასევე მხარს უჭერს kgdb-ს, რომელიც საშუალებას იძლევა წყაროს დონის გამართვა სერიული ან ქსელური კავშირის საშუალებით, თუმცა მისი დაყენება შეიძლება რთული იყოს.

თუ აღმოაჩენთ, რომ შეცდომის ეჭვი გაქვთ, შეგიძლიათ გამოიყენოთ მაკროები, როგორიცაა BUG_ON() ან panic(), რათა განზრახ დაიკიდოთ ავარიულად, როდესაც გარკვეული პირობები დაკმაყოფილდება. ეს ექსტრემალურად ჟღერს, მაგრამ მიზანმიმართული ავარიული გაშვება ზოგჯერ შეიძლება იყოს ფატალური ნაკლის დიაგნოსტიკის ყველაზე სწრაფი გზა – განსაკუთრებით მაშინ, როდესაც საქმე გაქვთ რასის პირობებთან ან მეხსიერების დაზიანებასთან.

პორტაბელურობა: რატომ მუშაობს Linux ყველაფერზე

Linux ბირთვის ერთ-ერთი ყველაზე ძლიერი თვისებაა მისი პორტაბელურობა. იქნება ეს პატარა ჩაშენებულ დაფაზე, სტანდარტულ x86 PC-ზე თუ მასიურ მეინფრეიმზე, Linux ინახავს თავისი კოდის უმეტეს ნაწილს არქიტექტურისგან დამოუკიდებლად. თითოეული პლატფორმა ახორციელებს საკუთარ დაბალი დონის რუტინებს (როგორიცაა პროცესების გადართვა, გამონაკლისების დამუშავება ან MMU-ს მართვა), მაგრამ საერთო ბირთვის ქვესისტემები იგივე რჩება.

თუ აღმოაჩენთ, რომ წერთ ბირთვის კოდს, არასოდეს იფიქროთ ისეთ რამეებზე, როგორიცაა სიტყვის ზომა, ენდიანობა ან თუნდაც ტაიმერის სიხშირე. გამოიყენეთ ბირთვის მაკროები და გაუმჭვირვალე ტიპები (u32atomic_t და ა.შ.) და დაეყრდნოთ შესაბამის API-ებს გასწორებისთვის ან დიდი ენდიანურიდან პატარა ენდიანურზე გადასაყვანად. ბირთვი სავსეა ამ ტიპის დამხმარე საშუალებებით ზუსტად იმიტომ, რომ კოდი უნდა მუშაობდეს სუფთად მრავალ არქიტექტურაზე.

შეჯამება

ბირთვის განვითარებაში ჩაძირვა თავიდან შეიძლება უცნობი იყოს. შეზღუდული სტეკი, თანხვედრის გამოწვევები და სპეციალიზებული მეხსიერების მართვა მას საკმაოდ განსხვავებულს ხდის მომხმარებლის სივრცის პროგრამირებისგან. მაგრამ როგორც კი დაიწყებთ იმის გაგებას, თუ როგორ ჯდება ყველაფერი ერთად, ეს დააჯილდოებს – თქვენ მოიპოვებთ პირდაპირ კონტროლს აპარატურაზე, შეიმუშავებთ დაბალი დონის მონაცემთა სტრუქტურებს და ხედავთ თქვენი ცვლილებების გავლენას რეალურ დროში.

გააზიარეთ: