Текст книги "Основы программирования в Linux"
Автор книги: Нейл Мэтью
Соавторы: Ричард Стоунс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 41 (всего у книги 67 страниц)
Устранение ошибок использования памяти
Распределение динамической памяти – богатый источник ошибок, которые трудно выявить. Если вы пишете программу, применяющую функции malloc
и free
для распределения памяти, важно внимательно следить за блоками, которые вы выделяете, и быть уверенным в том, что не используется блок, который вы уже освободили.
Обычно блоки памяти выделяются функцией malloc
и присваиваются переменным-указателям. Если переменная-указатель изменяется, и нет других указателей, указывающих на блок памяти, он становится недоступным. Это утечка памяти, вызывающая увеличение размера программы. Если вы потеряете большой объем памяти, скорость работы вашей системы, в конце концов, снизится, и система уйдет за пределы памяти.
Если вы записываете в область, расположенную после конца выделенного блока (или перед началом блока), вы с большой долей вероятности повредите структуры данных, используемые библиотекой malloc, следящей за распределением памяти. В этом случае в какой-то момент времени вызов malloc
или даже free
приведет к нарушению сегментации, и ваша программа завершится аварийно. Определение точного места возникновения сбоя может оказаться очень трудной задачей, поскольку нарушение могло возникнуть задолго до события, вызвавшего аварийное завершение программы.
Неудивительно, что существуют коммерческие и бесплатные средства, способные помочь в решении проблем этих двух типов. Например, есть много разных версий функций malloc
и free
, которые содержат дополнительный код для проверки выделения и освобождения блоков памяти и пытаются учесть двойное освобождение блока и другие типы неправильного использования памяти.
Библиотека ElectricFence была разработана Брюсом Перенсом (Bruce Perens). Она доступна как необязательный компонент в некоторых дистрибутивах Linux, таких как Red Hat (Enterprise и Fedora), SUSE и openSUSE, и может быть легко найдена в Интернете. Это средство пытается применять виртуальную память системы Linux для защиты памяти, используемой функциями malloc
и free
, и аварийного останова программы в момент повреждения памяти.
Выполните упражнение 10.3.
Упражнение 10.3. Применение библиотеки ElectricFence
Далее приведена программа efence.c, которая выделяет память с помощью функции malloc
и пишет данные за концом выделенного блока. Познакомьтесь с ней и посмотрите, что произойдет.
#include
#include
int main() {
char *ptr = (char *)malloc(1024);
ptr[0] = 0;
/* Теперь пишет за пределы блока */
ptr[1024] = 0;
exit(0);
}
Когда вы откомпилируете и выполните программу, то не увидите некорректного поведения. Однако вероятно, что область памяти, выделенная malloc
, повреждена, и вы, в конце концов, попадете в беду.
$ cc -о efence efence.с
$ ./efence
$
Тем не менее, если вы возьмете ту же самую программу и скомпонуйте ее с библиотекой ElectricFence (libefence.a), то получите немедленный отклик:
$ cc -о efence efence.с -lefence
$ ./efence
Electric Fence 2.2.0 Copyright (С) 1987-1999 Bruce Perens
Segmentation fault
$
Выполнение под контролем отладчика позволяет получить подробное описание проблемы;
$ cc -g -о efence efence.с -lefence
$ gdb efence
(gdb) run
Starting program: /home/neil/BLP4e/chapter10/efence
Electric Fence 2.2.0 Copyright (C) 1987-1999 Bruce Perens [email protected]
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 1024 (LWP 1869)]
0x08048512 in main () at efence.c:10
10 ptr[1024] = 0;
(gdb)
Как это работает
Библиотека ElectricFence заменяет функцию malloc
и связанные с ней функции версиями, применяющими аппаратные средства виртуальной памяти для защиты от несанкционированного доступа к памяти. При возникновении подобного обращения к памяти порождается сигнал нарушения сегментации и программа останавливается.
Средство valgrind
способно обнаруживать многие из обсуждавшихся нами проблем (упражнение 10.4). Прежде всего, оно умеет находить ошибки доступа, к массиву и утечки памяти. Это средство, возможно, не включено в ваш дистрибутив Linux, но его можно найти на Web-сайте http://valgrind.org.
Для применения valgrind
даже не требуется перекомпиляции программы, и вы можете находить ошибки доступа к памяти в выполняющейся программе. Данное средство заслуживает внимания; оно применяется в основных разработках, включая среду KDE версии 3.
Упражнение 10.4. Средство valgrind
Далее приведена программа checker.c, которая выделяет некоторый объем памяти, читает область памяти и записывает данные за пределами выделенного участка, а затем делает выделенный участок недоступным.
#include
#include
int main() {
char *ptr = (char *)malloc(1024);
char ch;
/* Неинициализированное чтение */
ch = ptr[1024];
/* Запись за пределами блока */
ptr[1024] = 0;
/* Потеря блока */
ptr = 0;
exit(0);
}
Для применения valgrind
вы просто выполняете команду valgrind
, передав ей опции, задающие нужные виды проверок, и далее указав программу для выполнения с ее аргументами (если таковые есть).
При выполнении программы с valgrind
вы увидите множество обнаруженных проблем:
$ valgrind –leak-check=yes -v ./checker
==4780== Memcheck, a memory error detector.
==4780== Copyright (C) 2002-2007, and GNU GPL'd, by Julian Seward et al.
==4780== Using LibVEX rev 1732, a library for dynamic binary translation.
==4780== Copyright (C) 2004-2007, and GNU GPL'd, by OpenWorks LLP.
==4780== Using valgrind-3.2.3, a dynamic binary instrumentation framework.
==4780== Copyright (C) 2000-2007, and GNU GPL'd, by Julian Seward et al.
==4780==
–4780– Command line
–4780– ./checker
–4780– Startup, with flags:
–4780– –leak-check=yes
–4780– -v
–4780– Contents of /рroc/version:
–4780– Linux version 2-6.20.2-2-default (geeko@buildhost) (gcc version 4.1.3 20070218 (prerelease) (SUSE Linux)) #1 SMP Fri Mar 9 21:54:10 UTC 2007
–4780– Arch and hwcaps: X86, x86-sse1-sse2
–4780– Page sizes: currently 4096, max supported 4096
–4780– Valgrind library directory: /usr/lib/valgrind
–4780– Reading syms from /lib/ld-2.5.so (0x4000000)
–4780– Reading syms from /home/neil/BLP4e/chapter10/checker (0x8048000)
–4780– Reading syms from /usr/lib/valgrind/x86-linux/memcheck (0x38000000)
–4780– object doesn't have a symbol table
–4780– object doesn't have a dynamic symbol table
–4780– Reading suppressions file: /usr/lib/valgrind/default.supp
–4780– REDIR: 0x40158B0 (index) redirected to 0x38027EDB (???)
–4780– Reading syms from /usr/lib/valgrind/x86-linux/vgpreload_core.so (0x401E000)
–4780– object doesn't have a symbol table
–4780– Reading syms from /usr/lib/valgrind/x86-linux/vgpreload_memcheck.so (0x4021000)
–4780– object doesn't have a symbol table
==4780= WARNING: new redirection conflicts with existing – ignoring it
–4780– new: 0x040158B0 (index ) R-> 0x04024490 index
–4780– REDIR: 0x4015A50 (strlen) redirected to 0x4024540 (strlen)
–4780– Reading syms from /lib/libc-2.5.so (0x4043000)
–4780– REDIR: 0x40ADFF0 (rindex) redirected to 0x4024370 (rindex)
–4780– REDIR: 0x40AAF00 (malloc) redirected to 0x4023700 (malloc)
==4780== Invalid read of size 1
==4780== at 0x804842C: main (checker.с: 10)
==4780== Address 0x4170428 is 0 bytes after a block of size 1,024 alloc'd
==4780== at 0x4023785: malloc (in /usr/lib/valgrind/x86-linux/vgpreload_memcheck.so)
==4780== by 0x8048420: main (checker.c: 6)
=4780=
==4780== Invalid write of size 1
==4780== at 0x804843A: main (checker.с: 13)
==4780== Address 0x4170428 is 0 bytes after a block of size 1,024 alloc'd
==4780== at 0x4 023785: malloc (in /usr/lib/valgrind/x86-linux/vgpreload_memcheck.so)
==4780== by 0x8048420: main (checker.c: 6)
–4780– REDIR: 0x40A8BB0 (free) redirected to 0x402331A (free)
–4780– REDIR: 0x40AEE70 (memset) redirected to 0x40248A0 (memset)
==4780==
==4780== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 3 from 1)
==4780==
==4780== 1 errors in context 1 of 2:
==4780== Invalid write of size 1
==4780== at 0x804843A: main (checker.с: 13)
==4780== Address 0x4170428 is 0 bytes after a block of size 1,024 alloc'd
==4780== at 0x4023785: malloc (in /usr/lib/valgrind/x86-linux/vgpreload_memcheck.so)
==4780== by 0x80484 20: main (checker.c: 6)
==4780==
==4780== 1 errors in context 2 of 2:
==4780== Invalid read of size 1
==4780== at 0x804842C: main (checker.c:10)
==4780== Address 0x4170428 is 0-bytes after a block of size 1,024 alloc'd
==4780== at 0x4023785: malloc (in /usr/lib/valgrind/x86-linux/vgpreload_memcheck.so)
==4780== by 0x8048420: main (checker.с: 6)
–4780–
–4780– supp: 3 dl-hack3
==4780==
==4780== IN SUMMARY: 2 errors from 2 contexts (suppressed: 3 from 1)
==4780==
==4780== malloc/free: in use at exit: 1,024 bytes in 1 blocks.
==4780== malloc/free: 1 allocs, 0 frees, 1,024 bytes allocated.
==4780==
==4780== searching for pointers to 1 not-freed blocks.
==4780== checked 65,444 bytes.
==4780==
==4780==
==4780== 1,024 bytes in 1 blocks are definitely lost in loss record 1 of 1
==4780== at 0x4023785: malloc (in /usr/lib/valgrind/x86-linux/vgpreload_memcheck.so)
==4780== by 0x8048420: main (checker.c: 6)
==4780==
==4780== LEAK SUMMARY:
==4780== definitely lost: 1,024 bytes in 1 blocks.
==4780== possibly lost: 0 bytes in 0 blocks.
==4780== still reachable: 0 bytes in 0 blocks.
==4780== suppressed: 0 bytes in 0 blocks.
–4780– memcheck: sanity checks: 0 cheap, 1 expensive
–4780– memcheck: auxmaps: 0 auxmap entries (0k, 0M) in use
–4780– memcheck: auxmaps: 0 searches, 0 comparisons
–4780– memcheck: SMs: n_issued = 9 (144k, 0M)
–4780– memcheck: SMs: n_deissued = 0 (0k, 0M)
–4780– memcheck: SMs: max_noaccess = 65535 (1048560k, 1023M)
–4780– memcheck: SMs: max_undefined = 0 (0k, 0M)
–4780– memcheck: SMs: max_defined = 19 (304k, 0M)
–4780– memcheck: SMs: max_non_DSМ = 9 (144k, 0M)
–4780– memcheck: max sec V bit nodes: 0 (0k, 0M)
–4780– memcheck: set_sec_vbits8 calls: 0 (new: 0, updates: 0)
–4780– memcheck: max shadow mem size: 448k, 0M
–4780– translate: fast SP updates identified: 1,456 ( 90.3%)
–4780– translate: generic_known SP updates identified: 79 ( 4.9%)
–4780– translate: generic_unknown SP updates identified: 76 ( 4.7%)
–4780– tt/tc: 3,341 tt lookups requiring 3,360 probes
–4780– tt/tc: 3,341 fast-cache updates, 3 flushes
–4780– transtab: new 1,553 (33,037 -> 538,097; ratio 162:10) [0 scs]
–4780– transtab: dumped 0 (0 -> ??)
–4780– transtab: discarded 6 (143 -> ??)
–4780– scheduler: 21,623 jumps (bb entries).
–4780– scheduler: 0/1,828 major/minor sched events.
–4780– sanity: 1 cheap, 1 expensive checks.
–4780– exectx: 30,011 lists, 6 contexts (avq 0 per list)
–4780– exectx: 6 searches, 0 full compares (0 per 1000)
–4780– exectx: 0 cmp2, 4 cmp4, 0 cmpAll $
Вы видите, что обнаружены некорректные считывания и записи, и интересующие нас блоки памяти приводятся с указанием места, которое для них отведено. Для прерывания выполнения программы в ошибочном месте можно применить отладчик.
У программы valgrind
есть много опций, включая подавление ошибок определенного типа и обнаружение утечки памяти. Для выявления такой утечки в примере вы должны использовать одну из опций, передаваемых valgrind
. Для контроля утечек памяти после завершения программы следует задать опцию –leak-check=yes
. Список опций можно получить с помощью команды valgrind –help
.
Как это работает
Программа выполняется под контролем средства valgrind, которое перехватывает действия, совершаемые программой, и выполняет множество проверок, включая обращения к памяти. Если обращение относится к выделенному блоку памяти и некорректно, valgrind выводит сообщение. В конце программы выполняется подпрограмма "сбора мусора", которая определяет, есть ли выделенные и неосвобожденные блоки памяти. Об этих потерянных блоках выводится сообщение.
Резюме
В этой главе обсуждались некоторые методы и средства отладки. Система Linux предоставляет ряд мощных инструментов для удаления ошибок из ваших программ. Вы устранили несколько ошибок в программе с помощью отладчика gdb и познакомились с некоторыми средствами статического анализа, такими как cflow
и splint
. В заключение были рассмотрены проблемы, возникающие при использовании динамически распределяемой памяти, и некоторые средства, способные помочь обнаружить их, например ElectricFence и valgrind
.
Утилиты, обсуждавшиеся в этой главе, в основном хранятся на FTP-серверах в Интернете. Авторы, имеющие к ним отношение, могут порой сохранять авторские права на них. Информацию о многих утилитах можно найти в архиве Linux, по адресу http://www.ibiblio.org/pub/Linux. Мы надеемся, что новые версии будут появляться на этом Web-сайте по мере их выхода в свет.
Глава 11
Процессы и сигналы
Процессы и сигналы формируют главную часть операционной среды Linux. Они управляют почти всеми видами деятельности ОС Linux и UNIX-подобных компьютерных систем. Понимание того, как Linux и UNIX управляют процессами, сослужит добрую службу системным и прикладным программистам или системным администраторам.
В этой главе вы узнаете, как обрабатываются процессы в рабочей среде Linux и как точно установить, что делает компьютер в любой заданный момент времени. Вы также увидите, как запускать и останавливать другие процессы в ваших собственных программах, как заставить процессы отправлять и получать сообщения и как избежать процессов-зомби. В частности, вы узнаете о:
□ структуре процесса, его типе и планировании;
□ разных способах запуска новых процессов;
□ порождающих (родительских), порожденных (дочерних) процессах и процессах-зомби;
□ сигналах и их применении.
Что такое процесс?
Стандарты UNIX, а именно IEEE Std 1003.1, 2004 Edition, определяют процесс как "адресное пространство с одним или несколькими потоками, выполняющимися в нем, и системные ресурсы, необходимые этим потокам. Мы будем рассматривать потоки в главе 12, а пока будем считать процессом просто любую выполняющуюся программу.
Многозадачные системы, такие как Linux, позволяют многим программам выполняться одновременно. Каждый экземпляр выполняющейся программы создает процесс. Это особенно заметно в оконной системе, например Window System (часто называемой просто X). Как и ОС Windows, X предоставляет графический пользовательский интерфейс, позволяющий многим приложениям выполняться одновременно. Каждое приложение может отображаться в одном или нескольких окнах.
Будучи многопользовательской системой, Linux разрешает многим пользователям одновременно обращаться к системе. Каждый пользователь в одно и то же время может запускать много программ или даже несколько экземпляров одной и той же программы. Сама система выполняет в это время другие программы, управляющие системными ресурсами и контролирующие доступ пользователей.
Как вы видели в главе 4, выполняющаяся программа или процесс состоит из программного кода, данных, переменных (занимающих системную память), открытых файлов (файловых дескрипторов) и окружения. Обычно в системе Linux процессы совместно используют код и системные библиотеки, так что в любой момент времени в памяти находится только одна копия программного кода.
Структура процесса
Давайте посмотрим, как организовано сосуществование двух процессов в операционной системе. Если два пользователя neil и rick запускают в одно и то же время программу grep для поиска разных строк в различных файлах, применяемые для этого процессы могут выглядеть так, как показано на рис. 11.1.
Рис. 11.1
Если вы сможете выполнить команду ps
, как в приведенном далее коде, достаточно быстро и до того, как завершатся поиски строк, вывод будет выглядеть подобно следующим строкам:
$ ps -ef
UID PID PPID С STIME TTY TIME CMD
rick 101 96 0 18:24 tty2 00:00:00 grep troi nextgen.doc
neil 102 92 0 18:24 tty4 00:00:00 grep kirk trek.txt
Каждому процессу выделяется уникальный номер, именуемый идентификатором процесса или PID. Обычно это положительное целое в диапазоне от 2 до 32 768. Когда процесс стартует, в последовательности выбирается следующее неиспользованное число. Когда все номера будут исчерпаны, выбор опять начнется с 2. Номер 1 обычно зарезервирован для специального процесса init
, который управляет другими процессами. Мы скоро вернемся к процессу init
. А пока вы видите, что двум процессам, запущенным пользователями neil и rick, выделены идентификаторы 101 и 102.
Код программы, которая будет выполняться командой grep
, хранится в файле на диске. Обычно процесс Linux не может писать в область памяти, применяемую для хранения кода программы, поэтому программный код загружается в память как доступный только для чтения. На рис. 11.1 видно, что несмотря на то, что в данную область нельзя писать, она может безопасно использоваться совместно.
Системные библиотеки также можно совместно использовать. Следовательно, в памяти нужна, например, только одна копия функции printf
, даже если многие выполняющиеся программы вызывают ее. Эта схема более сложная, но аналогичная той, которую используют для работы динамически подключаемые библиотеки в ОС Windows.
Как видно из приведенной схемы, дополнительное преимущество заключается в том, что дисковый файл, содержащий исполняемую программу grep
, меньше, т.к. не включает программный код совместно используемой библиотеки. Возможно, для одной программы это не слишком ощутимый выигрыш, но извлечение часто используемых подпрограмм, к примеру, из стандартной библиотеки С экономит значительный объем для операционной системы в целом.
Конечно не все, что нужно программе, может быть совместно использовано. Например, переменные отдельно используются каждым процессом. В данном примере искомая строка, передаваемая команде grep
, – это переменная s
, принадлежащая пространству данных каждого процесса. Эти пространства разделены и, как правило, не могут читаться другим процессом. Файлы, которые применяются в двух командах grep, тоже разные; у каждого процесса есть свой набор файловых дескрипторов, используемых для доступа к файлам.
Кроме того, у каждого процесса есть собственный стек, применяемый для локальных переменных в функциях и для управления вызовами функций и возвратом из них. У процесса также собственное окружение, содержащее переменные окружения, которые могут задаваться только для применения в данном процессе, например, с помощью функций putenv
и getenv
, как было показано в главе 4. Процесс должен поддерживать собственный счетчик программы, запись того места, до которого он добрался за время выполнения, или поток исполнения. В следующей главе вы увидите, что процессы могут иметь несколько потоков исполнения.
Во многих системах Linux и некоторых системах UNIX существует специальный набор "файлов" в каталоге /proc. Это скорее специальные, чем истинные файлы, т.к. позволяют "заглянуть внутрь" процессов во время их выполнения, как если бы они были файлами в каталогах, В главе 3 мы приводили краткий обзор файловой системы /proc.
И наконец, поскольку Linux, как и UNIX, обладает системой виртуальной памяти, которая удаляет страницы кода и данных на жесткий диск, можно управлять гораздо большим количеством процессов, чем позволяет объем физической памяти.
Таблица процессовТаблица процессов Linux подобна структуре данных, описывающей все процессы, загруженные в текущий момент, например, их PID, состояние и строку команды, разновидность информационного вывода команды ps
. Операционная система управляет процессами с помощью их идентификаторов, PID, которые применяются как указатели в таблице процессов. У таблицы ограниченный размер, поэтому число процессов, поддерживаемых системой, ограничено. В первых системах UNIX оно равнялось 256 процессам. Более современные реализации значительно ослабили это ограничение и ограничены только объемом памяти, доступным для формирования элемента таблицы процессов.
Команда ps
показывает выполняемые вами процессы, процессы, выполняемые другим пользователем, или все процессы в системе. Далее приведен еще один пример вывода:
$ ps -ef
UID PID PPID С STIME TTY TIME CMD
root 433 425 0 18:12 tty1 00:00:00 [bash]
rick 445 426 0 18:12 tty2 00:00:00 -bash
rick 456 427 0 18:12 tty3 00:00:00 [bash]
root 467 433 0 18:12 tty1 00:00:00 sh /usr/X11R6/bin/startx
root 474 467 0 18:12 tty1 00:00:00 xinit /etc/X11/xinit/xinitrc –
root 478 474 0 18:12 tty1 00:00:00 /usr/bin/gnome-session
root 487 1 0 18:12 tty1 00:00:00 gnome-smproxy –sm-client-id def
root 493 1 0 18:12 tty1 00:00:01 [enlightenment]
root 506 1 0 18:12 tty1 00:00:03 panel –sm-client-id defaults
root 508 1 0 18:12 tty1 00:00:00 xscreensaver -no-splash -timeout
root 510 1 0 18:12 tty1 00:00:01 gmc –sm-client-id default10
root 512 1 0 18:12 tty1 00:00:01 gnome-help-browser –sm-client-i
root 649 445 0 18:24 tty2 00:00:00 su
root 653 649 0 18:24 tty2 00:00:00 bash
neil 655 428 0 18:24 tty4 00:00:00 -bash
root 713 1 2 18:27 tty1 00:00:00 gnome-terminal
root 715 713 0 18:28 tty1 00:00:00 gnome-pty-helper
root 717 716 13 18:28 pts/0 00:00:01 emacs
root 718 653 0 18:28 tty2 00:00:00 ps -ef
Вывод отображает информацию о многих процессах, включая процессы, запущенные редактором Emacs в графической среде X ОС Linux. Например, столбец TTY
показывает, с какого терминала стартовал процесс, столбец TIME
показывает время ЦПУ, затраченное к данному моменту, а столбец CMD
– команду, примененную для запуска процесса. Давайте познакомимся поближе с некоторыми из этих процессов.
neil 655 428 0 18:24 tty4 00:00:00 -bash
Начальная регистрация была произведена на консоли номер 4. Это просто консоль на данном компьютере. Выполняемая программа командной оболочки – это стандартная оболочка Linux, bash
.
root 467 433 0 18:12 tty1 00:00:00 sh /usr/X11R6/bin/startx
X Window System была запущена командой startx
. Это сценарий командной оболочки, который запускает сервер X и выполняет некоторые начальные программы системы X.
root 717 716 13 18:28 pts/0 00:00:01 emacs
Этот процесс представляет окно в системе X, выполняющее программу Emacs. Он был запущен оконным диспетчером в ответ на запрос нового окна. Командной оболочке был назначен новый псевдотерминал pts/0 для считывания и записи.
root 512 1 0 18:12 tty1 00:00:01 gnome-help-browser –sm-client-i
Это обозреватель системы помощи среды GNOME, запущенный оконным диспетчером.
По умолчанию программа ps
выводит только процессы, поддерживающие подключение к терминалу, консоли, последовательной линии связи или псевдотерминалу. Другие процессы выполняются без взаимодействия с пользователем на терминале. Обычно это системные процессы, которые система Linux применяет для управления совместно используемыми ресурсами. Команду ps
можно применять для отображения всех таких процессов, использовав опцию -е
и запросив «полную» информацию с помощью опции -f
.
Примечание
Точная синтаксическая запись команды
ps
и формат вывода могут немного отличаться в разных системах. Версия GNU командыps
, применяемая в Linux, поддерживает опции, взятые из нескольких предшествующих реализацийps
, включая варианты из UNIX-систем BSD и AT&T, и добавляет множество своих опций. См. интерактивное справочное руководство для получения подробных сведений о доступных опциях и форматах вывода командыps
.