Главная страница
Навигация по странице:

  • Стандарты языка C

  • Linux и стандарты

  • Стандарты и эта книга

  • Системное программирование Линукс. Linux. Системное программирование. Вступление


    Скачать 0.65 Mb.
    НазваниеLinux. Системное программирование. Вступление
    АнкорСистемное программирование Линукс
    Дата23.01.2023
    Размер0.65 Mb.
    Формат файлаpdf
    Имя файлаLinuxSystemProgramming.pdf
    ТипКраткое содержание
    #900372
    страница4 из 14
    1   2   3   4   5   6   7   8   9   ...   14
    Библиотека 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 или x86­64. Таким образом, ABI является как элементом операционной системы (например, Linux), так и элементом архитек­
    туры (допустим, x86­64).
    Системные программисты должны ориентироваться в 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.1­1988 (сокращенно POSIX 1988). В 1990 году IEEE пересмотрел стандарт POSIX, выпустив новую версию IEEE Std 1003.1­1990 (POSIX 1990).
    Необязательная поддержка работы в реальном времени и потоков была докумен­
    тирована соответственно в стандартах IEEE Std 1003.1b­1993 (POSIX 1993 или
    POSIX.1b) и IEEE Std 1003.1c­1995 (POSIX 1995 или POSIX.1c). В 2001 году необязательные стандарты были объединены с базовым POSIX 1990, образовав единый стандарт IEEE Std 1003.1­2001 (POSIX 2001). Последняя на этот момент версия была выпущена в декабре 2008 года и называется IEEE Std 1003.1­2008
    (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.1­2008, объединяемый в рамках этой спецификации с несколькими другими стандартами. В данной книге я буду делать оговорки, когда системные вызовы и другие интерфейсы стандартизируются по 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. Введение и основополагающие концепции

    1   2   3   4   5   6   7   8   9   ...   14


    написать администратору сайта