Другие примеры скрытых типов данных в ядре — это dev_t
, gid_t
и uid_t
. При работе со скрытыми типами данных необходимо помнить о следующем.
• Нельзя предполагать, что данные скрытого типа имеют некоторый определенный размер в памяти.
• Нельзя преобразовывать скрытый тип обратно в стандартный тип данных.
Разрабатывать код необходимо с учетом того, что размер и внутреннее представление скрытого типа данных могут изменяться.
Специальные типы данных
Некоторые данные в ядре, кроме того, что представляются с помощью скрытых типов, требуют еще и специальных типов данных. Два примера — счетчик импульсов системного таймера jiffies
и параметр flags
, используемый для обработки прерываний. Для хранения этих данных всегда должен использоваться тип unsigned long
.
При хранении и использовании специфических данных всегда необходимо обращать особенное внимание на тот тип данных, который представляет эти данные, и использовать именно его. Часто встречающейся ошибкой является использование другого типа, например типа unsigned int
. Хотя для 32-разрядных аппаратных платформ это не приведет к проблемам, на 64-разрядных системах возникнут проблемы.
Типы с явным указанием размера
Часто при программировании необходимы типы данных заданного размера. Обычно это необходимо для удовлетворения некоторых внешних требований, связанных с аппаратным обеспечением, сетью или бинарной совместимостью. Например, звуковой адаптер может иметь 32-разрядный регистр, пакет сетевого протокола — 16-разрядное поле данных, а исполняемый файл — 8 битовый идентификатор cookie. В этих случаях тип, который представляет данные, должен иметь точно заданный размер.
В ядре типы данных явно заданного размера определены в файле <asm/types.h>
, который включается из файла <linux/types.h>
. В табл. 19.2 приведен полный список таких типов данных.
Таблица 19.2. Типы данных явно заданного размера
Тип | Описание |
---|---|
s8 |
байт со знаком |
u8 |
байт без знака |
s16 |
16-разрядное целое число со знаком |
u16 |
16-разрядное целое число без знака |
s32 |
32-разрядное целое число со знаком |
u32 |
32-разрядное целое число без знака |
s64 |
64-разрядное целое число со знаком |
u64 |
64-разрядное целое число без знака |
Варианты со знаком используются редко.
Эти типы данных, с явно заданным размером, просто определены с помощью оператора typedef
через стандартные типы данных языка С. Для 64-разрядной машины они могут быть определены следующим образом.
typedef signed char s8;
typedef unsigned char u8;
typedef signed short s16;
typedef unsigned short u16;
typedef signed int s32;
typedef unsigned int u32;
typedef signed long s64;
typedef unsigned long u64;
Для 32-разрядной машины их можно определить, как показано ниже.
typedef signed char s8;
typedef unsigned char u8;
typedef signed short s16;
typedef unsigned short u16;
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 байт (т.е. два младших бита адреса равны нулю). Таким образом, структура данные размером 2n байт должна храниться в памяти по адресу, младшие n битов которого равны нулю.
Для некоторых аппаратных платформ существуют строгие требования относительно выравнивания данных. На некоторых системах, обычно RISC, загрузка неправильно выровненных данных приводит к генерации системного прерывания (trap), ошибки, которую можно обработать. На других системах с неестественно выравниваемыми данными можно работать, но это приводит к уменьшению производительности. При написании переносимого кода необходимо предотвращать проблемы, связанные с выравниванием, а данные всех типов должны иметь естественное выравнивание.
Как избежать проблем с выравниванием
Компилятор обычно предотвращает проблемы, связанные с выравниванием, путем естественного выравнивания всех типов данных. На самом деле, разработчики ядра обычно не должны заниматься проблемами, связанными с выравниванием, об этом должны заботиться разработчики компилятора gcc. Однако такие проблемы все же могут возникать, когда разработчику приходится выполнять операции с указателями и осуществлять доступ к данным, не учитывая того, как компилятор выполняет операции доступа к данным.