Operating Systems: Three Easy Pieces. Part 1: Intro (пераклад)
Увядзенне ў аперацыйныя сістэмы
Прывітанне, Хабр! Жадаю прадставіць вашай увазе серыю артыкулаў-перакладаў адной цікавай на мой погляд літаратуры - OSTEP. У гэтым матэрыяле разглядаецца досыць глыбока праца unix-падобных аперацыйных сістэм, а менавіта - праца з працэсамі, рознымі планавальнікамі, памяццю і іншымі падобнымі кампанентамі, якія складаюць сучасную АС. Арыгінал усіх матэрыялаў вы можаце паглядзець вось тут. Прашу ўлічыць, што пераклад выкананы непрафесійна (дастаткова вольна), але спадзяюся агульны сэнс я захаваў.
Лабараторныя працы па дадзеным прадмеце можна знайсці вось тут:
А яшчэ можаце зазіраць да мяне на канал у тэлеграм =)
Праца праграмы
Што ж адбываецца, калі працуе якая-небудзь праграма? Запушчаная праграм выконвае адну простую рэч - яна выконвае інструкцыі. Кожную секунду мільёны і нават магчыма мільярды інструкцый здабываюцца працэсарам з аператыўнай памяці, у сваю чаргу ён дэкадуе іх (напрыклад, распазнае да якога тыпу, належаць гэтыя інструкцыі) і выконвае. Гэта могуць быць складанне двух лікаў, доступ да памяці, праверка ўмовы, пераход да функцыі і гэтак далей. Пасля заканчэння выканання адной інструкцыі працэсар пераходзіць да выканання іншай. І так інструкцыя за інструкцыяй, яны выконваюцца да таго часу, пакуль праграма не завершыцца.
Дадзены прыклад натуральна разгледжана спрошчана - насамрэч для паскарэння працы працэсара сучаснае жалеза дазваляе выконваць інструкцыі па-за чаргой, пралічваць магчымыя вынікі, выконваць інструкцыі адначасова і таму падобныя хітрыкі.
Фон-Нейманаўская мадэль вылічэння
Апісаная намі спрошчаная форма працы падобная да Фон-Нейманаўскай мадэлі вылічэнняў. Фон-Нэйман гэта адзін з піянераў кампутарных сістэм, таксама ён адзін з аўтараў тэорыі гульняў. Падчас працы праграмы адбываецца яшчэ куча іншых падзей, працуе мноства іншых працэсаў і іншай логікі, асноўная мэта якіх - спрашчэнне запуску, працы і абслугоўвання сістэмы.
Існуе набор праграмнага забеспячэння, які адказны за прастату запуску праграм (ці нават які дазваляе запускаць некалькі праграм адначасова), ён дазваляе праграмам падзяляць адну і тую ж памяць, а гэтак жа ўзаемадзейнічаць з рознымі прыладамі. Такі набор ПА (праграмнага забеспячэння) у сутнасці і завуць аперацыйнай сістэмай і ў яго задачы ўваходзіць адсочванне таго, каб сістэма працавала карэктна і эфектыўна, а таксама забеспячэнне прастаты кіравання гэтай сістэмай.
Аперацыйная сістэма
Аперацыйная сістэма, скарочана АС – комплекс узаемазлучаных праграм, прызначаных для кіравання рэсурсамі кампутара і арганізацыі ўзаемадзеяння карыстача з кампутарам.
АС дамагаецца сваёй эфектыўнасці ў першую чаргу, праз самую галоўную тэхніку - тэхніку віртуалізацыі. АС ўзаемадзейнічае з фізічным рэсурсам (працэсарам, памяццю, дыскам і таму падобныя) і трансфармуе яго ў больш агульную, з вялікімі магчымасцямі і прасцейшую для выкарыстання форму самога сябе. Таму для агульнага разумення, можна вельмі грубіянска параўнаць аперацыйную сістэму з віртуальнай машынай.
Для таго каб дазваляць карыстачам даваць каманды аперацыйнай сістэме і такім чынам выкарыстаць магчымасці віртуальнай машыны (такія як: запуск праграмы, вылучэнне памяці, доступ да файла і гэтак далей), аперацыйная сістэма падае некаторы інтэрфейс, званы API (application programming interface) і да якога можна рабіць выклікі (call). Тыповая аперацыйная сістэма дае магчымасць зрабіць сотні сістэмных выклікаў.
І нарэшце, паколькі віртуалізацыя дазваляе мноству праграм працаваць (такім чынам, сумесна выкарыстоўваць CPU), і адначасова атрымліваць доступ да іх інструкцый і дадзеным (тым самым падзяляючы памяць), а таксама атрымліваць доступ да дыскаў (такім чынам, сумесна выкарыстоўваць прылады ўводу-высновы ), аперацыйную сістэму яшчэ называюць мэнэджэрам рэсурсаў. Кожны працэсар, дыск і памяць гэта рэсурс сістэмы і такім чынам адной з роляў аперацыйнай сістэмы становіцца задача па кіраванні гэтымі рэсурсамі, робячы гэта эфектыўна, сумленна ці, наадварот, у залежнасці ад задачы, для якой гэтая аперацыйная сістэма распрацавана.
Яна не выконвае нейкіх асаблівых дзеянняў, па сутнасці ўсё што яна робіць - выклікае функцыю прасці(), задача якой цыклічная праверка часу і вяртанне, пасля таго як прайшла адна секунда. Такім чынам, яна паўтарае бясконца радок, які карыстач перадаў у якасці аргументу.
Запусцім гэтую праграму, і перадамо ёй аргументам сімвал "А". Вынік атрымліваецца не асабліва цікавы - сістэма проста выконвае праграму, якая перыядычна выводзіць на экран сімвал "А".
Цяпер паспрабуем варыянт, калі запушчана мноства асобнікаў адной і той жа праграмы, але якія выводзяць розныя літары, каб было зразумелей. У гэтым выпадку, вынік атрымаецца некалькі іншы. Нягледзячы на тое, што ў нас адзін працэсар, праграма выконваецца адначасова. Як жа гэта атрымліваецца? А атрымліваецца, што аперацыйная сістэма, не без дапамогі магчымасцяў абсталявання, стварае ілюзію. Ілюзію таго, што ў сістэме ёсць некалькі віртуальных працэсараў, ператвараючы адзін фізічны працэсар у тэарэтычна бясконцую колькасць і тым самым дазваляючы, праграмам з выгляду выконвацца адначасова. Такую ілюзію і завуць Віртуалізацыяй CPU.
Падобная карціна спараджае шмат пытанняў, напрыклад, калі некалькі праграм жадаюць запусціцца адначасова, то якая менавіта будзе запушчана? За гэтае пытанне адказваюць "палітыкі" АС. Палітыкі выкарыстоўваюцца ў шматлікіх месцах АС і адказваюць на падобныя пытанні, а гэтак жа з'яўляюцца базавымі механізмамі, якія АС увасабляе. Адгэтуль і роля АС як рэсурснага мэнэджара.
Віртуалізацыя памяці
Цяпер давайце разгледзім памяць. Фізічная мадэль памяці ў сучасных сістэмах уяўляецца як масіў байт.. Для чытання з памяці трэба пазначыць адрас ячэйкі, Каб атрымаць да яе доступ. Каб запісаць ці абнавіць дадзеныя трэба таксама паказаць дадзеныя і адрас ячэйкі, куды іх запісаць.
Звароты да памяці адбываецца ўвесь час падчас прац праграмы. Праграма захоўвае ў памяці ўсю яе структуру даных, і звяртаецца да яе, выконваючы розныя інструкцыі. Інструкцыі тым часам таксама захоўваюцца ў памяці, таму зварот да яе адбываецца таксама на кожны запыт да наступнай інструкцыі.
Выклік malloc()
Разгледзім наступную праграму, якая вылучае вобласць памяці, выкарыстоўваючы выклік малачок () (https://youtu.be/jnlKRnoT1m0):
Праграма робіць некалькі рэчаў. Па-першае, вылучае некаторы аб'ём памяці (радок 7), затым выводзіць адрас вылучанага вочка (радок 9), запісвае нуль у першы слот выдзеленай памяці. Далей праграма ўваходзіць у цыкл, у якім інкрыментуе значэнне, запісанае ў памяці па адрасе ў зменнай "p". Таксама яна выводзіць ідэнтыфікатар працэсу самога сябе. Ідэнтыфікатар працэсу ўнікальны для кожнага запушчанага працэсу. Запусціўшы некалькі копій, мы наткнёмся на цікавы вынік: У першым выпадку, калі не зрабіць нічога і проста запусціць некалькі копій, то адрасы будуць рознымі. Але ж гэта не трапляе пад нашу тэорыю! Правільна, паколькі ў сучасных дыстрыбутывах уключаная па змаўчанні функцыя рандомизации памяці. Калі яе адключыць, атрымаем чаканы вынік - адрасы памяці ў двух адначасова працавальных праграм будуць супадаць.
У выніку атрымліваецца, што дзве незалежныя праграмы працуюць са сваімі ўласнымі прыватнымі адраснымі прасторамі, якія ў сваю чаргу адлюстроўваюцца аперацыйнай сістэмай у фізічнай памяці.. Таму выкарыстанне адрасоў памяці ў межах адной праграмы ніяк не будзе закранаць іншыя і кожнай праграме здаецца, што ў яе ўласны кавалак фізічнай памяці, цалкам аддадзены ў яе распараджэнне. Рэальнасць, аднак, такая, што фізічная памяць - падзяляемы рэсурс, кіраванне якім ажыццяўляе аперацыйная сістэма.
ўзгодненасць
Яшчэ адна з важных тэм у рамках аперацыйных сістэм - узгодненасць. Гэты тэрмін выкарыстоўваецца, калі гаворка ідзе аб праблемах у сістэме, якія могуць узнікаць пры працы са шматлікімі рэчамі адначасова ў межах адной праграмы. Праблемы ўзгодненасці ўзнікаюць нават у самай аперацыйнай сістэме. У папярэдніх прыкладах з віртуалізацыяй памяці і працэсара, мы зразумелі, што АС кіруе шматлікімі рэчамі адначасова – запускае першы працэс, затым другі і гэтак далей. Як аказалася, такія паводзіны могуць прывесці да некаторых праблем. Так, напрыклад сучасныя шматструменныя праграмы адчуваюць такія цяжкасці.
Разгледзім наступную праграму:
Праграма ў галоўнай функцыі стварае два патоку, выкарыстоўваючы выклік Pthread_create(). У дадзеным прыкладзе аб струмені можна думаць як аб функцыі, запушчанай у адной прасторы памяці побач з іншымі функцыямі, прычым колькасць запушчаных адначасова функцый відавочна больш за адну. У дадзеным прыкладзе кожны паток стартуе і выконвае функцыю worker() якая ў сваю чаргу проста інкрыментуе зменную,.
Запусцім гэтую праграму з аргументам 1000. Як вы ўжо маглі здагадацца, вынікам павінна стаць 2000, паколькі кожны струмень інкрыментаваў зменную 1000 раз. Аднак усё не так проста. Паспрабуем запусціць праграму з лікам паўтораў на парадак болей.
Падаючы на ўваход лік, напрыклад, 100000 мы чакаем убачыць на выхадзе лік 200000. Аднак, запусціўшы лік 100000 некалькі разоў, мы не толькі не ўбачым правільны адказ, але і атрымаем розныя няправільныя адказы. Разгадка крыецца ў тым, што для павелічэння колькасці патрабуецца тры аперацыі - выманне ліку з памяці, інкрыментацыя і затым запіс ліку назад. Паколькі ўсе гэтыя інструкцыі не здзяйсняюцца атамарна (усе адначасова), такія дзіўныя рэчы могуць адбывацца. Гэтая праблема завецца ў праграмаванні race condition - стан гонкі. Калі невядомыя сілы ў невядомы момант могуць паўплываць на выкананне якіх-небудзь вашых аперацый.