Лекция 10. Linux-specific
Сегодня вместо Георгия Владимироваича Евгений Леонидович.
Тема сегодняшней лекции программирование в линукс.
Linux-specific.
Или за что вас будут не любить те, кто будет портировать ваш код на другие ос.
Линукс помимо того, что предоставляет апи позикса, предоставляет ряд средств для делания вещей более лучше или вообще иначе. Основным программным интерфейсом взаимодействия пользовательских программ и линукса является линукс апи, предоставляемое в виде системных вызовов.
Помимо декларированных в позикс есть ряд линукс-специфичных.
Итак, первое линукс-специфик
- API
- Следующее — набор виртуальных файловых систем. fs interface
- Третьим аспектом является ряд иных специфичных для Линукс вещей, всякая несистемная да.
API
Значительно пересекается с POSIX. В попытке реализовать POSIX-совместимое API становится понятно, что в реальной жизни всё не так хорошо. Есть ряд вещей, которых нет в POSIX’е, но хорошо бы иметь. Что сюда входит
- POSIX extensions. добавляем дополнительных флагов, всякие системные вызовы, согласуюся с позикс апи или просто не были включены в стандарт. Например, 64-битные офсеты в файлах. Например, когда стало понятно что файлы бывают больше 4 гигабайт, а ос были ещё 32. Понятно что это ксается только 32битных платформ. В начале было решени использовать набор спец вызовов с суффиксом 64. Потом перешли к более ормальному решению, которое заключалось в изменение типа оффсета.
- Linux extensions. За время разработки ядра, за более чем 30 лет, туда было включено много овещей, которыее делают жизнь удобней вне зависимости от стандартов. Поллинг epoll, посылка в сокет нескольких сообщений за раз. Всякие возможножности поддержки тредов, proc namespace и капабилити.
- среднее между двумя. Например доп возможности касающиеся управляения памятью -- вы говорите какой тип доступа к какому куску памяти будет чтобы лучше её кэшировать. Или, например madvise(). В очередной редакции позикса он появился, но был и до этого, и в линуксе немного отличался,линукс допускал ряд дополнительных флоагов и им вы могли давать хинты, которые ядро пправда воспринимало как команды, но это неважно. Суть в том что флагов было больше и они были линукс специфичны.
- костыли. obscure features. Хотите сбросить кэш на платформе мипс, говорите кэш флаш, и на мипсе аппаратно сбрасывается кеш. Или vm86 переход в виртуальный режим 8086. Или поддержка архитектру целл выливается в два системных вызова ... системный вызов айдл, который говорит процессцу 0 чтобы он удалился из себя, потому что он уже породил инит 1 и больше не нужен. Туда же относится управление ктрл-альт-дел.
POSIX&Linux extensions
Говорим про них вместе, так как достаточно сложно разделять, для много верны обе классификации.
Начнем с модели управления процесса и управления памятью. Это довольно важно.
Управление процессами
- Дистниктив фичерс.
- Поддержка тредов
- поддержка non uniform memory architecture
- поддержка капабилитис
- process namespaces
- process accaunting
СЕЛинукс? Раньше для него приходилось хачить ядро, теперь есть ядерные интерфейся для добавления секьюрити модуля, который добавляет секьюрити проверки соответственно собственному разумению, эти модули могут быть разными. Апарм, СЕЛинукс.
В чем идея с тредами. Для тредов есть local storage, чтобы у каждого было свое личное адресное пространство -- это может быть реализовано как компилятроом, так и на уровне ядра. влинуксе есть на уровне ядра. У вас есть дополнительные системные вызовы. gettid(), get/set_thread_area(), ну и, соответственно, tkill(). Чтобы эта реализация была более менее прозрачной, у вас есть главный тред и тред_ид главного треда совпдаает с процесс_ид. Если вы не думаете о тредах, у вас все прозрачно, иначе надо использовать дополнительные вызовы. Ими же пользуется библиотека pthreads. Есть ли смысл пользоваться напрямую ядерными вызовами? tkill вы не сможете сделать напрямую. Есть ряд системных вызовов которыми надо указывать тред_ид, а без системного вызова его не получить. птредс вещь внутри себя, внешней штукой ими управлять не получится. птредс они про другое, про многопоточное програмиирование, а системных вызовов полторы штуки. В птредс большинство вызовов именно тред_авар, а не для работы с собственно тредами. Ещё сисвызовы для тредов tgkill(), exit_group(). Как создать тред? clone(). Или clone2(). Есть такая мода давать цифровые суффиксы всяким модным вызовам. epoll_create epoll_create1 вызовы более крутые в том плане, что у них поменялся ап/би??, то есть не апби ааа.
- getcpu()
- migrate_pages()
- move_pages() -- перемещает не все страницы, а только указанные в другой полл.
- get/set_mempolicy()
- shed_setaffinity() высатвляет аффинити для тредов.
Как работает шедулер в линуксе? Линукс не парится. Когда аффинити не задан, то он будет раскидывать страницы как попало, и процесс будет работать на процессоре каком попало, потому что доступ к странице своей/нее своей все равно достаточно дорогие по сравнению с кешами. От перекидывания на другой процессор получается больше ущерба чем от неправильной аффинити. Но мемори менеджер это штука такая. Перебалансировка по нодам делается чуть ли не раз в полсекунды.
Есть возможность привязать процесс не к ядру? Сетаффинити. А сет_мем полиси позволяет указать мемори пулл, который надо использовать данному процессу.
капабилити это ряд прав/возможнстей, позволяющих выполнять различные системные вещи -- монтирование, сет_уид, итд. Капабилитей много. как рут так и не рут могут вполне себе ими обладать или не обладать. С одной стороны можете получить возможность сделать рута распредленным. Такое было впервые реализовано в солярис. Из соляриса перетащили много секьюити фишек - пам, капабилити, в каком-то плане контейнеры и процесс аккаунтинг. тем не менее сейчас влинуксе это все есть, капабилити появились достаточно давно,Ю кажется в 2.2. Управлять темикакпабилити которые вам доступны можно через prctl(). Там можно получить и задать маску доступных, можно задать маску капабилитей которые дети не получат, и так далее. когда вы планируете делать клон вы можете уаказать что наследует дочерний процесс в том числе и касательно капабилетей. Кстати, посредством клон сейчас реализуется форк, потому что он более круче. capget(), capset() позволяют указывать непосрдественные капабилити тредов. Да.
proc namespaces. Иногда хочется изолировать процессы от окружающего мира тем или иным образом. Изолировать хочется по разному, например дать и мдругой рут, например не показывать им другие процессы, не показыавть им какую-нибудь информацию о системе касающуюся памяти и процессора и так далее. Неймспейсы позволяют это делать. Вы можете породить новый неймспейс у процесса и сказать, что там новая жизнь. Он будет не в курсее про другие процессы и не сможет общаться с другими процессами даже посрдеством сигналов, хоть и будет от того же пользователя. вызов seths(). Gjxtve pfujdjhbkb ghj fengjqyn? gjnjve xnj [jntkb egjvzynm ghj зшмще_кщще и у какого то вызова , маунта или нет, есть возможность указывать какие маунты видит процесс и все его дети. Кажется это делается prctl().
Есть ещё такая смешная штука, которая позволяет менять поведение ядра для дерева процессов. Это нужно в основном для всякой портабилити ятобы всяике процессы написанные для других юниксов могли работать в линуксе. Такие специального вида костыли. getrusage()
Работа с файлами
Там гораздо больше всякого интересного. Файловый интерфейс этоосновной способ абстракции любого ввода-вывода. Как следствие этот интерфейс функуионально довольно богат и там наиболее интересные механизмы которые интересны и с тз юзерспейса. С расширениями касающимися работы с файлами вы столкнетесь почти наверняка.
- epoll. Для того чтобы обслуживать большое колво файловых дескрипторов, сетевые соединения или пайпы есть примерно два сисвызова -- селект и полл. работают они не лучшим образом если вы хотите следить за сотнями или тысячами дескрипторов. м надо указывать каждый раз за чем следить, то есть пересоздавать указывающую это структуру. Подход еполл заключается в том, что вы создаете некий ядерный объект, посредством специального интерфейса — eploo_create? epoll_create1, дальше посредством epoll_ctl можете понавешать в это т объект фд за которыми хотите следить, и для них указывать виды событий, которые хотите получать. Можете следить за тем что там произошли чтение, запись, закрыти едлескриптора. Перфикс довольно очевидные epoll_ctl(epfd, EPOLL_CTL_ADD, fd, event). В ивенте собственно описываете за каим событием следить. ивент имеет тип struct epoll_ecevnt у него есть поле events == EPOLL_IN|EPOLL_OUT). Событие может происходить по событию чтение/записи или по наличию данных edge triggersю Для дескриптора специального вида вы можете повесит еполл_евент на фд который соответсвует другому еполл_евента или фд который соответсвтует дескриптору для сигнала. или еще что — много всяких специальных дескрипторов и когда с ними что-то происхходит они будут считать что с ним что-то произошло, но вас интересует левел. Еще epoll one shot позволяет евент для однократного срабатывания. После того как вы еполл цтл подобавляли всяких ивентов вы можете делать epoll_wait epoll_pwait. Префикс P. Если вы делаете пселектесть проблема. Когда вам надо замаскировать сигналы и посавить селект, потому что вы хотите во время селекта, полла, вейт получать какие то сигналы. возникает рейс кодишн маску поменяли, а вейт не поставили. Наборы специальных вызовов с префиксом p ppoll, pwait, pselect -- дополнительный параметр маски сигналов, вместо лвух вызовов получается один. Если у вас левел-триггеред обработка, то вы можете безболезненно заменить poll на epoll и все будет работать.
Есть ещё одна задача, которая касается слежения за всякими событими в системе. Было два механизма dnotify и inotify. dnotify делался через fctrl вы могли взять дескриптор соотв директории и могли передать дескриптор директории для ..
На смену dnotify примерно во времена ядра 2.5. пришел inotify. В маске вы указываете много клёвых параметров, за которыми вы хотите наблюдать.
infd = inotify_init() wd = inotify_add_watch(infd, path, mask)
есть возможность указывать одноразовость. Вам возвращается дескриптор, который позволяет узнавать какой вотч это был.
Делается просто рид и читаете вы оттуда структуру соответствующего вида. структура Notify_event в которой указан вотч_дескриптор, указано чего случилось посредством аналогичной маски.
Когда говорили про управление памятью там еще один аспект возможно стоит подчеркнуть. Маппинги довольно смещно осуществлялись одно время. В связи с необходимостью 64 битных оффсетов. Помимо мап в ядре сейчас определён мап2, которые принимает смещение по страницам. Единственная архитектруа которая в 32 битном режиме может адресов более чем 32-битные адреса это х86 PAE, но в люом случае есть мап64 у которой 2 параметра типа старший бит и младший бит.
По поводу памяти ещё есть возможность ремапить всякие вещи. map из позикса, а вот remap нет и без него страдали.
Ещё можно делать нелинейный мапинг.
sys_info позволяет много чего получить.
Большинство из этого неактально для юзерспейс программистов.
Во Фре есть kqueue который очень крутой и умеет тоже самое что epoll, inotify и не только.
Юзерспейс программисты обычно просто берут и используют libevent.
А вот то, что касается файловых систем эт оштука более приближенная к юзерспейсу в том плане, что пользоваться ей можно из любой программы. Специальные фс доступны посредством стандартного файлового интерфейса.
Файловые системы специалных функций довольного много. Самые известные procfs. Впервые она появилась в солярисе или бзд, она позволяла и позволяет узнавать информацию о процессах. Линукс отличается тем что прокфс есть кроме информации о процессах много чего. Интерфейс прокфс довольно простой. Многие ядерные подсистемы экспрортируют туда информацию о себе и позволяют собой управлять. Когда создается там файл создается структура с коллбэками на чтение и на запись.
/proc/sys
/proc/sys/ возможность воздействия на ядро — лимиты, особенности поведения. То же самое делается посредством сисвызова sysctl(). Сисктл доступно довольно много чего. net, kernel, vm, fs. Помимо этого в самом прокфсе ещё... вот.
Гораздо интересней другая специальная фс.
sysfs. Это способ просмотра иерархии kobject это часть объектной модели ядра Линукс. Нельзя пафосно сказать, что она там есть, но там можно создавать объекты и предоставлять к ним доступ на чтение и запись и механизм событий. Из юзерспейса возможны колбэки на чтение запись в sysfs. Когда вы в sysfs добавляете файл, вы добьавляете объект в определённую подсистему, а там уже предоставляются аттрибуты для доступа к объектам. Сам кобжект это директория а аттрибуты это уже регулярные файлы.
Может кто-то не в курсе, как организуются подобные штуки в ядре.
Когда вы определяете структуру вы можете одним из аттрибуотв указать этото самый кобжект. Сам по себе он используется в основном для рефкаунтинга и всяких таки связанных с этим вещей по аналогии с ядерными списками. Чтобы заиметь доступ к окружающей структуре есть специальный макрос в ядре, акт же как и когда вы используете спсики в ядре вы можете воспользоваться макросом для получения доступа к щас.
struct my_obj{
struct kobjext kobj;}
container_of(obj, struct my_obj, kobj)
Далее после того как вы создали структуру вы можете делать kobject_init для инициализации этого поля внутри структуры. После вы можете делать kobject_add и на этом этапе вы должны указать ktyoe которая должна быть проассоцирована с этим объектом. Это надо затем что а, да. Структуру ктайп вы указывате ещё на этапе инициализации. Когда вы добавляете вы указываете родителя данного объекта. У кобжекта есть имя. Оно тоже определяется посредством API. . Это рассказывается чтобы вы сбее представляли как устроен сисфс.
Как устроен рефкаунтинг? Когда вы хотите поработать с кобжектом, вы сначала делеает kobject_get, потом kobject_put. Если рефкаунтер дропнулся до нуля, т ообъект удаляется. рефкаунтеры инкрементятся на каждый адд у парента, но если вы сделали циклическую зависимость, то вы перед кобжект релиз должны сделать кобжект дел.
Чтоюбы получать доступ к аттрибутам сисфс есть вызовы sysfs_create_file и sysfs_create_group — первое позволяет создавать кобжекты. второе каталоги их группирующие. Сисфс гораздо более внятно организована сама по себе когнитивных свойств не несет, а просто предоставляет доступ к иерархии кобжектов, но на ней надложено дополнительное полиси как должны себе вести какие модули. Модули ядра которые работают с пси, итд соблюдают неокторые правила и генерируют все красиво. Туда перетащили доступ к платформе, к ацпи. Все мигрирует из прока в сисфс, потому что уже ненужно например уеликом реализовать семантику чтения и доступа к файлам итп. Там еще подсистема наворчивает свои коллбэки.
Есть еще конфигфс, которая позволяет манипулировать иерархией в различных подсистемах, вы вешаете коллбэки, в делаете чтото а потом радостно видите на созданной диретрории какие-то аттрибуты которыми можете рудить.
у селинукс есть своя фс, которой он управляется и у cgroups тоже.
Костыли-костыли. Если кто не знает read ahead, который позволяет сказать закэшируй кусок файла я его сейчас прочитаю это тоже тркщтр
Поскольку с Линуксом ассоциировано некоторое окружение, то пользуясь расширениями этого окружения вы впадаете в линукс специфик.
glibc,gnu lib c , мантейнтится в районе редхата.Это двольно развесистая и тяжелая библиотека со своими особенностями. Там появились библиотечные вызовы с суффикосм с для некоторых вещей у которых не было нормального о. Помните наверное фгетс у которого не было нормальной обработки баффероверфлоу, таких вызово намного больше, которым хорошо было бы передавать количество байт.
coreutils также имеют расширений вагон и маленькую тележку потому что то, что специфицировано в POSIX, совсем грустно. Берёте, открываете любой мануал по coreutils и находите у команд по две трети GNU-специфичных опций. xargs
В Линуксе поддерживают расгиренные аттрибуты и есть специальное апи по их управлению. они умеют указывать что файл имеет соотв капабилити.
shell(bash). некоторые сичтают что програмиирование на баше это программирование на шелле. Это не так. Баш не стандартизован, набор возможностей меняется с каждой версией. и даже если говрите #!/bin/bash вы моложец что не сказали бин ш, но непонятно какой это бэш первый,второй, третий.
Башизмы
- pipestatus
- [[]] , но не $(()), но если вы пишите if (()) надеясь таким способом заиметь результат арифм вычислений для ифа вы будете страдать, потому что это расширение
- си стайл циклы
- variable expansions типа ${ / / } ${ : : } $RANDOM
Ещё в баше есть довольно удобная штука — как узнать, внутри какого файла вы находитесь, в случае сорсинга. В баше есть специальный массив exec path, в котором в нулевом элементе путь до текущего исп скрипта в следующих вся ... вызовов.
- FUNCNAME
<<<
< () > )
управление стдин стдерр &> > 2>&1
- shopt
- read опции вайл после пай
- $(cat file|while read do done)
- local
Далеко не всё, чем богат Линукс, здесь рассказано. xattr, и ещё много, что лектор вспомнил, но забыл. Среди костылей есть много костылей, без которых жить очень грустно.