Глава 5. Файловая система /proc 5.1. Файловая система /proc: создание файлов, доступных для чтения Linux предоставляет ядру и модулям ядра дополнительный механизм передачи информации заинтересованным в ней процессам -- это файловая система /proc. Первоначально она создавалась с целью получения сведений о процессах (отсюда такое название). Теперь она интенсивно используется и самим ядром, которому есть что сообщить! Например, /proc/modules -- список загруженных модулей, /proc/meminfo -- статистика использования памяти. Методика работы с файловой системой /proc очень похожа на работу драйверов с файлами устройств: вы создаете структуру со всей необходимой информацией, включая указатели на функции-обработчики (в нашем случае имеется только один обработчик, который обслуживает чтение файла в /proc). Функция init_module регистрирует структуру, а cleanup_module отменяет регистрацию. Основная причина, по которой используется proc_register_dynamic [6] состоит в том, что номер inode, для нашего файла, заранее неизвестен, поэтому мы даем возможность ядру определить его самостоятельно, чтобы предотвратить возможные конфликты. В обычных файловых системах, размещенных на диске, не в памяти, как /proc, inode указывает на то место в дисковом пространстве, где размещена индексная запись (index node, сокращенно -- inode) о файле. Inode содержит все необходимые сведения о файле, например права доступа, указатель на первый блок с содержимым файла. Поскольку мы не предусматриваем обработку операций открытия/закрытия файла в файловой системе /proc, то нам некуда вставлять вызовы функций try_module_get и try_module_put. Если вдруг случится так, что модуль был выгружен в то время как соответствующий файл в /proc оставался открытым, к сожалению у нас не будет возможности избежать возможных последствий. В следующем разделе мы расскажем о довольно сложном, но достаточно гибком способе защиты от подобных ситуаций. Пример 5-1. procfs.c /* * procfs.c - пример создания "файла" в /proc */ /* Необходимо для любого модуля */ #include <linux/module.h> /* Все-таки мы работаем с ядром! */ #include <linux/kernel.h> /* Необходимо для работы с файловой системой /proc */ #include <linux/proc_fs.h> struct proc_dir_entry *Our_Proc_File; /* Обработчик чтения из файла в /proc. * * Аргументы * ========= * 1. Буфер с данными. Как его заполнить -- вы решаете сами * 2. Указатель на указатель на строку символов. * Если вы не желаете использовать буфер * размещенный ядром. * 3. Текущая позиция в файле * 4. Размер буфера. * 5. Признак конца файла, "1" == EOF. * 6. Указатель на данные (необходим в случае единственного * обработчика на несколько файлов в /proc) * * Порядок использования и возвращаемое значение * ============================================= * Нулевое значение == "буфер пуст", т.е. "Конец файла". * Отрицательное значение == код ошибки. * * Дополнительные сведения * ======================= * Основные принципы реализации этой функции * я почерпнул не из документации, а из исходных текстов * модулей, выполняющих подобные действия. * Меня интересовало использование * поля get_info в структуре proc_dir_entry (Если вам это интересно * то для поиска я пользовался утилитами find и grep), * Интересующий меня пример я нашел в <kernel source * directory>/fs/proc/array.c. * * Когда вам что-то непонятно, то лучше всего * поискать примеры в исходных текстах ядра. В этом состоит * огромное преимущество Linux перед другими ОС, * так как нам доступны все исходные тексты, так что -- * пользуйтесь этим преимуществом! */ ssize_t procfile_read(char *buffer, char **buffer_location, off_t offset, int buffer_length, int *eof, void *data) { printk(KERN_INFO "inside /proc/test : procfile_read\n"); int len = 0; /* Фактическое число байт */ static int count = 1; /* * Мы всегда должны выдавать имеющуюся информацию, * если пользователь спрашивает -- мы должны ответить. * * Это очень важно, поскольку библиотечная функция read * будет продолжать обращаться к системному вызову * read до тех пор, пока ядро не ответит, что сведений больше нет * или пока буфер не будет заполнен. */ if (offset > 0) { printk(KERN_INFO "offset %d : /proc/test : procfile_read, \ wrote %d Bytes\n", (int)(offset), len); *eof = 1; return len; } /* * Заполнить буфер и получить его размер */ len = sprintf(buffer, "For the %d%s time, go away!\n", count, (count % 100 > 10 && count % 100 < 14) ? "th" : (count % 10 == 1) ? "st" : (count % 10 == 2) ? "nd" : (count % 10 == 3) ? "rd" : "th"); count++; /* * Вернуть размер буфера */ printk(KERN_INFO "leaving /proc/test : procfile_read, wrote %d Bytes\n", len); return len; } int init_module() { int rv = 0; Our_Proc_File = create_proc_entry("test", 0644, NULL); Our_Proc_File->read_proc = procfile_read; Our_Proc_File->owner = THIS_MODULE; Our_Proc_File->mode = S_IFREG | S_IRUGO; Our_Proc_File->uid = 0; Our_Proc_File->gid = 0; Our_Proc_File->size = 37; printk(KERN_INFO "Trying to create /proc/test:\n"); if (Our_Proc_File == NULL) { rv = -ENOMEM; remove_proc_entry("test", &proc_root); printk(KERN_INFO "Error: Could not initialize /proc/test\n"); } else { printk(KERN_INFO "Success!\n"); } return rv; } void cleanup_module() { remove_proc_entry("test", &proc_root); printk(KERN_INFO "/proc/test removed\n"); } Пример 5-2. Makefile obj-m += procfs.o 5.2. Файловая система /proc: создание файлов, доступных для записи Пока мы знаем о двух способах получения информации от драйвера устройства: можно зарегистрировать драйвер и создать файл устройства, и создать файл в файловой системе /proc. Единственная проблема -- мы пока ничего не можем передать модулю ядра. Для начала попробуем организовать передачу данных модулю ядра посредством файловой системы /proc. Поскольку файловая система /proc была написана, главным образом, для того чтобы получать данные от ядра, она не предусматривает специальных средств для записи данных в файлы. Структура proc_dir_entry не содержит указатель на функцию-обработчик записи. Поэтому, вместо того, чтобы писать в /proc напрямую, мы вынуждены будем использовать стандартный, для файловой системы, механизм. Linux предусматривает возможность регистрации файловой системы. Так как каждая файловая система должна иметь собственные функции, для обработки inode и выполнять файловые операции, [7] то имеется специальная структура, которая хранит указатели на все необходимые функции-обработчики -- struct inode_operations, которая включает указатель на struct file_operations. Файловая система /proc, всякий раз, когда мы регистрируем новый файл, позволяет указать -- какая struct inode_operations будет использоваться для доступа к нему. В свою очередь, в этой структуре имеется указатель struct file_operations, а в ней уже находятся указатели на наши функции-обработчики. Обратите внимание: стандартные понятия "чтение" и "запись", в ядре имеют противоположный смысл. Функции чтения используются для записи в файл, в то время как функции записи используются для чтения из файла. Причина в том, что понятия "чтение" и "запись" рассматриваются здесь с точки зрения пользователя: если процесс читает что-то из ядра -- ядро должно записать эти данные, если процесс пишет -- ядро должно прочитать то, что записано. Еще один интересный момент -- функция module_permission. Она вызывается всякий раз, когда процесс пытается обратиться к файлу в файловой системе /proc, и принимает решение -- разрешить доступ к файлу или нет. На сегодняшний день, решение принимается только на основе выполняемой операции и UID процесса, но в принципе возможна и иная организация принятия решения, например, разрешать ли одновременный доступ к файлу нескольким процессам и пр.. Причина, по которой для копирования данных используются функции put_user и get_user, состоит в том, что процессы в Linux (по крайней мере в архитектуре Intel) исполняются в изолированных адресных пространствах, не пересекающихся с адресным пространством ядра. Это означает, что указатель, не содержит уникальный адрес физической памяти -- он хранит логический адрес в адресном пространстве процесса. Единственное адресное пространство, доступное процессу -- это его собственное адресное пространство. Практически любой модуль ядра, должен иметь возможность обмена информацией с пользовательскими процессами. Однако, когда модуль ядра получает указатель на некий буфер, то адрес этого буфера находится в адресном пространстве процесса. Макрокоманды put_user и get_user позволяют обращаться к памяти процесса по указанному им адресу. Пример 5-3. procfs.c /* * procfs.c - Пример создания файла в /proc, * который доступен как на чтение, так и на запись. */ /* Необходимо для любого модуля */ #include <linux/module.h> /* Все-таки мы работаем с ядром! */ #include <linux/kernel.h> /* Необходимо для работы с файловой системой /proc */ #include <linux/proc_fs.h> /* определения функций get_user и put_user */ #include <asm/uaccess.h> /* * Место хранения последнего принятого сообщения, * которое будет выводиться в файл, чтобы показать, что * модуль действительно может получать ввод от пользователя */ #define MESSAGE_LENGTH 80 static char Message[MESSAGE_LENGTH]; static struct proc_dir_entry *Our_Proc_File; #define PROC_ENTRY_FILENAME "rw_test" static ssize_t module_output(struct file *filp, /* см. include/linux/fs.h */ char *buffer, /* буфер с данными */ size_t length, /* размер буфера */ loff_t * offset) { static int finished = 0; int i; char message[MESSAGE_LENGTH + 30]; /* * Для индикации признака конца файла возвращается 0. * Если этого не сделать, процесс будет продолжать * пытаться читать из файла, * угодив в бесконечный цикл. */ if (finished) { finished = 0; return 0; } /* * Для передачи данных из пространства ядра * в пространство пользователя * следует использовать put_user. * В обратном направлении -- get_user. */ sprintf(message, "Last input:%s", Message); for (i = 0; i < length && message[i]; i++) put_user(message[i], buffer + i); /* * Обратите внимание: в данной ситуации мы исходим из предположения, * что размер сообщения меньше, чем len, * в противном случае сообщение будт обрезано. * В реальной ситуации, если длина сообщения больше чем * len, то возвращается len, а остаток сообщения возвращается * на последующих вызовах. */ finished = 1; return i; /* Вернуть количество "прочитанных" байт */ } static ssize_t module_input(struct file *filp, const char *buff, size_t len, loff_t * off) { int i; /* * Переместить данные, полученные от пользователя в буфер, * который позднее будет выведен функцией module_output. */ for (i = 0; i < MESSAGE_LENGTH - 1 && i < len; i++) get_user(Message[i], buff + i); Message[i] = '\0'; /* Обычная строка, завершающаяся символом \0 */ return i; } /* * Эта функция принимает решение о праве на выполнение операций с файлом * 0 -- разрешено, ненулеое значение -- запрещено. * * Операции с файлом могут быть: * 0 - Исполнениеe (не имеет смысла в нашей ситуации) * 2 - Запись (передача от пользователя к модулю ядра) * 4 - Чтение (передача от модуля ядра к пользователю) * * Эта функция проверяет права доступа к файлу * Права, выводимые командой ls -l * могут быть проигнорированы здесь. */ static int module_permission(struct inode *inode, int op, struct nameidata *foo) { /* * Позволим любому читать файл, но * писать -- только root-у (uid 0) */ if (op == 4 || (op == 2 && current->euid == 0)) return 0; /* * Если что-то иное -- запретить доступ */ return -EACCES; } /* * Файл открыт -- пока нам нет нужды беспокоиться о чем-то * единственное, что нужно сделать -- это нарастить * счетчик обращений к модулю. */ int module_open(struct inode *inode, struct file *file) { try_module_get(THIS_MODULE); return 0; } /* * Файл закрыт -- уменьшить счетчик обращений. */ int module_close(struct inode *inode, struct file *file) { module_put(THIS_MODULE); return 0; /* все нормально! */ } static struct file_operations File_Ops_4_Our_Proc_File = { .read = module_output, .write = module_input, .open = module_open, .release = module_close, }; /* * Операции над индексной записью нашего файла. Необходима * для того, чтобы указать местоположение структуры * file_operations нашего файла, а так же, чтобы задать адрес * функции определения прав доступа к файлу. Здесь можно указать адреса * других функций-обработчиков, но нас они не интересуют. */ static struct inode_operations Inode_Ops_4_Our_Proc_File = { .permission = module_permission, /* проверка прав доступа */ }; /* * Начальная и конечная функции модуля */ int init_module() { int rv = 0; Our_Proc_File = create_proc_entry(PROC_ENTRY_FILENAME, 0644, NULL); Our_Proc_File->owner = THIS_MODULE; Our_Proc_File->proc_iops = &Inode_Ops_4_Our_Proc_File; Our_Proc_File->proc_fops = &File_Ops_4_Our_Proc_File; Our_Proc_File->mode = S_IFREG | S_IRUGO | S_IWUSR; Our_Proc_File->uid = 0; Our_Proc_File->gid = 0; Our_Proc_File->size = 80; if (Our_Proc_File == NULL) { rv = -ENOMEM; remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root); printk(KERN_INFO "Error: Could not initialize /proc/test\n"); } return rv; } void cleanup_module() { remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root); } Хотите еще примеры работы с файловой системой /proc? Хорошо, но имейте ввиду, ходят слухи, что /proc уходит в небытие и вместо нее следует использовать sysfs. Дополнительные сведения о файловой системе /proc вы найдете в linux/Documentation/DocBook/. Дайте команду make help, она выведет инструкции по созданию документации в различных форматах, например: make htmldocs. Глава 6. Работа с файлами устройств Файлы устройств представляют физические устройства. В большинстве своем, физические устройства используются как для вывода, так и для ввода, таким образом необходимо иметь некий механизм для передачи данных от процесса (через модуль ядра) к устройству. Один из вариантов -- открыть файл устройства и записать в него данные, точно так же, как в обычный файл. В следующем примере, операция записи реализуется функцией device_write. Однако, этого не всегда бывает достаточно. Допустим, что у вас есть модем, подключенный к компьютеру через последовательный порт (это может быть и внутренний модем, с точки зрения CPU он "выглядит" как модем, связанный с последовательным портом). Естественное решение -- использовать файл устройства для передачи данных модему (это могут быть команды модема или данные, которые будут посланы в телефонную линию) и для чтения данных из модема (ответы модема на команды или данные, полученные из телефонной линии). Однако, это оставляет открытым вопрос о том, как взаимодействовать непосредственно с последовательным портом, например, как настроить скорость обмена. Ответ: в Unix следует использовать специальную функцию с именем ioctl (сокращенно от Input Output ConTroL). Любое устройство может иметь свои команды ioctl, которые могут читать (для передачи данных от процесса ядру), писать (для передачи данных от ядра к процессу), и писать и читать, и ни то ни другое, [8] Функция ioctl вызывается с тремя параметрами: дескриптор файла устройства, номер ioctl и третий параметр, который имеет тип long, используется для передачи дополнительных аргументов. [9] Номер ioctl содержит комбинацию бит, составляющих старший номер устройства, тип команды и тип дополнительного параметра. Обычно номер ioctl создается макроопределением (_IO, _IOR, _IOW или _IOWR, в зависимости от типа) в файле заголовка. Этот заголовочный должен подключаться директивой #include, к исходным файлам программы, которая использует ioctl для обмена данными с модулем. В примере, приводимом ниже, представлены файл заголовка chardev.h и программа, которая взаимодействует с модулем ioctl.c. Если вы предполагаете использовать ioctl в ваших собственных модулях, то вам надлежит обратиться к файлу Documentation/ioctl-number.txt с тем, чтобы не "занять" зарегистрированные номера ioctl. Пример 6-1. chardev.c /* * chardev.c - Пример создания символьного устройства * доступного на запись/чтение */ #include <linux/module.h> /* Необходимо для любого модуля */ #include <linux/kernel.h> /* Все-таки мы работаем с ядром! */ #include <linux/fs.h> #include <asm/uaccess.h> /* определения функций get_user и put_user */ #include "chardev.h" #define SUCCESS 0 #define DEVICE_NAME "char_dev" #define BUF_LEN 80 /* * Устройство уже открыто? Используется для * предотвращения конкурирующих запросов к устройству */ static int Device_Open = 0; /* * Ответ устройства на запрос */ static char Message[BUF_LEN]; /* * Позиция в буфере. * Используется в том случае, если сообщение оказывется длиннее * чем размер буфера. */ static char *Message_Ptr; /* * Вызывается когда процесс пытается открыть файл устройства */ static int device_open(struct inode *inode, struct file *file) { #ifdef DEBUG printk("device_open(%p)\n", file); #endif /* * В каждый конкретный момент времени только * один процесс может открыть файл устройства */ if (Device_Open) return -EBUSY; Device_Open++; /* * Инициализация сообщения */ Message_Ptr = Message; try_module_get(THIS_MODULE); return SUCCESS; } static int device_release(struct inode *inode, struct file *file) { #ifdef DEBUG printk("device_release(%p,%p)\n", inode, file); #endif /* * Теперь мы готовы принять запрос от другого процесса */ Device_Open--; module_put(THIS_MODULE); return SUCCESS; } /* * Вызывается когда процесс, открывший файл устройства * пытается считать из него данные. */ static ssize_t device_read(struct file *file, /* см. include/linux/fs.h*/ char __user * buffer, /* буфер для сообщения */ size_t length, /* размер буфера */ loff_t * offset) { /* * Количество байт, фактически записанных в буфер */ int bytes_read = 0; #ifdef DEBUG printk("device_read(%p,%p,%d)\n", file, buffer, length); #endif /* * Если достигнут конец сообщения -- вернуть 0 * (признак конца файла) */ if (*Message_Ptr == 0) return 0; /* * Собственно запись данных в буфер */ while (length && *Message_Ptr) { /* * Поскольку буфер располагается в пространстве пользователя, * обычное присвоение не сработает. Поэтому * для записи данных используется put_user, * которая копирует данные из пространства ядра * в пространство пользователя. */ put_user(*(Message_Ptr++), buffer++); length--; bytes_read++; } #ifdef DEBUG printk("Read %d bytes, %d left\n", bytes_read, length); #endif /* * Вернуть количество байт, помещенных в буфер. */ return bytes_read; } /* * Вызывается при попытке записи в файл устройства */ static ssize_t device_write(struct file *file, const char __user * buffer, size_t length, loff_t * offset) { int i; #ifdef DEBUG printk("device_write(%p,%s,%d)", file, buffer, length); #endif for (i = 0; i < length && i < BUF_LEN; i++) get_user(Message[i], buffer + i); Message_Ptr = Message; /* * Вернуть количество принятых байт */ return i; } /* * Вызывается, когда процесс пытается * выполнить операцию ioctl над файлом устройства. * Кроме inode и структуры file функция получает * два дополнительных параметра: * номер ioctl и дополнительные аргументы. * */ int device_ioctl(struct inode *inode, /* см. include/linux/fs.h */ struct file *file, /* то же самое */ unsigned int ioctl_num, /* номер и аргументы ioctl */ unsigned long ioctl_param) { int i; char *temp; char ch; /* * Реакция на различные команды ioctl */ switch (ioctl_num) { case IOCTL_SET_MSG: /* * Принять указатель на сообщение (в пространстве пользователя) * и переписать в буфер. * Адрес которого задан в дополнительно аргументе. */ temp = (char *)ioctl_param; /* * Найти длину сообщения */ get_user(ch, temp); for (i = 0; ch && i < BUF_LEN; i++, temp++) get_user(ch, temp); device_write(file, (char *)ioctl_param, i, 0); break; case IOCTL_GET_MSG: /* * Передать текущее сообщение вызывающему процессу - * записать по указанному адресу. */ i = device_read(file, (char *)ioctl_param, 99, 0); /* * Вставить в буфер завершающий символ \0 */ put_user('\0', (char *)ioctl_param + i); break; case IOCTL_GET_NTH_BYTE: /* * Этот вызов является вводом (ioctl_param) и * выводом (возвращаемое значение функции) одновременно */ return Message[ioctl_param]; break; } return SUCCESS; } /* Объявлнеия */ /* * В этой структуре хранятся адреса функций-обработчиков * операций, производимых процессом над устройством. * Поскольку указатель на эту структуру хранится в таблице устройств, * она не может быть локальной для init_module. * Отсутствующие указатели в структуре забиваются значением NULL. */ struct file_operations Fops = { .read = device_read, .write = device_write, .ioctl = device_ioctl, .open = device_open, .release = device_release, /* оно же close */ }; /* * Инициализация модуля - Регистрация символьного устройства */ int init_module() { int ret_val; /* * Регистрация символьного устройства * (по крайней мере - попытка регистрации) */ ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops); /* * Отрицательное значение означает ошибку */ if (ret_val < 0) { printk("%s failed with %d\n", "Sorry, registering the character device ", ret_val); return ret_val; } printk("%s The major device number is %d.\n", "Registeration is a success", MAJOR_NUM); printk("If you want to talk to the device driver,\n"); printk("you'll have to create a device file. \n"); printk("We suggest you use:\n"); printk("mknod %s c %d 0\n", DEVICE_FILE_NAME, MAJOR_NUM); printk("The device file name is important, because\n"); printk("the ioctl program assumes that's the\n"); printk("file you'll use.\n"); return 0; } /* * Завершение работы модуля - дерегистрация файла в /proc */ void cleanup_module() { int ret; /* * Дерегистрация устройства */ ret = unregister_chrdev(MAJOR_NUM, DEVICE_NAME); /* * Если обнаружена ошибка -- вывести сообщение */ if (ret < 0) printk("Error in module_unregister_chrdev: %d\n", ret); } Пример 6-2. chardev.h /* * chardev.h - определения ioctl. * * Определения, которые здесь находятся, * должны помещаться в заголовочный файл потому, * что они потребуются как модулю ядра (chardev.c), так и * вызывающему процессу (ioctl.c) */ #ifndef CHARDEV_H #define CHARDEV_H #include <linux/ioctl.h> /* * Старший номер устройства. В случае использования ioctl, * мы уже лишены возможности воспользоваться динамическим номером, * поскольку он должен быть известен заранее. */ #define MAJOR_NUM 100 /* * Операция передачи сообщения драйверу устройства */ #define IOCTL_SET_MSG _IOR(MAJOR_NUM, 0, char *) /* * _IOR означает, что команда передает данные * от пользовательского процесса к модулю ядра * * Первый аргумент, MAJOR_NUM -- старший номер устройства. * * Второй аргумент -- код команды * (можно указать иное значение). * * Третий аргумент -- тип данных, передаваемых в ядро */ /* * Операция получения сообщения от драйвера устройства */ #define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *) /* * Эта команда IOCTL используется для вывода данных. * Нам по прежнему нужен буфер, размещенный в адресном пространстве * вызывающего процесса, куда это сообщение должно быть переписано. */ /* * Команда получения n-ного байта сообщения */ #define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int) /* * Здесь команда IOCTL работает как на ввод, так и на вывод. * Она принимает от пользователя номер байта (n), * и возвращает n-ный байт сообщения (Message[n]). */ /* * Имя файла устройства */ #define DEVICE_FILE_NAME "char_dev" #endif Пример 6-3. ioctl.c /* * ioctl.c - Пример программы, использующей * ioctl для управления модулем ядра * * До сих пор мы ползовались командой cat, * для передачи данных в/из модуля. * Теперь же мы должны написать свою программу, * которая использовала бы ioctl. */ /* * Определения старшего номера устройства и коды операций ioctl */ #include "chardev.h" #include <fcntl.h> /* open */ #include <unistd.h> /* exit */ #include <sys/ioctl.h> /* ioctl */ /* * Функции работы с драйвером через ioctl */ ioctl_set_msg(int file_desc, char *message) { int ret_val; ret_val = ioctl(file_desc, IOCTL_SET_MSG, message); if (ret_val < 0) { printf("Ошибка при вызове ioctl_set_msg: %d\n", ret_val); exit(-1); } } ioctl_get_msg(int file_desc) { int ret_val; char message[100]; /* * Внимание - ядро понятия не имеет -- какой длины буфер мы используем * поэтому возможна ошибка, связанная с переполнением буфера. * В реальных проектах вам необходимо предусмотреть * передачу в ioctl двух дополнительных параметров: * собственно буфера сообщения и его длину */ ret_val = ioctl(file_desc, IOCTL_GET_MSG, message); if (ret_val < 0) { printf("Ошибка при вызове ioctl_get_msg: %d\n", ret_val); exit(-1); } printf("Получено сообщение (get_msg): %s\n", message); } ioctl_get_nth_byte(int file_desc) { int i; char c; printf("N-ный байт в сообщении (get_nth_byte): "); i = 0; while (c != 0) { c = ioctl(file_desc, IOCTL_GET_NTH_BYTE, i++); if (c < 0) { printf ("Ошибка при вызове ioctl_get_nth_byte на %d-м байте.\n", i); exit(-1); } putchar(c); } putchar('\n'); } /* * Main - Проверка работоспособности функции ioctl */ main() { int file_desc, ret_val; char *msg = "Это сообщение передается через ioctl\n"; file_desc = open(DEVICE_FILE_NAME, 0); if (file_desc < 0) { printf("Невозможно открыть файл устройства: %s\n", DEVICE_FILE_NAME); exit(-1); } ioctl_get_nth_byte(file_desc); ioctl_get_msg(file_desc); ioctl_set_msg(file_desc, msg); close(file_desc); } Пример 6-4. Makefile obj-m += chardev.o Для облегчения сборки примера, предлагается скрипт, который выполнит эту работу за вас: Пример 6-5. build.sh #/bin/sh # сборка пользовательского приложения gcc -o ioctl ioctl.c # создание файла устройства mknod char_dev c 100 0
|