Главная » Ядро Linux » Выравнивание данных

0

Выравнивание  (alignment) соответствует размещению порции данных в  памяти. Говорят, что переменная имеет естественное выравнивание (naturally aligned), если она находится в памяти по  адресу, значение которого кратно размеру этой переменной. Например,  переменная 32-разрядного типа данных имеет естественное выравнивание, если она  находится в памяти по  адресу, кратному 4 байт (т.е.  два  младших бита адреса равны нулю). Таким образом, структура данные размером 2n    байт должна храниться в памяти по  адресу, младшие n битов которого равны нулю.

Для   некоторых аппаратных платформ  существуют строгие требования  относительно  выравнивания данных. На  некоторых системах, обычно RISC,  загрузка неправильно  выровненных данных приводит к  генерации системного прерывания (trap),  ошибки,  которую можно обработать. На  других системах  с  неестественно выравниваемыми данными можно работать, но  это  приводит к уменьшению произ-

водительности. При  написании переносимого кода  необходимо предотвращать проблемы, связанные с  выравниванием,  а данные всех  типов  должны иметь  естественное  выравнивание.

Как избежать проблем с выравниванием

Компилятор обычно предотвращает проблемы, связанные с выравниванием, путем  естественного выравнивания  всех типов  данных.  На  самом  деле, разработчики ядра  обычно не должны заниматься проблемами, связанными с выравниванием, об этом  должны заботиться разработчики компилятора gcc.  Однако такие  проблемы все же  могут  возникать,  когда  разработчику приходится выполнять операции с указателями  и осуществлять доступ  к данным, не учитывая того, как  компилятор выполняет операции доступа  к данным.

Доступ  к адресу  памяти, для  которого выполнено выравнивание,  через  преобразованный  указатель на  тип  данных   большего размера может  привести к  проблемам выравнивания (для  разных  аппаратных платформ  это  может  проявляться по-разному).  Следующий код  может  привести к указанной проблеме.

char dog [10];

char *p = &dog[1];

unsigned long 1 = * (unsigned long *)p;

В этом  примере указатель на данные типа  unsigne d   cha r используется, как указатель  на  тип  unsigne d   long , что  может  привести к тому, что  32-разрядное значение типа  unsigne d   lon g будет  считываться из  памяти по  адресу, не  кратному четырем.

Если вы думаете: "Зачем мне это может  быть нужно?’,  то вы, скорее всего, правы. Тем  не  менее, если  мы  такое  сделали только  что, то  такое  можно сделать  и  кто-нибудь  еще, поэтому необходимо быть  внимательными. Примеры,  которые встречаются  в реальной жизни,  не  обязательно будут так  же  очевидны.

Выравнивание нестандартных типов данных

Как  уже указывалось, при  выравнивании адрес  стандартного типа  данных  должен быть  кратным размеру  этого  типа.  Нестандартные (сложные) типы  данных  подчиняются  следующим правилам выравнивания.

• Выравнивание массива выполняется так  же, как  и выравнивание типа данных  первого элемента  (все  остальные элементы будут корректно  выровнены  автоматически).

• Выравнивание объединения  (union)  соответствует выравниванию  самого  большого, по  размеру, типа  данных  из  тех, которые включены в объединение.

• Выравнивание  структуры соответствует выравниванию  самого   большого,  по размеру, типа  данных  среди  типов  всех  полей   структуры.

В структурах  также  могут  использоваться различные способы заполнения  (padding).

Заполнение структур

Структуры заполняются  таким  образом,  чтобы каждый  ее  элемент  имел естественное выравнивание.  Например,  рассмотрим следующую структуру данных на  32разрядной машине.

struct animal_struct {

char dog;              /* 1 байт */ unsigned longcat;      /* 4 байт */ unsigned shortpig;     /* 2 байт */

charfox;               /* 1 байт */

};

Эта  структура данных в  памяти выглядит не  так,  что  связано с  необходимостью естественного выравнивания.  В памяти компилятор создает структуру данных,  которая   похожа на   следующую.

struct animal_struct {

char dog;              /* 1 байт */ u8 __pad0[3];          /* 3 байт */ unsigned long cat;     /* 4 байт */ unsigned short pig;    /* 2 байт */

char        fox;        /* 1 байт */

u8          padl;       /* 1 байт */

};

Переменные заполнения вводятся для того, чтобы обеспечить естественное выравнивание всех элементов структуры. Первая переменная заполнения вводит дополнительные затраты памяти для того, чтобы разместить поле ca t на границе

4-байтового адреса. Вторая переменная используется для выравнивания  размера самой структуры. Дополнительный байт гарантирует, что размер структуры будет кратен четырем байтам и что каждый элемент массива таких структур будет иметь естественное выравнивание.

Следует обратить внимание,  что выражение sizeo f (foo_struct )  равно значению 12 для любого экземпляра этой структуры на большинстве 32-разрядных аппаратных платформ. Компилятор языка С автоматически добавляет элементы заполнения, чтобы гарантировать необходимое выравнивание.

Часто имеется возможность переставить поля структуры так, чтобы избежать необходимости заполнения. Это позволяет получить правильно выровненные данные без введения дополнительных элементов заполнения и, соответственно, структуру меньшего размера.

struc t  animal  struc t  {

unsigned  long  cat ;          /*   4  байта */ unsigned  short  pig;         /*   2  байта  */ char  dog;              /*   1  байт  */

char  fox;               /*   1   байт  */

} ;

Эта  структура данных имеет  размер 8  байт.  Однако не  всегда существует возможность перестановки  элементов  структуры местами и  изменения  определения струк-

туры.  Например, если  структура  поставляется как  часть  стандарта, или уже используется  в существующем коде, то порядок следования полей  менять  нельзя. Иногда, по некоторым причинам, может  потребоваться специальный порядок следования полей  структуры, например специальное выравнивание переменных для  оптимизации попадания в кэш.  Заметим, что, согласно стандарту ANSI С, компилятор никогда не должен  менять  порядок следования полей  в структурах5   данных — этим  правом обладает  только  программист.

Разработчики ядра  должны  учитывать  особенности заполнения при  обмене структурами данных:  передача  структур  по  сети  или  непосредственное сохранение на диск, потому  что необходимое заполнение может  быть разным для различных аппаратных платформ. Это одна из причин, по которой в языке  программирования С нет оператора сравнения структур.  Память, которая используется для заполнения структур  данных, может  содержать случайную информацию,  что делает  невозможным  побайтовое сравнение структур.  Разработчики языка, программирования С правильно  сделали, что  оставили решение задачи  сравнения структур  на усмотрение программиста, который может создавать  свои функции сравнения в каждом  конкретном случае, чтобы  использовать особенности построения конкретных структур.

Источник: Лав,  Роберт. Разработка ядра  Linux, 2-е  издание. : Пер.  с англ.  — М.  : ООО  «И.Д.  Вильяме» 2006. — 448 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии