WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 |   ...   | 7 | 8 || 10 |

«; ...»

-- [ Страница 9 ] --

С противоположной стороны можно поставить операционные системы, в кото рых оптимизация кода выполняется в ущерб переносимости. Код, по возможности, пишется на языке ассемблера или специфические свойства аппаратных платформ ис пользуются каким-либо другим образом. Свойства ядра разрабатываются на основа нии свойств аппаратной платформы. В этом случае перенос операционной системы на другую аппаратную платформу сводится к написанию ядра с нуля. Оптимальность выполняется в ущерб переносимости. Примером таких систем могут быть DOS и Windows 9x. Сегодня таким системам нет необходимости иметь более оптимальный код, чем переносимым операционным системам, однако они предоставляют возмож ность в максимальной степени использовать ручную оптимизацию кода.

Операционная система Linux в плане переносимости занимает промежуточное положение. Насколько это целесообразно из практических соображений, интерфей сы и код сохраняются независимыми от аппаратной платформы и пишутся на язы ке С. Однако функции ядра, которые критичны к производительности, выполняются зависимыми от аппаратной платформы. Например, низкоуровневый код и код, кото рый должен выполняться очень быстро, разрабатывается зависимым от аппаратной платформы и обычно на языке ассемблера. Такой подход позволяет сохранить пере носимость ОС Linux и при этом воспользоваться оптимизациями.

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

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

Хороший пример — планртровщик. Большая часть планировщика-написана незави симым от аппаратной платформы образом на языке С. Реализация находится в фай ле kernel /sched. с. Некоторые из функций планировщика, такие как переключе ние состояния процессора или переключение адресного пространства, очень сильно зависят от аппаратной платформы. Следовательно, функция context_switch (), ко торая переключает выполнение от одного процесса к другому и написана на языке С, вызывает функции switch_to () и Kwitch_ram () для переключения состояния процессора и переключения адресного пространства соответственно.

Код функций switch_to () и switch_mm () выполнен отдельно для каждой ап паратной платформы, которую поддерживает операционная система Linux. Когда операционная система Linux портируется на новую аппаратную платформу, то для новой аппаратной платформы просто необходимо реализовать эти функции.

Файлы, которые относятся к определенной аппаратной платформе, находятся в каталоге arch/<аппаратная платформа>/ и include/asm-/, где <апппаратная платформа> — это короткое имя, которое представляет аппарат ную платформу, поддерживаемую ядром Linux. Например, аппаратной платформе Intel х8б присвоено имя i386. Для этого типа машин файлы находятся в каталогах arch/i386 и include/asm-i386. Ядра серии 2.6 поддерживают следующие аппа ратные платформы: alpha, arm, cr i s, h8300, i38, ia64, m68k, m68knommu, mips, mips64, pari sc, ppc, ppc64, s390, sh, spare, sparc64, um, v850 и х8б-б4. Более полное описание приведено в табл. 19.1.

История переносимости Linux Когда Линус Торвальдс впервые выпустил операционную систему Linux в ничего не подозревающий мир, эта ОС работала только на аппаратной платформе Intel i386.

Хотя данная операционная система и была достаточно хорошо обобщена и хорошо написана, переносимость для нее не была основным требованием. Однажды Линус даже говорил, что операционная система Linux не будет работать ни на какой ап паратной платформе, кроме i386! Тем не менее в 1993 году началась работа по пор 390 Глава тиропаиию ОС Linux на машины Digital Alpha. Аппаратная платформа Digital Alpha была повой высокопроизводительной RISC-платформой с поддержкой 64-разрядной адресации памяти. Она очень сильно отличалась от аппаратной платформы i386, о которой говорил Линус. Тем не менее, первоначальный перенос на аппаратную плат форму Alpha занял около года, и аппаратная платформа Alpha стала первой офици ально поддерживаемой аппаратной платформой после х8б. Это портировапие было, наверное, самым сложным, потому что — первым. Вместо простого переписывания ядра для поддержки новой аппаратной платформы, части ядра были переписаны с целью введения переносимости1. Хотя это и привело к выполнению большого коли чества работы, в результате получился более ясный для понимания код, и в будущем перенос стало выполнять более просто.

Первые выпуски ОС Linux поддерживали только платформу i386, а серия ядер 1.2 уже поддерживала Digital Alpha, Intel x86, MIPS и SPARC, хотя такая поддержка была отчасти экспериментальной.

С выпуском ядра версии 2.0 была добавлена официальная поддержка платформ Motorola 68k и PowerPC. В дополнение к этому поддержка всех аппаратных плат форм, которые ранее поддерживались ядрами серии 1.2, стала официальной и ста бильной.

В серию ядер 2.2 была введена поддержка еще большего количества аппаратных платформ: добавлены ARM, IBM S/390 и UltraSPARC. Через несколько лет в серии ядер 2.4 количество поддерживаемых аппаратных платформ было почти удвоено, и их количество стало равным 15. Была добавлена поддержка платформ CRIS, IA-64, 64-разрядная MIPS, HP PA-RISC, 64-разрядная IBM S/390 и Hitachi SH.

В серии 2.6 количество поддерживаемых аппаратных платформ было доведено до 20 за счет добавления платформ Motorola 68k бел устройства MMU, Н8/300, IBM POWER, v850, x86-64 и версии ядра, которое работает на виртуальной машине под ОС Linux - Usermode Linux. Поддержка 64-разрядной s390 была объединена с 32 разрядной платформой s390, чтобы избежать дублирования.

Необходимо заметить, что каждая из этих аппаратных платформ поддерживает различные типы машин и микросхем. Некоторые из поддерживаемых аппаратных платформ, такие как ARM и PowerPC, поддерживают очень большое количество ти пов микросхем и машин. Поэтому, хотя ОС Linux и работает на 20 аппаратных плат формах, она работает на гораздо большем количестве типов компьютеров!

Размер машинного слова и типы данных Машинное слово (word) — это количество данных, которые процессор может обработать за одну операцию. Здесь можно применить аналогию документа, состо ящего из символов (character, 8 бит) и страниц (много слов). Слово— это некоторое количество битов, как правило 16, 32 или 64. Когда говорят о "n-битовой" машине, то чаще всего имеют в виду размер машинного слова. Например, когда говорят, что процессор Intel Pentium — это 32-разрядный процессор, то обычно имеют в виду раз мер машинного слова, равный 32 бит, или 4 байт.

Это нормальная ситуация при разработке ядра. Если что-либо должно быть сделано, то это долж но быть сделано хорошо! Разработчики ядра неохотно переписывают большие участки кода даже во имя совершенства.

Переносимость Размер процессорных регистров общего назначения равен размеру машинного слова этого процессора. Обычно разрядность остальных компонентов этой же аппа ратной платформы в точности равна размеру машинного слова. Кроме того, по край ней мере для аппаратных платформ, которые поддерживаются ОС Linux, размер адресного пространства соответствует размеру машинного слова2. Следовательно, размер указателя равен размеру машинного слова. В дополнение к этому, размер типа long языка С также равен размеру машинного слова. Например, для аппарат ной платформы Alpha размер машинного слова равен 64 бит. Следовательно, реги стры, указатели и тип long имеют размер 64 бит. Тип int для этой платформы име ет размер 32 бит. Машины платформы Alpha могут обработать 64 бит— одно слово с помощью одной операции.

Слова, двойные слова и путаница Для некоторых операционных систем и процессоров стандартную порцию данных не называют машинным словом. Вместо этого, словом называется некоторая фиксированная порция данных, название которой выбрано случайным образом или имеет исторические корни. Например, в не которых системах данные могут разбиваться на байты (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 занимают столько же памяти, сколько и переменные типа long4.

Размер адресуемой памяти может быть меньше максимального значения машинного слова.

Например, для 64-разрядных аппаратных платформ размер указателя ранен 64 бит, однако только 48 бит можно использовать для адресации. В дополнение к этому, общее количество физической памяти может быть больше максимального значения машинного слова, как, например, это имеет место при наличии расширения Intel PAE.

За исключением размера типа char, который всегда равен 8 бит.

На самом деле, для 64-разрядных аппаратных платформ, которые поддерживаются ОС Linux, раз меры типов i nt и long не совпадают. Размер типа int равен 32 бит, а размер типа long — 64 бит.

Для хорошо знакомых 32-раphядных аппаратных платформ оба типа данных имеют размер 32 бит.

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

Всегда необходимо помнить о следующем.

• Как и требует стандарт языка С, размер типа char всегда равен 8 бит (1 байт), • Нет никакой гарантии, что размер типа int для всех поддерживаемых аппа ратных платформ будет равен 32 бит, хотя сейчас для всех платформ он равен именно этому числу.

• То же касается и типа short, который для всех поддерживаемых аппаратных платформ сейчас равен 16 бит.

• Никогда нельзя надеяться, что тип long или указатель имеет некоторый задан ный размер. Этот размер для поддерживаемых аппаратных платформ может быть равен 32, или 64 бит.

Переносимость • Так как размер типа long разный для различных аппаратных платформ, никог да нельзя предполагать, что sizeof (int.) == sizeof (long).

• Точно так же нельзя предполагать, что размер типа int и размер указателя со впадают.

Скрытые типы данных Скрытые (opaque) типы данных — это те типы, для которых не раскрывается их внутренняя структура, или формат. Они похожи на черный ящик, насколько это можно реализовать в языке программирования С. В этом языке программирования нет какой-либо особенной поддержки для этих типов. Вместо этого, разработчики определяют новый тип данных через оператор typedef, называют его скрытым и надеются на то, что никто не будет преобразовывать этот тип в стандартный тип данных языка С. Любые использования данных этих типов возможны только через специальные интерфейсы, которые также создаются разработчиком. Примером мо жет быть тип данных pid_t, в котором хранится информация об идентификаторе процесса. Размер этого типа данных не раскрывается, хотя каждый может смошен ничать, использовать размер по максимуму и работать с этим типом, как с типом int. Если нигде явно не используется размер скрытого типа данных, то размер этого типа может быть изменен, и это не вызовет никаких проблем. На самом деле так уже однажды случилось: в старых Unix-подобных операционных системах тип pid_t был определен как ahort.

Еще один пример скрытого типа данных — это тип atomic_t. Как уже обсужда лось в главе 9, "Средства синхронизации в ядре", этот тип содержит данные цело численного типа, с которыми можно выполнять атомарные операции. Хотя этот тип и соответствует типу int, использование скрытого типа данных позволяет гарантиро вать, что данные этого типа будут использоваться только в специальных функциях, которые выполняют атомарные операции. Скрытые типы позволяют скрыть размер типа данных, который не всегда равен полным 32 разрядам, как в случае платформы SPARC.

Другие примеры скрытых типов данных в ядре — это dev_t, gid _t и uid_t. При работе со скрытыми типами данных необходимо помнить о следующем.

• Нельзя предполагать, что данные скрытого типа имеют некоторый определен ный размер в памяти.

• Нельзя преобразовывать скрытый тип обратно в стандартный тип данных.

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

Специальные типы данных Некоторые данные в ядре, кроме того, что представляются с помощью скрытых типов, требуют еще и специальных типов данных. Два примера — счетчик импульсов системного таймера j i f f i es и параметр flags, используемый для обработки пре рываний. Для хранения этих данных всегда должен использоваться тип unsigned long.

394 Глава При хранении и использовании специфических данных всегда необходимо об ращать особенное внимание на тот тип данных, который представляет эти данные, и использовать именно его. Часто встречающейся ошибкой является использование другого типа, например типа unsigned int. Хотя для 32-разрядных аппаратных платформ это не приведет к проблемам, на 64-разрядных системах возникнут проб лемы.

Типы с явным указанием размера Часто при программировании необходимы типы данных заданного размера.

Обычно это необходимо для удовлетворения некоторых внешних требований, связанных с аппаратным обеспечением, сетью или бинарной совместимостью.

Например, звуковой адаптер может иметь 32-разрядный регистр, пакет сетевого протокола — 16-разрядное поле данных, а исполняемый файл - 8 битовый иденти фикатор cookie. В этих случаях тип, который представляет данные, должен иметь точно заданный размер.

В ядре типы данных явно заданного размера определены в файле, который включается из файла . В табл. 19.2 приведен полный спи сок таких типов данных.

Таблица 19.2. Типы данных явно заданного размера Тип Описание s байт со знаком u байт без знака s 16-разрядное целое число со знаком ul 16-разрядное целое число без знака s 32-разрядное целое число со знаком u 32-разрядное целое число без знака s 64-разрядное целое число со знаком u 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

;

Переносимость Для 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 байт (т.е. два младших бита адреса равны нулю). Таким образом, структура данные размером 2n байт должна хра ниться в памяти по адресу, младшие n битов которого равны нулю.

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

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

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

char dog [10]

;

char *p = &dog[1]

;

unsigned long 1 = * (unsigned long *)p

;

В этом примере указатель на данные типа unsigned char используется, как указа тель на тип unsigned long, что может привести к тому, что 32-разрядное значение типа unsigned long будет считываться из памяти по адресу, не кратному четырем.

Если вы думаете: "Зачем мне это может быть нужно?', то вы, скорее всего, правы.

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

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

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

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

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

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

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

struct animal_struct { char dog

;

/* 1 байт */ unsigned long cat

;

/* 4 байт */ unsigned short pig

;

/* 2 байт */ char fox

;

/* 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 байт */ }

;

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

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

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

struct animal struct { unsigned long cat

;

/* 4 байта */ unsigned short pig

;

/* 2 байта */ char dog

;

/* 1 байт */ char fox

;

/* 1 байт */ }

;

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

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

Порядок следования байтов Порядок следования байтов (byte ordering) — это порядок, согласно которому байты расположены в машинном слове. Для разных процессоров может использоваться один из двух типов нумерации байтов в машинном слове: наименее значимый (са мый младший) байт является либо самым первым (самым левым, left-most), либо самым последним (самым правым, right-most) в слове. Порядок байтов называется обратным (big-endian), если наиболее значимый (самый старший) байт хранится пер вым, а за ним идут байты в порядке убывания значимости. Порядок байтов называ ется прямым (little-endian), если наименее значимый (самый младший) байт хранится первым, а за ним следуют байты в порядке возрастания значимости.

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

На рис. 19.1 показан пример обратного порядка следования байтов, а на рис. 19.2 — прямого порядка следования байтов.

Аппаратная платформа i386 использует прямой (little-endian) порядок байтов.

Большинство других аппаратных платформ обычно использует обратный (big-endian) порядок.

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

Переносимость Номер байта 0 2 Старший Младший байт байт Рис. 19.1. Обратный (big-endian) порядок следования байтов Номер байта 3 2 1 О Старший Младший байт байт Рис. 19.2. Прямей (little-endian) порядок следования байтов Рассмотрим, что эти типы кодирования обозначают на практике и как выглядит двоичное представление числа 1027, которое хранится в виде четырехбайтового це лочисленного типа данных.

00000000 00000000 00000100 Внутренние представления этого числа в памяти при использовании прямого и обратного порядка байтов отличаются, как это показано в табл. 19.3.

Таблица 19.3. Расположение данных в памяти для разных порядков следования байтов Адрес Обратный порядок Прямой порядок 0 00000000 1 00000000 2 00000100 3 00000011 Обратите внимание на то, что для аппаратной платформы с обратным порядком байтов самый старший байт записывается в самый минимальный адрес памяти.

И наконец, еще один пример — фрагмент кода, который позволяет определить порядок байтов для той аппаратной платформы, на которой он выполняется.

int х = 1

;

if (*(char *)&х == 1) /* прямой порядок */ else /* обратный порядок */ Этот пример работает как в ядре, так и в пространстве пользователя.

400 Глава История терминов big-endian и little-endian Термины big-endian и little-endian заимствованы из сатирического романа Джонатана Свифта "Путешествие Гулливера", который был издан в 1726 году. В этом романс наиболее важной политической проблемой народа лилипутов была пробле ма, с какого конца следует разбивать яйцо: с тупого (big) или острого (little). Тех, кто предпочитал тупой конец называли "тупоконечниками" (big-endian), тех же, кто предпочитал острый конец, называли "остроконечниками" (little-endian).

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

Порядок байтов в ядре Для каждой аппаратной платформы, которая поддерживается ядром Linux, в файле определена одна из двух констант BIG_ENDIAN или LITTLE_ENDIAN, в соответствии с используемым порядком байтов.

В этот заголовочный файл также включаются макросы из каталога i ncl ude/ linux/byteorder/, которые помогают конвертировать один порядок байтов в дру гой. Ниже показаны наиболее часто используемые макросы.

u23cpu_to_be32(u32)

;

/* преобразовать порядок байтов текущего процессора в порядок big-endian */ u32cpu_to_le32(u32)

;

/* преобразовать порядок байтов текущего процессора в порядок little-endian */ u32 be32_to_cpu(u32)

;

/* преобразовать порядок байтов big-endian в порядок байтов текущего процессора */ u32lе32_to_cpus(u32)

;

/* преобразовать порядок байтов little-endian в порядок байтов текущего процессора */ Эти макросы выполняют преобразование одного порядка байтов в другой. В слу чае когда порядки байтов, между которыми выполняется преобразование, одинако вы (например, если выполняется преобразование в обратный порядоку байтов и процессор тоже использует такой же порядок), то эти макросы не делают ничего. В противном случае возвращается преобразованное значение.

Таймер Никогда нельзя привязываться к какой-либо конкретной частоте генерации пре рывания системного таймера и, соответственно, к тому, сколько раз в секунду изме няется переменная j i f f i es. Всегда необходимо использовать константу HZ, чтобы корректно определять интервалы времени. Это очень важно, потому что значение частоты системного таймера может отличаться не только для разных аппаратных платформ, но и для одной аппаратной платформы при использовании разных вер сий ядра.

Например, константа HZ для аппаратной платформы х86 сейчас равна 1000. Это значит, что прерывание таймера возникает 1000 раз в секунду, или каждую миллисе кунду. Однако до серии ядер 2.6 для аппаратной платформы х86 значение константы HZ было равно 100. Для разных аппаратных платформ эти значения отличаются: для аппаратной платформы alpha константа HZ равна 1024, а для платформы ARM — 100.

Переносимость Никогда нельзя сравнивать значение переменной j iffies с числом, таким как 1000, и думать, что это всегда будет означать одно и то же. Для получения интерва лов времени необходимо всегда умножать или делить на константу HZ, как в следую щем примере.

HZ /* одна секунда */ (2*HZ) /* две секунды */ (HZ/2) /* полсекунды */ (HZ/100) /* 10 мс */ (2*Н2/100) /* 20 мс */ Константа HZ определена п файле. Об этом подробно рассказано в главе 10, "Таймеры и упрадление временем".

Размер страницы памяти При работе со страницами памяти никогда нельзя привязываться к конкретному размеру страницы. Программисты, которые разрабатывают для аппаратной плат формы х86, часто делают ошибку, считая, что размер страницы всегда равен 4 Кбай та. Хотя это справедливо для платформы х86, для других аппаратных платформ раз мер станицы может быть другим. Некоторые аппаратные платформы поддерживают несколько размеров страниц! В табл. 19.-1 приведен список размеров страниц памяти для всех поддерживаемых аппаратных платформ.

Таблица 19.4. Размеры страниц памяти для разных аппаратных платформ Аппаратная платформа Значение PAGE_SHIFT Значение PAGE_SI ZE alpha 13 8 Кбайт arm 12, 14, 15 4 Кбайт, 16 Кбайт, 32 Кбайт cris 13 8 Кбайт h8300 12 4 Кбайт i386 12 4 Кбайт ia64 12, 13, 14, 16 4 Кбайт, 8 Кбайт, 32 Кбайт, 64 Кбайт m68k 12, 13 4 Кбайт, 8 Кбайт m86knommu 12 4 Кбайт mips 12 4 Кбайт mips64 12 4 Кбайт • parisc 12 4 Кбайт ррс 12 4 Кбайт ррс64 12 4 Кбайт S390 12 4 Кбайт sh 12 4 Кбайт spare 12,13 4 Кбайт, 8 Кбайт sparc64 13 8 Кбайт v850 12 4 Кбайт х86_64 12 4 Кбайт 402 Глава При работе со страницами памяти необходимо использовать константу PAGE_SIZE, которая содержит размер страницы памяти в байтах.

Значение макроса PAGE_SHIFT — это количество битов, на которое необходимо сдвинуть влево значение адреса, чтобы получить номер соответствующей страницы памяти. Например, для аппаратной платформы х86, для которой размер страницы равен 4 Кбайт, макрос PAGE_SIZE равен 4096, а макрос PAGE_SHIFT— 12. Эти значе ния содержатся в заголовочном файле.

Порядок выполнения операций процессором Вспомните из материала главы 9, "Средства синхронизации в ядре", что для раз личных аппаратных платформ процессоры в разной степени изменяют порядок вы полнения программных инструкций. Для некоторых процессоров порядок выполне ния операций строго соблюдается, запись данных в память и считывание данных из памяти выполняются в строго указанном в программе порядке. Другие процессоры имеют ослабленные требования к порядку выполнения операций считывания и за писи данных и могут изменять порядок выполнения этих операций с целью опти мизации.

Если код зависит от порядка выполнения операций чтения-записи данных, то не обходимо гарантировать, что даже процессор с самыми слабыми ограничениями на порядок выполнения чтения-записи будет выполнять эти операции в правильном по рядке. Это делается с помощью соответствующих барьеров, таких как rmb() и wmb().

Более подробная информация приведена в главе 9, "Средства синхронизации в ядре".

Многопроцессорность, преемптивность и верхняя память Может показаться неправильным включать поддержку симметричной многопро цессорности, возможность вытеснения процессов в режиме ядра и работу с верхней памятью в вопросы переносимости. В конце концов, это не особенности аппарат ной платформы, которые влияют на операционную систему, а функции ядра Linux, которые по многом не зависят от аппаратной платформы. Тем не менее для этих функций существуют важные конфигурационные параметры, которые необходимо учитывать при разработке кода. Программировать всегда необходимо под SMP, с поддержкой преемптивности и с использованием верхней памяти, чтобы код был безопасным всегда, при любых конфигурациях. Необходимо всегда соблюдать следу ющие правила.

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

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

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

Переносимость Пару слов о переносимости Если говорить коротко, то написание переносимого, ясного и красивого кода подразумевает следующие два момента.

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

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

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

404 Глава Заплаты, разработка и сообщество дно из самых больших преимуществ операционной системы Linux — это связанное с ней большое сообщество пользователей и разработчиков.

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

Сообщество Если говорить о том, где физически существует сообщество разработчиков ядра Linux, то можно сослаться на список рассылки разработчиков ядра Linux (Linux Kernel Mail List, или, сокращенно, LKML). Список разработчиков ядра Linux— это то место, где происходит большинство дискуссий, дебатов и флеймов вокруг ядра Linux. Здесь обсуждаются новые возможности, и большая часть кода отправляется в этот список рассылки перед тем, как этот код для чего-нибудь начинает использоваться. В списке рассылки насчитывается до 300 сообщений в день — количество не для слабонерв ных. Подписаться на этот список (или но крайней мере читать его обзор) рекомен дуется всем, кто серьезно занимается разработкой ядра. Даже только наблюдая за работой специалистов, можно узнать достаточно МНОГО.

Подписаться на данный список рассылки можно, отправив сообщение subscribe linux-kernel в виде обычного текста на адрес majordomo@vger.kernel.org. Больше информа ции доступно по Интернет-адресу http: //vger. kernel. org/, а список часто зада ваемых вопросов (FAQ) — по адресу ht t p : //www.tux.org/lkml/.

Много других WWW-сайтов и списков рассылки посвящены как ядру, так и вообще операционной системе Linux. Отличный ресурс для начинающих хакеров — ht t p: // www.kernelnewbies.org/, сайт, который сможет удовлетворить желания всех, кто, стачивая зубы, грызет основы разработки ядра. Два других отличных источника ин формации — это сайт http://www.lwn.net/, Linux Weekly News, на котором есть большая колонка новостей ядра, и сайт http://www.kerneltraffic.org, Kernel Traffic, который содержит сводку сообщений из списка рассылки разработчиков ядра Linux с. комментариями.

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

Пока стиль еще не выбран и широко не используется, не так важно, какой имен но стиль выбрать. К счастью, еще очень давно Линус представил на рассмотрение стиль, который необходимо использовать, и при написании большей части кода сей час стараются придерживаться именно этого стиля. Подробное описание стиля при ведено в файле Documentation/CodingStyle со свойственным Линусу юмором.

Отступы Для выравнивания текста и введения отступов используются символы табуляции.

Размер одного символа табуляции при отображении соответствует восьми позициям.

Это не означает, что для структурирования можно использовать восемь или четы ре символа "пробел" либо что-нибудь еще. Это означает, что каждый уровень отсту па равен одному символу табуляции от предыдущего и что при отображении длина символа табуляции равна восьми символам. По непонятным причинам, это правило почти всегда нарушается, несмотря на то что оно очень сильно влияет на читабель ность. Восьмисимвольная табуляция позволяет очень легко визуально различать от дельные блоки кода даже после нескольких часов работы.

Если табуляция в восемь символов кажется очень большой, то не нужно делать так много вложений кода. Почему ваши функции имеют пять уровней вложенности?

Необходимо исправлять код, а не отступы.

Фигурные скобки Как располагать фигурные скобки, это личное дело каждого, и практически нет никаких принципиальных причин, по которым одно соглашение было бы лучше дру гого, но какое-нибудь соглашение все-таки должно быть. Принятое соглашение при 406 Глава разработке ядра — это размещать открывающую скобку в первой строке, сразу за со ответствующим оператором. Закрывающая скобка помещается в первой позиции с новой строки, как в следующем примере.

if (fox) { dog()

;

cat()

;

} В случае, когда за закрывающей скобкой продолжается то же самое выражение, то продолжение выражения записывается в той же строке, что и закрывающая скоб ка, как показано ниже if (fox) { ant()

;

pig()

;

} else { dog()

;

cat()

;

} или следующим образом.

do { dog()

;

cat()

;

} while (fox)

;

Для функций это правило не действует, потому что внутри одной функции тело другой функции описывать нельзя.

unsigned long func(void) { /*.. */ } И наконец, для выражений, в которых фигурные скобки не обязательны, эти скобки можно опустить.

if (foo) bar()

;

Логика всего этого базируется на K&R1.

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

Брайан У. Керниган, Деннис М. Ритчи, Язык программирования С, 2-е изд. Пер. с англ. — М.: Издат.

дом "Вильяме", 2005.

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

Некоторые разработчики помещают параметры функции друг под другом, если параметры не помещаются в одной строке, как в следующем примере.

static void get_pirate_parrot(const char *name, unsigned long disposition, unsigned long feather_quality) Другие разработчики разбивают длинную строку на части, но не располагают па раметры функций друг под другом, а просто используют два символа табуляции для отделения продолжений длинной строки от ее начала, как показано ниже.

int find_pirate_flag_by_color(const char *color, const char *name, int len) Поскольку на этот счет нет определенного правила, выбор остается за разработ чиками, то есть за вами.

Имена В именах нельзя использовать символы разных регистров. Назвать переменную именем idx, или даже i — это очень хорошо, но при условии, что будет понятно на значение этой переменной. Слишком хитрые имена, такие как theLoopIndex, недо пустимы. Так называемая "венгерская запись" (Hungarian notation), когда тип пере менной кодируется в ее имени, В данном случае — признак плохого тона. Это С, а не Java и Unix, а не Windows.

Тем не менее, глобальные переменные и функции должны иметь наглядные име на. Если глобальной функции присвоить имя at t y( ), то это может привести к пу танице. Более подходящим будет имя g s t a c t i v e t t y ( ). Это все-таки Linux, а не BSD.

Функции Существует мнемоническое правило: функции не должны по объему кода пре вышать двух экранов текста и иметь больше десяти локальных переменных. Каждая функция должна выполнять одно действие, но делать это хорошо. Не вредно раз бить функцию на последовательность более мелких функций. Если возникает бес покойство по поводу накладных расходов за счет вызова функций, то можно исполь зовать подстановку тела — i nl i ne.

Комментарии Очень полезно использовать комментарии кода, но делать это нужно правильно.

Обычно необходимо описывать, что делает код и для чего это делается. То, как реа лизован алгоритм, описывать не нужно, это должно быть ясно из кода. Если так сде 408 Глава лать не получается, то, возможно, стоит пересмотреть то, что вы написали. Кроме того, комментарии не должны включать информацию о том, кто написал функцию, когда это было сделано, время модификации и др. Такую информацию логично раз мещать в самом начале файла исходного кода.

В ядре используются комментарии в стиле С, хотя компилятор gcc поддерживает также и комментарии в стиле C++. Обычно комментарии кода ядра должны быть по хожи на следующие (только на английском языке, конечно).

/* * get_ship_speed() - возвращает текущее значение скорости * пиратского корабля * Необходима для вычисления координат корабля.

* Может переходить в состояние ожидания, * нельзя вызывать при удерживаемой блокировке.

*/ Комментарии внутри функций встречаются редко, и их нужно использовать толь ко в специальных ситуациях, таких как документирование дефектов, или для важных замечаний. Важные замечания часто начинаются со строки "XXX: ", а информация о дефектах— со строки "FIXME: ", как в следующем примере.

/* * FIXME: Считается, что dog == cat.

* В будущем это может быть не так */ У ядра есть возможность автоматической генерации документации. Она основана на GNOME-doc, но немного модифицирована и называется Kernel-doc. Для создания документации в формате HTML необходимо выполнить следующую команду.

make htmldocs Для генерации документации в формате postscript команда должна быть следую щей.

make psdocs Документировать код можно путем введения комментариев в специальном фор мате.

/** * find_treasure - нахождение сокровищ, помеченных на карте крестом * @map - карта сокровищ * @time - момент времени, когда были зарыты сокровища * * Должна вызываться при удерживаемой блокировке pirate_ship_lock.

*/ void find_treasure (int dog, int cat) { /*.. */ } Для более подробной информации см. файл Documentation/kernel-doc-nano HOWTO.txt.

Заплаты, разработка и сообщество Использование директивы typedef Разработчики ядра не любят определять новые типы с помощью оператора typedef, и причины этого довольно трудно объяснить. Разумное объяснение может быть следующим.

• Определение нового типа через оператор typedef скрывает истинный вид структур данных.

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

• Использование оператора typedef — признак лени.

Чтобы избежать насмешек, лучше не использовать оператор typedef.

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

Использование того, что уже есть Не нужно изобретать паровоз. Ядро предоставляет функции работы со строками, подпрограммы для сжатия и декомпрессии данных и интерфейс работы со связан ными списками — их необходимо использовать.

Не нужно инкапсулировать стандартные интерфейсы в другие реализации обоб щенных интерфейсов. Часто приходится сталкиваться с кодом, который переносит ся с других операционных систем в систему Linux, при этом на основе существую щих интерфейсов реализуется некоторая громоздкая функция, которая служит для связи нового кода с существующим. Такое не нравится никому, поэтому необходимо непосредственно использовать предоставляемые интерфейсы.

Никаких директив i f def в исходном коде Использование директив препроцессора ifdef в исходном коде категорически не рекомендуется. Никогда не следует делать чего-нибудь вроде следующего.

...

#ifdef config_foo foo()

;

#endif...

Вместо этого, если макрос CONFIG_FOO не определен, необходимо определять функцию fоо(), как ту, которая ничего не делает.

tifdef CONFIG_FOO static int foo(void) { /*.. */ } #else static inline int foo(void) { } #endif 410 Глава После этого можно вызывать функцию foo() без всяких условий. Пусть компи лятор поработает за вас.

Инициализация структур Структуры необходимо инициализировать, используя метки полей. Это позво ляет предотвратить некорректную инициализацию при изменении структур. Это также позволяет выполнять инициализацию не всех полей. К сожалению, в стан дарте С99 принят довольно "страшненький" формат меток полей, а в компиляторе gcc ранее использовавшийся формат меток полей в стиле GNU признан устаревшим.

Следовательно, для кода ядра необходимо использовать новый формат, согласно стандарту С99, каким бы ужасным он ни был.

struct foo rny_foo = {.а = INITIAL_A,.Ь = INITIAL_B, }

;

где а и b — это поля структуры st ruct foo, а параметры INITIAL_A и INITIAL_B — соответственно, их начальные значения. Если поле не указано при инициализации, то оно устанавливается в свое начальное значение, согласно стандарту ANSI С (ука зателям присваивается значение NULL, целочисленным полям — нулевое значение, а нолям с плавающей точкой— значение 0.0). Например, если структура st ruct foo также имеет поле i nt с, то это поле в предыдущем примере будет инициализирова но в значение 0.

Да, это ужасно. Но у нас нет другого выбора.

Исправление ранее написанного кода Если в ваши руки попал код, который даже близко не соответствует стилю напи сания кода ядра Linux, то все равно не стоит терять надежды. Немного упорства, и утилита indent поможет сделать все как надо. Программа indent — отличная утили та GNU, которая включена во многие поставки ОС Linux и предназначена для фор матирования исходного кода в соответствии с заданными правилами. Установки по умолчанию соответствуют стилю форматирования GNU, который выглядит не очень красиво. Для того чтобы утилита выполняла форматирование в соответствии со сти лем написания кода ядра Linux, необходимо использовать следующие параметры.

indent -kr -i8 -ts8 -sob -180 -ss -bs -psl <файл> Можно также использовать сценарий scripts/Lindent, который вызывает ути литу indent с необходимыми параметрами.

Организация команды разработчиков Разработчики — это хакеры, которые занимаются развитием ядра Linux.

Некоторые делают это за деньги, для некоторых это хобби, но практически все де лают это с удовольствием. Разработчики ядра, которые внесли существенный вклад, перечислены в файле CREDITS, который находится в корневом каталоге дерева ис ходных кодов ядра.

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

Каждая подсистема ядра, например сетевая подсистема, также имеет связанного с ней ответственного. Разработчики, которые отвечают за определенный драйвер или подсистему, обычно перечислены в файле MAINTAINERS. Этот файл тоже находится в корневом каталоге дерева исходных кодов.

Среди ответственных разработчиков существует специальный человек, который отвечает за все ядро в целом (kernel maintainer). Исторически, Линус отвечает за разрабатываемую серию ядер (где наиболее интересно) и за первые выпуски ста бильной версии ядра. Вскоре после того как ядро становится стабильным, Линус пе редает бразды правления одному из ведущих разработчиков. Они продолжают под держку стабильной серии ядра, а Линус начинает работу над новой разрабатываемой серией. Таким образом, серии ядер 2.0, 2.4 и 2.6 все еще активно поддерживаются.

Несмотря на слухи, здесь нет никаких других, в том числе тайных, организаций.

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

Наиболее важная часть сообщения об ошибке — это полное описание проблемы.

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

Следующий этап — определение того, кому отправить сообщение об ошибке.

В файле MAINTAINERS приведен список людей, которые отвечают за каждый драй вер и подсистему. Эти люди должны получать все сообщения об ошибках, которые возникают в том коде, поддержку которого они выполняют. Если вы не смогли най ти необходимого разработчика, то отправьте вопрос в список рассылки разработчи ков ядра Linux по адресу linux-kernel@vger.kernel.org. Даже если вы нашли нужное ответственное лицо, то никогда не помешает отправить копию сообщения в список рассылки разработчиков.

Больше информации об этом приведено в файлах REPORTING-BUGS и Documentation/oops-tracing.txt.

Генерация заплат Все изменения исходного кода ядра Linux распространяются в виде заплат (patch).

Заплаты представляют собой результат вывода утилиты GNU di ff(1) в формате, который может подаваться на вход программы patch (1). Наиболее просто сгене рировать заплату можно в случае, когда имеется два дерева исходных кодов ядра:

одно — стандартное, а другое — с вашими изменениями. Обычная схема имен состоит в том, что каталог, в котором находится стандартное ядро, называется 1inux-x.у.z (каталог, в который разворачивается архив дерева исходного кода в формате tar), a 412 Глава имя модифицированного ядра — linux. Для генерации заплаты на основе двух ката логов с исходным кодом необходимо выполнить следующую команду из каталога, в котором находятся два рассмотренных дерева исходного кода.

diff -urN linux-x.y.z/linux/ > my-patch Обычно это делается где-нибудь в домашнем каталоге, а не в каталоге /usr/src/ linux, поэтому нет необходимости иметь права пользователя root. Флаг -u указыва ет, что необходимо использовать унифицированный формат вывода команды diff.

Без этого флага внешний вид заплаты получается неудобочитаемым. Флаг -r указы вает на необходимость рекурсивного анализа каталогов, а флаг -N указывает, что новые файлы, которые появились в измененном каталоге, должны быть включены в результат вывода команды diff. Если же необходимо получить только изменения одного файла, то можно выполнить следующую команду.

diff -u linux-x.y.z/some/file_linux/some/file > my-patch Обратите внимание на то, что вычислять изменения необходимо всегда, находясь в одном текущем каталоге, а именно в том, где находятся оба дерева исходного кода.

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

patch -p1 <../my-patch В этом примере имя файла, который содержит заплату, - my-patch, а находится он в родительском каталоге по отношению к каталогу, в котором хранится дерево исходного кода ядра. Флаг -p1 означает, что необходимо игнорировать (strip) имя первого каталога в путях всех файлов, в которые будут вноситься изменения. Это позволяет применить заплату независимо от того, какие имена каталогов кода ядра были на той машине, где создавалась заплата.

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

diffstat -p1 my-patch Обычно полезно включить результат выполнения этой команды при отправле нии заплаты п список рассылки lkml. Так как программа patch (1) игнорирует все строки до того момента, пока не будет обнаружен формат diff, то вначале заплаты можно включить короткое описание.

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

Если заплата касается определенного драйвера или подсистемы, то заплату нужно отправить соответствующему ответственному разработчику, одному из тех, которые перечислены в файле MAINTAINERS. Другой вариант — это отправить сообщение в список рассылки разработчиков ядра по адресу linux-kernel@vger.kernel.org.

Заплаты, разработка и сообщество Обычно тема (subject) письма, в котором содержится заплата, должна быть по хожа на следующую " [PATCH] короткое описание. ". В теле письма должны быть описаны основные технические детали изменений, которые вносятся заплатой, а также обоснования необходимости этих изменений. Описание должно быть макси мально конкретным. Также необходимо указать, на какую версию ядра рассчитана заплата.

Большинство разработчиков ядра будут просматривать заплату прямо в теле пись ма и при необходимости записывать все письмо в файл. Следовательно, лучше все го будет вставить заплату прямо в тело письма, в самом конце сообщения. Будьте внимательны, потому что некоторые злобные почтовые клиенты вводят в сообще ния дополнительное форматирование. Это испортит заплату и будет надоедать раз работчикам. Если ваш почтовый клиент делает такие вещи, то необходимо поискать возможность включения текста без изменений ("Insert Inline") или что-нибудь ана логичное. Нормально работает также присоединение (attachment) заплаты в виде обычного текста, без перекодировки.

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

После отправки наберитесь терпения и подождите ответа. Не нужно обижаться на негативный ответ — в конце концов это тоже ответ! Обдумайте проблему и вы шлите обновленную версию заплаты. Если ответа нет, то попытайтесь разобраться, что было сделано не так, и решить проблему. Спросите у ответственного разработ чика и в списке рассылки по поводу комментариев. Если повезет, то ваши изменения будут включены в новую версию ядра!

Заключение Наиболее важными качествами любого хакера являются желание и умение рабо тать — нужно искать себе проблемы и решать их. В этой книге приведено описание основных частей ядра, рассказано об интерфейсах, структурах данных, алгоритмах и принципах работы. Книга предоставляет вид ядра изнутри и делает это в практиче ской форме. Она предназначена для того, чтобы удовлетворить ваше любопытство и стать отправной точкой в разработке ядра.

Тем не менее, как уже было сказано, единственный способ начать разрабатывать ядро — это начать читать и писать исходный код. Операционная система Linux пре доставляет возможность работать в сообществе, которое не только позволяет это делать, но и активно побуждает к указанным действиям. Если есть желание действо вать — вперед!

414 Глава А Связанные списки вязанный список — это структура хранения информации (контейнер), которая может содержать переменное количество элементов данных, часто называе С мых узлами, и позволяет манипулировать этими данными. В отличие от статического массива, элементы связанного списка можно создавать динамически. Это дает воз можность создавать переменное количество элементов списка, причем указанное ко личество может быть неизвестно на этапе компиляции. Так как элементы связанных списков создаются в разные моменты времени, они не обязательно будут находиться в смежных областях оперативной памяти. Поэтому элементы списка должны быть связаны друг с другом таким образом, чтобы каждый элемент содержал указатель на следующий за ним элемент (next). Вставка или удаление элементов списка выполня ется простым измепением указателей на следующий элемент. Структура связанного списка показана на рис. А.1.

next next next NULL Рис. A.I. Однос&язный список В. некоторых связанных списках содержится указатель не только на следующий, но и на предыдущий элемент (prev). Эти списки называются двухсвязными (doubly link ed), потому что они связаны как вперед, так и назад. Связанные списки, аналогичные тем, что показаны на рис.А.1, называются односвязиыми (singly linked). Двухсвязный список показан на рис. А.2.

NULL prev next prev next prev next NULL Рис. А.2. Двухсвязный список Кольцевые связанные списки Последний элемент связанного списка не имеет следующего за ним элемента, и значение указателя next последнего элемента обычно устанавливается равным спе циальному значению, обычно NULL, чтобы показать, что этот элемент списка явля ется последним. в определенных случаях последний элемент списка не указывает на специальное значение, а указывает на первый элемент этого же списка. Такой список называется кольцевым связанным списком (circular linked list), поскольку связи образуют топологию кольца. Кольцевые связанные списки могут быть как односвязными, так и двухсвязными. В двухсвязных кольцевых списках указатель prev первого элемента указывает на последний элемент списка. На рис. А.З и А.4 показаны соответственно односвязные и двухсвязные кольцевые списки.

next next next Рис. A3. Односвязный кольцевой список prev next prev next prev next Рис. А.4. Двухсвязный кольцевой список Стандартной реализацией связанных списков в ядре Linux является двухсвязный кольцевой список. Такие связанные списки обеспечивают наибольшую гибкость работы.

Перемещение по связанному списку Перемещение по связанному списку выполняется последовательно (линейно).

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

416 Приложение А Часто первый элемент списка представлен с помощью специального указателя, который называется головным элементом или головой (head), что дает возможность бы стро и легко обращаться к первому элементу. В некольцевом связанном списке по следний элемент отличается тем, что его указатель равен значению NULL. В кольце вом связанном списке последний элемент отличается тем, что указывает на головной элемент. Таким образом прохождение списка можно выполнить линейно, начиная с первого элемента и заканчивая последним. В двухсвязном списке прохождение мож но также выполнить и в противоположном направлении, начиная с последнего и за канчивая первым элементом. Конечно, если задан определенный элемент списка, то можно перейти по списку вперед и назад на заданное количество элементов. При этом нет необходимости проходить весь список.

Реализация связанных списков в ядре Linux В ядре Linux для прохождения по связанным спискам используется унифициро ванный подход. При прохождении связанного списка, если не важен порядок про хода, эту операцию не обязательно начинать с головного элемента, на самом деле вообще не важно, с какого элемента списка начинать прохождение! Важно только, чтобы при таком прохождении были пройдены все узлы. В большинстве случаев нет необходимости вводить концепции первого и последнего элементов. Если в кольце вом связанном списке содержится коллекция несортированных данных, то любой эле мент можно назвать головным. Для прохождения всего связанного списка необходи мо взять любой элемент и следовать за указателями, пока снова не вернемся к тому элементу, с которого начали обход списка. Это избавляет от необходимости вводить специальный головной элемент. Кроме того, упрощаются процедуры работы со свя занными списками. Каждая подпрограмма должна просто принимать указатель на один элемент — любой элемент списка. Разработчики ядра даже немножко гордятся такой остроумной реализацией.

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

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

Код работы со связанными списками определен в файле , a основная структура данных имеет очень простой вид.

struct list_head { struct list_head *next, *prev

;

}

;

Обратите внимание на характерное имя структуры l i st head. Такое название выбрано, чтобы подчеркнуть, что списку не нужно головного элемента. Наоборот, Связанные списки обход списка можно начинать с любого элемента, и каждый элемент может быть головным. В связи с этим все элементы списка называются головными (list head).

Указатель next указывает на следующий элемент списка, а указатель prev— на пред ыдущий элемент списка. Если текущий элемент списка является последним, то его указатель next указывает на первый узел. Если же текущим элементом является пер вый, то его указатель prev указывает па последний узел списка. Благодаря элегант ной реализации связанных списков без концепции головного элемента, можно во обще не вводить понятия первого и последнего элементов.

Структура l i st head сама по себе бесполезна. Ее необходимо включать в другие структуры данных.

struct my_struct { struct list_head list

;

unsigned long dog

;

void *cat

;

}

;

Перед использованием элементы связанных списков должны быть инициализиро ваны. Так как элементы связанных списков обычно создаются динамически (иначе, вероятно, не нужно использовать связанный список), то эти элементы, как правило, инициализируются во время выполнения.

struct my_struct *р

;

/* выделить память для структуры my_struct.. */ p->dog = 0

;

p->cat = NULL

;

INIT_LIST_HEAD(&p->list)

;

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

struct my_struct mine = {.list = LIST_HEAD_INIT(mine.list),.dog = 0,.cat = NULL }

;

Для того чтобы создать и инициализировать связанный список, можно использо вать следующее объявление.

static LIST_HEAD(fox)

;

Эта команда позволяет статически создать связанный список с именем fox.

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

418 Приложение А Работа со связанными списками Для работы со связанными списками ядро предоставляет семейство функций. Все они принимают указатели на одну или более структур l i s t head. Все функции вы полнены как функции с подстановкой тела (inline) на языке С, и их все можно найти в файле .

Интересно, что время выполнения всех этих функций масштабируется как О (1) Это значит, что они выполняются в течение постоянного интервала времени независи мо от количества элементов списка, для которого они вызываются. Например, время добавления элемента в связанный список из 3 и 3000 элементов будет одинаковым.

Это, возможно, и не вызывает удивления, но тем не менее, приятно.

Для добавления элемента в связанный список можно использовать следующую функцию.

list_add(struct list_head *new, struct list head *head) Эта функция добавляет узел new в заданный связанный список сразу после эле мента head. Поскольку связанный список является кольцевым и для него не суще ствует понятий первого и последнего узлов, в качестве параметра head можно использо вать указатель на любой элемент списка. Если в качестве рассмотренного параметра всегда передавать указатель на последний элемент, то эту функцию можно использо вать для организации стека.

Для добавления элемента в конец связанного списка служит следующая функция.

list_add_tail (struct list_head *new, struct list_head *head) Эта функция добавляет новый элемент new в связанный список сразу перед эле ментом, на который указывает параметр head. Поскольку связанный список являет ся кольцевым, то, как и в случае функции list_add (), в качестве параметра head можно передавать указатель на любой элемент списка. Эту функцию можно исполь зовать для реализации очереди, если передавать указатель на первый элемент.

Для удаления узла списка служит следующая функция.

list_del (struct list_head *entry) Эта функция позволяет удалить из списка элемент, на который указывает пара метр entry. Обратите внимание, что эта функция не освобождает память, выде ленную под структуру данных, содержащую узел списка, на который указывает па раметр entry. Данная функция просто удаляет узел из списка. После вызова этой функции обычно необходимо удалить структуру данных, в которой находится узел list_head.

Для удаления узла из списка и повторной инициализации этого узла служит сле дующая функция.

list_del_init(struct list head *entry) Вопросы сложности алгоритмов рассматриваются в приложении В.

Связанные списки Эта. функция аналогична функции l i st _del (), за исключением того, что она до полнительно инициализирует указанную структуру l i s t he a d из тех соображений, что эта структура данных больше не нужна в качестве элемента текущего списка и ее повторно можно использовать.

Для перемещения узла из одного списка в другой предназначена следующая функ ция.

list_move(struct list_head *list, struct list_head *head) Эта функция удаляет элемент l i s t из одного связанного списка и добавляет его в другой связанный список после элемента head.

Для перемещения элемента из одного связанного списка в конец другого служит следующая функция.

list_move_tail (struct list_head *list, struct list_head *head) Эта функция выполняет то же самое, что и функция list_rnove (), но вставляет элемент перед элементом head.

Для проверки того, пуст ли список, служит функция.

list_empty (struct list_head *head) Эта функция возвращает ненулевое значение, если связанный список пуст, и ну левое значение в противном случае.

Для объединения двух не перекрывающихся связанных списков служит следую щая функция.

list_splice(struct list_head *list, struct list_head *head) Эта функция вставляет список, на который указывает параметр l i s t, в другой список после параметра head.

Для объединения двух не перекрывающихся списков и повторной инициализа ции старого головного элемента служит следующая функция.

list splice init(struct list head *list, struct list head *head) Эта функция аналогична функции l i s t s pl i c e ( ), за исключением того, что па раметр l i s t, представляющий список, из которого удаляются элементы, повторно инициализируется.

Как избежать двух лишних разыменований Если вам уже доступны указатели next и prev, то можно сэкономить пару процессорных тактов (в частности, время выполнения операций разыменования указателей) путем вызова внутрен них функций работы со связанными списками. Все ранее рассмотренные функции в сущности не делают ничего, кроме получения указателей next и prev и вызовов внутренних функций.

Внутренние функции имеют те же имена, что и их оболочки, но перед именем используется два символа подчеркивания. Вместо того чтобы вызвать функцию l i s t _ de l ( l i s t ), можно вызвать функцию l i st _del ( pr ev, next). Это имеет смысл, только когда указанные ука затели уже известны. В противном случае просто получится некрасивый код. Для подробной информации об этих интерфейсах можно обратиться к файлу .

420 Приложение А Перемещение по связанным спискам Теперь мы уже знаем, как объявлять, инициализировать и работать со связан ными списками в ядре. Это все хорошо, но не имеет никакого смысла, если нет возможности работать С данными, которые хранятся в списках! Связанный спи сок — это просто контейнер, в котором хранятся важные данные. Необходимо иметь способ перемещения по списку и доступа к данным. К счастью, ядро предоставляет набор полезных интерфейсов для перемещения по связанным спискам и обращения к структурам данных, которые хранятся в этих списках.

Обратите внимание, что, в отличие от подпрограмм управления списками, опера ции перебора элементов списка из н узлов масштабируются как О (n).

Наиболее простой способ выполнять итерации по элементам связанного спи ска — это использовать макрос list_for_each (). Этот макрос принимает два па раметра— указатели на структуры list_head. Первый параметр указывает на теку щий элемент списка, а второй — на любой элемент списка, для которого необходимо обойти все узлы. На каждой итерации цикла первый параметр макроса указывает на текущий элемент списка, пока не будут пройдены все элементы, как в следующем примере.

struct list_head *р

;

list_for_each(p, list) { /* р указывает на каждый элемент списка list */ } Это пока все еще бесполезно! Указатель на структуру узла списка — это не то, что нам нужно. Нам нужен указатель на структуру данных, в которой содержится струк тура узла. В показанном ранее примере структуры данных my_struct необходимо получить указатель на каждый экземпляр структуры my_struct, а не на их поля l i st. Макрос l i s t ent r y( ) возвращает структуру данных, которая содержит соот ветствующий элемент list_head. Этот макрос принимает три параметра: указатель на текущий узел, тип структуры данных, в которую включен узел списка, и имя поля структуры данных, в которой хранится этот узел.

struct list_head *р

;

struct my_struct *my

;

list_for_each(p, mine->list) { my = list_entry(p, struct my_struct, list)

;

/* * указатель my указывает на все структуры данных, * в которые включено поле list */ } Макрос list_for_each () раскрывается в обычный цикл for. Предыдущий при мер раскрывается следующим образом, for (р = mine->list->next

;

р != mine->list

;

р = p->next) Кроме этого, макрос list_for_each() также выполняет предварительную за грузку (prefetch) данных в память, если процессор поддерживает такую возможность, Связанные списки чтобы все данные следующих элементов списка гарантированно находились в памя ти. Когда нет необходимости выполнять предварительную загрузку, можно использо вать макрос list_for_each(), который работает в точности, как цикл for. Если нет гарантии, что список содержит очень мало элементов или пустой, то всегда не обходимо использовать версию с предварительной загрузкой. Никогда нельзя про граммировать цикл вручную, необходимо всегда использовать макрос.

Если необходимо выполнить прохождение по спискам в обратном порядке, то следует использовать макрос list_for_each_prev (), который использует для про хождения указатель prev, а не указатель next.

Обратите внимание, что при прохождении связанного списка ничто не мешает удалять элементы из этого списка. Обычно, чтобы предотвратить конкурентный до ступ, следует использовать блокировки. Макрос list_for_each_safe() использует временные переменные, чтобы сделать прохождение списка безопасным при одно временном удалении элементов.

struct list_head *p, *n

;

struct my_struct *my

;

list_for_each_safe (p, n, &mine->list) { my = list_entry (p, struct my_struct, list)

;

/* * указатель my указывает на каждый экземпляр * структуры my_struct в списке */ } Обратите внимание, что этот макрос защищен только от операций удаления узлов списка. Для защиты отдельных элементов списка от конкурентного доступа необхо димо использовать блокировки.

422 Приложение А Б Генератор случайных чисел ядра ядре Linux реализован генератор случайных чисел, который теоретически мо жет генерировать истинно случайные числа. Генератор случайных чисел собира В ет в пул энтропии шумы внешней среды, которые поступают из драйверов устройств.

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

Истинно случайные числа отличаются от псевдослучайных чисел, которые гене рируются библиотечными функциями языка С. Псевдослучайные числа создаются с помощью детерминированных функций. Хотя такие функции и могут генериро вать последовательности чисел, которые обладают некоторыми свойствами истин но случайных чисел, тем не менее такие числа только статистически случайны.

Псевдослучайные числа являются детерминированными, потому что если известно хотя бы одно число последовательности, то можно определить и все остальные.

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

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

Физический термин энтропия— это мера беспорядка и случайности в любой си стеме. Энтропия измеряется в единицах энергии на единицу температуры (Джоуль на градус Кельвина). Когда Клод Шеннон (Claude Shennon)1, создатель информаци онной теории, искал термин для представления случайности информации, великий Клод Шеннон (30 апреля 1916-24 февраля 2001) работал инженером в компании Bell Labs. В его наиболее известной работе Математическая теории связи, опубликованной в 1948 году, впервые были разработаны основы информационной теории и было введено понятие энтропии Шеннона.

Шеннон также любил кататься на одноколесном велосипеде.

математик Джон фон Нейман (John von Neumann)2 предложил ему использовать термин энтропия, потому что никто толком не понимает, что за этим понятием кро ется. Шеннон согласился, и сегодня это звучит как энтропия Шеннона. Некоторые ученые считают, что такое двойное название только вносит путаницу, и когда речь идет об информации, то используют термин неопределенность. Разработчики ядра, на оборот, считают, что "энтропия"- это "круто", и поддерживают использование дан ного термина.

При рассмотрении генераторов случайных чисел понятие энтропии Шеннона яв ляется очень важным. Эта характеристика измеряется в битах на символ. Высокое значение энтропии означает, что в последовательности символов мало полезной (точнее, предсказуемой) информации и много случайного "мусора". Ядро поддер живает пул энтропии, который пополняется данными, возникающими в результате недетерминированных событий, связанных с аппаратными устройствами. В идеале, этот пул содержит полностью случайные данные. Для того чтобы иметь представ ление о значении энтропии пула, ядро постоянно вычисляет меру неопределенно сти данных в пуле. По мере того как ядро добавляет данные в пул, оно оценивает меру случайности добавляемых данных. И наоборот, по мере того как данные из влекаются из пула, ядро уменьшает значение оценки энтропии. Соответствующая количественная характеристика называется оценкой энтропии. Если значение оценки энтропии становится равным нулю, то ядро может отказаться выполнять запрос по считыванию данных из пула.

Генератор случайных чисел ядра был предложен в версии 1.3.30 и находится в файле drivers/char/random.с.

Принцип работы и реализация Компьютеры— это предсказуемые устройства. Действительно, трудно найти слу чайное поведение в системе, поведение которой можно практически полностью программировать. Однако окружающая среда, где находится машина, полна различ ных шумов, которые недетерминированы и которые можно измерить. Источники таких шумов включают моменты времени, в которые возникают события, связанные с аппаратными устройствами, а также события, связанные с взаимодействием поль зователей и компьютера. Например, интервалы времени между нажатиями клавиш, перемещения мыши, интервалы времени между некоторыми типами прерываний и время выполнения запроса блочного ввода-вывода являются недетерминированны ми, и, кроме того, их не может измерить внешний злоумышленник. Случайная ин формация, которая получается из этих событий, записывается в пул энтропии. Пул растет и заполняется случайными и непредсказуемыми шумовыми данными. По мере добавления данных в пул вычисляется оценка энтропии, и итоговое значение запо минается. Это позволяет всегда иметь информацию о значении энтропии в пуле. На рис. Б.1 показана диаграмма прохождения потока энтропии в пул и из пула.

Джон фон Нейман (28 декабря 1903-8 февраля 1957) работал в Институте специальных исследо ваний в Принстоне (Institute for Advanced Study

;

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

424 Приложение Б Для доступа к пулу энтропии, как из пространства ядра, так и из пространства пользователя, ядро предоставляет набор интерфейсов. Когда выполняется обра щение к этим интерфейсам, ядро вначале вычисляет хеш-значение SHA данных из пула. Алгоритм SHA (Secure Hash Algorithm, алгоритм вычисления безопасного хеш значения) — это алгоритм вычисления дайджеста сообщения (профиля сообщения, message digest), который был разработан Агентством национальной безопасности (National Security Agency, NSA) и утвержден в качестве федерального стандарта США Национальным институтом стандартов и технологий (NIST) (федеральный стандарт по обработке информации, FIPS 186). Вычисление дайджеста сообщения выполняет ся с помощью специального алгоритма, который принимает сообщение переменно го размера (большое или маленькое) и выдает на выходе хеш-значение фиксирован ного размера (обычно размером 128 или 160 байт). Это фиксированное значение и представляет собой дайджест. Входное сообщение не может быть реконструировано по его хеш-значению. Более того, простое изменение входного сообщения (напри мер, изменение одного символа) приведет к радикальному изменению хеш-значения.

Алгоритмы вычисления дайджестов сообщений могут использоваться по-разному, включая проверку подлинности данных и дактилоскопию. Другие алгоритмы вычис ления дайджестов — это MD4 и MD5. Пользователю возвращается хеш-значение SHA пула, к содержимому пула энтропии непосредственно обращаться нельзя. Считается, что по хеш-значению невозможно получить никакую информацию о состоянии пула.

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

<- Пространство ядра Пространство пользователя -> При нажатии клавиш 1111010000-> генерируются прерывания 10110001- Ядро использует значения интервалов времени между Приложения считывают информацию Пул энтропии успешными прерываниями из пула через специальные файлы для заполнения пула энтропии /dev/random и /dev/urandom с помощью функции add_keyboard_random ness() Рис. Б.1. Прохождение энтропии через пул энтропии ядра Когда значение оценки энтропии достигает нуля, то ядро все равно может воз вращать случайные числа. Однако в этом случае теоретически появляется возмож ность того, что злоумышленник сможет предугадать результат вывода. Для этого тре буются все результаты вывода из пула энтропии, а также чтобы злоумышленник смог Генератор случайных чисел ядра выполнить криптографический анализ алгоритма SHA. Так как алгоритм SHA счи тается безопасным, то это невозможно. Для высоконадежной криптографии оценка энтропии позволяет гарантировать устойчивость случайных чисел. Для большинства пользователей такая дополнительная гарантия не нужна.

Почему это реализовано в ядре?

Критерием того, что какую-либо возможность необходимо реализовать в ядре, является слож ность реализации этой возможности в пространстве пользователя. Недопустимо вводить что-либо в ядро только потому, что мы это можем сделать. Может показаться, что генерато ру случайных чисел и пулу энтропии не место в ядре. Однако существует, по крайней мере, три причины, по которым они должны быть в ядре. Во первых, генератору необходим доступ к системным событиям, таким как прерывания и ввод данных пользователями. Для обеспечения доступа к информации об этих событиях из пространства пользователя необходимо экспорти ровать специальные интерфейсы, чтобы информировать пространство пользователя о том, что эти события произошли. Даже если эти данные будут экспортироваться, то доступ к ним будет не простым и не быстрым. Во-вторых, генератор случайных чисел должен быть безопасным.

Хотя такая система и может выполняться с правами пользователя root, тем не менее ядро явля ется значительно более безопасным местом для пула энтропии. И наконец, самому ядру также необходимы случайные числа. Получать информацию о случайных числах, которая необходима ядру, из пространства пользователя — это не практично. В связи с этим генератор случайных чисел работает в ядре.

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

Для решения проблемы большинство Linux-систем сохраняет на диске содер жимое части пула энтропии между перегрузками системы. При старте системы со храненные данные считываются и записываются в пул энтропии. Загрузка предыду щего содержимого пула в текущий пул позволяет обойтись без увеличения оценки энтропии.

Таким образом, злоумышленник не может предугадать состояние пула энтропии, не зная одновременно предыдущего и текущего состояний системы.

Интерфейсы для ввода энтропии Ядро экспортирует следующее семейство интерфейсов, которые могут использо ваться драйверами и системами для ввода данных в пул энтропии.

void add_interrupt_randomness(int irq) void add_keyboard_randomness (unsigned char scancode) void add_mouse_randomness(u32 mouse_data) 426 Приложение Б Функция add_interrupt_randomness () вызывается системой обработки преры ваний, когда приходит прерывание, обработчик которого зарегистрирован с флагом SA_SAMPLE_RANDOM. Параметр irq — это номер прерывания. Генератор случайных чисел использует интервалы времени между прерываниями, как источник шума.

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

Подходящее устройство — жесткий диск, который генерирует прерывания с непред сказуемой частотой.

Функция add_keyboard_randomness () использует скан-коды и интервалы вре мени между нажатиями клавиш для ввода энтропии в пул. Интересно, что эта функ ция достаточно интеллектуальна и игнорирует повторение символов при постоян ном нажатии клавиши, потому что повторяющиеся скан-коды и интервалы времени вносят мало энтропии.

Функция add_mouse_randoraness () использует позицию указателя мыши и интер валы времени между прерываниями для заполнения пула. Параметр mouse_data — это позиция указателя, которая возвращается аппаратным обеспечением.

Эти три функции добавляют передаваемые данные в пул энтропии, вычисляют оценку энтропии добавляемых данных и увеличивают оценку энтропии пула на вы численное значение.

Все эти экспортируемые интерфейсы используют внутреннюю функцию add_timer_randomness () для ввода данных в пул. Эта функция вычисляет интер валы времени между успешными событиями одного типа и добавляет эти значения в пул. Например, интервалы времени между успешными прерываниями жесткого дис ка достаточно случайны, особенно если измерять достаточно точно. Самые младшие биты — это обычно электрический шум. После того как эта функция вводит данные в пул, она вычисляет количественную характеристику того, насколько эти данные случайны. Это делается путем вычисления отклонения первого, второго и третье го порядка от предыдущего момента времени и изменения этих отклонений перво го, второго и третьего порядка. Наибольшее из этих отклонений, округленное до 12 бит, используется в качестве оценки энтропии.

Интерфейсы для вывода энтропии Для получения случайных чисел внутри ядра экспортируется один интерфейс.

void get_random_bytes(void *buf, int nbytes) Эта функция сохраняет nbytes случайных байтов в буфере памяти, на который указывает параметр buf. Функция возвращает данные, даже если оценка энтропии равна нулю. Для ядра это не так критично, как для пользовательских криптографи ческих программ. Случайные данные мало используются в ядре, в основном они нужны сетевой подсистеме для генерации стартового номера последовательности сегментов при соединении по протоколу TCP.

Код ядра может выполнить следующий код для получения случайных данных раз мером в одно машинное слово.

Генератор случайных чисел ядра unsigned long rand

;

get_random_bytes(&rand, sizeof(rand))

;

Для программ, которые выполняются в пространстве пользователя, предоставля ется два символьных устройства: /dev/random и /dev/urandom. Первое устройство, /dev/random, используется, когда необходимы гарантированно случайные данные для криптографических приложений с высоким уровнем безопасности. Это устрой ство выдает только то количество битов данных, которое соответствует оценке эн тропии в ядре. Когда оценка энтропии становится равной нулю, операция чтения устройства /dev/random блокируется и не возвращает данные, пока значение эн тропии не станет существенно положительным. Устройство /dev/urandom не имеет последней возможности, а в остальном работает аналогично. Оба устройства возвра щают данные из одного и того же пула.

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

, i...

unsigned long get_random(void) ( unsigned long seed = 0

;

int fd

;

fd = open("/dev/urandom", O_RDONLY)

;

if (fd == -1) { perror("open")

;

return 0

;

} if (read (fd, &seed, sizeof(seed)) < 0) { perror("read")

;

seed = 0

;

} if (close(fd)) perror("close")

;

return seed

;

} Можно также считать $bytes байтов в файл $file, используя программу del.

dd if=/dev/urandom of=$file count=l bs=$bytes 428 Приложение Б в Сложность алгоритмов компьютерных и связанных с ними дисциплинах полезно выражать сложность, или масштабируемость, алгоритмов с помощью количественных значащих ха В рактеристик (в отличие от менее наглядных характеристик, таких как быстрый или медленный). Существуют различные методы представления масштабируемости.

Один из наиболее часто используемых подходов — это исследование асимптотиче ского поведения алгоритмов. Асимптотическое поведение — это поведение алгоритма при достаточно больших значениях входных параметров или, другими словами, при стремлении входных параметров к бесконечности. Асимптотическое поведение пока зывает, как масштабируется алгоритм, когда его входные параметры принимают все большие и большие значения. Исследование масштабируемости алгоритмов, т.е. из учение свойств алгоритма при больших значениях входных параметров, позволяет смоделировать поведение алгоритма по отношению к тестовым задачам и лучше по нять особенности этого поведения.

Алгоритмы Алгоритм — это последовательность действий, возможно, с одним входом или более и, в конечном счете, с одним результатом или выходом. Например, подсчет количества людей в комнате представляет собой алгоритм, для которого люди, на ходящиеся в комнате, являются входными данными, а количество людей в комнате — выходными данными. Операции замещения страниц в ядре Linux или планирование выполнения процессов — это тоже примеры алгоритмов. Математически алгоритм аналогичен функции (или, по крайней мере, может быть смоделирован с помощью функции). Например, если мы обозначим алгоритм подсчета людей в комнате бук вой f, а количество людей, которых необходимо посчитать, буквой х, то функцию подсчета количества людей можно записать следующим образом.

y=f(х) В этом выражении буквой у обозначено время подсчета количества людей в ком нате.

Множество О Полезным обозначением асимптотического поведения функции является верхняя граница — функция, значения которой всегда больше значений изучаемой функции.

Говорят, что верхняя граница некоторой функции растет быстрее, чем рассматрива емая функция. Специальное обозначение "большого-О" используется для описания этого роста. Это записывается как f (х) О (g (х} ) и читается так: f принадлежит множеству "О-болыпого" от g. Формальное математическое определение имеет сле дующий вид.

Ес ли f ( x ) прина дле жит множе с т в у боль шог о O( g ( х ) ), т о Это означает, что время вычисления функции f(x) всегда меньше времени вы числения функции g (х), умноженного на некоторую константу, и это справедливо всегда, для всех значений х, больших некоторого начального значения х'.

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

Множество большого-тета Когда говорят об обозначении большого-О, то чаще всего имеют в виду то, что Дональд Кнут (Donald Knulh) описывал с помощью обозначения "большого-тета".

Обозначение "болыпого-О" соответствует верхней границе. Например, число 7 — это верхняя граница числа 6, кроме того, числа 9, 12 и 65— это тоже верхние границы числа 6. Когда рассматривают рост функции, то обычно наиболее интересна наимень шая верхняя граница или функция, которая моделирует как верхнюю, так и нижнюю границу'. Профессор Кнут описывает это с помощью обозначения большого-тета следующим образом.

Если f (х) принадлежит множеству большого-тета от g (x), то g(х) является одновременно и верхней и нижней границей f(х) Можно также сказать, что функция f (x) порядка функции g (х). Порядок, или множество "большого-тета" алгоритма, — один из наиболее важных математических инструментов изучения алгоритмов.

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

Если интересно, то нижняя граница описывается с помощью обозначения большого-омега.

Определение аналогично определению множества большого-О, за исключением того, что значе ния функции g(s) должны быть меньше значений функции f (х) или равны им.

430 Приложение В Объединяем все вместе Вернемся снова к подсчету количества людей в комнате. Допустим, что можно считать по одному человеку за секунду. Следовательно, если в комнате находится человек, то подсчет займет 7 секунд. Очевидно, что если будет n человек, то подсчет всех займет n секунд. Поэтому можно сказать, что этот алгоритм масштабируется, как О(n). Что если задача будет состоять в том, чтобы станцевать перед всеми, кто находится в комнате? Поскольку, независимо от того, сколько человек будет в комна те, это займет одно и то же время, значит, этот алгоритм масштабируется, как O(1).

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

Таблица В.1. Значения масштабируемости алгоритмов во времени O(g(x)) Название 1 Постоянная (отличная масштабируемость) log(n) Логарифмическая n Линейная n2 Квадратичная Кубическая n 2n Показательная, или экспоненциальная (плохо) n! Факториал (очень плохо) Как масштабируется алгоритм представления всех людей в комнате друг другу?

Какая функция может промоделировать этот алгоритм? Для представления одного человека необходимо 30 секунд, сколько времени займет представление 10 человек друг другу? Что будет в случае 100 человек?

Опасность, связанная со сложностью алгоритмов Очевидно, что будет разумным избегать алгоритмов, которые масштабируются, как О(n!) или О(2n). Более того, замена алгоритма, который масштабируется, как О(n), алгоритмом, который масштабируется, как O(1), — это обычно серьезное улуч шение. Тем не менее это не всегда так, и нельзя принимать решение вслепую, бази руясь только на описании "болыпого-О". Вспомните, что в определении множества О(g(х)) фигурирует константа, на которую умножается значение функции g(х).

Поэтому есть возможность, что алгоритм, который масштабируется, как O(1), будет выполняться в течение 3 часов. Следовательно, он будет выполняться всегда в те чение 3 часов, независимо от количества входных данных, но это может оказаться дольше, по сравнению с алгоритмом, который масштабируется, как О(n), при не большом количестве входных данных. При сравнении алгоритмов необходимо всег да принимать во внимание количество входных данных. Не стоит слепо оптимизи ровать для некоторого случайно выбранного варианта.

Сложность алгоритмов г Библиография и список литературы писок литературы отсортирован и содержит некоторые из самых интересных и полезных книг, которые близки по теме, или дополняют материал данной С книги.

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

Наилучшая ссылка на "дополнительное чтение", которая лучше всего дополняет материал данной книги — это исходный код ядра. Для работы с ОС Linux у нас есть неограниченный доступ к полному исходному коду ядра современной операционной системы. Не принимайте это как должное! Разберитесь с ним! Читайте код! Пишите код!

Книги по основам построения операционных систем В этих книгах рассмотрены принципы работы операционных систем в объеме учебных курсов. В них описываются основные понятия, алгоритмы и проблемы, свя занные с построением высокофункциональных операционных систем, а также реше ния указанных проблем. Все эти книги могут быть рекомендованы, но если нужно выделить одну, то это, конечно, книга Н. Deitel.

• Deitel H., Deitel P. and Choffnes D. Operating Systems. Prentice Hall, 2003.

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

• Tanenbaum Andrew. Operating Systems: Design and Implementation. Prentice Hall, 1997.

Хорошие начальные сведения об оснопах построения, принципах работы и ре ализации Unix-подобной операционной системы Minix.

• Tanenbaum Andrew. Modern Operating Systems. Prentice Hall, 2001. Детальный об зор стандартных проблем разработки операционных систем, а также обсужде ние многих концепций, которые используются в современных операционных системах, таких как Unix и Windows.

• Silberschatz A., Galvin P. and Gagne G. Operating System Concepts. John Wiley and Sons, 2001. Также известна, как "книга про динозавров", в связи с тем что на обложке нарисованы динозавры, которые не имеют никакого отношения к теме. Хорошее введение в основы построения операционных систем. Книга часто перерабатывается, но все издания должны быть хорошими.

Книги о ядрах Unix В этих книгах описываются принципы работы и особенности реализации ядер Unix. В первых пяти рассмотрены конкретные варианты Unix, в двух последних — общие моменты всех вариантов Unix.

• Bach Maurice. The Design of the Unix Operating System. Prentice Hall, 1986.

Обсуждение особенностей построения операционной системы Unix System V, Release 2.

• McKusick M., Bostic K., Karcls M. and Quarterman J. The Design and Implementation of the 4.4BSD Operating System. Addison-Wesley, 1996. Описание особенностей по строения и реализации операционной системы 4.4BSD от разработчиков этой системы.

• McKusick M. and Neville-Neil G. The Design and Implementation of the FreeBSD Operating System. Addison-Wesley, 2004. Основы построения операционной систе мы FreeBSD 5.

• Mauro J. and McDougall R. Solaris Internals: Core Kernel Architecture. Prentice Hall, 2000. Интересное обсуждение основных подсистем и алгоритмов работы ядра ОС Solaris.

• Cooper С. and Moore С. HP-UX Lli Internals. Prentice Hall, 2004. Обзор внутренне го устройства операционной системы HP-UX аппаратной платформы PA-RISC.

• Vahalia, Uresh. Unix Internals:The New Frontiers. Prentice Hall, 1995. Отличная кни га о возможностях современных Unix-подобных операционных систем, вклю чая управление потоками и вытеснением кода в режиме ядра.

• Schimmel Curt. UNIX Systems for Modern Architectures: Symmetric Multiprocessing and Caching for Kernel Programmers. Addison-Wesley, 1994. Прекрасная книга о пробле мах поддержки современных аппаратных платформ современными Unix-подоб ными операционными системами.

Книги о ядрах Linux В этих книгах, как и в текущей, рассказывается о ядрах Linux.

• Rubini A. and Corbet J. Linux Device Drivers. O'Reilly and Associates, 2001. Прекрасная книга о том, как писать драйверы устройств для ядер Linux серии 2.4.

434 Приложение Г • Bovet D. and Cesati M. Understanding the. Linux Kemel O'Reilly and Associates, 2002.

Обсуждение основных алгоритмов работы ядер Linux серии 2.4. Основное вни мание уделено основополагающим принципам функционирования ядра.

• Mosberger D. and Eranian S. IA-64 Linux Kernel: Design and Implementation. Prentice Hall, 2002. Отличная книга, посвященная аппаратной платформе Intel Itanium и ядру Linux серии 2.4 для этой аппаратной платформы.

Книги о ядрах других операционных систем Понимать врагов, точнее не врагов, а конкурентов, — никогда не повредит. В этих книгах обсуждаются основы работы и особенности реализации операционных си стем, отличных от операционной системы Linux. Смотрите, что у них хорошо, а что — плохо.

• Kogan M. and Deitel H. The Design of OS/2. Addison-Wesley, 1996. Интересный об зор операционной системы OS/2 2.0.

• Solomon D. and Russinovich M. Inside Windows 2000. Microsoft Press, 2000.

Интересный взгляд на операционную систему, которая чрезвычайно отличает ся от Unix.

• Richter Jeff. Advanced Windows. Microsoft Press, 1997. Описание низкоуровневого и системного программирования под ОС Windows.

Книги по API Unix Детальное описание системы Unix и API этой операционной системы важно не только для того, чтобы писать мощные прикладные программы, но и для понимания того, что требуется от ядра.

• Stevens W. Richard. Advanced Programming in the UNIX Environment. Addison-Wesley, 1992. Отличное, если не самое полное, обсуждение интерфейса системных вы зовов Unix.

• Stevens W. Richard. UNIX Network Programming, Volume 1. Prentice Hall, 1998.

Классический учебник по API сокетов операционной системы Unix.

• Johnson M. and Troan E. Linux Application Development. Addison-Wesley, 1998.

Общий обзор операционной системы Linux и интерфейсов, которые специ фичны для этой операционной системы.

Другие работы Книги, которые не посвящены операционным системам, но имеют к ним прямое отношение.

• Knuth Donald. The Art of Computer Programming, Volume 1. Addison-Wesley, 1997.

Бесценный курс по фундаментальным алгоритмам и теории вычислительных систем, который включает лучшие и не самые лучшие алгоритмы управления памятью. (Имеется русский перевод: Кнут Дональд Эрвин. Искусство програм мирования. Том 1. Основные алгоритмы, 3-е издание. - М: "Вильяме", 2000.) Библиография и список литературы • Kernighan В. and Ritchie D. The С Programming Language. Prentice Hall, 1988.

Наилучшая книга по языку программирования С. (Имеется русский перевод:

Брайан Керниган, Деннис Ритчи. Язык программирования С— М: "Вильяме", 2005 г.) • Hofstadter Douglas. Godel, Escher, Bach: An Eternal Golden Braid. Basic Books, 1999.

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

Web-сайты Эти WWW-сайты предоставляют последние новости и другую информацию, свя занную с операционной системой Linux и ее ядром.

• Kernel Traffic. Отличный обзор сообщений в списке рассылки разработчи ков ядра Linux (lkml) за последнюю неделю (очень рекомендуется), ht t p: / / www.kerneltraffic.org/ • Linux Weekly News. Хороший сайт новостей о том, что произошло касательно ядра Linux за последнюю неделю, с прекрасными комментариями (очень реко мендуется), http://www.lwn.net/ • Kernel Newbies. Сайт "Kernel Newbies" — это проект сообщества разработчиков с целью предоставления информации и помощи начинающим хакерам, которые стремятся заниматься разработкой ядра. http://www. kernelnewbies -org/ • Kernel.org. Официальный архив исходных кодов ядра. Здесь также находятся до машние страницы многих разработчиков основных подсистем ядра с соответ ствующими заплатами http://www.kernel.org/ • KernelTrap. Сайт, посвященный всему, что связано с ядром операционной систе мы, с большим уклоном в сторону ядра Linux. На сайте много информации о новостях и обзорах из области разработки ядра Linux. Здесь также в большом количестве размещаются интервью с ведущими разработчиками ядра. ht t p: // www.kerneltrap.org • OS News. Новости об операционных системах, а также статьи, интервью и обзо ры из этой же области. http://www.osnews.com/ • Сайт, посвященный этой книге. Новости, сообщения об ошибках и другая инфор мация, которая касается этой замечательной книги. http://tech9.net/rral/ kernel_book/ 436 Приложение Г Предметный указатель test_and_set_bit(), А test_bit(), Application Programing Interface, API, отладка, тип atomic_t, E целочисленные, Exception, 99

;

atomic_add(), atomic_add_negative(), G atomic_dec_and_test(), gec, atomic_inc(), аннотация ветвлений, atomic_inc_and_test(), встроенный ассемблер, atomic_read(), функции с подстановкой тела, atomic_set(), Granularity, atomic_sub(), atomic_sub_and_test(), L Linux Kernel Mail List, Б lkml, Бинарное дерево, базисное, P красно-черное, POSIX, Блок, Блокировки S advisory, IBS SHA, 425 dcache_lock, SMP-привязка, 71 deadlock, deadly embrace, T dentry->d_lock, inode_lock, Task, lock contention, Translation lookaside buffer, TLB, mmlist_lock, page_table_lock, V sell-deadlock, Virtual memory area, voluntary, VMA, xtime_lock, большая блокировка ядра (BKL), A lock_kernel(), Адресное пространство unlock_kernel(), плоское, захват, процесса, SI 1

;

освобождение, сегментированное, взаимоблокировка, структура типа AВВА, address_space, 297

;

в обработчиках нижних половин, address_space_operations, защита данных, Алгоритм, конфликт при захвате, 168

;

Аппаратная платформа, навязываемые, Атомарные операции, необязательные, битовые, обязательные, change_bit(), порядок захвата, clear_bit(), преемптивность, set_bit(), применение, test_and_change_bit(), рекомендуемые, test_and_clear_bit(), рекурсивность, 185 в обработчиках нижних половин, самоблокировка, 172

;

189 записи, секвентные, 199

;

захват, read_seqbegin(), 199 захват на запись, read_seqretry(), захват на чтение, write_seqlock(), инициализация, 187

;

write_sequnlock(), использование,189

;

захват на запись, освобождение, проверка чтения, отладка, 186

;

семафоры, 184

;

преемптивность, DECLARE_MUTEX(), 193 проверка, DECLARE_RWSEM(), 195 проверка состояние, down(), 192

;

чтения, down_interruptible(), 193 структуры down_read(), completion, down_write(), rw_semaphore, init_MUTEX(), semaphore, init_rwsem(), тупиковая ситуация, sema_init(), 193 условные переменные, up(), complete(), up_read{), wait_for_completion(), up_write(), 195 Блочное устройство бинарные, 192 RAID, захват, очередь запросов, захват на запись, 195 сегмент, захват на чтение, 195 структура инициализация, 193

;

194 bio, инкремент, bio_vec, использование, 196 buffer_head, 296

;

мьютекс, request, освобождение, 194 request_queue, особенности, 191 Буфер, осовобождение, быстрого преобразования адреса (TLB), проверка, 192 заголовок, реализация, 190 кольцевой, счетчик, 192 сообщений ядра, состояние, чтения-записи, состояние конфликта, спин-блокировки, 183 В read_lock(), Ввод-вывод read_unlock(), страничный, spin_is_locked(), Виртуальный ресурс spin_lock(), память, 45

;

spm_lockbh(), процессор, spin_lock_init(), Возможность использования, spin_lock_irq{), CAP_SYS_TIME, spin_lock_irqsave(), Время spin_try_lock(), абсолютное (wall time), 207

;

208

;

spin_unlock(), начало эпохи (epoch), spin_unlock_bh(), операции spin_unlock_irq{), gettimeofday(), spin_unlock_irqrestore(), settimeofday(), write_lock(), относительное, write_unlock(), time_after(), Предметный указатель time_after_eq(), И time_before(), Инсталляция time_before_eq(), модулей, 38

;

работы системы (uptime), ядра, часовой пояс (time zone), Исключительная ситуация, 99

;

Выравнивание, Истинно случайные числа, естественное, массивов, К нестандартных типов данных, Кен Томпсон, объединений, Кластер, переменных, Клод Шеннон, полей структур, Код ядра проблемы, дерево, структур, заплаты, Вытеснение, 66

;

инсталляция, конфигурация, г make config, Генератор случайных чисел, make defconfig, Головка, make gconfig, Гранулярность, make menuconfig, cource, make oldconfig, fine, make xconfig, уровень крупных структурных единиц, переменные, уровень мелких структурных единиц, файл.config, получение, сборка, д Дайджест сообщения, параллельная, Демон Константа klogd, BIG_ENDIAN, kupdated, LITTLE_ENDIAN, pdflush, 297

;

337

;

BITTS_PER_LONG, diity_background_ratio, HZ, dirty_expire_centisecs, PAGE_SHIFT, dirty_rado, PAGE_SIZE, dirty_writeback_centisecs, Конфигурация laptop_mode, CONFIG_PREEMPT, предотвращения зависания, CONFIG_SMP, syslogd, Кэш Деннис Ритчи, буферный, Джон фон Нейман, дисковый, Дональд Кнут, страничный, Л Задание, Линус Торвальдс, Задача, Заплата м генерация, Масштабируемость, 71

;

представление, О(1),419

;

применение, О(n),421

;

утилиты алгоритмов, diff, Машинное слово, diffstat, Многозадачность patch, preemptive, Заполнение структур, вытесняющая, Предметный указатель кооперативная, 66 индекс, преемптивная, использование, Множество отдообработчик, большого-О, регистрация, болыного-тета, тасклет, Модуль структура загружаемый, softirq_action, depmod, tasklet_struct, 141

;

EXPORT_SYMROL(), 353 тасклет, 134

;

139

;

169

;

EXPORT_SYMBOL_GPL(), 353 DECLARE _TASKLET(), insmod, 348 DECLARE_TASKLET_DISABLED(), Makefile, tasklet_disable(), make modules_install, tasklet_disable_nosync(), modprobe, 348 tasklet_enable(), MODULE_AUTHOR(), 345 tasklet_hi_scbedule(), module_exit(), 344 tasklet_init(), module_init(), 344 tasklet_kill(), MODULE_LICENSE(), 345

;

354 tasklet_schedule(), 142

;

обработчик, module_param(), планирование, 142

;

module_param_array(), реализация, module_param_array_named(), создание, module_param_named(), module_param_string, MODULE_PARM_DESC(), 353 О rmmod, Область памяти, 311

;

зависимости, деревья, загрузка, интервал адресов, инсталляция, операции, конфигурация, close(), параметры, nopage(), разработка, open(), сборка, populate{), экспорт символов, права доступа, списки, н структура Наименьшая верхняя граница, 430 vm_area_struct, Неатомарные операции vm_operations_struct, битовые, флаги, find_first_bit(), защиты, find_first_zero_bit(), 183 Объект sched_find_first_bit(), 75 kobject, Нижние половины, 132

;

187 kobject_get(), bottom half, 134 kobject_init(), ]ocal_bh_disable(), kobject_put(), local_bh_enable(), kobject_set_name(}, softirq, 135

;

декларация, запрещение, захват, :

интерфейс ВН, 134

;

инициализация, отложенное прерывание, 134

;

169

;

освобождение, open_softirq(), присвоение имени, raise_softirq(), счетчик ссылок, выполнение, 137 Операционная система вытеснение, 137 Linux, генерация,137 Multics, Unix, демон ksoftirqd, 138

;

Предметный указатель AT&T, 23 файла, 322

;

BSD, 24 частное, особенности, 24 Очередь ожидания add_wait_queue(), многозадачная, DECLARE_WAIT_O_UEUE_HEAD(), определение, remove_wait_queue(), с виртуальной памятью, Отладка BUG(), 382 п BUG_ON(), Память dump_stack(), MMU, Memory Management Unit, атомарные операции, адрес, генерация ошибок, верхняя исследование и тестирование, отладка, конфигурационные параметры, переносимость, магическая клавиша SysRq, виртуальная, 234

;

ограничение частоты событий, выделение утилита ksymoops, alloc_percpu(), функция kallsyms, get_free_pages(), Отладчик alloc_page(), gdb, alloc_pages, kdb, alloc_percpu(), kgdb, DEFINE_PER_CPU(), Отложенное действие, get_zeroed_page(), cancel_delayed_work(), gfp_mask, 238

;

create_workqueue(), kmalloc(), DECLARE_WORK(), kmap(), flush_scheduled_work{), kmap_atomic(), flush_workqueue(), page_address(), INIT_WORK{), vmalloc(), queue_work{), виртуально непрерывный участок, run_workqueue(), временное отображение, schedule_delayed_work(), высокоуровневое, schedule_work{), контекст процесса, work_handler(), модификаторы зоны, work queue, модификаторы операций, демон keventd, нижняя половина, использование, низкоуровневое, ожидание завершения, обработчик прерывания, очереди заданий, 134

;

отображение верхней памяти, планирование, постоянное отображение, рабочий поток, связанная с процессором (per-CPU), реализация, слябовый распределитель (slab layer, slab создание, allocator), структура списки свободных ресурсов, cpu_workqueue_struct, стек ядра, work_struct, физически непрерывный участок, 238

;

workqueue_struct, 240

;

Отображение флаги типов, анонимное, 312

;

дескриптор, выполняемого кода, выделение, инициализированных переменных, счетчик использования, области ввода-вывода, удаление, отладка, защита, совместно используемое, 318

;

зоны, страницы памяти, заполненной нулями, Предметный указатель ZONE_DMA, 235 Параллелизм ZONE_HIGHMEM, 235 pseudo-concurrency, ZONE_NORMAL, 235 race condition, безопасность верхняя память (high memory), 234

;

нижняя память (low memory), 236 при SMP-обработке, кэширование, 263 при прерываниях, область, 311 защита данных, освобождение истинный, конкурентный доступ, _free_pages(), критический участок, free_page(), многопоточность, free_pages(), многопроцессорность, free_percpu(), псевдопараллелизм, kfree(), симметричная многопроцессорность, kunmap(), 163

;

kunmap_atomic(), 259 синхронизация, vftee(), прямой доступ (ПДП, DMA), 235 состояние "гонок", связанная с процессором состояние конкуренции за ресурс, 42

;

get_cpu_ptr(), 262 Переключение контекста, get_cpu_var(), 260 Переносимость, 43

;

put_cpu_ptr(), 262 Планирование put_cpu_var(), 260 передача управления, слябовый распределитель, 47 с динамическим управлением по kmem_cache_alloc(), 254 приоритетам, kmem_cache_create(), 252 с управлением по приоритетам, kmem_cache_destroy{), 253 Планировщик, NUMA, 0(1), 66

;

71

;

дескриптор сляба, 250 балансировка нагрузки, кэш, 249 битовая маска приоритетов, отладка, 381 ввода-вывода, сляб (slab), 249 CFQ, стек deadline, проверка переполнения, 381 noop, процесса, 312 задержки обслуживания, ядра, 42

;

256 лифтовый алгоритм, страница, 233 объединение, page_count(), 234 прогнозирующий, виртуальный адрес, 234 слияние, вытеснение, 234 сортировка, гигантская, 318 с лимитом по времени, данные буфера, 297 с отсутствием операций, измененная, 337 с полностью равноправными копирование при записи, 54 очередями, нулевая, 312 массив приоритетов, размер, 402 активный, флаги, 234 истекший, страничный кэш, 235 очередь выполнения, 72

;

структура реального времени, kmem_cache_s, 250 стратегия, mm_struct, 313 SCHED_FIFO, page, 234

;

SCHED_RR, slab, 250 структура vm_area_struct, 316

;

319 prio_array, zone, 237 runqueue, физическая, 234 Подсистема, 442 Предметный указатель Порядок выполнения, 180 описание, барьер, 403 освобождение, барьеры, 180

;

202 регистрация, реентерабельность, barrier(), mb(), 203 совместно используемый, 113

;

read_barrier_depends(), 203 реализация обработки, управление, rmb(), smp_mb()(204 cli(), smp_rmb(), 204 disable_irq(), smp_wmb(), 204 disable_irq_nosync(), wmb(), 203 enable_irq(), записи памяти, 203 in_interrupt(), компилятора, 204 in_irq(), чтения памяти, 202 irq_disabled(), переносимость, 403 local_irq_disable(), Порядок следования local_irq_enable(), be32_to_cpu(), local_irq_restore(), cpu_to_be32(), local_irq_save(), cpu_to_le32(), sti(), le32_to_cpus(), synchronize_irq(), big-endian, запрещение, little-endian, разрешение, байтов, флаг определение,400 SA_INTERRUPT, 113

;

обратный, 399 SA_SAMPLE_RANDOM, 113

;

прямой,399 SA_SHIRQ, 113

;

Поток, 45

;

57

;

315 Пространство задачи, пространства ядра, 59

;

пользователя, kernel_thread(), ядра, 27

;

Преемптивность, 164

;

Процесс данные связанные с процессорами, запрещение, 200 I/O-bound, preempt_disable(), 201 ink, preerapt_enable{), 201 parent, переносимость, 403 processor-bound, счетчик preempt._count, 89

;

runnable, Прерывание, 27

;

109

;

169

;

timeslice, /proc/interrupts, wake_up(), do_IRQ(), адресное пространство, 51

;

handler, вытеснение, interrupt request line, 110 пространства пользователя, interrupt service routine, 111 пространства ядра, IRQ, 110 готовый к выполнению, ret_from_intr(), 123 дескриптор, 46

;

верхняя половина, 111

;

создание, контекст, 111

;

удаление, линия запроса, завершение, нижняя половина, 111

;

идентификатор, обработчик, иерархия, add_interrupt_randomness(), 122 квант времени, 66

;

69

;

free_irq(), 114 контекст, 52

;

request_irq(), 112 корневой каталог, RTC, 117 макрос current, shared, 116 не готовый к выполнению, быстрый, 113 ограниченный скоростью ввода-вывода, Предметный указатель ограниченный скоростью процессора, 67 list_del(), операции list_del_init(), wakc_up(), 232 list_cmpty(), определение, list_cntry(), парамтер list_for_each(), nice, 68 fist_for_each_prev(), переназначение родительского процесса, 61 list_for_each_safe(), порожденный, list_move(), приоритет, 68

;

list_move_tail(), пространство имен (namespace), 291. iist_splice(), родительский, 46 list_splice_init(), создание, 53 перемещение, 416

;

состояние, 5() структура элемента, Сегмент.

sel_current_stale(), bss, set_task_state(), данных, sleep, TASK_UNTERRUFTIBLE, 50

;

81

;

230 кода, 312..

TASK_RUNNING, 50

;

70 Сектор, TASK_STOPPED, 51 размер, TASK_UNINTERRUPTIBLE, 51

;

81

;

230 Система, TASK_ZOMBIE, 51 Системный вызов, ожидания, 81 errno, заблокированное, 81 errnosys_call_table, ожидания, 170 getpid(), структура int $0x80, ioctl(), 101

;

task_struct, mmap(), thread_info, mmap2(),, текущий каталог. munmapO, трассировка, sys_ni_syscall(), флаг syscall, need_resched, 83

;

syscall(), Псевдослучайные числа, Пул энтропии, доступ из пространства пользователя, операции модификатор asmlinkage, add_inlerrupl_randomness(), 426 номер, обработчик, add_keyboard_randomness(), передача параметров, add_mouse_randomness(), планировщика, get_random_bytes{), nice(), р sched_get_priority_max{), sched_get_priority_min(), 91

;

Режим ноутбука, sched_getaffinity(), sched_getcheduler(), С sched_getparam(), Сборка schcd_getscheduler(), 91.

модулей, sched_rr_get_interval(), Связанный список, 320

;

sched_setaffinity(), головной элемент, sched_setparam(), двухсвязный, sched_setscheduler(), инициализация, sched_yield(),91

;

кольцевой, производительность, однослязный, процессорной привязки операции schcd_getaffinity(), list_for_each(), schcd_sctaffinity(), list_add(), реализация, list_add_tail(), Предметный указатель регистрация,104 временная отметка (tick), файле entry.S, 98

;

105 гранулярность, Сообщение декрементный счетчик, динамический, 208

;

Oops, уровень вывода, 376 обработчик, Сообщество разработчиков, 32

;

405 задержки, maintainers, 412 BogoMIPS, ответственные разработчики, 412 mdelay(), отправка сообщений об ошибках, 412 udelay(), список рассылки, 32

;

Pages:     | 1 |   ...   | 7 | 8 || 10 |



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.