万卷书   首页 > 电脑书籍 > Linux >Linux内核模块编程之字符设备文件
 
 范例 chardev.c

/* chardev.c
* Copyright (C) 1998-1999 by Ori Pomerantz
*
* 创建一个字符设备(只读)
*/

/* 必要的头文件 */

/* 内核模块标准头文件 */
#include /* 内核工作 */
#include /* 明确指定是模块 */

/* 处理 CONFIG_MODVERSIONS */
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include
#endif

/* 对于字符设备 */
#include /* 字符设备定义 */
#include /* 目前对下一步没有任何用的包装
* 但对未来的Linux 版本在兼容性上可能有帮助*/

/* 在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

/* 条件编译。 LINUX_VERSION_CODE 是版本代号(经 KERNEL_VERSION) 。 */
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)
#include /* for 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)
{
static int counter = 0;

#ifdef DEBUG
printk ("device_open(%p,%p)\n", inode, file);
#endif

/* 这是当你有多个物理设备使用那个驱动程序时如何得到次设备号。 */
printk("Device: %d.%d\n",
inode->i_rdev >> 8, inode->i_rdev & 0xFF);

/* 我们不想同时和两个进程通话 */
if (Device_Open)
return -EBUSY;

/* 如果这是一个进程,我们将必须在这儿更小心。
*
* 在进程的情况下,危险在于一个进程已经检查过 Device_Open ,
* 然后由于另一个进程运行这个函数而被调度程序代替。
* 当第一个进程处于后台,它会假设设备仍然没有打开。
*
* 然而,Linux 保证当一个进程在内核环境下运行时不能被代替。
*
* 在对称多处理的情况下,一个 CPU 可能增加Device_Open 而另一个CPU 也在这里
* 刚好在检查完后。然而在 2.0 版内核中这不成为问题,因为有一个锁保证在同一
* 时间只有一个CPU是内核模块。这在性能上不好,因此 2.2 版改变了它。
* 不幸的是,我不能使用对称多处理单元去检查它在对称多处理下是如何工作的。
*/

Device_Open++;

/* 初始化消息。 */
sprintf(Message,
"If I told you once, I told you %d times - %s",
counter++,
"Hello, world\n");
/* 我们允许用 sprintf 的唯一原因是消息(假设是32位整数,等于10位带符号的十进制数)
* 的最大长度小于 BUF_LEN的大小--80。在内核中尤其要注意缓冲区溢出!!!
*/

Message_Ptr = Message;

/* 通过增加使用计数确保模块在文件正被打开时不被移除
* (对该模块的打开的引用数非零时rmmod 将失败)
*/
MOD_INC_USE_COUNT;

return SUCCESS;
}

/* 这个函数在一个进程关闭设备文件时被调用。在 2.0.x 版中没有返回值,因为它不能失败
* (你必须总可以关闭设备)。在 2.2.x 版中允许失败 - 但我们不能允许。
*/
#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;

/* 如果在消息尾,返回 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
{
return -EINVAL;
}

/* 模块声明 ***************************** */

/* 设备的主设备号。这是全局的(是的,是静态的,在这个文件中是全局的)
* 因为在登记和释放时它都应可见。 */
static int Major;

/* 当一个进程对我们创建的设备做什么时,这个结构将保存被调用的函数。
* 既然这个结构的指针保存在设备表中,所以它不能对init_module是局部的.
* NULL 代替未实现的函数。 */

struct file_operations Fops = {
NULL, /* 寻找 */
device_read,
device_write,
NULL, /* 读目录 */
NULL, /* 选择 */
NULL, /* ioctl */
NULL, /* mmap */
device_open,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
NULL, /* 刷新 */
#endif
device_release /* 关闭 */
};

/* 初始化模块 - 登记字符设备 */
int init_module()
{
/* 登记字符设备(至少试图登记) */
Major = module_register_chrdev(0,
DEVICE_NAME,
&Fops);

/* 负数表示错误 */
if (Major < 0) {
printk ("%s device failed with %d\n",
"Sorry, registering the character",
Major);
return Major;
}

printk ("%s The major device number is %d.\n",
"Registeration is a success.",
Major);
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 c %d \n", Major);
printk ("You can try different minor numbers %s",
"and see what happens.\n");

return 0;
}


/* 清除 - 从 from /proc 注销适当的文件*/
void cleanup_module()
{
int ret;

/* 注销设备 */
ret = module_unregister_chrdev(Major, DEVICE_NAME);

/* 如果有错则报告 */
if (ret < 0)
printk("Error in unregister_chrdev: %d\n", ret);
}

上一页 下一页