Системное программирование Линукс. Linux. Системное программирование. Вступление
Скачать 0.65 Mb.
|
Глава 1. Введение и основополагающие концепции. Она — введение в пробле му. Здесь делается обзор Linux, системного программирования, ядра, библио теки C и компилятора C. Главу следует изучить даже самым опытным пользо вателям. Глава 2. Файловый ввод-вывод. Тут дается вводная информация о фай лах — наиболее важной абстракции в экосистеме UNIX, а также файловом вводе/выводе, который является основой процесса программирования для Linux. Подробно рассматриваются считывание информации из файлов и запись информации в них, а также другие базовые операции файлового вводавыво да. Итоговая часть главы рассказывает, как ядро Linux внедряет (реализует) концепцию файлов и управляет ими. Глава 3. Буферизованный ввод-вывод. Здесь обсуждается проблема, связанная с базовыми интерфейсами вводавывода — управление размером буфера, — и рассказывается о буферизованном вводевыводе вообще, а также стандартном вводевыводе в частности как о возможных решениях. Глава 4. Расширенный файловый ввод-вывод. Завершает трио тем о вводе выводе и рассказывает о продвинутых интерфейсах вводавывода, способах распределения памяти и методах оптимизации. В заключение главы мы пого ворим о том, как избегать подвода головок, и о роли планировщика вводавыво да, работающего в ядре Linux. Вступление 21 Глава 5. Управление процессами. В ней читатель познакомится со второй по важности абстракцией UNIX — процессом — и семейством системных вызовов, предназначенных для базового управления процессами, в частности древним феноменом ветвления ( fork ). Глава 6. Расширенное управление процессами. Здесь продолжается обсужде ние процессов. Глава начинается с рассмотрения продвинутых способов управ ления процессами, в частности управления в реальном времени. Глава 7. Поточность. Здесь обсуждаются потоки и многопоточное программи рование. Глава посвящена в основном высокоуровневым концепциям проекти рования. В частности, в ней читатель познакомится с API многопоточности POSIX, который называется Pthreads. Глава 8. Управление файлами и каталогами. Тут обсуждаются вопросы созда ния, перемещения, копирования, удаления и других приемов, связанных с управ лением файлами и каталогами. Глава 9. Управление памятью. В ней рассказывается об управлении памятью. Глава начинается с ознакомления с основными концепциями UNIX, связанны ми с памятью, в частности с адресным пространством процесса и подкачкой страниц. Далее мы поговорим об интерфейсах, к которым можно обращаться для получения памяти и через которые можно возвращать память обратно в ядро. В заключение мы ознакомимся с продвинутыми интерфейсами, предназначен ными для управления памятью. Глава 10. Сигналы. Здесь рассматриваются сигналы. Глава начинается с обсу ждения природы сигналов и их роли в системе UNIX. Затем описываются сигнальные интерфейсы, от самых простых к наиболее сложным. Глава 11. Время. Она посвящена обсуждению времени, спящего режима и управления часами. Здесь рассмотрены все базовые интерфейсы вплоть до часов POSIX и таймеров высокого разрешения. Приложение А. В нем рассматриваются многие языковые расширения, предо ставляемые gcc и GNU C, в частности атрибуты, позволяющие сделать функцию константной, чистой или внутристрочной. Приложение Б. Здесь собрана библиография работ, которые я рекомендую для дальнейшего изучения. Они служат не только важным дополнением к изложен ному в книге материалу, но и рассказывают об обязательных темах, не затрону тых в моей работе. Версии, рассмотренные в книге Системный интерфейс Linux определяется как бинарный (двоичный) интерфейс приложений и интерфейс программирования приложений, предоставляемый бла годаря взаимодействию трех сущностей: ядра Linux (центра операционной систе мы), библиотеки GNU C ( glibc ) и компилятора GNU C ( gcc — в настоящее время Версии, рассмотренные в книге 22 он официально называется набором компиляторов для GNU и применяется для работы с различными языками, но нас интересует только C). В этой книге рассмот рен системный интерфейс, определенный с применением версии ядра Linux 3.9, версий glibc 2.17 и gcc 4.8. Более новые интерфейсы этих компонентов должны и далее соответствовать интерфейсам и поведениям, документированным в данной книге. Аналогично многие интерфейсы, о которых нам предстоит поговорить, дав но используются в составе Linux и поэтому обладают обратной совместимостью с более ранними версиями ядра, glibc и gcc Если любую развивающуюся операционную систему можно сравнить со сколь зящей мишенью, то Linux — это просто гепард в прыжке. Прогресс измеряется днями, а не годами, частые релизы ядра и других компонентов постоянно меняют и правила игры, и само игровое поле. Ни в одной книге не удалось бы сделать до статочно долговечный слепок такого динамичного явления. Тем не менее экосистема, в которой протекает системное программирование, очень стабильна. Разработчикам ядра приходится проявлять недюжинную изо бретательность, чтобы не повредить системные вызовы, разработчики glibc край не высоко ценят прямую и обратную совместимость, а цепочка инструментов Linux (набор программ для написания кода) создает взаимно совместимый код в раз личных версиях. Следовательно, при всей динамичности Linux системное про граммирование для этой операционной системы остается стабильным. Книга, представляющая собой «мгновенный снимок» системы, особенно на современном этапе развития Linux, обладает исключительной фактической долговечностью. Я пытаюсь сказать: не беспокойтесь, что системные интерфейсы вскоре изменят ся, и смело покупайте эту книгу! Условные обозначения В книге применяются следующие условные обозначения. Курсивный шрифт Им обозначаются новые термины и понятия. Шрифт для названий Используется для обозначения URL, адресов электронной почты, а также соче таний клавиш и названий элементов интерфейса. Шрифт для команд Применяется для обозначения программных элементов — переменных и названий функций, типов данных, переменных окружения, операторов и ключевых слов и т. д. Шрифт для листингов Используется в листингах программного кода. ПРИМЕЧАНИЕ Данная врезка содержит совет, замечание практического характера или общее замечание. Вступление 23 ВНИМАНИЕ Такая врезка содержит какое-либо предостережение. Большинство примеров кода в книге представляют собой краткие фрагменты, которые легко можно использовать повторно. Они выглядят примерно так: while (1) { int ret; ret = fork (); if(ret== –1) perror("fork"); } Пришлось проделать огромную работу, чтобы фрагменты кода получились столь краткими и при этом не утратили практической ценности. Для работы вам не по требуется никаких специальных заголовочных файлов, переполненных безумными макросами и сокращениями, о смысле которых остается только догадываться. Я не писал нескольких гигантских программ, а ограничился многочисленными, но сжа тыми примерами, которые, будучи практическими и наглядными, сделаны макси мально компактными и ясными. Надеюсь, при первом прочтении книги они послу жат вам удобным пособием, а на последующих этапах работы станут хорошим справочным материалом. Почти все примеры являются самодостаточными. Это означает, что вы можете просто скопировать их в текстовый редактор и смело использовать на практике. Если не указано иное, сборка всех фрагментов кода должна происходить без при менения какихлибо специальных индикаторов компилятора (в отдельных случа ях понадобится связь со специальной библиотекой). Рекомендую следующую команду для компиляции файла исходников: $ gcc -Wall -Wextra -O2 -g -o snippet snippet.c Она собирает файл исходного кода snippet.c в исполняемый бинарный файл snippet , обеспечивая выполнение многих предупреждающих проверок, значитель ных, но разумных оптимизаций, а также отладку. Код из книги должен компили роваться без возникновения ошибок или предупреждений — хотя, конечно, вам для начала может потребоваться построить скелетное приложение на базе того или иного фрагмента кода. Когда в какомлибо разделе вы знакомитесь с новой функцией, она записывается в обычном для UNIX формате справочной страницы такого вида: #include int posix_fadvise (int fd, off_t pos, off_t len, int advice); Все необходимые заголовки и определения находятся вверху, за ними следует полный прототип вызова. Условные обозначения 24 Работа с примерами кода Эта книга написана, чтобы помочь вам при работе. В принципе, вы можете исполь зовать код, содержащийся в ней, в ваших программах и документации. Можете не связываться с нами и не спрашивать разрешения, если собираетесь воспользовать ся небольшим фрагментом кода. Например, если вы пишете программу и коегде вставляете в нее код из книги, никакого особого разрешения не требуется. Однако если вы запишете на диск примеры из книги и начнете раздавать или продавать такие диски, то на это необходимо получить разрешение. Если вы цитируете это издание, отвечая на вопрос, или воспроизводите код из него в качестве примера, разрешение не нужно. Если вы включаете значительный фрагмент кода из данной книги в документацию по вашему продукту, необходимо разрешение. Вступление 28 концепций. Даже при программировании в среде разработки, например в системе X Window, в полной мере задействовались системные API ядра UNIX. Соответ ственно, можно сказать, что эта книга — о программировании для Linux вообще. Однако учтите, что в книге не рассматриваются среды разработки для Linux, напри мер вообще не затрагивается тема make . Основное содержание книги — это API системного программирования, предоставляемые для использования на современной машине Linux. Можно сравнить системное программирование с программированием прило жений — и мы сразу заметим как значительное сходство, так и важные различия этих областей. Важная черта системного программирования заключается в том, что программист, специализирующийся в этой области, должен обладать глубокими знаниями оборудования и операционной системы, с которыми он имеет дело. Сис темные программы взаимодействуют в первую очередь с ядром и системными библиотеками, а прикладные опираются и на высокоуровневые библиотеки. Такие высокоуровневые библиотеки абстрагируют детальные характеристики оборудо вания и операционной системы. У подобного абстрагирования есть несколько целей: переносимость между различными системами, совместимость с разными версиями этих систем, создание удобного в использовании (либо более мощного, либо и то и другое) высокоуровневого инструментария. Соотношение, насколько активно конкретное приложение использует высокоуровневые библиотеки и на сколько — систему, зависит от уровня стека, для которого было написано приложе ние. Некоторые приложения создаются для взаимодействия исключительно с вы сокоуровневыми абстракциями. Однако даже такие абстракции, весьма отдаленные от самых низких уровней системы, лучше всего получаются у специалиста, имею щего навыки системного программирования. Те же проверенные методы и пони мание базовой системы обеспечивают более информативное и разумное програм мирование для всех уровней стека. Зачем изучать системное программирование В течение прошедшего десятилетия в написании приложений наблюдалась тен денция к уходу от системного программирования к высокоуровневой разработке. Это делалось как с помощью вебинструментов (например, JavaScript), так и по средством управляемого кода (Java). Тем не менее такие разработки не свидетель ствуют об отмирании системного программирования. Действительно, ведь комуто приходится писать и интерпретатор JavaScript, и виртуальную машину Java, кото рые создаются именно на уровне системного программирования. Более того, даже разработчики, которые программируют на Python, Ruby или Scala, только выигра ют от знаний в области системного программирования, поскольку будут понимать всю подноготную машины. Качество кода при этом гарантированно улучшится независимо от части стека, для которой он будет создаваться. Несмотря на описанную тенденцию в программировании приложений, большая часть кода для UNIX и Linux попрежнему создается на системном уровне. Этот код написан преимущественно на C и C++ и существует в основном на базе Глава 1. Введение и основополагающие концепции 29 интерфейсов, предоставляемых библиотекой C и ядром. Это традиционное сис темное программирование с применением Apache, bash , cp , Emacs, init , gcc , gdb , glibc , ls , mv , vim и X. В обозримом будущем эти приложения не сойдут со сцены. К области системного программирования часто относят и разработку ядра или как минимум написание драйверов устройств. Однако эта книга, как и большин ство работ по системному программированию, никак не касается разработки ядра. Ее основной фокус — системное программирование для пользовательского про странства, то есть уровень, который находится выше ядра. Тем не менее знания о ядре будут полезным дополнительным багажом при чтении последующего текста. Написание драйверов устройств — это большая и объемная тема, которая подроб но описана в книгах, посвященных конкретно данному вопросу. Что такое системный интерфейс и как я пишу системные приложения для Linux? Что именно при этом мне предоставляют ядро и библиотека C? Как мне удается создавать оптимальный код, какие приемы возможны в Linux? Какие интересные системные вызовы есть в Linux, но отсутствуют в других UNIXподобных системах? Как все это работает? Именно эти вопросы составляют суть данной книги. Краеугольные камни системного программирования В системном программировании для Linux можно выделить три основных крае угольных камня: системные вызовы, библиотеку C и компилятор C. О каждом из этих феноменов следует рассказать отдельно. Системные вызовы Системные вызовы — это начало и конец системного программирования. Системные вызовы (в англоязычной литературе встречается сокращение syscall) — это вызовы функций, совершаемые из пользовательского пространства. Они направлены из приложений (например, текстового редактора или вашей любимой игры) к ядру. Смысл системного вызова — запросить у операционной системы определенную службу или ресурс. Системные вызовы включают как всем знакомые операции, например read() и write() , так и довольно экзотические, в частности get_thread_area() и set_tid_address() В Linux реализуется гораздо меньше системных вызовов, чем в ядрах большин ства других операционных систем. Например, в системах с архитектурой x8664 таких вызовов насчитывается около 300 — сравните это с Microsoft Windows, где предположительно задействуются тысячи подобных вызовов. При работе с ядром Linux каждая машинная архитектура (например, Alpha, x8664 или PowerPC) может дополнять этот стандартный набор системных вызовов своими собственными. Сле довательно, системные вызовы, доступные в конкретной архитектуре, могут отли чаться от доступных в другой. Тем не менее значительное подмножество всех систем ных вызовов — более 90 % — реализуется во всех архитектурах. К этим разделяемым 90 % относятся и общие интерфейсы, о которых мы поговорим в данной книге. Системное программирование 30 Активация системных вызовов. Невозможно напрямую связать приложения пользовательского пространства с пространством ядра. По причинам, связанным с обеспечением безопасности и надежности, приложениям пользовательского про странства нельзя разрешать непосредственно исполнять код ядра или манипулиро вать данными ядра. Вместо этого ядро должно предоставлять механизм, с помощью которого пользовательские приложения будут «сигнализировать» ядру о требовании активировать системный вызов. После этого приложение сможет осуществить сис- темное прерывание ядра (trap) в соответствии с этим строго определенным меха низмом и выполнить только тот код, который разрешит выполнить ядро. Детали этого механизма в разных архитектурах немного различаются. Например, в процес сорах i386 пользовательское приложение выполняет инструкцию программного прерывания int со значением 0x80 . Эта инструкция осуществляет переключение на работу с пространством ядра — защищенной областью, — где ядром выполняется обработчик программного прерывания. Что же такое обработчик прерывания 0x80 ? Это не что иное, как обработчик системного вызова! Приложение сообщает ядру, какой системный вызов требуется выполнить и с ка кими параметрами. Это делается посредством аппаратных регистров. Системные вызовы обозначаются по номерам, начиная с 0 . В архитектуре i386, чтобы запросить системный вызов 5 (обычно это вызов open() ), пользовательское приложение за писывает 5 в регистр eax , после чего выдает инструкцию int Передача параметров обрабатывается схожим образом. Так, в архитектуре i386 регистр применяется для всех возможных параметров — например, регистры ebx , ecx , edx , esi и edi в таком же порядке содержат первые пять параметров. В редких случаях, когда системный вызов имеет более пяти параметров, всего один регистр применяется для указания на буфер в пользовательском пространстве, где хранятся все эти парамет ры. Разумеется, у большинства системных вызовов имеется всего пара параметров. В других архитектурах активация системных вызовов обрабатывается иначе, хотя принцип остается тем же. Вам, как системному программисту, обычно не нуж но знать, как именно ядро обрабатывает системные вызовы. Эта информация уже интегрирована в стандартные соглашения вызова, соблюдаемые в конкретной ар хитектуре, и автоматически обрабатывается компилятором и библиотекой C. |