Linux内核模块编程之字符设备文件-C / C++-优质IT资源分享社区

admin
管理员
管理员
  • UID1
  • 粉丝32
  • 关注4
  • 发帖数581
  • 社区居民
  • 忠实会员
  • 原创写手
阅读:295回复:0

  Linux内核模块编程之字符设备文件

楼主#
更多 发布于:2016-05-07 22:08
 字符设备文件
            
            
因此现在我们是大胆的内核程序员而且我们知道如何写什么也不做的内核模块。我们为自己而自豪并且可以高高的仰起我们的头。但是不知何故我们感到遗失了什么。令人紧张的模块并不是很有趣的。
            
            
内核模块有两种主要的途径和进程对话。一种是通过设备文件 (像 /dev 目录中的文件), 另一种是使用 proc
文件系统。因为写内核中的某些东西的一个主要的原因是去支持某种硬件,所以我们从设备文件开始。

设备文件的原始目的是允许进程和内核中的设备驱动程序通信以通过它们和物理设备通信 (调制解调器, 终端, 等等)。 这个办法的实现是像下面这样的。

每个设备驱动程序负责某种硬件,它被分配一个主设备号。驱动程序的列表和它们的主设备号可以在
/proc/devices找到。每一个物理设备由设备驱动程序控制且被分配一个次设备号。 /dev
目录被假设包含每一个这样的设备的被称之为设备文件的特殊文件,无论它是否被真正的安装在系统上。

例如,如果你 ls -l
/dev/hd[ab]*,你将看到所有的可能被连接到系统上的IDE硬盘的分区。注意它们都使用相同的主设备号,3。但是次设备号彼此都不相同。
否认申明:
这是假设你正字使用 PC 架构的系统。我不知道基于其他架构的Linux设备的情况。.

当系统被安装,所有的那些设备文件被 mknod 命令创建。从技术上说没有必须将它们放在
/dev目录的原因,这只是一个有用的惯例。像练习所示的那样,当为了测试的目的而创建一个设备文件,将它放在你编译内核模块的那个目录也许更有意义。

设备分为两种:字符设备和块设备。不同之处在于块设备对于请求有缓冲区,因此它们可以选择以什么顺序进行响应。对于存储设备而言这一点是很重要的,因为在读写连续的扇区时比远远的分离的扇区更快。另一个不同就是块设备只能以块为单位接受输入和返回输出(块的大小根据设备的不同而不同),而字符设备只能使用它们可能使用的或多或少的字节大小。大多数设备是字符设备,因为它们不需要这种缓冲而且不以固定块大小进行操作。你可以用ls
-l区分一个设备文件是块设备还是字符设备.如果开头是“b”,那么它就是块设备;如果是“c”,那么就是字符设备。

这个模块分为两部分:登记设备模块部分和设备驱动部分。 init_module 调用 module_register_chrdev
而将设备驱动程序加入内核的字符设备驱动程序表。它也返回该设备将使用的主设备号。 cleanup_module 注销该设备。

这(登记什么和注销它)是那两种功能的常规功能。内核中的东西不想普通进程那样主动运行自己,而是由进程通过系统调用,或者由硬件通过中断,或者由内核的其他部分(简单的讲,由特殊的函数调用)进行调用。结果,当你向内核中加入代码,你被假设将之登记为某种事件句柄,而当你移除它时,你被假设出注销它。

严格意义上讲,设备驱动程序由四个device_函数组成,当某人试图用我们的主设备号的设备文件做什么事时它被调用。内核是通过
file_operations结构知道要调用它们的,Fops, 在设备被登记时被给出,它包含那四个函数的指针。

另一点我们在这必须记住的是我们不能允许内核模块在任何根感觉需要的时候被rmmod。原因是如果设备文件正被一个进程打开然后我们移除那个内核模块,这将使用那个文件,而这又将导致对那个适当的读写函数所在的内存区域的调用。如果幸运的话,没有其他的代码被加载到那儿,我们得到一个难看的错误消息。如果不幸运的话,另一个内核模块被加载到同一区域,这就意味着跳到内核中的另一个函数的中间,结果是不可预见的,但肯定不是什么好事。

通常,当你不允许什么事情发生,你会从被假设做这件事的函数返回一个用负数表示的错误代码。使用cleanup_module
是不可能的,因为它不返回任何值。一旦cleanup_module
被调用,模块即死亡了。然而,这儿有一个被称为引用计数器(在/proc/modules中的相应行的最后一个数字)的计数器计算有多少其他的内核模块正在使用该模块。如果该数字非零
rmmod 调用将失败。模块的引用计数器在变量mod_use_count_中. 因为有处理这个变量的宏定义 (MOD_INC_USE_COUNT and
MOD_DEC_USE_COUNT),我们最好使用它们而不直接使用 mod_use_count_ ,这样,如果将来实现方法改变了我们会更安全。
范例
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;









优质IT资源分享社区为你提供此文。 [font=Tahoma  ]
本站有大量优质C、C++教程视频,资料等资源,包含C,C++基础教程,高级进阶教程等等,教程视频资源涵盖传智播客,极客学院,达内,北大青鸟,猎豹网校等等IT职业培训机构的培训教学视频,价值巨大。欢迎点击下方链接查看。 [font=Tahoma  ]

C、C++教程视频
优质IT资源分享社区(www.itziyuan.top)
一个免费,自由,开放,共享,平等,互助的优质IT资源分享网站。
专注免费分享各大IT培训机构最新培训教学视频,为你的IT学习助力!

!!!回帖受限制请看点击这里!!!
!!!资源失效请在此版块发帖说明!!!

[PS:按 CTRL+D收藏本站网址~]

——“优质IT资源分享社区”管理员专用签名~

本版相似帖子

游客