Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

На главную -> MyLDP -> Программирование и алгоритмические языки


Ulrich Drepper "Как писать разделяемые библиотеки"
Назад Оглавление Вперед

3.5. Обработка совместимых изменений (GNU)

Два основных варианта, позволяющие обрабатывать совместимые изменения, с помощью которых расширяется функциональность уже существующих интерфейсов и добавляются новые интерфейсы, аналогичны, но не одинаковы. И нам потребуется слегка различный код для использования в Linux/Hurd и для использования в Solaris. Чтобы проиллюстрировать изменения, мы расширим пример, приведенный в разделе 2.2. Функции index в том виде, как она определена, не может обрабатывать отрицательные параметры. Версия, в которой исправлен этот недостаток, может делать все, что может делать старая реализация, но не наоборот. Поэтому приложения, использующие новый интерфейс, не должны запускаться в случае, когда имеется только старый объект DSO. Предполагается, что в качестве второго варианта будет предложено определение новой функции indexp1. Когда используется Linux/Hurd, код будет выглядеть следующим образом:

static int last;

static int next (void) {
	return ++last;
}

int index1__ (int scale) {
	return next () < (scale>0 ? scale : 0);
}
extern int index2__ (int)
   __attribute ((alias ("index1__")));
asm(".symver index1__,index@VERS_1.0");
asm(".symver index2__,index@@VERS_2.0");

int indexp1 (int scale) {
	return index2__ (scale) + 1;
}

Здесь нужно объяснить несколько моментов. Во-первых, мы больше явно не определяем функцию index. Вместо этого, определяется функция index1 (обратите внимание на завершающие символы подчеркивания; предваряющие символы подчеркивания зарезервированы для реализации). Эта функция определяется с новой семантикой. Объявление extern, следующее за определением функции, на самом деле является определением алиаса index1. Для этого здесь используется синтаксис gcc. Есть другие способы для того, чтобы это выразить, но только в этом случае используются конструкции языка C, которые видны компилятору. Причину, по которой было добавлено определение этого алиаса, можно найти в следующих двух строчках. Здесь добавляется "магия", определяющая символ с контролем версий для более чем одного определения. Для определения алиаса символа, в котором указывается официальное название, символ @ или символы @@ и имя версии, используется ассемблерная превдооперация .symver. Имя алиаса будет именем, используемым для доступа к символу. Это должно быть точно такое же имя, которое используется в исходном коде, в нашем случае — это index. Имя версии должно соответствовать имени, используемому в файле таблицы символов (смотрите пример в предыдущем разделе).

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

В этом примере мы определяем обе версии символа для использования в том же самом коде. Мы могли бы так же просто подправить и сохранить старое определение функции и добавить как новое. Это бы увеличило размер кода, но мы бы получили точно такой же интерфейс. Код, который вызывает старую версию, index@VERS 1.0, был бы причиной неопределенного поведения для старого DSO, а теперь он вернет то же самое, что и вызов index@@VERS 2.0. Но поскольку такой вызов все равно неправильный, не следует ожидать, что в интерфейсе ABI в этом отношении ничего не изменится.

Поскольку в этом коде добавено имя новой версии, должен также измениться файл таблицы символов.

VERS_1.0 {
	global: index;
	local: *;
};

VERS_2.0 {
	global: index; indexp1;
}   VERS_1.0;

Здесь важно отметить то, что index упоминается в более чем одной из версий, indexp1 появляется только в VERS 2.0, определения local: есть только в определении VERS 1.0 , а определение VERS 2.0 ссылается на VERS 1.0. Первый факт должен быть очевиден: нам нужно, чтобы былы две версии функции index , это все, что сообщается в коде. Второй факт также легко понять: indexp1 является новой функцией, которой не было на момент выпуска DSO версии 1. В исходных кодах не нужно помечать определение indexp1 с использованием имени версии. Поскольку есть только одно определение, компоновщик в состоянии справиться с этим самостоятельно.

Отсутствие local: * может показаться несколько удивительным. В определении VERS 2.0 вообще отсутствует local: . А как насчет внутренних символов, добавляемых в DSO версии 2? Чтобы разобраться с этим, нужно обратить внимание, что всем символам, помеченным в определении версии как local:, имя версии не присваивается. Они получают специальное внутреннее имя версии, которое присваивается всем локальным символам. Таким образом, local: можно было вставить - результат был бы аналогичным. Дублирование local: * могло бы сбить компоновщик с толку, поскольку теперь мы имеем две версии. Можно без проблем явно пометить новые добавляемые локальные символы как local:, но обычно в этом нет необходимости, поскольку всегда есть еще один вариант.

Четвертый момент состоит в том, что то, что в определении VERS 2.0 на версию VERS 1.0 делается ссылка, на самом деле не является чем-то действительно важным в системе контроля версий символов. Здесь помечается отношение предшествования двух версий и это делается для обеспечения схожести с внутренней системой контроля версий, имеющейся в Solaris. Никаких проблем из-за этого не возникает, но это удобно при чтении, поскольку предшественники должны всегда упоминаться.


Предыдущий раздел:   Следующий раздел:
Назад Оглавление Вперед