#c #linux #shared-libraries
#c #linux #общие библиотеки
Вопрос:
У меня есть общий объект gateway.so (в Linux / C). И приложение.out использует его.
ВОПРОС A
Я предполагаю: при запуске процесса a.out загрузчик загружает gateway.so (Я не использую такие функции dl, как dlopen
). Таким образом, все разрешения символов времени выполнения передаются шлюзу.так будет происходить в памяти. Ему не нужно обращаться gateway.so с диска больше нет.
Я прав?
Поэтому я не могу заменить шлюз.итак, с обновленной версией, пока выполняется .out, верно?
ВОПРОС B
Еще один связанный с этим вопрос: однажды, когда я заменил устаревшую версию gateway.so файл, я получил сообщение
«a.out: не удается разрешить символ ‘Test_OpenGateway'»
Какой программный компонент (загрузчик / компоновщик …) отправляет этот вывод? Этот компонент выполняется как часть того же контекста процесса?
Комментарии:
1. @all ответчик, если возможно, пожалуйста, предоставьте некоторые доказательства вашего ответа в этом случае…
Ответ №1:
Вопрос A
Вы можете заменить библиотеку во время ее использования приложением, если вы сделаете это правильно.
Прежде чем мы туда доберемся, давайте посмотрим на двоичный файл основной программы. Вот пример программы:
#include <unistd.h>
void justsit(void) {
for (;;) {
sleep(1);
}
}
int main(int argc, char **argv) {
printf("My PID is %dn", getpid());
justsit();
return 0;
}
Скомпилируйте и запустите его:
$ gcc -Wall -o example example.c
$ ./example
My PID is 4339
Теперь он просто останется там, поэтому откройте новый терминал для этого:
$ gcc -Wall -o example-updated example.c
$ cp example-updated example
cp: cannot create regular file `example': Text file busy
Что произошло сейчас? Ядро отказалось изменить пример файла, потому что у него есть процесс, который запускает этот файл.
Теперь давайте попробуем удалить его:
$ rm example
Что? Это сработало? Почему файл может быть удален, но не заменен? Да, или, скорее, файл на самом деле не был удален, только «имя», ядро сообщает файловой системе сохранить содержимое файла. Когда файл больше не открывается, содержимое также удаляется. (dentry немедленно удаляется, а inode освобождается, когда у него нет пользователей, как сказали бы люди файловой системы)
Это можно увидеть в /proc : (вот почему программа печатает свой PID, чтобы вы могли легко это проверить)
$ readlink /proc/4339/exe
/tmp/t/example (deleted)
Во всяком случае. Тот факт, что это работает так, означает, что можно безопасно обновить программу, удалив старый двоичный файл и поместив новый на то же место. Для этого есть программа: install(1) .
Хорошо, вернемся к вашему вопросу — общие объекты.
Давайте разделим пример на две части: main.c и shared.c:
/* main.c */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
void justsit(void);
int main(int argc, char **argv) {
printf("My PID is %dn", getpid());
justsit();
return 0;
}
и
/* shared.c */
#include <stdio.h>
#include <unistd.h>
void justsit(void) {
for (;;) {
sleep(1);
}
}
Скомпилируйте их следующим образом:
$ gcc -Wall --shared -o libshared.so shared.c
$ gcc -Wall -L. -o main main.c -lshared
Теперь, надеюсь, если мы попытаемся заменить libshared.so мы получили бы аналогичную ошибку «Текстовый файл занят»? Давайте посмотрим. Сначала запустите основную программу — текущий каталог не находится в пути поиска библиотеки, поэтому попросите динамический компоновщик выполнить поиск там:
$ LD_LIBRARY_PATH=. ./main
My PID is 5697
Перейдите на другой терминал и замените библиотеку чем-то явно сломанным:
$ echo "junk" > libshared.so
$
Во-первых, это не было отклонено, как замена двоичного файла программы.
И в другом терминале произошло что-то интересное, программа перестала работать со следующим сообщением об ошибке:
Segmentation fault
$
Таким образом, НЕ запрещается заменять библиотеку, используемую программой! Но, как видно из приведенного выше примера, это может иметь катастрофические последствия.
К счастью, тот же «трюк», который использовался для замены работающего двоичного файла, можно использовать для замены используемой библиотеки. Перезапустите основную программу (не забудьте перекомпилировать libshared.so тоже, поскольку это было заменено мусором) и посмотрите, как безопасно выполнять rm в библиотеке. можно проверить /proc/PID/maps, чтобы увидеть, какие общие объекты использует процесс:
$ cat /proc/5733/maps | grep libshared.so
008a8000-008a9000 r-xp 00000000 08:01 2097292 /tmp/t/libshared.so
008a9000-008aa000 r--p 00000000 08:01 2097292 /tmp/t/libshared.so
008aa000-008ab000 rw-p 00001000 08:01 2097292 /tmp/t/libshared.so
$ rm libshared.so
$ cat /proc/5733/maps | grep libshared.so
008a8000-008a9000 r-xp 00000000 08:01 2097292 /tmp/t/libshared.so (deleted)
008a9000-008aa000 r--p 00000000 08:01 2097292 /tmp/t/libshared.so (deleted)
008aa000-008ab000 rw-p 00001000 08:01 2097292 /tmp/t/libshared.so (deleted)
Основная программа продолжает работать нормально. Опять же, это связано с тем, что с диска было удалено только имя (dentry), а не фактическое содержимое (индекс). После удаления можно безопасно создать новый файл с именем libshared.so не влияя на запущенную программу.
Итак, подведем итог — просто используйте команду install для установки программ и двоичных файлов.
Вопрос B
Да, это печатается динамическим компоновщиком в пользовательском пространстве.
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv) {
execl("./main", "main", NULL);
printf("exec failed?n");
return 0;
}
Скомпилируйте его с gcc -Wall -o execit execit.c
помощью . Помните, что execl
это заменяет текущий процесс указанной командой.
$ ./execit
main: error while loading shared libraries: libshared.so: cannot open shared object file: No such file or directory
$ rm main
$ ./execit
exec failed?
Что произошло и о чем это нам говорит? Сначала есть error while loading shared libraries
without exec failed?
. Отсутствие «ошибка выполнения» означает, что процесс был успешно заменен. Это означает, что ядро передало управление динамическому компоновщику, который потерпел неудачу. После удаления «main» он завершается преждевременным сбоем, и процесс не заменяется.
Комментарии:
1. Я просто хочу прокомментировать, что это фантастический ответ из-за объяснения того, что происходит за экраном. Но я хотел бы попросить дополнительное разъяснение: so
rm
отсоединяет файлы от dentry, но не от inode. В какой момент файл полностью удаляется? Это когда приложение остановлено (т.е. /proc/<PID>/ … удаляется)? Или мы начинаем использовать больше места в файловой системе с каждымrm
— я пытаюсь выяснить, когда файл будет окончательно и полностью удален. Спасибо!2. @code_fodder Я думаю, что современные файловые системы Unix выполняют подсчет ссылок. При завершении процесса это последняя ссылка на файл, поэтому файл освобождается.
3. По
Text file busy
какой-то причине ошибка больше не выдается в современных версиях ядра.4. Я думаю, что причина, по которой произошел сбой при замене
.so
файла, заключается в том, что он еще не был загружен. Если он был загружен, то изменение файла на диске не повлияет ни на один процесс, который его уже загрузил.
Ответ №2:
Нет, файл все равно может потребоваться прочитать с диска после того, как компоновщик среды выполнения ( ld.so
) отобразил его в адресное пространство процесса. Это сопоставление происходит с помощью mmap(2)
системного вызова и флага PROT_EXEC
, разрешающего выполнение.
Карта не помещает весь файл в память после его отображения, но фактически создает область памяти, которая будет вызывать ошибку страницы по требованию, если запрошенная часть памяти еще не скопирована, и эта ошибка страницы обрабатывается в пространстве ядра путем чтения с соответствующим смещением в файле.
Что касается второго вопроса, на это жалуется компоновщик среды выполнения ( ld.so
). Загружаемый код ld.so
выдается как код запуска программы компоновщиком времени компиляции ( ld
), поэтому он выполняется в пространстве пользователя, прежде main
чем вызывается.
Комментарии:
1. если возможно, пожалуйста, предоставьте некоторые доказательства / документацию по этой теме
2. Лучшим доказательством должна быть попытка удалить
gateway.so
файл во время выполнения программы. Поскольку этоmmap
-ped, ядро должно было заблокировать его и не должно разрешать удаление.3. Я могу удалить шлюз. поэтому, когда основная программа запущена, а .out продолжает работать без проблем. Но в следующий раз, когда я запускаю .out, я получаю сообщение об ошибке «не удается загрузить библиотеку»
4. (Комментарий выше: термином «удалить gateway.so » , я на самом деле переименовал gateway.so . Не удалено)
5. Это может быть «удачным» случаем, если ядро решит, что все области
mmap
-ped короткие и могут быть непосредственно помещены в память, оно может безопасно разблокировать файл, но в принципе идеяmmap(2)
заключается в реализации подкачки по запросу .
Ответ №3:
Для A: Действительно, как только общая библиотека отображается в памяти, вы больше не можете ее заменить. Может быть даже, что система уже загрузила предыдущую версию библиотеки для какого-то другого процесса и обнаруживает, что so уже отображен в память, и переназначает его как часть процесса запуска. Вот почему вам всегда нужно перезапускать (даже * nixes) после критических обновлений 😉
К B: символы, используемые исполняемым файлом, записываются в таблицу символов в двоичном файле. Системный загрузчик сканирует эту таблицу и пытается разрешить адреса требуемой функции. Если он не может его найти, вы получите эту ошибку. Итак, ответ заключается в том, что сообщение создается загрузчиком динамических ссылок.
Ответ №4:
a. Правильно. В этом случае вы должны работать с dl_*()
материалом и закрыть файл как можно скорее.
b. Если вы замените указанный файл, и он не содержит требуемого символа, загрузка завершится неудачно, и вы получите указанную ошибку.