设备文件应该代表物理设备。大多数物理设备既用于输出也用于输入,因此必须存在某种机制,让内核中的设备驱动程序能够从进程获取要发送到设备的输出。 这是通过打开设备文件进行输出并写入来实现的,就像写入文件一样。在以下示例中,这是通过以下方式实现的device_write.
这并不总是足够的。 想象一下,你有一个连接到调制解调器的串行端口(即使你有一个内置调制解调器,从 CPU 的角度来看,它仍然被实现为连接到调制解调器的串行端口,所以你不必过度发挥你的想象力)。 自然的做法是使用设备文件向调制解调器写入内容(调制解调器命令或要通过电话线发送的数据)并从调制解调器读取内容(命令的响应或通过电话线接收的数据)。 但是,这留下了一个悬而未决的问题,即当你需要与串行端口本身通信时该怎么办,例如发送数据发送和接收的速率。
Unix 中的答案是使用一个名为ioctl(输入输出控制的缩写)的特殊函数。 每个设备都可以有自己的ioctl命令,可以进行读ioctl(将信息从进程发送到内核),写ioctl(将信息返回到进程),[1] 两者或都不进行。ioctl该函数使用三个参数调用:相应设备文件的文件描述符、ioctl 编号和一个参数,该参数的类型为 long,因此你可以使用强制类型转换来传递任何内容。 [2]
ioctl 编号编码了主设备号、ioctl 的类型、命令和参数的类型。 此 ioctl 编号通常由宏调用创建(_IO, _IOR, _IOW或_IOWR--- 取决于类型)在头文件中。 然后,程序和内核模块都应包含此头文件,以便程序可以使用ioctl(以便它们可以生成适当的ioctl),而内核模块可以理解它。 在下面的示例中,头文件是chardev.h而使用它的程序是ioctl.c.
如果你想在自己的内核模块中使用ioctl,最好接收官方的ioctl分配,这样如果你不小心获得了别人的ioctl,或者他们获得了你的,你就会知道有问题。 有关更多信息,请查阅内核源代码树,网址为Documentation/ioctl-number.txt.
示例 7-1. chardev.c
/* * chardev.c - Create an input/output character device */ #include <linux/kernel.h> /* We're doing kernel work */ #include <linux/module.h> /* Specifically, a module */ #include <linux/fs.h> #include <asm/uaccess.h> /* for get_user and put_user */ #include "chardev.h" #define SUCCESS 0 #define DEVICE_NAME "char_dev" #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 is called whenever a process attempts to open the device file */ static int device_open(struct inode *inode, struct file *file) { #ifdef DEBUG printk(KERN_INFO "device_open(%p)\n", file); #endif /* * We don't want to talk to two processes at the same time */ if (Device_Open) return -EBUSY; Device_Open++; /* * Initialize the message */ Message_Ptr = Message; try_module_get(THIS_MODULE); return SUCCESS; } static int device_release(struct inode *inode, struct file *file) { #ifdef DEBUG printk(KERN_INFO "device_release(%p,%p)\n", inode, file); #endif /* * We're now ready for our next caller */ Device_Open--; module_put(THIS_MODULE); return SUCCESS; } /* * This function is called whenever a process which has already opened the * device file attempts to read from it. */ static ssize_t device_read(struct file *file, /* see include/linux/fs.h */ char __user * buffer, /* buffer to be * filled with data */ size_t length, /* length of the buffer */ loff_t * offset) { /* * Number of bytes actually written to the buffer */ int bytes_read = 0; #ifdef DEBUG printk(KERN_INFO "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(KERN_INFO "Read %d bytes, %d left\n", 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. */ static ssize_t device_write(struct file *file, const char __user * buffer, size_t length, loff_t * offset) { int i; #ifdef DEBUG printk(KERN_INFO "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; /* * 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 is returned to the * calling process), the ioctl call returns the output of this function. * */ int device_ioctl(struct inode *inode, /* see include/linux/fs.h */ struct file *file, /* ditto */ unsigned int ioctl_num, /* number and param for ioctl */ unsigned long ioctl_param) { int i; char *temp; char ch; /* * Switch according to the ioctl called */ switch (ioctl_num) { case IOCTL_SET_MSG: /* * Receive a pointer to a message (in user space) and set that * to be the device's message. Get the parameter given to * ioctl by the process. */ temp = (char *)ioctl_param; /* * Find the length of the message */ 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: /* * Give the current message to the calling process - * the parameter we got is a pointer, fill it. */ i = device_read(file, (char *)ioctl_param, 99, 0); /* * Put a zero at the end of the buffer, so it will be * properly terminated */ put_user('\0', (char *)ioctl_param + i); break; case IOCTL_GET_NTH_BYTE: /* * This ioctl is both input (ioctl_param) and * output (the return value of this function) */ return Message[ioctl_param]; break; } return SUCCESS; } /* Module Declarations */ /* * This structure will hold the functions to be called * when a process does something to the device we * created. Since a pointer to this structure is kept in * the devices table, it can't be local to * init_module. NULL is for unimplemented functions. */ struct file_operations Fops = { .read = device_read, .write = device_write, .ioctl = device_ioctl, .open = device_open, .release = device_release, /* a.k.a. close */ }; /* * Initialize the module - Register the character device */ int init_module() { int ret_val; /* * Register the character device (atleast try) */ ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops); /* * Negative values signify an error */ if (ret_val < 0) { printk(KERN_ALERT "%s failed with %d\n", "Sorry, registering the character device ", ret_val); return ret_val; } printk(KERN_INFO "%s The major device number is %d.\n", "Registeration is a success", MAJOR_NUM); printk(KERN_INFO "If you want to talk to the device driver,\n"); printk(KERN_INFO "you'll have to create a device file. \n"); printk(KERN_INFO "We suggest you use:\n"); printk(KERN_INFO "mknod %s c %d 0\n", DEVICE_FILE_NAME, MAJOR_NUM); printk(KERN_INFO "The device file name is important, because\n"); printk(KERN_INFO "the ioctl program assumes that's the\n"); printk(KERN_INFO "file you'll use.\n"); return 0; } /* * Cleanup - unregister the appropriate file from /proc */ void cleanup_module() { int ret; /* * Unregister the device */ ret = unregister_chrdev(MAJOR_NUM, DEVICE_NAME); /* * If there's an error, report it */ if (ret < 0) printk(KERN_ALERT "Error: unregister_chrdev: %d\n", ret); } |
示例 7-2. chardev.h
/* * chardev.h - the header file with the ioctl definitions. * * The declarations here have to be in a header file, because * they need to be known both to the kernel module * (in chardev.c) and the process calling ioctl (ioctl.c) */ #ifndef CHARDEV_H #define CHARDEV_H #include <linux/ioctl.h> /* * The major device number. We can't rely on dynamic * registration any more, because ioctls need to know * it. */ #define MAJOR_NUM 100 /* * Set the message of the device driver */ #define IOCTL_SET_MSG _IOR(MAJOR_NUM, 0, char *) /* * _IOR means that we're creating an ioctl command * number for passing information from a user process * to the kernel module. * * The first arguments, MAJOR_NUM, is the major device * number we're using. * * The second argument is the number of the command * (there could be several with different meanings). * * The third argument is the type we want to get from * the process to the kernel. */ /* * Get the message of the device driver */ #define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *) /* * This IOCTL is used for output, to get the message * of the device driver. However, we still need the * buffer to place the message in to be input, * as it is allocated by the process. */ /* * Get the n'th byte of the message */ #define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int) /* * The IOCTL is used for both input and output. It * receives from the user a number, n, and returns * Message[n]. */ /* * The name of the device file */ #define DEVICE_FILE_NAME "char_dev" #endif |
示例 7-3. ioctl.c
/* * ioctl.c - the process to use ioctl's to control the kernel module * * Until now we could have used cat for input and output. But now * we need to do ioctl's, which require writing our own process. */ /* * device specifics, such as ioctl numbers and the * major device file. */ #include "chardev.h" #include <stdio.h> #include <stdlib.h> #include <fcntl.h> /* open */ #include <unistd.h> /* exit */ #include <sys/ioctl.h> /* ioctl */ /* * Functions for the ioctl calls */ 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 failed:%d\n", ret_val); exit(-1); } } ioctl_get_msg(int file_desc) { int ret_val; char message[100]; /* * Warning - this is dangerous because we don't tell * the kernel how far it's allowed to write, so it * might overflow the buffer. In a real production * program, we would have used two ioctls - one to tell * the kernel the buffer length and another to give * it the buffer to fill */ ret_val = ioctl(file_desc, IOCTL_GET_MSG, message); if (ret_val < 0) { printf("ioctl_get_msg failed:%d\n", ret_val); exit(-1); } printf("get_msg message:%s\n", message); } ioctl_get_nth_byte(int file_desc) { int i; char c; printf("get_nth_byte message:"); i = 0; do { c = ioctl(file_desc, IOCTL_GET_NTH_BYTE, i++); if (c < 0) { printf ("ioctl_get_nth_byte failed at the %d'th byte:\n", i); exit(-1); } putchar(c); } while (c != 0); putchar('\n'); } /* * Main - Call the ioctl functions */ main() { int file_desc, ret_val; char *msg = "Message passed by ioctl\n"; file_desc = open(DEVICE_FILE_NAME, 0); if (file_desc < 0) { printf("Can't open device file: %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); } |
[1] | 请注意,在这里,读和写的作用再次反转,所以在ioctl中,读是将信息发送到内核,而写是从内核接收信息。 |
[2] | 这并不完全准确。 你无法通过 ioctl 传递结构,例如,但你可以传递指向结构的指针。 |