/* A pointer to the inode structure for
* the file, if we need it. In our case we
* do, because we need a write function. */
NULL
/* The read function for the file. Irrelevant,
* because we put it in the inode structure above */
};
/* Module initialization and cleanup ******************* */
/* Initialize the module - register the proc file */
int init_module() {
/* Success if proc_register[_dynamic] is a success, failure otherwise */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
/* In version 2.2, proc_register assign a dynamic
* inode number automatically if it is zero in the
* structure, so there's no more need for
* proc_register_dynamic */
return proc_register(&proc_root, &Our_Proc_File);
#else
return proc_register_dynamic(&proc_root, &Our_Proc_File);
#endif
}
/* Cleanup - unregister our file from /proc */
void cleanup_module() {
proc_unregister(&proc_root, Our_Proc_File.low_ino);
}
Работа с файлами устройств (запись и IOCTL)
Файлы устройства представляют физические устройства. Многие физические устройства используются для вывода и для ввода, так должен иметься некоторый механизм для драйверов устройства в ядре, чтобы послать вывод устройству из процесса. Это выполняется, открывая файл устройства для вывода и записывая в него точно так же, как в обычный файл. В следующем примере, это выполнено функцией device_write.
Этого не всегда достаточно. Вообразите, что Вы имеете последовательный порт, связанный с модемом (даже если Вы имеете внутренний модем, он выглядит с точки зрения CPU как последовательный порт, связанный с модемом, так что Вы не должны слишком сдерживать Ваше воображение). Естественное решение использовать файл устройства, чтобы передавать модему команды модема или данные, которые будут посланы через телефонную линию и читать из модема ответы для команд или данные, полученные через телефонную линию. Однако, это оставляет открытым вопрос о том что делать, когда Вы должны работать с последовательным портом непосредственно, например настроить скорость обмена данными с портом.
Ответ в Unix должен использовать специальную функцию, названную ioctl (сокращение от input output control). Каждое устройство может иметь собственные команды ioctl, которые могут читать ioctl (для передачи данных от процесса ядру), записывать ioctl (чтобы возвратить информацию процессу)[4], выполнять оба действия или ни одно из них. Функция ioctl вызывается с тремя параметрами: описатель файла соответствующий файлу устройства, ioctl номер, и параметра, который имеет тип long, так что Вы можете использовать приведение, чтобы передать что-нибудь.[5]
Ioctl номер кодирует главный номер устройства, тип ioctl команды и тип параметра. Этот ioctl номер обычно создается макрообращением (_IO, _IOR, _IOW или _IOWR: в зависимости от типа) в файле заголовка. Этот файл заголовка должен быть присоединен командой #include программой, которая использует ioctl и модулем (так что они могут генерировать соответствующие ioctl). В примере ниже, файл заголовка chardev.h и программа, которая использует это ioctl.c.
Если Вы хотите использовать ioctl в ваших собственных модулях, самое лучшее получить официальное ioctl назначение, так, если Вы случайно получаете ioctl кого-то другого вы будете знать, что что-то неправильно. Для большего количества информации, проконсультируйтесь в файле `Documentation/ioctl-number.txt' дерева исходников ядра.
chardev.c
/* chardev.c
*
* Create an input/output character device
*/
/* Copyright (C) 1998-99 by Ori Pomerantz */
/* The necessary header files */
/* Standard in kernel modules */
#include <linux/kernel.h> /* We're doing kernel work */
#include <linux/module.h> /* Specifically, a module */
/* Deal with CONFIG_MODVERSIONS */
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include <linux/modversions.h>
#endif
/* For character devices */
/* The character device definitions are here */
#include <linux/fs.h>
/* A wrapper which does next to nothing at
* at present, but may help for compatibility
* with future versions of Linux */
#include <linux/wrapper.h>
/* Our own ioctl numbers */
#include "chardev.h"
/* In 2.2.3 /usr/include/linux/version.h includes a
* macro for this, but 2.0.35 doesn't - so I add it
* here if necessary. */
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
#include <asm/uaccess.h> /* for get_user and put_user */
#endif
#define SUCCESS 0
/* Device Declarations ******************************** */
/* The name for our device, as it will appear in /proc/devices */
#define DEVICE_NAME "char_dev"
/* The maximum length of the message for the device */
#define BUF_LEN 80
/* Is the device open right now? Used to prevent concurent access into the same device */
static int Device_Open = 0;
/* The message the device will give when asked */
static char Message[BUF_LEN];
/* How far did the process reading the message get?
* Useful if the message is larger than the size of the
* buffer we get to fill in device_read. */
static char *Message_Ptr;
/* This function is called whenever a process attempts to open the device file */
static int device_open(struct inode *inode, struct file *file) {
#ifdef DEBUG
printk("device_open(%p)n", file);
#endif
/* We don't want to talk to two processes at the same time */
if (Device_Open) return -EBUSY;
/* If this was a process, we would have had to be
* more careful here, because one process might have
* checked Device_Open right before the other one
* tried to increment it. However, we're in the
* kernel, so we're protected against context switches.
*
* This is NOT the right attitude to take, because we
* might be running on an SMP box, but we'll deal with
* SMP in a later chapter. */
Device_Open++;
/* Initialize the message */
Message_Ptr = Message;
MOD_INC_USE_COUNT;
return SUCCESS;
}
/* This function is called when a process closes the
* device file. It doesn't have a return value because
* it cannot fail. Regardless of what else happens, you
* should always be able to close a device (in 2.0, a 2.2
* device file could be impossible to close). */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static int device_release(struct inode *inode, struct file *file)
#else
static void device_release(struct inode *inode, struct file *file)
#endif
{
#ifdef DEBUG
printk("device_release(%p,%p)n", inode, file);
#endif
/* We're now ready for our next caller */
Device_Open--;
MOD_DEC_USE_COUNT;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
return 0;
#endif
}
/* This function is called whenever a process which
* has already opened the device file attempts to read from it. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t device_read(
struct file *file,
char *buffer, /* The buffer to fill with the data */
size_t length, /* The length of the buffer */
loff_t *offset) /* offset to the file */
#else
static int device_read(
struct inode *inode, struct file *file,
char *buffer, /* The buffer to fill with the data */
int length) /* The length of the buffer (mustn't write beyond that!) */
#endif
{
/* Number of bytes actually written to the buffer */
int bytes_read = 0;
#ifdef DEBUG
printk("device_read(%p,%p,%d)n", file, buffer, length);
#endif
/* If we're at the end of the message, return 0 (which signifies end of file) */
if (*Message_Ptr == 0) return 0;
/* Actually put the data into the buffer */
while (length && *Message_Ptr) {
/* Because the buffer is in the user data segment,
* not the kernel data segment, assignment wouldn't
* work. Instead, we have to use put_user which
* copies data from the kernel data segment to the
* user data segment. */
put_user(*(Message_Ptr++), buffer++);
length--;
bytes_read++;
}
#ifdef DEBUG
printk("Read %d bytes, %d leftn", bytes_read, length);
#endif
/* Read functions are supposed to return the number
* of bytes actually inserted into the buffer */
return bytes_read;
}
/* This function is called when somebody tries to write into our device file. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t device_write(struct file *file, const char *buffer, size_t length, loff_t *offset)
#else
static int device_write(struct inode *inode, struct file *file, const char *buffer, int length)
#endif
{
int i;
#ifdef DEBUG
printk("device_write(%p,%s,%d)", file, buffer, length);
#endif
for(i=0; i<length && i<BUF_LEN; i++)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
get_user(Message[i], buffer+i);
#else
Message[i] = get_user(buffer+i);
#endif
Message_Ptr = Message;
/* Again, return the number of input characters used */
return i;
}
/* This function is called whenever a process tries to
* do an ioctl on our device file. We get two extra
* parameters (additional to the inode and file
* structures, which all device functions get): the number
* of the ioctl called and the parameter given to the ioctl function.
*
* If the ioctl is write or read/write (meaning output