📄 和设备文件对话(写和 ioctl).htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0059)http://yangxingjun.myrice.com/chinesehow/kernel/node18.html -->
<!--Converted with LaTeX2HTML 98.1 release (February 19th, 1998)originally by Nikos Drakos (nikos@cbl.leeds.ac.uk), CBLU, University of Leeds* revised and updated by: Marcus Hennecke, Ross Moore, Herb Swan* with significant contributions from: Jens Lippmann, Marek Rouchal, Martin Wilck and others --><HTML><HEAD><TITLE>和设备文件对话(写和 IOCTL)</TITLE>
<META content="Talking to Device Files (writes and IOCTLs)" name=description>
<META content=mpg name=keywords>
<META content=document name=resource-type>
<META content=global name=distribution>
<META http-equiv=Content-Type content="text/html; charset=GB2312"><LINK
href="C:\Documents and Settings\nonoka\My Documents\和设备文件对话(写和 IOCTL).files\error(2).htm"
rel=STYLESHEET><LINK href="node19.html" rel=next><LINK href="node17.html"
rel=previous><LINK href="mpg.html" rel=up><LINK href="node19.html" rel=next>
<META content="MSHTML 6.00.2800.1106" name=GENERATOR></HEAD>
<BODY><!--Navigation Panel--><A
href="http://yangxingjun.myrice.com/chinesehow/kernel/node19.html"
name=tex2html599><IMG height=24 alt=next
src="和设备文件对话(写和 IOCTL).files/next_motif.gif" width=37 align=bottom border=0></A>
<A href="http://yangxingjun.myrice.com/chinesehow/kernel/mpg.html"
name=tex2html595><IMG height=24 alt=up src="和设备文件对话(写和 IOCTL).files/error.htm"
width=26 align=bottom border=0></A> <A
href="http://yangxingjun.myrice.com/chinesehow/kernel/node17.html"
name=tex2html589><IMG height=24 alt=previous
src="C:\Documents and Settings\nonoka\My Documents\和设备文件对话(写和 IOCTL).files\error(1).htm"
width=63 align=bottom border=0></A> <A
href="http://yangxingjun.myrice.com/chinesehow/kernel/node1.html"
name=tex2html597><IMG height=24 alt=contents
src="和设备文件对话(写和 IOCTL).files/contents_motif.gif" width=65 align=bottom
border=0></A> <A
href="http://yangxingjun.myrice.com/chinesehow/kernel/node34.html"
name=tex2html598><IMG height=24 alt=index
src="和设备文件对话(写和 IOCTL).files/index_motif.gif" width=43 align=bottom
border=0></A> <BR><B>Next:</B> <A
href="http://yangxingjun.myrice.com/chinesehow/kernel/node19.html"
name=tex2html600>启动参数</A> <B>Up:</B> <A
href="http://yangxingjun.myrice.com/chinesehow/kernel/mpg.html"
name=tex2html596>Linux 内核模块编程</A> <B>Previous:</B> <A
href="http://yangxingjun.myrice.com/chinesehow/kernel/node17.html"
name=tex2html590>将 /proc 作为输入</A> <BR><BR><!--End of Navigation Panel-->
<H1><A name=SECTION00700000000000000000> </A><A name=dev-input> </A><A
name=392> </A> <A name=393> </A> <A name=394> </A> <A
name=395> </A> <BR>和设备文件对话(写和 IOCTL) </H1>
<P>设备文件应该表现物理设备。大多物理设备既作为输出也作为输入,因此必须有某个机制使内核中的设备驱动程序得到来自进程的输出以便发送到设备。通过为输出打开设备文件并向其写而做到这个,就像写一个普通文件。在下面的例子中,这是用
<TT>device_write</TT> 实现的。
<P>这不总是足够的。想象你有一个串行口连接到一个调制解调器(即使你有一个内置的调制解调器,从CPU的观点看它仍然是通过串行口连接到调制解调器,因此你不必责备你的想象力)。自然而然的事情是使用设备文件向调制解调器写(要么是调制解调器命令,要么是要通过电话线发送的数据)和从中读(要么是命令回应,要么是接收的数据)。然而,这留下了当你需要和串行口对话时该做什么的问题,例如以什么速率接收和发送数据。
<A name=397> </A> <A name=398> </A>
<P>在 Unix 中,答案是使用特殊的函数调用 <TT>ioctl</TT> ( <B>i</B>nput <B>o</B>utput
<B>c</B>on<B>t</B>ro<B>l</B> 的缩写)。每个设备可以有自己的 <TT>ioctl</TT> 命令,它可以读
<TT>ioctl</TT> (从进程向内核发送信息)和写 <TT>ioctl</TT>(返回信息给进程)<A
href="http://yangxingjun.myrice.com/chinesehow/kernel/footnode.html#foot851"
name=tex2html133><SUP>5.1</SUP></A>或者什么也不做。 ioctl 使用三个参数调用: 合适的设备文件的文件描述符, ioctl
号及一个参数,该参数是类型长度,因此你可以使用一个模型传递任何东西。 <A
href="http://yangxingjun.myrice.com/chinesehow/kernel/footnode.html#foot410"
name=tex2html134><SUP>5.2</SUP></A>
<P>ioctl 号用主设备号, ioctl 类型,命令和参数类型编码。这个 ioctl 号通常用一个头文件中的宏调用 (<TT>_IO</TT>,
<TT>_IOR</TT>, <TT>_IOW</TT> 或 <TT>_IOWR</TT> --
取决于类型)创建。头文件必须被使用<TT>ioctl</TT>的程序(因此它们可以生成合适的<TT>ioctl</TT>)及内核模块(因此它可以理解它)
<TT>#include</TT>。 在下面的范例中,头文件是 <TT>chardev.h</TT> 而使用它的程序是 <TT>ioctl.c</TT>。 <A
name=420> </A> <A name=421> </A> <A name=422> </A> <A
name=423> </A>
<P>如果你想在你自己的模块中使用 <TT>ioctl</TT> ,最好接受官方的 <TT>ioctl</TT>
分配,因此如果你碰巧得到别人的<TT>ioctl</TT>或它们得到你的,你就可以知道某些事是错的。需要更多信息,请参考
`<TT>Documentation/ioctl-number.txt</TT>' 内核源代码树。 <A name=428> </A> <A
name=429> </A>
<P>范例 <FONT size=+1><B>chardev.c</B></FONT> <A name=434> </A><A
name=435> </A>
<P><PRE>
/* chardev.c
*
* 创建输入输出的字符设备
*/
/* Copyright (C) 1998-99 by Ori Pomerantz */
/* 必要头文件 */
/* 标准头文件 */
#include <linux/kernel.h> /* 内核工作 */
#include <linux/module.h> /* 明确指定是模块 */
/* 处理 CONFIG_MODVERSIONS */
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include <linux/modversions.h>
#endif
/* 为了字符设备 */
/* 字符设备的定义在此 */
#include <linux/fs.h>
/* 目前对后面妹妹有用的包装,但可能对未来LINUX版本的兼容性有帮助 */
#include <linux/wrapper.h>
/* 我们自己的ioctl 号 */
#include "chardev.h"
/* 在 2.2.3 版/usr/include/linux/version.h 包含该宏,
* 但 2.0.35版不包含-加入以备需要 */
#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> /* 为了 get_user 和 put_user */
#endif
#define SUCCESS 0
/* 设备声明 ******************************** */
/* 将出现在 /proc/devices 中设备名 */
#define DEVICE_NAME "char_dev"
/* 设备的消息的最大长度 */
#define BUF_LEN 80
/* 设备正打开?防止对同一设备的同时访问 */
static int Device_Open = 0;
/* 当被询问时设备将给出的消息 */
static char Message[BUF_LEN];
/* 进程读取消息到哪儿?如果消息的长度大于我们将用于填充在 device_read的缓冲区的大小,这将有用。*/
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
* 之前检查过它。然而我们是在内核中,因此我们在上下文切换上被保护。
*
* 这不是我们应该采取的态度,因为我们可能运行在一个 SMP 单元上,但我们将在后面一章处理SMP
*/
Device_Open++;
/* 初始化消息 */
Message_Ptr = Message;
MOD_INC_USE_COUNT;
return SUCCESS;
}
/* 当一个进程关闭设备文件时该函数被调用。它没有返回值因为它不能失败。不要考虑其他任何事的发生
* 你总应该可以关闭一个设备(在 2.0 版中情况如此,在 2.2 版中设备文件可能不能关闭)。 */
#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
/* 为下个调用者做准备 */
Device_Open --;
MOD_DEC_USE_COUNT;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
return 0;
#endif
}
/* 当一个已经打开设备文件的进程试图从它读时该函数被调用。 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t device_read(
struct file *file,
char *buffer, /* 填充数据的缓冲区 */
size_t length, /* 缓冲区长度 */
loff_t *offset) /* 文件偏移量 */
#else
static int device_read(
struct inode *inode,
struct file *file,
char *buffer, /* 填充数据的缓冲区 */
int length) /* 缓冲区长度(一定不能在写时超过它!) */
#endif
{
/* 实际写入缓冲区的字节数 */
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;
}
/* 当有人向我们的设备文件写时该函数被调用。 */
#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;
/* 又一次返回使用过的输入的字节数 */
return i;
}
/* 当一个进程试图在我们的设备文件上做 ioctl 时该函数被调用。我们需要两个额外的参数
* (附加于节点结构和文件结构,那是所有的设备函数都需要的): ioctl 号和给出 ioctl 函数的参数
*
* 如果 ioctl 是写或读/写(意味着输出被返回给调用进程), ioctl 调用返回这个函数的输出。
*/
int device_ioctl(
struct inode *inode,
struct file *file,
unsigned int ioctl_num,/* ioctl 号 */
unsigned long ioctl_param) /* 对它的参数 */
{
int i;
char *temp;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
char ch;
#endif
/* 根据 ioctl 调用选择 */
switch (ioctl_num) {
case IOCTL_SET_MSG:
/* 接收指向消息的指针(在用户空间)并将它设为设备的消息 */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -