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

  • Таблица 1 9 . 1 .

  • Скрытые типы данных

  • Специальные типы данных

  • Таблица 19.2.

  • Порядок следования байтов

  • Второе издание


    Скачать 3.09 Mb.
    НазваниеВторое издание
    Дата08.09.2019
    Размер3.09 Mb.
    Формат файлаpdf
    Имя файлаLav_Robert_Razrabotka_yadra_Linux_Litmir.net_264560_original_254.pdf
    ТипДокументы
    #86226
    страница48 из 53
    1   ...   45   46   47   48   49   50   51   52   53
    Слова, двойные слова и путаница
    Для некоторых операционных систем и процессоров стандартную порцию данных не называют машинным словом. Вместо этого, словом называется некоторая фиксированная порция данных,
    название которой выбрано случайным образом или имеет исторические корни. Например, в не- которых системах данные могут разбиваться на байты (byte — 8 бит), слова (word — 16 бит),
    двойные слова (double word— 32 бит) и четверные слова (quad word— 64 бит), несмотря на то что на самом деле система является 32-разрядной. В этой книге и вообще в контексте опе- рационной системы Linux под машинным словом понимают стандартную порцию данных про- цессора, как обсуждалось ранее.
    Для каждой аппаратной платформы, поддерживаемой операционной системой
    Linux, в файле определяется константа BITTS_PER_LONG, которая равна размеру типа long языка С и совпадает с размером машинного слова системы.
    Полный список всех поддерживаемых аппаратных платформ и их размеры машин- ного слова приведены в табл. 19.1.
    Стандарт языка С явно указывает, что размер памяти, которую занимают пере- менные стандартных типов данных, зависит от аппаратной реализации
    3
    , при этом также определяется минимально возможный размер типа. Неопределенность разме- ров стандартных типов языка С для различных аппаратных платформ имеет свои положительные и отрицательные стороны. К плюсам можно отнести то, что для стандартных типов языка С можно пользоваться преимуществами, связанными с раз- мером машинного слова, а также отсутствие необходимости явного указания разме- ра. Для ОС Linux размер типа long гарантированно равен размеру машинного слова.
    Это не совсем соответствует стандарту ANSI С, однако является стандартной прак- тикой в ОС Linux. Как недостаток можно отметить, что при разработке кода нельзя рассчитывать на то, что данные определенного типа занимают в памяти определен- ный размер. Более того, нельзя гарантировать, что переменные типа int занимают столько же памяти, сколько и переменные типа long
    4 2
    Размер адресуемой памяти может быть меньше максимального значения машинного слова.
    Например, для 64-разрядных аппаратных платформ размер указателя ранен 64 бит, однако только
    48 бит можно использовать для адресации. В дополнение к этому, общее количество физической памяти может быть больше максимального значения машинного слова, как, например, это имеет место при наличии расширения Intel PAE.
    3
    За исключением размера типа c h a r , который всегда равен 8 бит.
    4
    На самом деле, для 64-разрядных аппаратных платформ, которые поддерживаются ОС Linux, раз- меры типов i n t и l o n g не совпадают. Размер типа int равен 32 бит, а размер типа l o n g — 64 бит.
    Для хорошо знакомых 32-раphядных аппаратных платформ оба типа данных имеют размер 32 бит.
    3 9 2 Глава 19

    Таблица 1 9 . 1 . Поддерживаемые аппаратные платформы
    Аппаратная платформа Описание Размер машинного слова alpha arm cris h8300
    I386
    ia64
    m68k m86knommu mips mips64
    parisc ppc ppc64
    s390
    sh spare sparc64
    um v850
    x8_ 64
    Digital Alpha
    ARM и StrongARM
    CRIS
    H8/300
    Intel x86
    IA-64
    Motorola 68k m68k без устройства MMU
    MIPS
    64-разрядная MIPS
    HP PA-RISC
    PowerPC
    POWER
    IBM S/390
    Hitachi SH
    SPARC
    UltraSPARC
    Usermode Linux v850
    X86-64 64 бит
    32 бит
    32 бит
    32 бит
    32 бит
    64 бит
    32 бит
    32 бит
    32 бит
    64 бит
    32 бит, или 64 бит
    32 бит
    64 бит
    32 бит, или 64 бит
    32 бит
    32 бит
    64 бит
    32 бит, или 64 бит
    32 бит
    64 бит
    Ситуация еще более запутывается тем, что одни и те же типы данных в простран- стве пользователя и в пространстве ядра не обязательно должны соответствовать друг другу. Аппаратная платформа sparc64 предоставляет 32-разрядное пространство пользователя, а поэтому указатели, типы i n t и long имеют размер 32 бит. Однако в пространстве ядра для аппаратной платформы размер типа i n t равен 32 бит, а раз- мер указателей и типа long равен 64 бит. Тем не менее такая ситуация не является обычной.
    Всегда необходимо помнить о следующем.
    • Как и требует стандарт языка С, размер типа char всегда равен 8 бит (1 байт),
    • Нет никакой гарантии, что размер типа i n t для всех поддерживаемых аппа- ратных платформ будет равен 32 бит, хотя сейчас для всех платформ он равен именно этому числу.
    • То же касается и типа short, который для всех поддерживаемых аппаратных платформ сейчас равен 16 бит.
    • Никогда нельзя надеяться, что тип long или указатель имеет некоторый задан- ный размер. Этот размер для поддерживаемых аппаратных платформ может быть равен 32, или 64 бит.
    Переносимость
    393

    • Так как размер типа long разный для различных аппаратных платформ, никог- да нельзя предполагать, что s i z e o f (int.) == s i z e o f (long).
    • Точно так же нельзя предполагать, что размер типа int и размер указателя со- впадают.
    Скрытые типы данных
    Скрытые (opaque) типы данных — это те типы, для которых не раскрывается их внутренняя структура, или формат. Они похожи на черный ящик, насколько это можно реализовать в языке программирования С. В этом языке программирования нет какой-либо особенной поддержки для этих типов. Вместо этого, разработчики определяют новый тип данных через оператор typedef, называют его скрытым и надеются на то, что никто не будет преобразовывать этот тип в стандартный тип данных языка С. Любые использования данных этих типов возможны только через специальные интерфейсы, которые также создаются разработчиком. Примером мо- жет быть тип данных p i d _ t , в котором хранится информация об идентификаторе процесса. Размер этого типа данных не раскрывается, хотя каждый может смошен- ничать, использовать размер по максимуму и работать с этим типом, как с типом int. Если нигде явно не используется размер скрытого типа данных, то размер этого типа может быть изменен, и это не вызовет никаких проблем. На самом деле так уже однажды случилось: в старых Unix-подобных операционных системах тип p i d _ t был определен как a h o r t .
    Еще один пример скрытого типа данных — это тип a t o m i c _ t . Как уже обсужда- лось в главе 9, "Средства синхронизации в ядре", этот тип содержит данные цело- численного типа, с которыми можно выполнять атомарные операции. Хотя этот тип и соответствует типу int, использование скрытого типа данных позволяет гарантиро- вать, что данные этого типа будут использоваться только в специальных функциях,
    которые выполняют атомарные операции. Скрытые типы позволяют скрыть размер типа данных, который не всегда равен полным 32 разрядам, как в случае платформы
    SPARC.
    Другие примеры скрытых типов данных в ядре — это dev_t, g i d _t и u i d _ t . При работе со скрытыми типами данных необходимо помнить о следующем.
    • Нельзя предполагать, что данные скрытого типа имеют некоторый определен- ный размер в памяти.
    • Нельзя преобразовывать скрытый тип обратно в стандартный тип данных.
    Разрабатывать код необходимо с учетом того, что размер и внутреннее представ- ление скрытого типа данных могут изменяться.
    Специальные типы данных
    Некоторые данные в ядре, кроме того, что представляются с помощью скрытых типов, требуют еще и специальных типов данных. Два примера — счетчик импульсов системного таймера j i f f i e s и параметр f l a g s , используемый для обработки пре- рываний. Для хранения этих данных всегда должен использоваться тип u n s i g n e d long.
    394 Глава 19

    При хранении и использовании специфических данных всегда необходимо об- ращать особенное внимание на тот тип данных, который представляет эти данные,
    и использовать именно его. Часто встречающейся ошибкой является использование другого типа, например типа unsigned i n t . Хотя для 32-разрядных аппаратных платформ это не приведет к проблемам, на 64-разрядных системах возникнут проб- лемы.
    Типы с явным указанием размера
    Часто при программировании необходимы типы данных заданного размера.
    Обычно это необходимо для удовлетворения некоторых внешних требований,
    связанных с аппаратным обеспечением, сетью или бинарной совместимостью.
    Например, звуковой адаптер может иметь 32-разрядный регистр, пакет сетевого протокола — 16-разрядное поле данных, а исполняемый файл - 8 битовый иденти- фикатор cookie. В этих случаях тип, который представляет данные, должен иметь
    точно заданный размер.
    В ядре типы данных явно заданного размера определены в файле ,
    который включается из файла . В табл. 19.2 приведен полный спи- сок таких типов данных.
    Таблица 19.2. Типы данных явно заданного размера
    Тип Описание s8
    u8
    s16
    ul6
    s32
    u32
    s64
    u64
    байт со знаком байт без знака
    16-разрядное целое число со знаком
    16-разрядное целое число без знака
    32-разрядное целое число со знаком
    32-разрядное целое число без знака
    64-разрядное целое число со знаком
    64-разрядное целое число без знака
    Варианты сo знаком используются редко.
    Эти типы данных, с явно заданным размером, просто определены с помощью оператора typedef через стандартные типы данных языка С. Для 64-разрядной ма- шины они могут быть определены следующим образом.
    typedef signed char s8;
    typedef unsigned char u8;
    typedef signed short s16;
    typedef unsigned short ul6;
    typedef signed int s32;
    typedef unsigned int u32;
    typedef signed long s64;
    typedef unsigned long u64;
    Переносимость 395

    Для 32-разрядной машины их можно определить, как показано ниже.
    typedef signed char s8;
    typedef unsigned char u8;
    typedef signed short s16;
    typedef unsigned short ul6;
    typedef signed int s32;
    typedef unsigned int u32;
    typedef signed long long s64;
    typedef unsigned long long u64;
    Знак типа данных char
    В стандарте языка С сказано, что тип данных char может быть со знаком или без знака. Ответственность за определение того, какой вариант типа данных char ис- пользовать по умолчанию, лежит на компиляторе, препроцессоре или на обоих.
    Для большинства аппаратных платформ тип char является знаковым, а диапазон значений данных этого типа от -128 до 127. Для небольшого количества аппаратных платформ, таких как ARM, тип char по умолчанию без знака, а возможные значения данных этого типа лежат в диапазоне от 0 до 255.
    Например, для систем, на которых тип char без знака, выполнение следующего кода приведет к записи в переменную i числа 255 вместо -1.
    char i = -1;
    На других машинах, где тип char является знаковым, этот код выполнится пра- вильно и в переменную i запишется значение - 1 . Если действительно нужно, чтобы в любом случае было записано значение - 1 , то предыдущий код должен выглядеть следующим образом.
    signed char i = -1;
    Если в вашем коде используется тип данных char, то следует помнить, что этот тип может на самом деле быть как signed char, так и unsigned char. Если необ- ходим строго определенный вариант, то это нужно явно декларировать.
    Выравнивание данных
    Выравнивание (alignment) соответствует размещению порции данных в памяти.
    Говорят, что переменная имеет естественное выравнивание (naturally aligned), если она находится в памяти по адресу, значение которого кратно размеру этой переменной.
    Например, переменная 32-разрядного типа данных имеет естественное выравнива- ние, если она находится в памяти по адресу, кратному 4 байт (т.е. два младших бита адреса равны нулю). Таким образом, структура данные размером 2
    n
    байт должна хра- ниться в памяти по адресу, младшие n битов которого равны нулю.
    Для некоторых аппаратных платформ существуют строгие требования относи- тельно выравнивания данных. На некоторых системах, обычно RISC, загрузка не- правильно выровненных данных приводит к генерации системного прерывания
    (trap), ошибки, которую можно обработать. На других системах с неестественно выравниваемыми данными можно работать, но это приводит к уменьшению произ-
    396 Глава 19
    водительности. При написании переносимого кода необходимо предотвращать про- блемы, связанные с выравниванием, а данные всех типов должны иметь естествен- ное выравнивание.
    Как избежать проблем с выравниванием
    Компилятор обычно предотвращает проблемы, связанные с выравниванием, пу- тем естественного выравнивания всех типов данных. На самом деле, разработчики ядра обычно не должны заниматься проблемами, связанными с выравниванием, об этом должны заботиться разработчики компилятора gcc. Однако такие проблемы все же могут возникать, когда разработчику приходится выполнять операции с указате- лями и осуществлять доступ к данным, не учитывая того, как компилятор выполняет операции доступа к данным.
    Доступ к адресу памяти, для которого выполнено выравнивание, через преобра- зованный указатель на тип данных большего размера может привести к проблемам выравнивания (для разных аппаратных платформ это может проявляться по-разно- му). Следующий код может привести к указанной проблеме.
    char dog [10];
    char *p = &dog[1];
    unsigned long 1 = * (unsigned long *)p;
    В этом примере указатель на данные типа unsigned char используется, как указа- тель на тип u n s i g n e d long, что может привести к тому, что 32-разрядное значение типа unsigned long будет считываться из памяти по адресу, не кратному четырем.
    Если вы думаете: "Зачем мне это может быть нужно?', то вы, скорее всего, правы.
    Тем не менее, если мы такое сделали только что, то такое можно сделать и кто-ни- будь еще, поэтому необходимо быть внимательными. Примеры, которые встречают- ся в реальной жизни, не обязательно будут так же очевидны.
    Выравнивание нестандартных типов данных
    Как уже указывалось, при выравнивании адрес стандартного типа данных должен быть кратным размеру этого типа. Нестандартные (сложные) типы данных подчиня- ются следующим правилам выравнивания.
    • Выравнивание массива выполняется так же, как и выравнивание типа данных первого элемента (все остальные элементы будут корректно выровнены авто- матически).
    • Выравнивание объединения (union) соответствует выравниванию самого боль- шого, по размеру, типа данных из тех, которые включены в объединение.
    • Выравнивание структуры соответствует выравниванию самого большого, по размеру, типа данных среди типов всех полей структуры.
    В структурах также могут использоваться различные способы заполнения (padding).
    Переносимость 397

    Заполнение структур
    Структуры заполняются таким образом, чтобы каждый ее элемент имел есте- ственное выравнивание. Например, рассмотрим следующую структуру данных на 32- разрядной машине.
    Эта структура данных в памяти выглядит не так, что связано с необходимостью естественного выравнивания. В памяти компилятор создает структуру данных, кото- рая похожа на следующую.
    struct animal_struct {
    char dog; /* 1 байт */
    u8 __pad0[3]; /* 3 байт */
    unsigned long cat; /* 4 байт */
    unsigned short pig; /* 2 байт */
    char fox; /* 1 байт */
    u8 __padl; /* 1 байт */
    };
    Переменные заполнения вводятся для того, чтобы обеспечить естественное вы- равнивание всех элементов структуры. Первая переменная заполнения вводит до- полнительные затраты памяти для того, чтобы разместить поле c a t на границе
    4-байтового адреса. Вторая переменная используется для выравнивания размера самой структуры. Дополнительный байт гарантирует, что размер структуры будет кратен четырем байтам и что каждый элемент массива таких структур будет иметь естественное выравнивание.
    Следует обратить внимание, что выражение sizeof (foo_struct) равно значе- нию 12 для любого экземпляра этой структуры на большинстве 32-разрядных аппарат- ных платформ. Компилятор языка С автоматически добавляет элементы заполнения,
    чтобы гарантировать необходимое выравнивание.
    Часто имеется возможность переставить поля структуры так, чтобы избежать не- обходимости заполнения. Это позволяет получить правильно выровненные данные без введения дополнительных элементов заполнения и, соответственно, структуру меньшего размера.
    s t r u c t animal s t r u c t {
    unsigned long c a t ; /* 4 байта */
    unsigned short pig; /* 2 байта */
    char dog; /* 1 байт */
    char fox; /* 1 байт */
    } ;
    Эта структура данных имеет размер 8 байт. Однако не всегда существует возмож- ность перестановки элементов структуры местами и изменения определения струк-
    398 Глава 19
    struct animal_struct {
    char dog; /* 1 байт */
    unsigned long cat; /* 4 байт */
    unsigned short pig; /* 2 байт */
    char fox; /* 1 байт */
    };
    туры. Например, если структура поставляется как часть стандарта, или уже исполь- зуется в существующем коде, то порядок следования полей менять нельзя. Иногда,
    по некоторым причинам, может потребоваться специальный порядок следования полей структуры, например специальное выравнивание переменных для оптимиза- ции попадания в кэш. Заметим, что, согласно стандарту ANSI С, компилятор никогда не должен менять порядок следования полей в структурах
    5
    данных — этим правом обладает только программист.
    Разработчики ядра должны учитывать особенности заполнения при обмене структурами данных: передача структур по сети или непосредственное сохранение на диск, потому что необходимое заполнение может быть разным для различных ап- паратных платформ. Это одна из причин, по которой в языке программирования
    С нет оператора сравнения структур. Память, которая используется для заполнения структур данных, может содержать случайную информацию, что делает невозмож- ным побайтовое сравнение структур. Разработчики языка, программирования С пра- вильно сделали, что оставили решение задачи сравнения структур на усмотрение программиста, который может создавать свои функции сравнения в каждом конкрет- ном случае, чтобы использовать особенности построения конкретных структур.
    Порядок следования байтов
    Порядок следования байтов (byte ordering) — это порядок, согласно которому байты расположены в машинном слове. Для разных процессоров может использоваться один из двух типов нумерации байтов в машинном слове: наименее значимый (са- мый младший) байт является либо самым первым (самым левым, left-most), либо самым последним (самым правым, right-most) в слове. Порядок байтов называется
    обратным (big-endian), если наиболее значимый (самый старший) байт хранится пер- вым, а за ним идут байты в порядке убывания значимости. Порядок байтов называ- ется прямым (little-endian), если наименее значимый (самый младший) байт хранится первым, а за ним следуют байты в порядке возрастания значимости.
    Даже не пытайтесь основываться на каких-либо предположениях о порядке сле- дования байтов при написании кода ядра (конечно, если код не предназначен для какой-либо конкретной аппаратной платформы). Операционная система Linux под- держивает аппаратные платформы с обоими порядками байтов, включая и те маши- ны, на которых используемый порядок байтов можно сконфигурировать на этапе за- грузки системы, а общий код должен быть совместим с любым порядком байтов.
    На рис. 19.1 показан пример обратного порядка следования байтов, а на рис. 19.2 — прямого порядка следования байтов.
    Аппаратная платформа i386 использует прямой (little-endian) порядок байтов.
    Большинство других аппаратных платформ обычно использует обратный (big-endian)
    порядок.
    5
    Если бы компилятор имел возможность изменять порядок следования нолей структуры данных, то любой существующий код, который уже использует эту структуру, мог бы испортить данные. В язы- ке программирования С функции вычисляют положение полей просто путем введения смещений от начального адреса структуры в памяти.
    Переносимость 399

    Старший байт
    Младший байт
    1   ...   45   46   47   48   49   50   51   52   53


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