В вычислительной технике ошибка шины — это сбой , вызванный оборудованием, уведомляющий операционную систему (ОС) о том, что процесс пытается получить доступ к памяти , которую ЦП не может физически адресовать: недопустимый адрес для адресной шины , отсюда и название. В современном использовании на большинстве архитектур они встречаются гораздо реже, чем ошибки сегментации , которые возникают в основном из-за нарушений доступа к памяти: проблем в логическом адресе или разрешениях.
На платформах, совместимых с POSIX , ошибки шины обычно приводят к отправке сигнала SIGBUS в процесс, вызвавший ошибку. SIGBUS также может быть вызвана любой общей неисправностью устройства, которую обнаруживает компьютер, хотя ошибка шины редко означает, что оборудование компьютера физически сломано — обычно она вызвана ошибкой в программном обеспечении . [ необходима цитата ] Ошибки шины также могут возникать из-за некоторых других ошибок подкачки; см. ниже.
Существует как минимум три основные причины ошибок шины:
Программное обеспечение дает команду ЦП прочитать или записать определенный адрес физической памяти . Соответственно, ЦП устанавливает этот физический адрес на своей адресной шине и запрашивает у всего остального оборудования, подключенного к ЦП, ответ с результатами, если они отвечают на этот конкретный адрес. Если никакое другое оборудование не отвечает, ЦП выдает исключение , заявляя, что запрошенный физический адрес не распознан всей компьютерной системой. Обратите внимание, что это касается только адресов физической памяти. Попытка доступа к неопределенному адресу виртуальной памяти обычно считается ошибкой сегментации, а не ошибкой шины, хотя, если MMU является отдельным, процессор не может заметить разницу.
Большинство процессоров имеют байтовую адресацию , где каждый уникальный адрес памяти относится к 8-битному байту . Большинство процессоров могут получать доступ к отдельным байтам из каждого адреса памяти, но они, как правило, не могут получать доступ к более крупным блокам (16 бит, 32 бита, 64 бита и т. д.) без того, чтобы эти блоки были « выровнены » по определенной границе ( платформа x86 является заметным исключением).
Например, если многобайтовый доступ должен быть выровнен по 16 битам, адреса (заданные в байтах) 0, 2, 4, 6 и т. д. будут считаться выровненными и, следовательно, доступными, в то время как адреса 1, 3, 5 и т. д. будут считаться невыровненными. Аналогично, если многобайтовый доступ должен быть выровнен по 32 битам, адреса 0, 4, 8, 12 и т. д. будут считаться выровненными и, следовательно, доступными, а все адреса между ними будут считаться невыровненными. Попытка доступа к блоку, большему, чем байт, по невыровненному адресу может вызвать ошибку шины.
Некоторые системы могут иметь гибрид этих вариантов в зависимости от используемой архитектуры. Например, для оборудования на базе мэйнфрейма IBM System/360 , включая IBM System z , Fujitsu B8000, RCA Spectra и UNIVAC Series 90 , инструкции должны находиться на 16-битной границе, то есть адреса выполнения должны начинаться с четного байта. Попытки перейти на нечетный адрес приводят к исключению спецификации. [1] Однако данные могут быть извлечены из любого адреса в памяти и могут быть длиной в один байт или больше в зависимости от инструкции.
Процессоры обычно обращаются к данным по всей ширине своей шины данных в любое время. Для адресации байтов они обращаются к памяти по всей ширине своей шины данных, затем маскируют и сдвигают для адресации отдельного байта. Системы терпят этот неэффективный алгоритм, так как это существенная функция для большинства программ, особенно для обработки строк . В отличие от байтов, более крупные блоки могут охватывать два выровненных адреса и, таким образом, потребуют более одной выборки на шине данных. Процессоры могут поддерживать это, но эта функциональность редко требуется непосредственно на уровне машинного кода , поэтому разработчики процессоров обычно избегают ее реализации и вместо этого выдают ошибки шины для невыровненного доступа к памяти.
FreeBSD , Linux и Solaris могут сообщать об ошибке шины, когда страницы виртуальной памяти не могут быть подкачаны , например, из-за ее исчезновения (например, при доступе к отображенному в память файлу или выполнении двоичного образа , который был усечен во время работы программы), [2] [ ненадежный источник? ] или из-за того, что только что созданный отображенный в память файл не может быть физически выделен, поскольку диск заполнен.
На x86 существует старый механизм управления памятью, известный как сегментация . Если приложение загружает сегментный регистр с селектором несуществующего сегмента (что в POSIX-совместимых ОС можно сделать только с помощью языка ассемблера ), генерируется исключение. Некоторые ОС использовали это для подкачки, но в Linux это генерирует SIGBUS.
Это пример невыровненного доступа к памяти, написанный на языке программирования C с синтаксисом ассемблера AT&T .
#include <stdlib.h> int main ( int argc , char ** argv ) { int * iptr ; char * cptr ; #if defined(__GNUC__) # if defined(__i386__) /* Включить проверку выравнивания на x86 */ __asm__ ( "pushf \n orl $0x40000,(%esp) \n popf" ); # elif defined(__x86_64__) /* Включить проверку выравнивания на x86_64 */ __asm__ ( "pushf \n orl $0x40000,(%rsp) \n popf" ); # endif #endif /* malloc() всегда предоставляет память, выровненную для всех основных типов */ cptr = malloc ( sizeof ( int ) + 1 ); /* Увеличиваем указатель на единицу, делая его невыровненным */ iptr = ( int * ) ++ cptr ; /* Разыменовываем его как указатель int, что приводит к невыровненному доступу */ * iptr = 42 ; /* Последующие попытки доступа также приведут к ошибке sigbus. short *sptr; int i; sptr = (short *)&i; // Для всех нечетных значений это приведет к sigbus. sptr = (short *)(((char *)sptr) + 1); *sptr = 100; */ вернуть 0 ; }
Компиляция и запуск примера в ОС, совместимой с POSIX на платформе x86, демонстрирует ошибку:
$ gcc -ansi sigbus.c -o sigbus $ ./sigbus Ошибка шины $ gdb ./sigbus (gdb) r Программа получила сигнал SIGBUS, Ошибка шины. 0x080483ba in main () (gdb) x/i $pc 0x80483ba <main+54>: mov DWORD PTR [eax],0x2a (gdb) p/x $eax $ 1 = 0x804a009 (gdb) p/t $eax & (sizeof(int) - 1) $ 2 = 1
Отладчик GDB показывает, что непосредственное значение 0x2a сохраняется в месте, сохраненном в регистре EAX , с использованием языка ассемблера X86 . Это пример косвенной адресации регистра.
Печать младших бит адреса показывает, что он не выровнен по границе слова («dword» в терминологии x86).