Системное программирование Линукс. Linux. Системное программирование. Вступление
Скачать 0.65 Mb.
|
Библиотека C Библиотека C ( libc ) — это сердце всех приложений UNIX. Даже если вы програм мируете на другом языке, то библиотека C, скорее всего, при этом задействуется. Она обернута более высокоуровневыми библиотеками и предоставляет основные службы, а также способствует активации системных вызовов. В современных сис темах Linux библиотека C предоставляется в форме GNUlibc , сокращенно glibc (произносится как «джилибси», реже «глибси»). Библиотека GNU C предоставляет гораздо больше возможностей, чем может показаться из ее названия. Кроме реализации стандартной библиотеки C, glibc дает обертки для системных вызовов, поддерживает работу с потоками и основные функции приложений. Глава 1. Введение и основополагающие концепции 31 Компилятор C В Linux стандартный компилятор языка C предоставляется в форме коллекции компиляторов GNU (GNU Compiler Collection, сокращенно gcc ). Изначально gcc представляла собой версию cc (компилятора C) для GNU. Соответственно gcc расшифровывалась как GNU C Compiler. Однако впоследствии добавилась под держка других языков, поэтому сегодня gcc служит общим названием всего семей ства компиляторов GNU. При этом gcc — это еще и двоичный файл, используемый для активации компилятора C. В этой книге, говоря о gcc , я, как правило, имею в виду программу gcc , если из контекста не следует иное. Компилятор, используемый в UNIXподобных системах, в частности в Linux, имеет огромное значение для системного программирования, поскольку помогает внедрять стандарт языка C (см. подразд. «Стандарты языка С» разд. «Стандарты» данной главы), а также системный двоичный интерфейс приложений (см. разд. «API и ABI» текущей главы), о которых будет рассказано далее. С++ В этой главе речь пойдет в основном о языке C — лингва франка системного про- граммирования. Однако C++ также играет важную роль. В настоящее время C++ уступил ведущие позиции в системном программиро- вании своему старшему собрату C. Исторически разработчики Linux всегда отдава- ли C предпочтение перед C++: основные библиотеки, демоны, утилиты и, разуме- ется, ядро Linux написаны на C. Влияние C++ как «улучшенного C» в большинстве «нелинуксовых» систем можно назвать каким угодно, но не универсальным, поэтому в Linux C++ также занимает подчиненное положение относительно C. Тем не менее далее в тексте в большинстве случаев вы можете заменять «C» на «С++». Действительно, C++ — отличная альтернатива C, подходящая для решения практически любых задач в области системного программирования. C++ может свя- зываться с кодом на C, активизировать системные вызовы Linux, использовать glibc При написании на C++ в основу системного программирования закладывается еще два краеугольных камня — стандартная библиотека C++ и компилятор GNUC++. Стандартная библиотека C++ реализует системные интерфейсы C++ и использует стандарт ISOC++ 11. Он обеспечивается библиотекой libstdc++ (иногда используется название libstdcxx ). Компилятор GNUC++ — это стандартный компилятор для кода на языке C++ в системах Linux. Он предоставляется в двоичном файле g++ API и ABI Разумеется, программист заинтересован, чтобы его код работал на всех системах, которые планируется поддерживать, как в настоящем, так и в будущем. Хочется быть уверенными, что программы, создаваемые на определенном дистрибутиве Linux, будут работать на других дистрибутивах, а также иных поддерживаемых архитектурах Linux и более новых (а также ранних) версиях Linux. API и ABI 32 На системном уровне существует два отдельных множества определений и опи саний, которые влияют на такую переносимость. Одно из этих множеств называет ся интерфейсом программирования приложений (Application Programming Interface, API), а другое — двоичным интерфейсом приложения (Application Binary Interface, ABI). Обе эти концепции определяют и описывают интерфейсы между различными компонентами программного обеспечения. API API определяет интерфейсы, на которых происходит обмен информацией между двумя компонентами программного обеспечения на уровне исходного кода. API обеспечивает абстракцию, предоставляя стандартный набор интерфейсов — как правило, это функции, — которые один программный компонент (обычно, но не обязательно это более высокоуровневый компонент из пары) может вызывать из другого (обычно более низкоуровневого). Например, API может абстрагировать концепцию отрисовки текста на экране с помощью семейства функций, обеспечи вающих все необходимые аспекты для отрисовки текста. API просто определяет интерфейс; тот компонент программы, который обеспечивает работу API, обычно называется реализацией этого API. API часто называют «контрактом». Это неверно как минимум в юридическом смысле этого слова, поскольку API не имеет ничего общего с двусторонним согла шением. Пользователь API (обычно более высокоуровневая программа) распола гает нулевым входным сигналом для данного API и реализацией этой сущности. Пользователь может применять API «как есть» или не использовать его вообще: возьми или не трогай! Задача API — просто гарантировать, что, если оба компонен та ПО воспользуются этим API, они будут совместимы на уровне исходного кода. Это означает, что пользователь API сможет успешно скомпилироваться с зависи мостью от реализации этого API. Практическим примером API служат интерфейсы, определенные в соответствии со стандартом C и реализуемые стандартной библиотекой C. Этот API определяет семейство простейших и критически важных функций, таких как процедуры для управления памятью и манипуляций со строками. На протяжении всей книги мы будем опираться на разнообразные API, напри мер стандартную библиотеку вводавывода, которая будет подробно рассмотрена в гл. 3. Самые важные API, используемые при системном программировании в Linux, описаны в разд. «Стандарты» данной главы. ABI Если API определяет интерфейсы в исходном коде, то ABI предназначен для определения двоичного интерфейса между двумя и более программными ком понентами в конкретной архитектуре. ABI определяет, как приложение взаи модействует с самим собой, с ядром и библиотеками. В то время как API обес печивает совместимость на уровне исходного кода, ABI отвечает за совместимость Глава 1. Введение и основополагающие концепции 33 на двоичном уровне. Это означает, что фрагмент объектного кода будет функ ционировать в любой системе с таким же ABI без необходимости перекомпи ляции. ABI помогают решать проблемы, связанные с соглашениями на уровне вызовов, порядком следования байтов, использованием регистров, активацией системных вызовов, связыванием, поведением библиотек и форматом двоичных объектов. Например, соглашения на уровне вызовов определяют, как будут вызываться функции, как аргументы передаются функциям, какие регистры сохраняются, а ка кие — искажаются, как вызывающая сторона получает возвращаемое значение. Несколько раз предпринимались попытки определить единый ABI для мно гих операционных систем, взаимодействующих с конкретной архитектурой (в частности, для различных UNIXподобных систем, работающих на i386), но эти усилия не увенчались какимилибо заметными успехами. Напротив, в опе рационных системах, в том числе Linux, сохраняется тенденция к определению собственных ABI по усмотрению разработчиков. ABI тесно связаны с архитек турой; абсолютное большинство ABI оперирует машинноспецифичными кон цепциями, в частности Alpha или x8664. Таким образом, ABI является как элементом операционной системы (например, Linux), так и элементом архитек туры (допустим, x8664). Системные программисты должны ориентироваться в ABI, но запоминать их обычно не требуется. Структура ABI определяется цепочкой инструментов — компилятором, компоновщиком и т. д. — и никак иначе обычно не проявляется. Однако знание ABI положительно сказывается на качестве программирования, а также требуется при написании ассемблерного кода или разработке самой цепочки инструментов (последняя — классический пример системного програм мирования). Стандарты Системное программирование для UNIX — старинное искусство. Основы про граммирования для UNIX остаются незыблемыми в течение десятилетий. Од нако сами системы UNIX развиваются достаточно динамично. Поведение изме няется — добавляются новые возможности. Чтобы както справиться с хаосом, целые группы, занятые стандартизацией, кодифицируют системные интерфейсы в специальных официальных документах. Существует множество таких стан дартов, но фактически Linux официально не подчиняется какимлибо из них. Linux просто стремится соответствовать двум наиболее важным и превалиру ющим стандартам — POSIX и Single UNIX Specification (SUS, единая специфи кация UNIX). В POSIX и SUS, в частности, документирован API языка C для интерфейса, обеспечивающего взаимодействие с UNIXподобными операционными системами. Фактически эти стандарты определяют системное программирование или как минимум его общее подмножество для UNIXсовместимых систем. Стандарты 34 История POSIX и SUS В середине 1980х годов Институт инженеров по электротехнике и электронике (Institute of Electrical and Electronics Engineers, IEEE) возглавил начинания по стандартизации системных интерфейсов в UNIXподобных операционных систе мах. Ричард Столлман (Richard Stallman), основатель движения Free Software, предложил назвать этот стандарт POSIX (произносится «пазикс», Portable Operating System Interface — интерфейс переносимых операционных систем UNIX). Первым результатом этой работы, обнародованным в 1988 году, стал стандарт IEEE Std 1003.11988 (сокращенно POSIX 1988). В 1990 году IEEE пересмотрел стандарт POSIX, выпустив новую версию IEEE Std 1003.11990 (POSIX 1990). Необязательная поддержка работы в реальном времени и потоков была докумен тирована соответственно в стандартах IEEE Std 1003.1b1993 (POSIX 1993 или POSIX.1b) и IEEE Std 1003.1c1995 (POSIX 1995 или POSIX.1c). В 2001 году необязательные стандарты были объединены с базовым POSIX 1990, образовав единый стандарт IEEE Std 1003.12001 (POSIX 2001). Последняя на этот момент версия была выпущена в декабре 2008 года и называется IEEE Std 1003.12008 (POSIX 2008). Все основные стандарты POSIX сокращаются до аббревиатур вида POSIX.1, версия от 2008 года является новейшей. В конце 1980х — начале 1990х годов между производителями UNIXподобных систем бушевали настоящие «юниксовые войны»: каждый старался закрепить за своим продуктом статус единственной настоящей UNIXсистемы. Несколько крупных производителей сплотились вокруг The Open Group — промышленного консорциума, сформировавшегося в результате слияния Open Software Foundation (OSF) и X/Open. The Open Group стала заниматься сертификацией, публикацией научных статей и тестированием соответствия. В начале 1990х годов, когда «юник совые войны» были в самом разгаре, The Open Group выпустила Единую специ фикацию UNIX (SUS). Популярность SUS быстро росла, во многом благодаря тому, что она была бесплатной, а стандарт POSIX оставался дорогостоящим. В насто ящее время SUS включает в себя новейший стандарт POSIX. Первая версия SUS была опубликована в 1994 году. Затем последовали пере смотренные версии, выпущенные в 1997м (SUSv2) и 2002 году (SUSv3). Последний вариант SUS, SUSv4, был опубликован в 2008 году. В SUSv4 пересмотрен стандарт IEEE Std 1003.12008, объединяемый в рамках этой спецификации с несколькими другими стандартами. В данной книге я буду делать оговорки, когда системные вызовы и другие интерфейсы стандартизируются по POSIX. Стандартизацию по SUS я отдельно указывать не буду, так как SUS входит в состав POSIX. Стандарты языка C Знаменитая книга Денниса Ричи (Dennis Ritchie) и Брайана Кернигана (Brian Kernighan) The C Programming Language, впервые опубликованная в 1978 году, в течение многих лет использовалась как неофициальная спецификация языка C. Эта версия C была известна в кругах специалистов под названием K&R C. Язык C уже Глава 1. Введение и основополагающие концепции 35 стремительно заменял BASIC и другие языки того времени, превращаясь в лингва франка микрокомпьютерного программирования, поэтому в 1983 году Американ ский национальный институт стандартов (ANSI) сформировал специальный ко митет. Этот орган должен был разработать официальную версию C и стандартизи ровать самый популярный на тот момент язык программирования. Новая версия включала в себя разнообразные доработки и усовершенствования, сделанные раз личными производителями, а также новый язык C++. Это был долгий и трудоем кий процесс, но к 1989 году версия ANSI C была готова. В 1990 году Международ ная организация по стандартизации (ISO) ратифицировала стандарт ISO C90, основанный на ANSI C с небольшими модификациями. В 1995 году ISO выпустила обновленную (но редко используемую) версию языка C, которая называется ISO C95. В 1999 году последовала новая, значительно пересмотренная версия языка — ISO C99. В ней множество нововведений, в част ности внутристрочные функции, новые типы данных, массивы переменной длины, комментарии в стиле C++ и новые библиотечные функции. Последняя версия этого стандарта называется ISO C11, в которой следует отметить формализованную модель памяти. Она обеспечивает переносимость при использовании потоков в многоплатформенной среде. Что касается C++, ISOстандартизация этого языка протекала медленнее. В 1998 году после долгих лет разработки и выпуска компилятора, не обладавше го прямой совместимостью, был ратифицирован первый стандарт C, ISO C98. Он значительно улучшил совместимость между различными компиляторами, одна ко некоторые аспекты этого стандарта ограничивали согласованность и перено симость. Стандарт ISOC++03 появился в 2003 году. В нем были исправлены некоторые ошибки, а также добавлены изменения, облегчившие работу разработ чикам компиляторов, но незаметные на пользовательском уровне. Следующий стандарт ISO, самый актуальный в настоящее время, называется C++11 (ранее он обозначался как C++0x, поскольку не исключалась более ранняя дата выхода). В этой версии появилось множество дополнений как на уровне языка, так и в стан дартных библиотеках. На самом деле их оказалось настолько большое количест во, что многие даже считают язык C++11 совершенно самостоятельным, незави симым от более ранних версий C++. Linux и стандарты Как было сказано выше, Linux стремится соответствовать стандартам POSIX и SUS. В Linux предоставляются интерфейсы, документированные в SUSv4 и POSIX 2008, а также поддерживается работа в реальном времени (POSIX.1b) и работа с пото ками (POSIX.1c). Гораздо важнее, что Linux стремится работать в соответствии с требованиями POSIX и SUS. В принципе, любое несоответствие стандартам является ошибкой. Считается, что Linux также соответствует POSIX.1 и SUSv3, но, поскольку никакой официальной сертификации POSIX или SUS не проводилось (в частности, во всех существующих версиях Linux), нельзя сказать, что Linux официально соответствует POSIX или SUS. Стандарты 36 Что касается языковых стандартов, в Linux все хорошо. Компилятор gcc язы ка C соответствует стандарту ISO C99; планируется обеспечить поддержку C11. Компилятор g++ языка C++ соответствует стандарту ISO C++03, поддержка стан дарта C++11 находится в разработке. Кроме того, компиляторы gcc и g++_ реали зуют расширения для языков C и C++. Все эти расширения объединяются под общим названием GNU C и документированы в приложении А. Linux не может похвастаться большими достижениями в области обеспечения прямой совместимости 1 , хотя сегодня и в этой области ситуация значительно улуч шилась. Интерфейсы, документированные в соответствии со стандартами, в част ности стандартная библиотека C, очевидно, навсегда останутся совместимыми на уровне исходников. Двоичная совместимость поддерживается как минимум на уровне основной, крупной версии glibc , а поскольку язык C стандартизирован, gcc всегда будет компилировать код, написанный на правильном языке C. Правда, различные специфичные расширения gcc могут устаревать и в конце концов исче зать из новых релизов gcc . Важнее всего, что ядро Linux гарантирует стабильность системных вызовов. Если системный вызов реализован в стабильной версии ядра Linux, можно быть уверенными, что такой вызов точно сработает. В различных дистрибутивах Linux многие компоненты операционной системы определяются в LSB (Linux Standard Base). LSB — это совместный проект несколь ких производителей Linux, проводящийся под эгидой Linux Foundation (ранее эта организация называлась Free Standards Group). LSB дополняет POSIX и SUS, а также добавляет собственные стандарты. Организация стремится предоставить двоичный стандарт, позволяющий в неизменном виде выполнять объектный код в системах, отвечающих этому стандарту. Большинство производителей Linux в той или иной степени придерживаются LSB. Стандарты и эта книга В данной книге я намеренно стараюсь не разглагольствовать о какомлибо стан дарте. Слишком часто авторы книг по системному программированию для UNIX излишне увлекаются сравнениями, как интерфейс работает по одним стандартам и как по другим, как конкретный системный вызов реализован в той или иной системе, — словом, льют воду. Эта книга посвящена именно системному програм мированию в современных вариантах Linux, в которых используются новейшие версии ядра Linux (3.9), компилятора gcc (4.8) и библиотеки C (2.17). Системные интерфейсы можно считать практически неизменными — разработ чики ядра Linux проделали огромную работу, чтобы, например, никогда не пришлось ломать интерфейсы системных вызовов. Эти интерфейсы обеспечивают известный уровень совместимости на уровне исходного кода и двоичном уровне, поэтому вы бранный в данной книге подход позволяет подробно рассмотреть детали системных 1 Возможно, опытные пользователи Linux помнят переход с a.out на ELF, переход с libc5 на glibc, изменения gcc, фрагментацию шаблонов ABI C++ и т. д. К счастью, эти времена давно позади. Глава 1. Введение и основополагающие концепции |