第六章 和设备文件“交谈”(writes 和 IOCTLs)
设备文件假定是对应于物理设备的。大多数的物理设备既可以输入又可以输
出。所以内核中的设备驱动程序应该有某些机制来取得进程的输出并送到设备。
这可以通过用输出的方式打开设备文件并向它写入来作到,就好象写一个文件。
下面的例子是用device_write来实现。
这通常还是不够的。假设你有一个串口连到modem上(即使你有内置modem,从
CPU的角度看,它仍然要被实现为串口和modem连接,但物理上并没有)。自然的方
式就是用设备文件来向modem写(modem命令或数据通过电话线传送)和从modem读入
数据(命令的回应或数据的接收也是通过电话线)。当你需要和串口自身通信时会
出现问题,例如设置数据传送和接收的速率。
Unix下的答案是使用专用的ioctl函数(input output control的缩写)。每个
设备都有自身的ioctl命令,既可以读ioctl's(从进程向内核发数据),又可以写
ioctl's(返回信息到进程)。ioctl函数用三个参数调用:设备文件描述符、ioct
l号和一个long型的参数(你可以通过它传送任何信息)。
ioctl号包括主设备号、ioctl类型(命令)和参数类型。ioctl号一般用头文件
里的宏来生成(_IO, _IOR, _IOW 或 _IORW,取决于类型)。这个头文件应该被使
用ioctl系统调用的程序包含,也应该被实现ioctl的内核模块包含。本例中,这
个头文件是chardev.h,它被chardev.c(内核模块的实现)和ioctl.c使用(调用io
ctl)。
如果你想在你的内核模块中使用ioctl,最好用一个官方指定的ioctl约定。
可以参考/usr/src/linux/Documentation/ioctl-number.txt文件。
例子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>
/* 主设备号,你不能依赖动态分配的主设备号了,
因为ioctl要用到*/
#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
/*-----------end of chardev.h-------------*/
例子chardev.c
/* chardev.c
*
* Create an input/output character device
*/
/* Copyright (C) 1998-99 by Ori Pomerantz */
/* 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
#include <linux/fs.h>
#include <linux/wrapper.h>
/* Our own ioctl numbers */
#include "chardev.h"
#include <asm/uaccess.h> /* for get_user and put_user */
#define SUCCESS 0
/* The name for our device, as it will appear in
* /proc/devices */
#define DEVICE_NAME "char_dev"
#define BUF_LEN 80
static int Device_Open = 0;
static char Message[BUF_LEN];
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). */
static int device_release(struct inode *inode,
struct file *file)
{
#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;
return 0;
}
/* 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,
char *buffer, /* The buffer to fill with the data */
size_t length, /* The length of the buffer */
loff_t *offset) /* offset to the file */
{
/* 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 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 *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;
/* 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,
struct file *file,
unsigned int ioctl_num,/* The number of the ioctl */
unsigned long ioctl_param) /* The parameter to it */
{
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);
/* Don't reinvent the wheel - call device_write */
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);
/* Warning - we assume here the buffer length is
* 100. If it's less than that we might overflow
* the buffer, causing the process to core dump.
* The reason we only allow up to 99 characters is
* that the NULL which terminates the string also
* needs room. */
/* 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;
}
struct file_operations Fops = {
NULL, /* seek */
device_read,
device_write,
NULL, /* readdir */
NULL, /* select */
device_ioctl, /* ioctl */
NULL, /* mmap */
device_open,
NULL, /* flush */
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 = module_register_chrdev(MAJOR_NUM,
DEVICE_NAME,
&Fops);
/* Negative values signify an error */
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;
}
/* Cleanup - unregister the appropriate file from /proc */
void cleanup_module()
{
int ret;
/* Unregister the device */
ret = module_unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
/* If there's an error, report it */
if (ret < 0)
printk("Error in module_unregister_chrdev: %d/n", ret);
}
/*------- end of chardev.c -----------*/
用于测试ioctl的例子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.
*/
/* Copyright (C) 1998 by Ori Pomerantz */
/* device specifics, such as ioctl numbers and the
* major device file. */
#include "chardev.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=1;
printf("get_nth_byte message:");
i = 0;
while (c != 0) {
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);
}
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_set_msg(file_desc, msg);
ioctl_get_nth_byte(file_desc);
ioctl_get_msg(file_desc);
close(file_desc);
}
编译和测试:
1、编译
cc -D__KERNEL__ -DMODULE -DLINUX -DDEBUG -O6 -c chardev.c
cc ioctl.c -o ~/bin/ioctl
su to root:
mknod char_dev c 100 0
kevintz注:总觉得作者写的ioctl.c不是很好,我已经作了小小的修改,已经能
够合理输出结果。
2、测试
insmod后,看看/proc/modules /proc/devices的变化。运行ioctl进行测试,看
看结果和内核模块的输出。
--
那一刹那,我开始用心去看这个世界,所有的事物真的可以看得前
所未有的那么清楚……