При взаимодействии компьютера и устройства хранения компьютер обычно выступает в роли инициатора SCSI сессии, посылающего SCSI-команды. Устройство хранения обычно действует как целевое устройство SCSI (далее просто цель), принимающее и обрабатывающее SCSI-команды. Цель SCSI ожидает команды от инициатора и затем обеспечивает затребованную передачу входящих или исходящих данных.
Цель обычно предоставляет инициатору один или несколько номеров логических устройств(LUN - logical unit number). В системах хранения LUN – это просто некоторый номер (адрес), назначаемый логическому устройству. Логическое устройство– это единичная сущность протокола SCSI, которой можно адресовать фактические операции ввода/вывода. Каждая SCSI-цель предоставляет один или несколько адресов LUN. Цель не выполняет ввод/вывод самостоятельно, а действует от лица конкретной логической единицы.
В системах хранения данных LUN часто представляет SCSI-диск, на котором компьютер может осуществлять операции чтения и записи. На рисунке 1 показана работа клиент-серверной модели SCSI.
Рисунок 1. Клиент-серверная модель SCSI
Сначала инициатор посылает команду цели. Цель декодирует команду и запрашивает данные от инициатора, либо посылает данные инициатору, после чего отсылает инициатору статус выполнения операции. Если статус сигнализирует об ошибке, инициатор посылает цели команду с запросом детальной информации об ошибке. Цель возвращает детальную информацию, в которой указывается, что именно пошло не так.
Теперь мы рассмотрим SCSI-команды для работы с устройствами хранения.
SCSI-команды для работы с устройствами хранения данных
SCSI-команды, работающие с устройствами хранения данных, в основном определяются в архитектурной модели SCSI (SAM - SCSI Architecture Model), наборе основных SCSI-команд (SPC - SCSI Primary Commands) и наборе блочных SCSI команд (SBC - SCSI Block Commands):
- SAM определяет модель SCSI-системы, разделение на функциональные блоки, стандарты реализации и требования, применимые ко всем реализациям SCSI.
- SPC определяет поведение, являющееся общим для всех архитектурных моделей SCSI-устройств.
- SBC определяет дополнительные наборы команд для облегчения операций с блочными SCSI-устройствами прямого доступа.
Каждая SCSI-команда описывается блоком дескриптора команды (CDB - Command Descriptor Block), определяющим операции, которые необходимо выполнить SCSI-устройству. Различают SCSI-команды для передачи данных между устройством хранения и клиентом и настроечные команды, позволяющие узнать или задать значения параметров SCSI-устройства. В таблице 1 показаны наиболее широко распространенные команды
Таблица 1. Самые распространенные SCSI-команды
| Команда | Описание |
|---|---|
| Inquiry | Запрашивает основную информацию о целевом устройстве |
| Test/Unit/Ready | Проверяет, готово ли устройство к передаче данных |
| READ | Чтение данных с SCSI-устройства |
| WRITE | Запись данных на SCSI-устройство |
| Request Sense | Запрашивает информацию об ошибке выполнения последней команды |
| Read Capacity | Запрашивает ёмкость устройства хранения |
В первом байте каждой SCSI-команды должен находиться код операции, которую представляет эта команда. Кроме того, SCSI-команды должны содержать контрольный байт. Обычно это последний байт команды, выделенный для передачи информации, специфичной для производителя устройства, а также иных нужд.
Теперь перейдем к рассмотрению универсального SCSI-драйвера.
Универсальный SCSI-драйвер Linux
В Linux SCSI-устройства часто имеют названия, которые помогают пользователю их идентифицировать. Например,
первый SCSI CD-ROM называется /dev/scd0. SCSI-диски называются /dev/sda, /dev/sdb, /dev/sdc и т.д.
После окончания инициализации устройства в Linux интерфейсы драйвера SCSI-диска (sd) посылают
только SCSI-команды
READ и WRITE.
SCSI-устройства также могут иметь универсальные имена или интерфейсы, такие как /dev/sg0, /dev/sg1 или /dev/sga, /dev/sgb и т.д. С помощью интерфейсов универсального драйвера вы можете передавать SCSI-команды напрямую устройству, минуя уровень файловой системы, обычно создаваемой на SCSI-диске и монтируемой в директорию. На рисунке 2 вы можете видеть, как различные приложения взаимодействуют со SCSI-устройствами.
Рисунок 2. Множество способов взаимодействия со SCSI-устройством
С помощью интерфейса универсального драйвера Linux вы можете создавать приложения, умеющие посылать SCSI-устройствам еще более широкий набор команд. Иными словами, у вас есть выбор. Чтобы определить, какому SCSI-устройству соответствует некоторый интерфейс sg, используйте команду
sg_map, которая выводит карту SCSI-устройств:
Во-первых, убедимся, что у нас загружен драйвер sg:
[root@taomaoy ~]# lsmod | grep sg sg 31028 0 ... |
Если grep не выводит никакой информации о драйвере sg, загрузите его вручную:
[root@taomaoy ~]# modprobe sg |
Затем с помощью команды sg_map посмотрите отображение SCSI-устройств в интерфейсы sg:
[root@taomaoy ~]# sg_map -i /dev/sg0 /dev/sda ATA ST3160812AS 3.AA /dev/sg1 /dev/scd0 HL-DT-ST RW/DVD GCC-4244N 1.02 |
Если вы работаете с Fedora или RedHat, у вас должен быть установлен пакет
sg3_utils. Давайте теперь посмотрим, как выполнить типичный вызов SCSI-команды.
Типичные команды универсального SCSI-драйвера
Универсальный SCSI-драйвер поддерживает множество типичных системных вызовов к своему устройству, в том числе
open(),
close(), read(),
write, poll(),
ioctl(). Процедура посылки SCSI-команды на устройство также очень проста:
- Открываем файл универсального SCSI-устройства, (например sg1) чтобы получить файловый дескриптор SCSI-устройства.
- Подготавливаем SCSI-команду.
- Задаем необходимые буферы памяти.
- Вызываем функцию
ioctl()для выполнения SCSI-команды. - Закрываем файл устройства.
Типичный вызов функции ioctl() может выглядеть так:
ioctl(fd,SG_IO,p_io_hdr);.
Здесь ioctl() принимает три обязательных параметра:
fd– дескриптор файла устройства. Этот параметр можно получить, вызвавopen()для файла устройства.SG_IOуказывает, что функцииioctl()третьим параметром передается объект типаsg_io_hdrи что именно этот объект будет возвращен после выполнения SCSI-команды.p_io_hdr- указатель на объектsg_io_hdr, содержащий SCSI-команду и другие параметры операции.
Самой важной структурой данных для универсального драйвера SCSI является структура
struct
sg_io_hdr, определенная в scsi/sg.h. Она содержит в себе информацию о том, как использовать SCSI-команду.
В листинге 1 показано определение этой структуры.
Листинг 1. Определение структуры sg_io_hdr
typedef struct sg_io_hdr
{
int interface_id; /* [i] 'S' (обязательно) */
int dxfer_direction; /* [i] */
unsigned char cmd_len; /* [i] */
unsigned char mx_sb_len; /* [i] */
unsigned short iovec_count; /* [i] */
unsigned int dxfer_len; /* [i] */
void * dxferp; /* [i], [*io] */
unsigned char * cmdp; /* [i], [*i] */
unsigned char * sbp; /* [i], [*o] */
unsigned int timeout; /* [i] unit: millisecs */
unsigned int flags; /* [i] */
int pack_id; /* [i->o] */
void * usr_ptr; /* [i->o] */
unsigned char status; /* [o] */
unsigned char masked_status; /* [o] */
unsigned char msg_status; /* [o] */
unsigned char sb_len_wr; /* [o] */
unsigned short host_status; /* [o] */
unsigned short driver_status; /* [o] */
int resid; /* [o] */
unsigned int duration; /* [o] */
unsigned int info; /* [o] */
} sg_io_hdr_t; /* 64 байта длиной (на i386) */
|
Не все поля этой структуры являются обязательными, поэтому я опишу здесь только наиболее часто используемые:
interface_id: всегда должно быть равноS.dxfer_direction: указывает направление передачи данных; может принимать одно из значений:SG_DXFER_NONE: передача данных не осуществляется. Пример: SCSI-команда Test Unit Ready.SG_DXFER_TO_DEV: передача данных на устройство. Пример: команда SCSI WRITE.SG_DXFER_FROM_DEV: передача данных с устройства. Пример: команда SCSI READ.SG_DXFER_TO_FROM_DEV: передача данных в обоих направлениях.SG_DXFER_UNKNOWN: направление передачи данных неизвестно.
cmd_len: размер указателяcmdp, указывающего на SCSI-команду.mx_sb_len: максимальный размер данных, которые могут быть записаны по адресу указателяsbp, когда вывод осуществляется вsense_buffer.dxfer_len: размер пользовательской памяти, предназначенной для передачи данных.dxferp: указатель на пользовательскую память размером не менееdxfer_lenбайт, предназначенную для передачи данных.cmdp: указатель на SCSI-команду, которую нужно выполнить.sbp: указатель на буфер информации об ошибке.timeout: используется для задания таймаута выполнения команды command.status: байт состояния, определяемый согласно стандарту SCSI.
Схематично работа с этой структурой выглядит так: указатель
cmdp должен указывать на блок SCSI CDB, длина которого хранится в
cmd_len;
sbp указывает на пользовательскую память размером максимум
mx_sb_len. По этому адресу в случае возникновения ошибки будет записана
подробная информация об ошибке. Поле
dxferp указывает на память SCSI-устройства, откуда (или куда) будут
переданы данные (в зависимости от параметра
dxfer_direction).
И, наконец, давайте рассмотрим команду inquiry и ее выполнение с помощью универсального драйвера.
Пример: выполнение команды inquiry
Команда inquiry – это наиболее распространенная команда, поддерживаемая всеми SCSI-устройствами.
Эта команда используется для получения основной информации о SCSI-устройстве и часто используется в
качестве операции ping
для проверки доступности SCSI-устройства. В таблице 2 показан формат команды inquiry согласно стандарту SCSI.
Таблица 2. Формат команды inquiry
| бит 7 | бит 6 | бит 5 | бит 4 | бит 3 | бит 2 | бит 1 | бит 0 | |
|---|---|---|---|---|---|---|---|---|
| байт 0 | код операции = 12h | |||||||
| байт 1 | LUN | зарезервировано | EVPD | |||||
| байт 2 | код страницы | |||||||
| байт 3 | зарезервировано | |||||||
| байт 4 | размер выделенной памяти | |||||||
| байт 5 | контрольный | |||||||
Если бит параметра EVPD (enable vital product data - выводить главную информацию о продукте) равен 0 и байт параметра Page Code также равен 0, то будет возвращен стандартный результат команды inquire. Если бит EVPD равен 1, то будет возвращена некая специфичная для производителя устройства информация в соответствии с полем Page Code.
В листинге 2 показаны фрагменты кода, иллюстрирующие использование универсального API для SCSI.
Давайте сначала рассмотрим пример задания значений
sg_io_hdr.
Листинг 2. Задание значений sg_io_hdr.
struct sg_io_hdr * init_io_hdr() {
struct sg_io_hdr * p_scsi_hdr = (struct sg_io_hdr *)malloc(sizeof(struct sg_io_hdr));
memset(p_scsi_hdr, 0, sizeof(struct sg_io_hdr));
if (p_scsi_hdr) {
p_scsi_hdr->interface_id = 'S'; /* у нас нет другого выбора! */
/* помещаем LUN во второй байт блока cdb*/
p_scsi_hdr->flags = SG_FLAG_LUN_INHIBIT;
}
return p_scsi_hdr;
}
void destroy_io_hdr(struct sg_io_hdr * p_hdr) {
if (p_hdr) {
free(p_hdr);
}
}
void set_xfer_data(struct sg_io_hdr * p_hdr, void * data, unsigned int length) {
if (p_hdr) {
p_hdr->dxferp = data;
p_hdr->dxfer_len = length;
}
}
void set_sense_data(struct sg_io_hdr * p_hdr, unsigned char * data,
unsigned int length) {
if (p_hdr) {
p_hdr->sbp = data;
p_hdr->mx_sb_len = length;
}
}
|
Эти функции используются для задания значений полей объекта
sg_io_hdr. Некоторые его поля указывают на память в пользовательском
пространстве. При успешном выполнении выходные данные команды SCSI, копируются в память, на которую указывает
dxferp. В противном случае туда, куда указывает
sbp будет записана подробная информация о возникшей ошибке.
В листинге 3 показан пример посылки команды inquiry целевому SCSI-устройству.
Листинг 3. Посылка команды inquiry целевому SCSI-устройству.
int execute_Inquiry(int fd, int page_code, int evpd, struct sg_io_hdr * p_hdr) {
unsigned char cdb[6];
/* задаем формат блока cdb */
cdb[0] = 0x12; /*код операции для Inquiry*/
cdb[1] = evpd & 1;
cdb[2] = page_code & 0xff;
cdb[3] = 0;
cdb[4] = 0xff;
cdb[5] = 0; /*контрольный байт, просто используем 0 */
p_hdr->dxfer_direction = SG_DXFER_FROM_DEV;
p_hdr->cmdp = cdb;
p_hdr->cmd_len = 6;
int ret = ioctl(fd, SG_IO, p_hdr);
if (ret<0) {
printf("Sending SCSI Command failed.\n");
close(fd);
exit(1);
}
return p_hdr->status;
}
|
Итак, сначала функция подготавливает блок CDB в соответствии со стандартным форматом
команды inquiry. Затем она вызывает функцию ioctl(),
передавая в нее файловый дескриптор SG_IO и объект
sg_io_hdr; статус ответа потом помещается в поле
status объекта
sg_io_hdr.
Теперь давайте посмотрим, как приложение использует эту функцию для выполнения команды inquiry (Листинг 4):
Листинг 4. Приложение выполняет команду inquiry
unsigned char sense_buffer[SENSE_LEN];
unsigned char data_buffer[BLOCK_LEN*256];
void test_execute_Inquiry(char * path, int evpd, int page_code) {
struct sg_io_hdr * p_hdr = init_io_hdr();
set_xfer_data(p_hdr, data_buffer, BLOCK_LEN*256);
set_sense_data(p_hdr, sense_buffer, SENSE_LEN);
int status = 0;
int fd = open(path, O_RDWR);
if (fd>0) {
status = execute_Inquiry(fd, page_code, evpd, p_hdr);
printf("the return status is %d\n", status);
if (status!=0) {
show_sense_buffer(p_hdr);
} else{
show_vendor(p_hdr);
show_product(p_hdr);
show_product_rev(p_hdr);
}
} else {
printf("failed to open sg file %s\n", path);
}
close(fd);
destroy_io_hdr(p_hdr);
}
|
Процесс отсылки команды SCSI в данном случае довольно прост. Во-первых, нужно выделить
пользовательскую память для буфера данных и буфера описания ошибки и указать на них в объекте
sg_io_hdr. Затем нужно открыть файл устройства и получить файловый
дескриптор. С этими параметрами команда может быть передана на целевое устройство. После выполнения
команды результат копируется с целевого SCSI-устройства в буфер(ы), находящиеся в пользовательской
памяти.
Листинг 5. Передача SCSI-команды с параметрами на целевое устройство
void show_vendor(struct sg_io_hdr * hdr) {
unsigned char * buffer = hdr->dxferp;
int i;
printf("vendor id:");
for (i=8; i<16; ++i) {
putchar(buffer[i]);
}
putchar('\n');
}
void show_product(struct sg_io_hdr * hdr) {
unsigned char * buffer = hdr->dxferp;
int i;
printf("product id:");
for (i=16; i<32; ++i) {
putchar(buffer[i]);
}
putchar('\n');
}
void show_product_rev(struct sg_io_hdr * hdr) {
unsigned char * buffer = hdr->dxferp;
int i;
printf("product ver:");
for (i=32; i<36; ++i) {
putchar(buffer[i]);
}
putchar('\n');
}
int main(int argc, char * argv[]) {
test_execute_Inquiry(argv[1], 0, 0);
return EXIT_SUCCESS;
}
|
Стандартный ответ на SCSI-команду inquiry (когда поля Page Code и EVPD равны 0) имеет довольно сложный формат. Согласно стандарту, ID производителя находится в байтах с 8-го по 15-й, ID продукта – с 16-го по 31-й, а версия продукта – с 32-го по 35-й байты. Эту информацию можно извлечь, чтобы проверить, что команда была успешно выполнена.
Создав этот простой пример, запустите его на устройстве /dev/sg0 (обычно это жесткий диск). Вы должны получить следующий результат:
[root@taomaoy scsi_test]# ./scsi_test /dev/sg0 the return status is 0 vendor id:ATA product id:ST3160812AS product ver:3.AA |
Результат совпадает с данными программы sg_map.
В Linux есть универсальный драйвер SCSI-устройств и интерфейс разработки приложений к нему,
позволяющий вам в ваших приложениях посылать SCSI-команды напрямую SCSI-устройствам. Вы можете
вручную создавать SCSI-команды, задавать необходимые параметры в объекте sg_io_hdr,
затем вызывать ioctl() для выполнения этих команд и получать результат
в том же самом объекте sg_io_hdr.
| Описание | Имя | Размер | Метод загрузки |
|---|---|---|---|
| Образцы кода для этой статьи | scsi_test.zip | 3КБ | HTTP |
Научиться
- Оригинал cтатьи (EN).
- Хотите узнать больше о подсистеме SCSI в Linux?
Познакомьтесь со следующими статьями:
- В статье " Анатомия подсистемы SCSI в Linux" (developerWorks, ноябрь 2007 года) рассказывается о подсистеме SCSI в Linux и обсуждаются пути ее развития в будущем.
- В статье " Анатомия методов синхронизации в Linux" (EN, developerWorks, октябрь 2007 года) описываются атомарные операции синхронизации (часто используемые драйверами SCSI-устройств).
- В статье" Применение специальных возможностей GCC в ядре Linux" (developerWorks, ноябрь 2008 года) рассказывается о наборе компиляторов GNU; в ней вы можете найти пример использования интервалов в блоке switch драйвера SCSI (листинг 2).
-
Интерфейсы устройств хранения TC T10 SCSI - это отличная база знаний по
I/O интерфейсам, особенно SCSI-3 и
SAS.
- В этой замечательной библиотеке об
универсальном SCSI-драйвере Linux
вы можете найти обновления версии драйвера, общую информацию о нем, список возможностей драйвера, ссылки на загрузку
драйверов устройств, утилиты
и родственные Web сайты.
- В основном разделе Linux и в разделе Linux для новичков вы найдете множество ресурсов для Linux-разработчиков; также просмотрите наши самые популярные статьи и руководства (EN).
- Ознакомьтесь с другими советами и руководствами по Linux на сайте developerWorks (EN).
- Следите за новостями в разделе технических мероприятий и Web-трансляций (EN) developerWorks.
Получить продукты и технологии
- Разработайте ваш следующий Linux-проект с помощью ознакомительного ПО от IBM, которое можно загрузить непосредственно с сайта developerWorks.
Обсудить
- Участвуйте в жизни сообщества developerWorks (EN) - посещайте блоги, форумы, подкасты и дискуссионные пространства.