Использование портов ввода/вывода в программах на C

Стандартный способ

Процедуры доступа к портам ввода/выводы находятся в /usr/include/asm/io.h (или в linux/include/asm-i386/io.h в некоторых дистрибутивах ядра). Процедуры представляют собой встроенные (inline) макроопределения, так что вам не нужны никакие библиотеки, и достаточно просто добавить #include <asm/io.h>.

Из-за ограничения в gcc (присутствующего, как минимум, в версии 2.7.2.3 и ниже) и в egcs (всех версий), вы должны компилировать любые исходные тексты, использующие эти процедуры, с включенной оптимизацией (gcc -O1 или выше), или же определить пустое #define extern перед #include <asm/io.h>..

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

Перед тем, как вы получите доступ к какому-нибудь порту, вы должны дать вашей программе права на это. Это выполняется, при помощи функции ioperm(from, num, turn_on) (определенной в unistd.h и находящейся в ядре), где from это первый порт, а num это количество подряд идущих портов, которым нужно дать доступ. Например, ioperm(0x300, 5, 1) дает доступ к порту с 0x300 по 0x304 (всего 5 портов). Последний аргумент - это двоичное значение, определяющее, дать ли доступ к портам (истина (1)) или запретить его (ложь (0)). Для включения портов, идущих не подряд, вы можете вызывать ioperm() несколько раз. Для дополнительной информации читайте руководство ioperm(2).

Для вызова ioperm() необходимо иметь права root; таким образом, вы должны запускать программу от пользователя root или установить на файл флаг setuid. После того, как определен доступ к портам, права суперпользователя больше вам не нужны. Вам не нужно непосредственно освобождать порты при помощи ioperm(..., 0), т.к. это делается автоматически, когда программа заканчивает работу.

Выполнение setuid() для переключения на другого пользователя не отключает доступ к портам, данный ioperm(), но это происходит при fork (наследованный процесс теряет доступ, когда как у порождающего процесса он остается).

ioperm() может дать доступ только к портам с 0x000 по 0x3ff; для других портов вам нужно использовать iopl() (который дает доступ ко всем портам сразу). Используйте уровень 3 (iopl(3)), чтобы дать доступ вашей программе ко всем портам ввода/вывода (но будьте осторожны --- доступ к неправильным портам может сделать некоторые нехорошие вещи с вашим компьютером). Опять таки, вам необходимы привилегии root. Дополнительно читайте руководство iopl(2).

Теперь собственно о доступе к портам: Чтобы считать байт (8 бит) из порта, вызовите функцию inb(port), возвращающую считанный байт. Чтобы вывести байт в порт, вызовите процедуру outb(value, port) (обратите внимание на порядок аргументов). Чтобы считать компьютерное слово (16 бит) из портов x и x+1 (по одному байту из каждого образуют слово), вызовите функцию inw(x). Чтобы вывести слово в два порта, используйте outw(value, x). Если вы не уверены, работать с одинарным или двойным портом, вам, скорее всего, нужны inb() и outb(), т.к. порты большинства устройств имеют однобайтный доступ. Также замечу, что все функции, работающие с портами, требуют, как минимум, около микросекунды для выполнения.

Макросы inb_p(), outb_p(), inw_p() и outw_p() работают аналогично вышеуказанным, но добавляют короткую (около микросекунды) задержку после доступа к порту; вы можете установить задержку около четырех микросекунд при помощи #define REALLY_SLOW_IO перед #include <asm/io.h>. Обычно эти макросы (за исключением случаев с #define SLOW_IO_BY_JUMPING, при которых выполнение становится менее точным) используют вывод в порт 0x80 в качестве задержки, так что вам необходимо сначала дать доступ к порту 0x80, при помощи ioperm() (вывод в порт 0x80 никак не влияет на систему). О других способах задержки см. руководства.

См. руководства по ioperm(2), iopl(2) и вышеуказанных макросах.

Альтернативный способ: /dev/port

Другой путь доступа к портам ввода/вывода лежит через открытие файла /dev/port (символьное устройство - major 1, minor 4) на чтение и/или запись (функции f*() из stdio.h работают через буфер, поэтому избегайте их). Затем выполняем lseek() на необходимый байт в файле (позиция файла 0 = порт 0x00, позиция файла 1 = порт 0x01, и т.д.), и считываем/записываем байт или слово при помощи read() или write().

Естественно, для работы программы вам нужен доступ на чтение/запись к файлу /dev/port. Этот способ конечно медленней, чем стандартный, но не требует ни оптимизации при компиляции, ни ioperm(). Кроме того, если вы дадите доступ соответствующим пользователю/группе к файлу /dev/port, вашей программе не нужны права root --- впрочем, это не очень хорошая вещь с точки зрения безопасности, поскольку это может нарушить работу системы путем получения прав root через доступ к диску, сетевой плате и т.д. напрямую.