როგორ განვავითაროთ ოპერაციული სისტემა კომპიუტერისთვის. Os - რა არის საჭირო ოპერაციული სისტემის დასაწერად? როგორ შევქმნათ საკუთარი ოპერაციული სისტემა


თუ აზრამდე მივალთ...

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

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

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

კომპიუტერისთვის, თქვენ უნდა დაწეროთ ჩამტვირთავი ASMA-ზე, რომელსაც გამოიძახებს BIOS და რომელიც, ოთხ და რამდენიმე ასეულ ბაიტზე მეტის გარეშე, უნდა გააკეთოს რაღაც და გაუშვას მთავარი OS - გადაიტანოს კონტროლი მთავარ კოდზე, რომელიც უახლოესი მომავალი შეიძლება დაიწეროს C.

ARM-ისთვის ACMA-ზე უნდა შექმნათ შეფერხების ცხრილი (გადატვირთვა, სხვადასხვა შეცდომები, IRQ, FIQ შეფერხებები და ა.შ.) და გადაიტანოთ კონტროლი მთავარ კოდზე. თუმცა, განვითარების ბევრ გარემოში ასეთი კოდი ხელმისაწვდომია თითქმის ნებისმიერი კონტროლერისთვის.

ანუ, ამისათვის გჭირდებათ:

  1. იცოდე სამიზნე პლატფორმის ასამბლერის ენა.
  2. იცოდეთ პროცესორის არქიტექტურა და ყველა სახის სერვისის ბრძანებები და რეგისტრები, რათა დააკონფიგურიროთ ის, რომ იმუშაოს სასურველ რეჟიმში. კომპიუტერში ეს არის გადასვლა დაცულ რეჟიმზე, მაგალითად, ან 64-ბიტიან რეჟიმში... ARM-ში ეს არის ბირთვის და პერიფერიული მოწყობილობების ქრონიკის დაყენება.
  3. ზუსტად იცოდეთ როგორ დაიწყება OS, სად და როგორ უნდა დააყენოთ თქვენი კოდი.
  4. იცოდე C ენა - ძნელია ასმაში დიდი კოდის დაწერა გამოცდილების გარეშე, მისი შენარჩუნება კიდევ უფრო რთული იქნება. ამიტომ, თქვენ უნდა დაწეროთ ბირთვი C-ში.
  5. იცოდე ოპერაციული ოპერაციული პრინციპები. ისე, რუსულ ენაზე ბევრი წიგნია ამ თემაზე, თუმცა არ ვიცი, ყველა კარგია თუ არა.
  6. გქონდეთ ბევრი და ბევრი მოთმინება და შეუპოვრობა. იქნება შეცდომები და მათი აღმოჩენა და გამოსწორება იქნება საჭირო. ასევე დაგჭირდებათ ბევრი კითხვა.
  7. ბევრი და ბევრი დრო გქონდეს.

Უფრო. ვთქვათ თქვენ დაწერეთ რამე. ჩვენ უნდა გამოვცადოთ ეს ნივთი. ან გჭირდებათ ფიზიკური მოწყობილობა, რომელზეც ჩატარდება ექსპერიმენტები (გამართვის დაფა, მეორე კომპიუტერი), ან მის ემულატორი. მეორე ჩვეულებრივ უფრო მარტივი და სწრაფი გამოსაყენებელია. კომპიუტერისთვის, მაგალითად, VMWare.

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

თუ ნამდვილად გსურთ, შეგიძლიათ გადახედოთ NT სისტემების (Windows) ძველი ბირთვის საწყის კოდს, როგორც ცალ-ცალკე (რომელიც გამოქვეყნდა მაიკროსოფტის მიერ, კომენტარებითა და სხვადასხვა სახის საცნობარო მასალებით), ასევე ძველთან ერთად. OS (გაჟონა).

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


გამარჯობა მსოფლიო!

მოდით დავწეროთ ბირთვი, რომელიც ჩაიტვირთება GRUB-ის მეშვეობით x86-თან თავსებად სისტემებზე. ჩვენი პირველი ბირთვი აჩვენებს შეტყობინებას ეკრანზე და იქ გაჩერდება.

როგორ ჩაიტვირთება x86 მანქანები

სანამ ვიფიქრებთ იმაზე, თუ როგორ დავწეროთ ბირთვი, მოდით შევხედოთ როგორ ჩაიტვირთება კომპიუტერი და გადასცემს კონტროლს ბირთვს. x86 პროცესორის რეგისტრების უმეტესობას აქვს კონკრეტული მნიშვნელობები ჩატვირთვის შემდეგ. ინსტრუქციის მაჩვენებლის რეესტრი (EIP) შეიცავს ინსტრუქციის მისამართს, რომელიც შესრულდება პროცესორის მიერ. მისი მყარი კოდირებული მნიშვნელობა არის 0xFFFFFFFF0. ანუ, x86 პროცესორი ყოველთვის დაიწყებს შესრულებას ფიზიკური მისამართიდან 0xFFFFFFF0. ეს არის 32-ბიტიანი მისამართების სივრცის ბოლო 16 ბაიტი. ამ მისამართს ეწოდება გადატვირთვის ვექტორი.

ჩიპსეტში მოთავსებულ მეხსიერების ბარათში ნათქვამია, რომ მისამართი 0xFFFFFFF0 ეხება BIOS-ის კონკრეტულ ნაწილს და არა RAM-ს. ამასთან, BIOS აკოპირებს საკუთარ თავს RAM-ში უფრო სწრაფი წვდომისთვის - ამ პროცესს ეწოდება "ჩრდილი", ქმნის ჩრდილოვანი ასლს. ასე რომ, მისამართი 0xFFFFFFF0 შეიცავს მხოლოდ ინსტრუქციას მეხსიერების იმ მდებარეობაზე გადასვლის შესახებ, სადაც თავად BIOS-მა დააკოპირა.

ასე რომ, BIOS იწყებს შესრულებას. პირველ რიგში, ის ეძებს მოწყობილობებს, საიდანაც მას შეუძლია ჩატვირთვა პარამეტრებში მითითებული თანმიმდევრობით. ის ამოწმებს მედიას „ჯადოსნურ ნომერზე“, რომელიც განასხვავებს ჩამტვირთველ დისკებს ჩვეულებრივიდან: თუ პირველ სექტორში ბაიტი 511 და 512 არის 0xAA55, მაშინ დისკი ჩამტვირთავია.

როგორც კი BIOS იპოვის ჩატვირთვის მოწყობილობას, ის დააკოპირებს პირველი სექტორის შიგთავსს RAM-ში, დაწყებული მისამართიდან 0x7C00, შემდეგ გადაიტანს შესრულებას ამ მისამართზე და დაიწყებს მის მიერ ახლახან ჩატვირთული კოდის შესრულებას. ამ კოდს Bootloader ეწოდება.

ჩამტვირთველი ატვირთავს ბირთვს ფიზიკურ მისამართზე 0x100000. ეს არის ის, რასაც ყველაზე პოპულარული x86 ბირთვი იყენებს.

x86-თან თავსებადი ყველა პროცესორი იწყება პრიმიტიული 16-ბიტიანი რეჟიმით, რომელსაც ეწოდება "რეალური რეჟიმი". GRUB ჩამტვირთველი ცვლის პროცესორს 32-ბიტიან დაცულ რეჟიმზე CR0 რეგისტრის ქვედა ბიტის ერთზე დაყენებით. ამრიგად, ბირთვი იწყებს ჩატვირთვას 32-ბიტიან დაცულ რეჟიმში.

გაითვალისწინეთ, რომ GRUB, Linux-ის ბირთვების შემთხვევაში, ირჩევს შესაბამის ჩატვირთვის პროტოკოლს და ჩატვირთავს ბირთვს რეალურ რეჟიმში. Linux-ის ბირთვი ავტომატურად გადადის დაცულ რეჟიმში.

რა გვჭირდება

  • x86 თავსებადი კომპიუტერი (ცხადია)
  • Linux
  • NASM ასამბლეერი,
  • ld (GNU Linker),
  • GRUB.

ასამბლეის ენის შესასვლელი წერტილი

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

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

აქ არის ასამბლერის კოდი.

ბირთვი.ასმ
ბიტი 32 განყოფილება .ტექსტი გლობალური დაწყება გარე kmain დაწყება: cli mov esp, stack_space ზარი kmain hlt განყოფილება .bss resb 8192 stack_space:

პირველი ბიტი 32 ინსტრუქცია არ არის x86 ასამბლეერი, არამედ NASM დირექტივა, რომელიც ეუბნება მას, რომ შექმნას კოდი, რომ პროცესორი იმუშაოს 32-ბიტიან რეჟიმში. ეს არ არის აუცილებელი ჩვენი მაგალითისთვის, მაგრამ კარგი პრაქტიკაა ამის ცალსახად მითითება.

მეორე სტრიქონი იწყებს ტექსტის განყოფილებას, რომელიც ასევე ცნობილია როგორც კოდის განყოფილება. ყველა ჩვენი კოდი აქ წავა.

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

kmain არის ფუნქცია, რომელიც განისაზღვრება ჩვენს kernel.c ფაილში. extern აცხადებს, რომ ფუნქცია გამოცხადებულია სხვაგან.

შემდეგ მოდის start ფუნქცია, რომელიც უწოდებს kmain-ს და აჩერებს პროცესორს hlt ინსტრუქციით. შეფერხებებმა შეიძლება გააღვიძოს პროცესორი hlt-ის შემდეგ, ამიტომ პირველ რიგში გამორთეთ შეფერხებები cli (გაასუფთავეთ შეფერხებები) ინსტრუქციით.

იდეალურ შემთხვევაში, ჩვენ უნდა გამოვყოთ მეხსიერების გარკვეული რაოდენობა სტეკისთვის და მივუთითოთ სტეკის მაჩვენებელი (esp). როგორც ჩანს, GRUB ამას აკეთებს ჩვენთვის და ამ ეტაპზე სტეკის მაჩვენებელი უკვე დაყენებულია. თუმცა, ყოველი შემთხვევისთვის, მოდით გამოვყოთ გარკვეული მეხსიერება BSS განყოფილებაში და მივუთითოთ სტეკის მაჩვენებელი მის დასაწყისზე. ჩვენ ვიყენებთ resb ინსტრუქციას - ის ინახავს მეხსიერებას მითითებულ ბაიტებში. ამის შემდეგ რჩება ნიშანი, რომელიც მიუთითებს მეხსიერების რეზერვირებული ნაწილის ზღვარზე. kmain-ის გამოძახებამდე, სტეკის მაჩვენებელი (esp) მიმართულია ამ ზონაში mov ინსტრუქციით.

ბირთვი C

kernel.asm ფაილში ჩვენ გამოვიძახეთ kmain() ფუნქცია. ასე რომ, C კოდში, შესრულება დაიწყება იქიდან.

ბირთვი.გ
void kmain(void) ( const char *str = "ჩემი პირველი ბირთვი"; char *vidptr = (char*)0xb8000; ხელმოუწერელი int i = 0; ხელმოუწერელი int j = 0; while(j< 80 * 25 * 2) { vidptr[j] = " "; vidptr = 0x07; j = j + 2; } j = 0; while(str[j] != "\0") { vidptr[i] = str[j]; vidptr = 0x07; ++j; i = i + 2; } return; }

ჩვენი ბირთვი ყველაფერს გააკეთებს არის ეკრანის გასუფთავება და ჩემი პირველი ბირთვის ხაზის ამობეჭდვა.

პირველი, ჩვენ ვქმნით vidptr მაჩვენებელს, რომელიც მიუთითებს მისამართზე 0xb8000. დაცულ რეჟიმში, ეს არის ვიდეო მეხსიერების დასაწყისი. ტექსტური ეკრანის მეხსიერება უბრალოდ მისამართების სივრცის ნაწილია. მეხსიერების განყოფილება გამოყოფილია ეკრანის I/O-სთვის, რომელიც იწყება მისამართიდან 0xb8000; მასში მოთავსებულია 80 ASCII სიმბოლოს 25 ხაზი.

ტექსტის მეხსიერებაში თითოეული სიმბოლო წარმოდგენილია 16 ბიტით (2 ბაიტი), ვიდრე 8 ბიტით (1 ბაიტი), რომელსაც ჩვენ შეჩვეული ვართ. პირველი ბაიტი არის სიმბოლოს ASCII კოდი, ხოლო მეორე ბაიტი არის ატრიბუტი-ბაიტი. ეს არის სიმბოლოების ფორმატის განმარტება, მისი ფერის ჩათვლით.

სიმბოლოს მწვანე შავზე გამოსატანად, ჩვენ უნდა ჩავდოთ s ვიდეო მეხსიერების პირველ ბაიტში და მნიშვნელობა 0x02 მეორე ბაიტში. 0 აქ ნიშნავს შავ ფონს და 2 ნიშნავს მწვანე ფერს. გამოვიყენებთ ღია ნაცრისფერ ფერს, მისი კოდია 0x07.

პირველ while ციკლში, პროგრამა ავსებს 80 სიმბოლოსგან შემდგარ 25 ხაზს ცარიელი სიმბოლოებით 0x07 ატრიბუტით. ეს გაასუფთავებს ეკრანს.

მეორე while მარყუჟში, null-შეწყვეტილი სტრიქონი, ჩემი პირველი ბირთვი, იწერება ვიდეო მეხსიერებაში და თითოეული სიმბოლო იღებს ატრიბუტ-ბაიტს 0x07. ეს უნდა გამოვიდეს სტრიქონი.

განლაგება

ახლა ჩვენ უნდა შევადგინოთ kernel.asm ობიექტურ ფაილში NASM-ის გამოყენებით და შემდეგ გამოვიყენოთ GCC kernel.c სხვა ობიექტის ფაილში შესადგენისთვის. ჩვენი ამოცანაა დავაკავშიროთ ეს ობიექტები ჩატვირთვისთვის შესაფერის შესრულებად ბირთვში. ამისათვის დაგვჭირდება ლინკერის (ld) სკრიპტის დაწერა, რომელსაც არგუმენტად გადავიტანთ.

ბმული.ld
OUTPUT_FORMAT(elf32-i386) ENTRY(დაწყება) SECTIONS (. = 0x100000; .ტექსტი: ( *(.ტექსტი) ) .მონაცემები: ( *(.მონაცემები) ) .bss: ( *(.bss) ) )

აქ ჩვენ პირველად დავაყენეთ ჩვენი შესრულებადი ფაილის ფორმატი (OUTPUT_FORMAT) 32-ბიტიან ELF-ზე (შესრულებადი და დამაკავშირებელი ფორმატი), სტანდარტული ორობითი ფორმატი Unix-ზე დაფუძნებული სისტემებისთვის x86 არქიტექტურისთვის.

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

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

ხვეული ბრეკეტებში, რომლებიც მიჰყვება SECTIONS გამოსახულებას, წერტილი მიუთითებს მდებარეობის მრიცხველზე. ის ავტომატურად ინიციალიზდება 0x0-მდე SECTIONS ბლოკის დასაწყისში, მაგრამ შეიძლება შეიცვალოს ახალი მნიშვნელობის მინიჭებით.

ადრე დავწერე, რომ ბირთვის კოდი უნდა დაიწყოს მისამართიდან 0x100000. ამიტომ პოზიციის მრიცხველს მივანიჭებთ მნიშვნელობას 0x100000.

შეხედეთ ხაზს.ტექსტი: ( *(.ტექსტი) ) . ვარსკვლავი აქ განსაზღვრავს ნიღაბს, რომელიც შეიძლება შეესაბამებოდეს ფაილის ნებისმიერ სახელს. შესაბამისად, გამოთქმა *(.text) ნიშნავს ყველა შეყვანილ .ტექსტის განყოფილებას ყველა შეყვანის ფაილში.

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

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

GRUB და multiboot

ახლა ყველა ჩვენი ფაილი მზად არის ბირთვის ასაშენებლად. მაგრამ რადგან ჩვენ ჩავტვირთავთ ბირთვს GRUB-ის გამოყენებით, დარჩა კიდევ ერთი ნაბიჯი.

არსებობს სტანდარტი სხვადასხვა x86 ბირთვების ჩატვირთვისთვის ჩატვირთვის გამოყენებით. ამას ჰქვია "multiboot სპეციფიკაცია". GRUB ჩატვირთავს მხოლოდ ბირთვებს, რომლებიც შეესაბამება მას.

ამ სპეციფიკაციის მიხედვით, ბირთვი შეიძლება შეიცავდეს სათაურს (Multiboot header) პირველ 8 კილობაიტში. ეს სათაური უნდა შეიცავდეს სამ ველს:

  • მაგია- შეიცავს "ჯადოსნურ" ნომერს 0x1BADB002, რომლითაც ხდება სათაურის იდენტიფიცირება;
  • დროშები- ეს ველი ჩვენთვის არ არის მნიშვნელოვანი, შეგიძლიათ ნული დატოვოთ;
  • საკონტროლო ჯამი- საკონტროლო ჯამი, უნდა მისცეს ნულს, თუ დაემატება მაგიური და დროშების ველებს.

ჩვენი kernel.asm ფაილი ახლა ასე გამოიყურება.

ბირთვი.ასმ
ბიტი 32 განყოფილება .ტექსტი ;მრავალგადატვირთვის სპეციფიკა გასწორება 4 dd 0x1BADB002 ;ჯადოსნური dd 0x00 ;დროშები dd - (0x1BADB002 + 0x00) ;შემოწმების გლობალური დაწყება გარე kmain დაწყება: cli mov esp.sp. სივრცე:

dd ინსტრუქცია განსაზღვრავს 4 ბაიტიან ორმაგ სიტყვას.

ბირთვის აწყობა

ასე რომ, ყველაფერი მზად არის, რომ შევქმნათ ობიექტის ფაილი kernel.asm-დან და kernel.c-დან და დავაკავშიროთ ისინი ჩვენი სკრიპტის გამოყენებით. ჩვენ ვწერთ კონსოლში:

$ nasm -f elf32 kernel.asm -o kasm.o

ამ ბრძანების გამოყენებით, ასამბლეერი შექმნის ფაილს kasm.o ELF-32 ბიტიანი ფორმატით. ახლა GCC-ის ჯერია:

$ gcc -m32 -c kernel.c -o kc.o

-c პარამეტრი მიუთითებს, რომ ფაილის დაკავშირება არ არის საჭირო კომპილაციის შემდეგ. ჩვენ თვითონ გავაკეთებთ ამას:

$ ld -m elf_i386 -T ბმული.ld -o ბირთვი kasm.o kc.o

ეს ბრძანება გაუშვებს ლინკერს ჩვენი სკრიპტით და წარმოქმნის შესრულებადს სახელად kernel.

გაფრთხილება

ბირთვის გატეხვა საუკეთესოდ ხდება ვირტუალურ გარემოში. ბირთვის გასაშვებად QEMU-ში GRUB-ის ნაცვლად, გამოიყენეთ ბრძანება qemu-system-i386 -kernel kernel .

GRUB-ის დაყენება და ბირთვის გაშვება

GRUB მოითხოვს, რომ ბირთვის ფაილის სახელი მიჰყვეს ბირთვს-<версия>. მოდით გადავარქვათ სახელი ფაილს - დავარქმევ ჩემს ბირთვს-701.

ახლა ჩვენ ვათავსებთ ბირთვს /boot დირექტორიაში. ამას დასჭირდება სუპერმომხმარებლის პრივილეგიები.

თქვენ უნდა დაამატოთ მსგავსი რამ GRUB კონფიგურაციის ფაილში grub.cfg:

სათაური myKernel root (hd0,0) kernel /boot/kernel-701 ro

არ დაგავიწყდეთ ფარული მენიუს დირექტივის ამოღება, თუ ის შედის.

GRUB 2

GRUB 2-ში ჩვენ მიერ შექმნილი ბირთვის გასაშვებად, რომელიც ნაგულისხმევად არის მოწოდებული ახალ დისტრიბუციებში, თქვენი კონფიგურაცია ასე უნდა გამოიყურებოდეს:

მენიუ "kernel 701" (set root="hd0,msdos1" multiboot /boot/kernel-701 ro)

მადლობა რუბენ ლაგუანას ამ დამატებისთვის.

გადატვირთეთ კომპიუტერი და თქვენ უნდა ნახოთ თქვენი ბირთვი სიაში! და როცა აირჩევთ, დაინახავთ იმავე ხაზს.



ეს არის თქვენი ბირთვი!

ბირთვის დაწერა კლავიატურისა და ეკრანის მხარდაჭერით

ჩვენ დავასრულეთ მუშაობა მინიმალურ ბირთვზე, რომელიც ჩაიტვირთება GRUB-ის მეშვეობით, მუშაობს დაცულ რეჟიმში და ეკრანზე ბეჭდავს ერთ ხაზს. დროა გააფართოვოთ და დაამატოთ კლავიატურის დრაივერი, რომელიც წაიკითხავს სიმბოლოებს კლავიატურიდან და აჩვენებს მათ ეკრანზე.

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

პორტებთან მუშაობა: კითხვა და გამომავალი

წაკითხვის_პორტი: mov edx, al, dx ret write_port: mov edx, mov al, out dx, al ret

I/O პორტებზე წვდომა ხდება x86 კომპლექტში შეტანილი და გასვლის ინსტრუქციების გამოყენებით.

read_port-ში პორტის ნომერი გადაეცემა არგუმენტად. როდესაც შემდგენელი იძახებს ფუნქციას, ის უბიძგებს ყველა არგუმენტს სტეკზე. არგუმენტი კოპირებულია edx რეესტრში სტეკის მაჩვენებლის გამოყენებით. dx რეგისტრი არის edx რეგისტრის ქვედა 16 ბიტი. ინსტრუქცია აქ კითხულობს პორტის ნომერს, რომელიც მოცემულია dx-ში და აყენებს შედეგს al-ში. al რეგისტრი არის eax რეგისტრის ქვედა 8 ბიტი. შესაძლოა კოლეჯიდან გახსოვთ, რომ ფუნქციების მიერ დაბრუნებული მნიშვნელობები გადაეცემა eax რეესტრში. ასე რომ read_port საშუალებას გვაძლევს წავიკითხოთ I/O პორტებიდან.

write_port ფუნქცია მუშაობს ანალოგიურად. ჩვენ ვიღებთ ორ არგუმენტს: პორტის ნომერი და მონაცემები, რომლებიც დაიწერება. გამოსვლის ინსტრუქცია წერს მონაცემებს პორტში.

წყვეტს

ახლა, სანამ დრაივერის დაწერას დავუბრუნდებით, უნდა გავიგოთ, როგორ იცის პროცესორმა, რომ ერთ-ერთმა მოწყობილობამ შეასრულა ოპერაცია.

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

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

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

PIC-ის გარეშე, ჩვენ მოგვიწევდა სისტემაში არსებული ყველა მოწყობილობის გამოკითხვა, რათა გვენახა, მოხდა თუ არა მოვლენა რომელიმე მათგანთან.

ვნახოთ, როგორ მუშაობს ეს კლავიატურაზე. კლავიატურა კიდია 0x60 და 0x64 პორტებზე. პორტი 0x60 აგზავნის მონაცემებს (ღილაკზე დაჭერისას), ხოლო პორტი 0x64 აგზავნის სტატუსს. თუმცა, ჩვენ უნდა ვიცოდეთ ზუსტად როდის წავიკითხოთ ეს პორტები.

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

ამ მისამართის კოდი შესრულებულია და ამუშავებს შეფერხებას.

დააყენეთ IDT

struct IDT_entry( unsigned short int offset_lowerbits; unsigned short int selector; unsigned char zero; unsigned char type_attr; unsigned short int offset_higherbits; ); struct IDT_entry IDT; void idt_init(void) ( ხელმოუწერელი გრძელი კლავიატურა_მისამართი; ხელმოუწერელი გრძელი idt_address; ხელმოუწერელი გრძელი idt_ptr; კლავიატურის_მისამართი = (ხელმოუწერელი გრძელი)keyboard_handler; IDT.offset_lowerbits = keyboard_address & 0xffff; IDT.xERN8 */ IDT.zero = 0 ; IDT.type_attr = 0x8e; /* INTERRUPT_GATE */ IDT.offset_higherbits = (კლავიატურის_მისამართი & 0xffff0000) >> 16; write_port (0x20 , 0x11); write_port (0xA0, 0x10, პორტი, 0x10x1); 0x28); write_port (0x21, 0x00); write_port (0xA1, 0x00); write_port (0x21, 0x01); write_port (0xA1, 0x01); write_port (0x21, 0xff); write_port (0xA1, 0x00); )IDT; idt_ptr = (ზომა (სტრუქტურა IDT_entry) * IDT_SIZE) + ((idt_address & 0xffff)<< 16); idt_ptr = idt_address >> 16; load_idt (idt_ptr); )

IDT არის IDT_entry სტრუქტურების მასივი. ჩვენ მოგვიანებით განვიხილავთ კლავიატურის შეფერხების დაკავშირებას დამმუშავებელთან, მაგრამ ახლა ვნახოთ, როგორ მუშაობს PIC.

თანამედროვე x86 სისტემებს აქვთ ორი PIC ჩიპი, თითოეული რვა შეყვანის ხაზით. ჩვენ მათ დავარქმევთ PIC1 და PIC2. PIC1 იღებს IRQ0-დან IRQ7-მდე და PIC2 იღებს IRQ8-დან IRQ15-მდე. PIC1 იყენებს პორტს 0x20 ბრძანებებისთვის და 0x21 მონაცემებისთვის, ხოლო PIC2 იყენებს პორტს 0xA0 ბრძანებებისთვის და 0xA1 მონაცემებისთვის.

ორივე PIC ინიციალიზებულია რვა ბიტიანი სიტყვით, რომელსაც ეწოდება ინიციალიზაციის ბრძანების სიტყვები (ICW).

დაცულ რეჟიმში, ორივე PIC-მა ჯერ უნდა გასცეს ინიციალიზაციის ბრძანება ICW1 (0x11). ის ეუბნება PIC-ს, რომ დაელოდოს კიდევ სამი ინიციალიზაციის სიტყვის ჩამოსვლას მონაცემთა პორტში.

ეს ბრძანებები გაივლის PIC-ს:

  • შეწევის ვექტორი (ICW2),
  • რა არის Master/Slave ურთიერთობები PIC-ებს შორის (ICW3),
  • დამატებითი ინფორმაცია გარემოს შესახებ (ICW4).

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

PIC-ები საშუალებას აძლევს მათ ქინძისთავებს კასკადად იქცეს ერთმანეთის შეყვანაში. ეს კეთდება ICW3-ის გამოყენებით და თითოეული ბიტი წარმოადგენს კასკადის სტატუსს შესაბამისი IRQ-ისთვის. ახლა ჩვენ არ გამოვიყენებთ კასკადურ გადამისამართებას და დავაყენებთ ნულზე.

ICW4 განსაზღვრავს დამატებით გარემო პარამეტრებს. ჩვენ მხოლოდ უნდა განვსაზღვროთ დაბალი ბიტი ისე, რომ PIC-ებმა იცოდნენ, რომ ჩვენ ვმუშაობთ 80x86 რეჟიმში.

ტა-დამ! PIC-ები ახლა ინიციალიზებულია.

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

მონაცემთა პორტიდან კითხვა აბრუნებს მნიშვნელობას IMR რეესტრში, ხოლო ჩაწერა ცვლის რეესტრს. ჩვენს კოდში, PIC-ის ინიციალიზაციის შემდეგ, ჩვენ ვაყენებთ ყველა ბიტს ერთზე, რაც გამორთავს ყველა IRQ ხაზს. მოგვიანებით ჩვენ გავააქტიურებთ ხაზებს, რომლებიც შეესაბამება კლავიატურის შეფერხებებს. მაგრამ ჯერ გამორთეთ!

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

რა შეფერხების ნომერი გვჭირდება IDT-ში კლავიატურის დამმუშავებლის დასაკავშირებლად?

კლავიატურა იყენებს IRQ1. ეს არის შეყვანის ხაზი 1 და მუშავდება PIC1-ის მიერ. ჩვენ ინიციალიზაცია მოვახდინეთ PIC1 ოფსეტურით 0x20 (იხ. ICW2). შეფერხების ნომრის მისაღებად, თქვენ უნდა დაამატოთ 1 და 0x20, მიიღებთ 0x21. ეს ნიშნავს, რომ კლავიატურის დამმუშავებლის მისამართი მიბმული იქნება IDT-ში 0x21 შეფერხებისთვის.

ამოცანა მოდის IDT-ის შევსებაზე 0x21 შეფერხებისთვის. ამ შეფერხებას გავაფორმებთ keyboard_handler ფუნქციაზე, რომელსაც ჩავწერთ ასამბლეის ფაილში.

IDT-ში თითოეული ჩანაწერი შედგება 64 ბიტისაგან. შეფერხების შესაბამის ჩანაწერში ჩვენ არ ვინახავთ დამმუშავებლის ფუნქციის მთელ მისამართს. ამის ნაცვლად, ჩვენ გავყავით ორ 16-ბიტიან ნაწილად. დაბალი რიგის ბიტი ინახება IDT ჩანაწერის პირველ 16 ბიტში, ხოლო მაღალი რიგის 16 ბიტი ინახება ჩანაწერის ბოლო 16 ბიტში. ეს ყველაფერი კეთდება 286 პროცესორთან თავსებადობისთვის. როგორც ხედავთ, Intel აწარმოებს მსგავს ციფრებს რეგულარულად და ბევრ, ბევრ ადგილას!

IDT ჩანაწერში ჩვენ უბრალოდ უნდა დავარეგისტრიროთ ტიპი, რაც მიუთითებს იმაზე, რომ ეს ყველაფერი კეთდება შეფერხების დასაჭერად. ჩვენ ასევე უნდა დავაყენოთ ბირთვის კოდის სეგმენტის ოფსეტური. GRUB ადგენს ჩვენთვის GDT-ს. თითოეული GDT ჩანაწერი არის 8 ბაიტის სიგრძე, სადაც ბირთვის კოდის აღმწერი არის მეორე სეგმენტი, ამიტომ მისი ოფსეტი იქნება 0x08 (დეტალები ამ სტატიის ფარგლებს სცილდება). შეწყვეტის კარიბჭე წარმოდგენილია როგორც 0x8e. შუაში დარჩენილი 8 ბიტი ივსება ნულებით. ამ გზით ჩვენ შევავსებთ IDT ჩანაწერს, რომელიც შეესაბამება კლავიატურის შეფერხებას.

მას შემდეგ რაც დავასრულებთ IDT რუკებს, ჩვენ უნდა ვუთხრათ პროცესორს სად არის IDT. ამისათვის არის ასამბლეის ინსტრუქცია სახელწოდებით lidt; მას სჭირდება ერთი ოპერანდი. ეს არის მითითება სტრუქტურის აღწერისკენ, რომელიც აღწერს IDT-ს.

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

idr_ptr ცვლადში გვაქვს მაჩვენებელი, რომელსაც გადავცემთ lidt ინსტრუქციას load_idt() ფუნქციაში.

Load_idt: mov edx, lidt sti ret

გარდა ამისა, load_idt() ფუნქცია აბრუნებს შეფერხებას sti ინსტრუქციის გამოყენებისას.

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

Void kb_init(void) (writ_port(0x21, 0xFD);)

0xFD არის 11111101 - ჩართეთ მხოლოდ IRQ1 (კლავიატურა).

ფუნქცია - კლავიატურის შეფერხების დამმუშავებელი

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

Keyboard_handler: დარეკეთ keyboard_handler_main iretd

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

Void keyboard_handler_main(void) ( ხელმოუწერელი სიმბოლოს სტატუსი; სიმბოლოს კლავიშის კოდი; /* ჩაწერეთ EOI */ write_port (0x20, 0x20); status = read_port (KEYBOARD_STATUS_PORT); /* სტატუსის ქვედა ბიტი დაყენდება, თუ ბუფერი ცარიელი არ არის */ if (სტატუსები & 0x01) ( გასაღების კოდი = წაკითხვის_პორტი (KEYBOARD_DATA_PORT); თუ (კლავიშის კოდი< 0) return; vidptr = keyboard_map; vidptr = 0x07; } }

აქ ჩვენ პირველ რიგში ვაძლევთ EOI (End Of Interrupt) სიგნალს PIC ბრძანების პორტში ჩაწერით. მხოლოდ ამის შემდეგ PIC დაუშვებს შემდგომ შეწყვეტის მოთხოვნას. ჩვენ უნდა წავიკითხოთ ორი პორტი: მონაცემთა პორტი 0x60 და ბრძანების პორტი (aka status port) 0x64.

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

კოდის მარტივი შესანარჩუნებლად, მე ვამუშავებ მხოლოდ მცირე ასოებს a-დან z-მდე და ციფრებს 0-დან 9-მდე. შეგიძლიათ მარტივად დაამატოთ სპეციალური სიმბოლოები, Alt, Shift და Caps Lock. თქვენ შეგიძლიათ გაიგოთ, რომ კლავიშს დააჭირეს ან გაათავისუფლეს ბრძანების პორტის გამოსასვლელიდან და შეასრულოთ შესაბამისი მოქმედება. ანალოგიურად, თქვენ შეგიძლიათ დაუკავშიროთ კლავიატურის ნებისმიერი მალსახმობები სპეციალურ ფუნქციებს, როგორიცაა გამორთვა.

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

ბოლო ორი წლის განმავლობაში Habr-ის წაკითხვისას, მე ვნახე მხოლოდ რამდენიმე მცდელობა OS-ის შემუშავების (კონკრეტულად: მომხმარებლებისგან და (გადაიდო განუსაზღვრელი ვადით) და (არ მიტოვებული, მაგრამ ახლა ეს უფრო ჰგავს დაცული რეჟიმის ფუნქციონირების აღწერას. x86-თან თავსებადი პროცესორებიდან, რაც უდავოდ ასევე უნდა იცოდე, რომ დაწერო OS x86-ზე); და დასრულებული სისტემის აღწერა (თუმცა არა ნულიდან, თუმცა ამაში ცუდი არაფერია, შესაძლოა პირიქითაც კი)) . რატომღაც, ვფიქრობ, რომ თითქმის ყველა სისტემის (და ზოგიერთი აპლიკაციის) პროგრამისტს ერთხელ მაინც უფიქრია საკუთარი ოპერაციული სისტემის დაწერაზე. ამასთან დაკავშირებით, 3 OS ამ რესურსის დიდი საზოგადოებისგან სასაცილო რიცხვად გამოიყურება. როგორც ჩანს, მათი უმრავლესობა, ვინც ფიქრობს საკუთარ OS-ზე, არსად სცილდება იდეის მიღმა, მცირე ნაწილი ჩერდება ჩამტვირთველის დაწერის შემდეგ, რამდენიმე წერს ბირთვის ნაწილს და მხოლოდ უიმედოდ ჯიუტი ქმნის რაღაც ბუნდოვნად, რაც OS-ს მოგაგონებთ. (თუ შევადარებთ Windows/Linux-ის მსგავს რამეს). ამის მრავალი მიზეზი არსებობს, მაგრამ მთავარი, ჩემი აზრით, არის ის, რომ ადამიანები უარს ამბობენ განვითარებაზე (ზოგიერთები ჯერ კიდევ დაწყებამდე) OS-ის დაწერისა და გამართვის პროცესის აღწერების მცირე რაოდენობის გამო, რაც საკმაოდ განსხვავდება იმისგან, რაც ხდება. აპლიკაციის პროგრამული უზრუნველყოფის განვითარების დროს.

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

Როგორ Არ არის საჭიროებადაწყება
გთხოვთ, არ მიიღოთ შემდეგი ტექსტი, როგორც აშკარა კრიტიკა ვინმეს სტატიების ან ოპერაციული სისტემის დაწერის სახელმძღვანელოს მიმართ. უბრალოდ, ძალიან ხშირად ასეთ სტატიებში ხმამაღალი სათაურებით აქცენტი კეთდება ზოგიერთი მინიმალური სამუშაო ნაწილის განხორციელებაზე და იგი წარმოდგენილია როგორც ბირთვის პროტოტიპი. სინამდვილეში, თქვენ უნდა იფიქროთ ბირთვის სტრუქტურაზე და მთლიანობაში OS-ის ნაწილების ურთიერთქმედებაზე და განიხილოთ ეს პროტოტიპი, როგორც სტანდარტული "Hello, World!" აპლიკაცია აპლიკაციის პროგრამული უზრუნველყოფის სამყაროში. როგორც ამ კომენტარების უმნიშვნელო დასაბუთება, უნდა ითქვას, რომ ქვემოთ არის ქვეგანყოფილება „გამარჯობა, სამყარო!“, რომელსაც ამ შემთხვევაში ექცევა ზუსტად იმდენი ყურადღება, რამდენიც საჭიროა და მეტი არა.

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

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

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

მომზადება
დასაწყისისთვის, როგორც ყოველთვის, თქვენ უნდა გაეცნოთ ზოგად თეორიას, რათა გქონდეთ გარკვეული წარმოდგენა სამუშაოს მომავალი მასშტაბის შესახებ. განსახილველ საკითხზე კარგი წყაროა E. Tanenbaum-ის წიგნები, რომლებიც უკვე ნახსენებია სხვა სტატიებში Habré-ზე OS-ის დაწერის შესახებ. ასევე არსებობს სტატიები, რომლებიც აღწერენ არსებულ სისტემებს და არის სხვადასხვა სახელმძღვანელო/მიგზავნი/სტატია/მაგალითი/საიტი, სადაც აქცენტი კეთდება OS-ის განვითარებაზე, რომელთაგან ზოგიერთის ბმულები მოცემულია სტატიის ბოლოს.

საწყისი საგანმანათლებლო პროგრამის შემდეგ თქვენ უნდა გადაწყვიტოთ ძირითადი კითხვები:

  • სამიზნე არქიტექტურა - x86 (რეალური/დაცული/გრძელი რეჟიმი), PowerPC, ARM, ...
  • ბირთვის/OS არქიტექტურა - მონოლითი, მოდულური მონოლითი, მიკროკერნელი, ეგზოკერნელი, სხვადასხვა ჰიბრიდები
  • ენა და მისი შემდგენელი - C, C++, ...
  • ბირთვის ფაილის ფორმატი - elf, a.out, coff, ორობითი, ...
  • განვითარების გარემო (დიახ, ეს ასევე მნიშვნელოვან როლს ასრულებს) - IDE, vim, emacs, ...
შემდეგი, თქვენ უნდა გაიღრმაოთ თქვენი ცოდნა არჩეულის მიხედვით და შემდეგ სფეროებში:
  • ვიდეო მეხსიერება და მასთან მუშაობა - მუშაობის დამადასტურებელი გამომავალი თავიდანვე აუცილებელია
  • HAL (Hardware Abstraction layer) - მაშინაც კი, თუ არსებობს რამდენიმე აპარატურის არქიტექტურის მხარდაჭერა და არ არის დაგეგმილი ბირთვის ყველაზე დაბალი დონის ნაწილების სწორად გამოყოფა ისეთი აბსტრაქტული საგნების განხორციელებისგან, როგორიცაა პროცესები, სემაფორები და ა.შ. არ იყოს ზედმეტი
  • მეხსიერების მართვა - ფიზიკური და ვირტუალური
  • შესრულების მენეჯმენტი - პროცესები და ძაფები, მათი დაგეგმვა
  • მოწყობილობის მართვა - დრაივერები
  • ვირტუალური ფაილური სისტემები - ერთიანი ინტერფეისის უზრუნველყოფა სხვადასხვა ფაილური სისტემების შიგთავსისთვის
  • API (აპლიკაციის პროგრამირების ინტერფეისი) - ზუსტად როგორ მოხვდება აპლიკაციები ბირთვში
  • IPC (Interprocess Communication) - ადრე თუ გვიან პროცესებს მოუწევთ კომუნიკაცია
ხელსაწყოები
არჩეული ენისა და განვითარების ინსტრუმენტების გათვალისწინებით, თქვენ უნდა აირჩიოთ უტილიტების ნაკრები და მათი პარამეტრები, რომლებიც მომავალში, სკრიპტების დაწერით, რაც შეიძლება მარტივად და სწრაფს გახდის შექმნას, გამოსახულების მომზადებას და ვირტუალური მანქანის გაშვებას. პროექტი. მოდით უფრო ახლოს მივხედოთ თითოეულ ამ პუნქტს:
  • ნებისმიერი სტანდარტული ხელსაწყო შესაფერისია ასამბლეისთვის, როგორიცაა make, cmake,... აქ შეიძლება გამოყენებულ იქნას ლინკერის სკრიპტები და (სპეციალურად დაწერილი) უტილიტები Multiboot header-ის დასამატებლად, შემოწმების ჯამები ან სხვა მიზნებისთვის.
  • სურათის მომზადებაში ვგულისხმობთ მის დამონტაჟებას და ფაილების კოპირებას. შესაბამისად, გამოსახულების ფაილის ფორმატი ისე უნდა იყოს შერჩეული, რომ მას მხარდაჭერილი ჰქონდეს როგორც სამონტაჟო/კოპირების პროგრამა, ასევე ვირტუალური მანქანა. ბუნებრივია, არავინ კრძალავს მოქმედებების შესრულებას ამ წერტილიდან, როგორც შეკრების საბოლოო ნაწილი, ან როგორც მომზადება ემულატორის გასაშვებად. ეს ყველაფერი დამოკიდებულია კონკრეტულ საშუალებებზე და მათი გამოყენების არჩეულ ვარიანტებზე.
  • ვირტუალური მანქანის გაშვება არ არის რთული, მაგრამ უნდა გახსოვდეთ, რომ ჯერ სურათის დემონტაჟი უნდა მოხდეს (ამ მომენტში დემონტაჟი, ვინაიდან ვირტუალური მანქანის დაწყებამდე ამ ოპერაციაში რეალური წერტილი არ არის). ასევე სასარგებლო იქნება სკრიპტის არსებობა ემულატორის გამართვის რეჟიმში (თუ ეს შესაძლებელია).
თუ ყველა წინა ნაბიჯი დასრულებულია, უნდა დაწეროთ მინიმალური პროგრამა, რომელიც ჩაიტვირთება როგორც ბირთვი და დაბეჭდავს რაღაცას ეკრანზე. თუ არჩეული საშუალების უხერხულობა ან ნაკლოვანება გამოვლინდა, აუცილებელია მათი აღმოფხვრა (ნაკლოვანებები), ან, უარეს შემთხვევაში, თავისთავად მიღება.

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

ამ ეტაპის წარმატებით დასრულების შემდეგ იწყება რეალური განვითარება.

მუშაობის დროის მხარდაჭერის უზრუნველყოფა
ვინაიდან შემოთავაზებულია მაღალი დონის ენებზე დაწერა, ყურადღება უნდა მიექცეს ზოგიერთი ენის ფუნქციის მხარდაჭერას, რომლებიც, როგორც წესი, დანერგილია შემდგენელი პაკეტის ავტორების მიერ. მაგალითად, C++-ისთვის, ეს მოიცავს:
  • ფუნქცია დასტაზე მონაცემთა ბლოკის დინამიურად განაწილებისთვის
  • გროვასთან მუშაობა
  • მონაცემთა ბლოკის ასლის ფუნქცია (memcpy)
  • ფუნქცია არის პროგრამაში შესვლის წერტილი
  • მოუწოდებს გლობალური ობიექტების კონსტრუქტორებსა და დესტრუქტორებს
  • გამონაკლისებთან მუშაობის რამდენიმე ფუნქცია
  • ნაკერი არარეალიზებული სუფთა ვირტუალური ფუნქციებისთვის
როდესაც წერთ "გამარჯობა, სამყარო!" ამ ფუნქციების არარსებობამ შეიძლება არ იგრძნოს თავი, მაგრამ კოდის დამატებისას, ლინკერი დაიწყებს ჩივილს დაუკმაყოფილებელ დამოკიდებულებებზე.

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

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

ჩვენ შეგვიძლია გირჩიოთ შემდეგი:

  • რა თქმა უნდა, გამართვის გამომავალი
  • მტკიცება დაუყოვნებლივ გასვლით "გამმართველში" (იხილეთ შემდეგი აბზაცი)
  • კონსოლის გამართვის გარკვეული გარეგნობა
  • შეამოწმეთ, თუ ემულატორი გაძლევთ საშუალებას დააკავშიროთ გამართვა, სიმბოლო ცხრილები ან სხვა რამ
ბირთვში ჩაშენებული გამართვის გარეშე, შეცდომების ძიებას კოშმარად გადაქცევის ძალიან რეალური შანსი აქვს. ასე რომ, განვითარების რაღაც ეტაპზე მისი დაწერისგან გაქცევა უბრალოდ არ არის. და რადგან ეს გარდაუვალია, უმჯობესია წინასწარ დაიწყოთ მისი წერა და ამით მნიშვნელოვნად შეუწყოთ ხელი განვითარებას და დაზოგოთ საკმაოდ დიდი დრო. მნიშვნელოვანია, რომ შეძლოთ გამართვის დანერგვა ბირთვისგან დამოუკიდებელი გზით, რათა გამართვამ მინიმალური გავლენა მოახდინოს სისტემის ნორმალურ მუშაობაზე. აქ არის რამდენიმე ტიპის ბრძანება, რომელიც შეიძლება სასარგებლო იყოს:
  • სტანდარტული გამართვის ოპერაციების ნაწილი: breakpoints, call stack, printing მნიშვნელობები, printing dump, ...
  • ბრძანებები სხვადასხვა სასარგებლო ინფორმაციის საჩვენებლად, როგორიცაა განრიგის შესრულების რიგი ან სხვადასხვა სტატისტიკა (ის ისეთი უსარგებლო არ არის, როგორც ერთი შეხედვით შეიძლება ჩანდეს)
  • ბრძანებები სხვადასხვა სტრუქტურების მდგომარეობის თანმიმდევრულობის შესამოწმებლად: თავისუფალი/გამოყენებული მეხსიერების სიები, გროვა ან შეტყობინების რიგი
განვითარება
შემდეგი, აუცილებელია OS-ის ძირითადი ელემენტების ჩაწერა და გამართვა, რამაც ამ მომენტში უნდა უზრუნველყოს მისი სტაბილური მუშაობა, ხოლო მომავალში - მარტივი გაფართოება და მოქნილობა. მეხსიერების/პროცესების/(რაც არ უნდა იყოს) მენეჯერების გარდა, ძალიან მნიშვნელოვანია დრაივერების და ფაილური სისტემების ინტერფეისი. მათ დიზაინს განსაკუთრებული სიფრთხილით უნდა მივუდგეთ მოწყობილობის ტიპების/FS-ის მრავალფეროვნების გათვალისწინებით. დროთა განმავლობაში, რა თქმა უნდა, მათი შეცვლა შესაძლებელია, მაგრამ ეს არის ძალიან მტკივნეული და შეცდომებისადმი მიდრეკილი პროცესი (და ბირთვის გამართვა არ არის ადვილი ამოცანა), ასე რომ დაიმახსოვრეთ - იფიქრეთ ამ ინტერფეისებზე მინიმუმ ათჯერ, სანამ დაიწყებთ განხორციელებას. მათ.
მსგავსი SDK
როგორც პროექტი ვითარდება, მას ახალი დრაივერები და პროგრამები უნდა დაემატოს. სავარაუდოდ, უკვე მეორე დრაივერზე (შესაძლოა გარკვეული ტიპის)/პროგრამაზე შესამჩნევი იქნება ზოგიერთი საერთო მახასიათებელი (დირექტორის სტრუქტურა, ასამბლეის კონტროლის ფაილები, დამოკიდებულებების დაზუსტება მოდულებს შორის, განმეორებითი კოდი ძირითად ან სისტემურ მოთხოვნის დამმუშავებლებში (მაგალითად, თუ დრაივერები თავად ამოწმებენ მათ თავსებადობას მოწყობილობასთან )). თუ ეს ასეა, მაშინ ეს არის თქვენი ოპერაციული სისტემისთვის სხვადასხვა ტიპის პროგრამებისთვის შაბლონების შემუშავების საჭიროების ნიშანი.

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

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

ორიგინალი: "Roll your own toy UNIX-clone OS"
ავტორი: ჯეიმს მოლოი
გამოცემის თარიღი: 2008 წ
თარგმანი: ნ.რომოდანოვი
თარგმანის თარიღი: 2012 წლის იანვარი

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

სახელმძღვანელოების ეს ნაკრები ძალიან პრაქტიკული ხასიათისაა. თითოეული ნაწილი იძლევა თეორიულ ინფორმაციას, მაგრამ სახელმძღვანელოს უმეტესობა ეხება პრაქტიკაში განხილული აბსტრაქტული იდეებისა და მექანიზმების განხორციელებას. მნიშვნელოვანია აღინიშნოს, რომ ბირთვი განხორციელებულია როგორც სასწავლო ბირთვი. მე ვიცი, რომ გამოყენებული ალგორითმები არც სივრცეში ყველაზე ეფექტურია და არც ოპტიმალური. ისინი ძირითადად აირჩიეს მათი სიმარტივისა და გაგების სიმარტივის გამო. ამის მიზანია მოგაწოდოთ სწორი აზროვნება და საფუძველი, რომლითაც იმუშაოთ. ეს ბირთვი გაფართოებულია და თქვენ შეგიძლიათ მარტივად დააკავშიროთ საუკეთესო ალგორითმები. თუ თქვენ გაქვთ რაიმე პრობლემა თეორიასთან დაკავშირებით, არსებობს მრავალი საიტი, რომელიც დაგეხმარებათ ამის გარკვევაში. OSDev-ის ფორუმზე განხილული კითხვების უმეტესობა დაკავშირებულია იმპლემენტაციასთან ("My gets ფუნქცია არ მუშაობს! დახმარება!") და ბევრისთვის კითხვა თეორიის შესახებ სუფთა ჰაერის მსგავსია. ბმულები შეგიძლიათ იხილოთ ამის ბოლოს. შესავალი.

წინასწარი მომზადება

მაგალითის კოდის შედგენისა და გასაშვებად, ვფიქრობ, დაგჭირდებათ მხოლოდ GCC, ld, NASM და GNU Make. NASM არის ღია კოდის x86 ასამბლეერი და არის მრავალი x86 OS დეველოპერების არჩევანი.

თუმცა, აზრი არ აქვს უბრალოდ მაგალითების შედგენას და გაშვებას, თუ მათ არ ესმით. თქვენ უნდა გესმოდეთ, რა არის კოდირებული და ამისათვის თქვენ უნდა იცოდეთ C ენა ძალიან კარგად, განსაკუთრებით მაშინ, როდესაც საქმე პოინტერებს ეხება. თქვენ ასევე უნდა გესმოდეთ ასამბლეის ენა (ეს გაკვეთილები იყენებს Intel სინტაქსს), მათ შორის, რისთვის გამოიყენება EBP რეგისტრი.

რესურსები

ბევრი რესურსია თუ იცით როგორ მოვძებნოთ ისინი. კერძოდ, გამოგადგებათ შემდეგი ბმულები:

  • RTFM! Intel-ის სახელმძღვანელოები ღვთის საჩუქარია.
  • საიტის ვიკი გვერდები და ფორუმი osdev.org.
  • არის ბევრი კარგი გაკვეთილი და სტატია Osdever.net-ზე და, კერძოდ, Bran-ის ბირთვის განვითარების გაკვეთილები, რომელთა წინა კოდს ეფუძნება ეს სახელმძღვანელო. მე თვითონ გამოვიყენე ეს გაკვეთილები დასაწყებად და მათში არსებული კოდი იმდენად კარგი იყო, რომ გავაკეთე არ შეცვალოს იგი რამდენიმე წლის განმავლობაში.
  • თუ არ ხართ დამწყები, მაშინ შეგიძლიათ მიიღოთ პასუხი ჯგუფში ბევრ კითხვაზე

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

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

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

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

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

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

იცოდეთ ეს: სისტემა, რომელიც ნებისმიერ კომპიუტერზეა, პასუხისმგებელია ამაზე და მის სახელს - არა, Windows-ს კი არა, ენას დააწვინეთ - მას BIOS ჰქვია. მისი სახელი ნიშნავს Basic Input-Output System, ანუ ძირითადი შეყვანის-გამომავალი სისტემა. BIOS მდებარეობს დედაპლატზე არსებულ პატარა ჩიპზე და იწყება დიდი ღილაკზე ON დაჭერისთანავე. BIOS-ს აქვს სამი ძირითადი ამოცანა:

  1. აღმოაჩინე ყველა დაკავშირებული მოწყობილობა (პროცესორი, კლავიატურა, მონიტორი, ოპერატიული მეხსიერება, ვიდეო ბარათი, თავი, მკლავები, ფრთები, ფეხები და კუდები...) და შეამოწმეთ მათი ფუნქციონირება. ამაზე პასუხისმგებელია POST (Power On Self Test) პროგრამა. თუ სასიცოცხლო მნიშვნელობის აპარატურა არ არის აღმოჩენილი, მაშინ ვერანაირი პროგრამული უზრუნველყოფა ვერ დაგვეხმარება და ამ დროს სისტემის დინამიკი რაღაც აზარტულს ატყდება და ოპერაციული სისტემა საერთოდ ვერ მივა პუნქტამდე. მოდი სამწუხარო რამეებზე არ ვისაუბროთ, დავუშვათ, რომ გვაქვს სრულად მომუშავე კომპიუტერი, გაიხარეთ და გადავიდეთ მეორე BIOS ფუნქციის განხილვაზე:
  2. ოპერაციული სისტემის უზრუნველყოფა ფუნქციების ძირითადი ნაკრებით აპარატურასთან მუშაობისთვის. მაგალითად, BIOS ფუნქციების საშუალებით შეგიძლიათ ეკრანზე ტექსტის ჩვენება ან კლავიატურიდან მონაცემების წაკითხვა. ამიტომ მას უწოდებენ ძირითადი შეყვანის/გამოსვლის სისტემას. როგორც წესი, ოპერაციული სისტემა ამ ფუნქციებზე წვდომას წყვეტს.
  3. ოპერაციული სისტემის ჩამტვირთველის გაშვება. ამ შემთხვევაში, როგორც წესი, იკითხება ჩატვირთვის სექტორი - შენახვის საშუალების პირველი სექტორი (ფლოპი დისკი, მყარი დისკი, CD, ფლეშ დრაივი). მედია გამოკითხვის რიგის დაყენება შესაძლებელია BIOS SETUP-ში. ჩატვირთვის სექტორი შეიცავს პროგრამას, რომელსაც ზოგჯერ პირველადი ჩამტვირთველი ეწოდება. უხეშად რომ ვთქვათ, ჩამტვირთველის ამოცანაა ოპერაციული სისტემის გაშვება. ოპერაციული სისტემის ჩატვირთვის პროცესი შეიძლება იყოს ძალიან სპეციფიკური და დიდად დამოკიდებული მის მახასიათებლებზე. ამიტომ, პირველადი ჩამტვირთველი იწერება უშუალოდ OS-ის დეველოპერების მიერ და იწერება ჩატვირთვის სექტორში ინსტალაციის დროს. როდესაც ჩამტვირთავი იწყება, პროცესორი რეალურ რეჟიმშია.
სამწუხარო ამბავი ის არის, რომ ჩამტვირთველი უნდა იყოს მხოლოდ 512 ბაიტის ზომა. რატომ ასე ცოტა? ამისათვის ჩვენ უნდა გავეცნოთ ფლოპი დისკის სტრუქტურას. აქ არის ინფორმაციული სურათი:

სურათზე ნაჩვენებია დისკის ზედაპირი. ფლოპი დისკს აქვს 2 ზედაპირი. თითოეულ ზედაპირს აქვს რგოლის ფორმის ბილიკები (ტრასები). თითოეული ტრეკი დაყოფილია რკალის ფორმის პატარა ნაჭრებად, რომლებსაც სექტორები ეწოდება. ასე რომ, ისტორიულად, ფლოპი დისკის სექტორს აქვს 512 ბაიტის ზომა. დისკზე პირველივე სექტორი, ჩატვირთვის სექტორი, იკითხება BIOS-ის მიერ მეხსიერების ნულოვან სეგმენტში ოფსეტურით 0x7C00 და შემდეგ კონტროლი გადადის ამ მისამართზე. ჩამტვირთველი ჩვეულებრივ იტვირთება მეხსიერებაში არა თავად OS, არამედ სხვა ჩამტვირთველი პროგრამა. ინახება დისკზე, მაგრამ რატომღაც (სავარაუდოდ, ეს არის ზომა) არ ჯდება ერთ სექტორში და რადგან ამ დროისთვის ჩვენი OS-ის როლს ასრულებს ბანალური Helloworld, ჩვენი მთავარი მიზანია კომპიუტერს დავაჯეროთ ჩვენი OS-ის არსებობაში, თუნდაც ერთ სექტორში, და გაუშვით იგი.

როგორ არის სტრუქტურირებული ჩატვირთვის სექტორი? კომპიუტერზე, ჩატვირთვის სექტორისთვის ერთადერთი მოთხოვნაა, რომ მისი ბოლო ორი ბაიტი შეიცავდეს მნიშვნელობებს 0x55 და 0xAA - ჩატვირთვის სექტორის ხელმოწერა. ასე რომ, უკვე მეტ-ნაკლებად გასაგებია, რა უნდა გავაკეთოთ. მოდით დავწეროთ კოდი! მოცემული კოდი იწერება yasm ასამბლერისთვის.

განყოფილება .ტექსტის გამოყენება16 org 0x7C00 ; ჩვენი პროგრამა ჩატვირთულია 0x7C00 start მისამართზე: mov ax, cs mov ds, ax ; აირჩიეთ მონაცემთა სეგმენტი mov si, შეტყობინება cld ; მიმართულება სიმებიანი ბრძანებებისთვის mov ah, 0x0E ; BIOS ფუნქციის ნომერი mov bh, 0x00 ; ვიდეო მეხსიერების გვერდი puts_loop: lodsb ; ჩატვირთეთ შემდეგი სიმბოლო al test al, al ; null სიმბოლო ნიშნავს ხაზის დასასრულს jz puts_loop_exit int 0x10 ; გამოიძახეთ BIOS ფუნქცია jmp puts_loop puts_loop_exit: jmp $ ; მარადიული მარყუჟის შეტყობინება: db "Hello World!", 0 დასრულება: ჯერ 0x1FE-დასრულება+დაწყება db 0 db 0x55, 0xAA ; ჩატვირთვის სექტორის ხელმოწერა

ეს მოკლე პროგრამა მოითხოვს რამდენიმე მნიშვნელოვან განმარტებას. ხაზი org 0x7C00 საჭიროა იმისათვის, რომ ასამბლერმა (იგულისხმება პროგრამა და არა ენა) სწორად გამოთვალოს მისამართები ლეიბლებისა და ცვლადების (puts_loop, puts_loop_exit, message). ამიტომ ვაცნობებთ მას, რომ პროგრამა ჩაიტვირთება მეხსიერებაში მისამართზე 0x7C00.
ხაზებში
mov ax, cs mov ds, ax
მონაცემთა სეგმენტი (ds) დაყენებულია კოდის სეგმენტის (cs) ტოლი, ვინაიდან ჩვენს პროგრამაში მონაცემებიც და კოდიც ინახება ერთ სეგმენტში.

შემდეგ მარყუჟში, შეტყობინება "Hello World!" ნაჩვენებია სიმბოლოების მიხედვით. ამ მიზნით გამოიყენება 0x10 შეწყვეტის ფუნქცია 0x0E. მას აქვს შემდეგი პარამეტრები:
AH = 0x0E (ფუნქციის ნომერი)
BH = ვიდეო გვერდის ნომერი (ჯერ არ ინერვიულოთ, მიუთითეთ 0)
AL = ASCII სიმბოლოს კოდი

სტრიქონზე "jmp$" პროგრამა იყინება. და მართალია, არ არის საჭირო დამატებითი კოდის შესრულება. თუმცა, იმისათვის, რომ კომპიუტერმა კვლავ იმუშაოს, თქვენ უნდა გადატვირთოთ.

სტრიქონში „ჯერ 0x1FE-finish+start db 0“ პროგრამის დანარჩენი კოდი (გარდა ბოლო ორი ბაიტისა) ივსება ნულებით. ეს კეთდება ისე, რომ შედგენის შემდეგ, პროგრამის ბოლო ორი ბაიტი შეიცავს ჩატვირთვის სექტორის ხელმოწერას.

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

$ yasm -f bin -o გამარჯობა.bin გამარჯობა.asm

შედეგად მიღებული hello.bin ფაილი უნდა ჩაიწეროს ფლოპი დისკის ჩატვირთვის სექტორში. ეს კეთდება დაახლოებით ასე (რა თქმა უნდა, fd-ის ნაცვლად თქვენ უნდა შეცვალოთ თქვენი დისკის სახელი).

$ dd if=hello.bin of=/dev/fd

ვინაიდან ყველას ჯერ კიდევ არ აქვს დისკი და ფლოპი დისკები, შეგიძლიათ გამოიყენოთ ვირტუალური მანქანა, მაგალითად, qemu ან VirtualBox. ამისათვის თქვენ მოგიწევთ დისკის სურათის შექმნა ჩვენი ჩამტვირთველით და ჩასმა "ვირტუალურ ფლოპი დისკში".
შექმენით დისკის სურათი და შეავსეთ იგი ნულებით:

$ dd if=/dev/zero of=disk.img bs=1024 count=1440

ჩვენ ვწერთ ჩვენს პროგრამას სურათის დასაწყისში:
$ dd if=hello.bin of=disk.img conv=notrunc

ჩვენ ვხსნით მიღებულ სურათს qemu-ში:
$ qemu -fda disk.img -boot a

გაშვების შემდეგ, თქვენ უნდა ნახოთ qemu ფანჯარა ბედნიერი ხაზით "Hello World!" აქ მთავრდება პირველი სტატია. მოხარული ვიქნებით ვიხილოთ თქვენი გამოხმაურება და სურვილები.