PersonalCorpus 版 (精华区)

发信人: AA (振奋,成长中的AA), 信区: Linux
标  题: Linux内核编程(字符设备文件)(zz)
发信站: 哈工大紫丁香 (2002年06月27日19:55:58 星期四), 站内信件

2.字符设备文件 
那么,现在我们是原始级的内核程序员,我们知道如何写不做任何事情的内核模块
。我们为自己而骄傲并且高昂起头来。但是不知何故我们感觉到缺了什么东西。患
有精神紧张症的模块不是那么有意义。 
内核模块同进程对话有两种主要途径。一种是通过设备文件(比如/dev 目录中的
文件),另一种是使用proc文件系统。我们把一些东西写入内核的一个主要原因就
是支持一些硬件设备,所以我们从设备文件开始。 
设备文件的最初目的是允许进程同内核中的设备驱动通信,并且通过它们和物理设
备通信(modem,终端,等等)。这种方法的实现如下: 
每个设备驱动都对应着一定类型的硬件设备,并且被赋予一个主码。设备驱动的列
表和它们的主码可以在in/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函数撤销设备的注册。 
这些操作(注册和注销)是这两个函数的主要功能。内核中的函数不是象进程一样
自发运行的,而是通过系统调用,或硬件中断或者内核中的其它部分(只要是调用
具体的函数)被进程调用的。所以,当你向内和中增加代码时,你应该把它注册为
具体某种事件的句柄,而当你把它删除的时候,你需要注销这个句柄。 
设备驱动完全由四个设备_ 另一点我们需要记住的是,我们不能允许管理员随心所
欲的删除内核模块。这是因为如果设备文件是被进程打开的,那么我们删除内核模
块的时候,要使用这些文件就会导致访问正常的函数(读/写)所在的内存位置。
如果幸运,那里不会有其他代码被装载,我们将得到一个恶性的错误信息。如果不
行,另一个内核模块会被装载到同一个位置,这将意味着会跳入内核中另一个程序
的中间,结果将是不可预料的恶劣。 
通常你不希望一个函数做什么事情的时候,会从那个函数返回一个错误码(一个负
数)。但这在cleanup_module中是不可能的,因为它是一个void型的函数。一旦
cleanup_module被调用,这个模块就死掉了。然而有一个计数器记录着有多少个内
核模块在使用这个模块,这个计数器称为索引计数器(/proc/modules中没行的最
后一个数字)。如果这个数字不是0,删除就会失败。模块的索引计数器包含在变
量mod_use_count_中。有定义好的处理这个变量的宏(MOD_INC_USE_COUNT和
MOD_DEC_USE_COUNT),所以我们一般使用宏而不是直接使用变量mod_use_count_
,这样在以后实现变化的时候会带来安全性。 

ex chardev.c 

/* chardev.c 
* Copyright (C) 1998-1999 by Ori Pomerantz 

* Create a character device (read only) 
*/ 

/* The necessary header files */ 

/* Standard in kernel modules */ 
#include /* Were doing kernel work */ 
#include /* Specifically, a module */ 

/* Deal with CONFIG_MODVERSIONS */ 
#if CONFIG_MODVERSIONS==1 
#define MODVERSIONS 
#include 
#endif 

/* For character devices */ 
#include /* The character device 
* definitions are here */ 
#include /* A wrapper which does 
* next to nothing at 
* at present, but may 
* help for compatibility 
* with future versions 
* of Linux */ 


/* In 2.2.3 /usr/include/linux/version.h includes 
* a macro for this, but 2.0.35 doesnt - so I add 
* it here if necessary. */ 
#ifndef KERNEL_VERSION 
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c)) 
#endif 


/* Conditional compilation. LINUX_VERSION_CODE is 
* the code (as per KERNEL_VERSION) of this version. */ 
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0) 
#include /* for put_user */ 
#endif 



#define SUCCESS 0 


/* Device Declarations **************************** */ 

/* The name for our device, as it will appear 
* in /proc/devices */ 
#define DEVICE_NAME "char_dev" 


/* The maximum length of the message from the device */ 
#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 function is called whenever a process 
* attempts to open the device file */ 
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 

/* This is how you get the minor device number in 
* case you have more than one physical device using 
* the driver. */ 
printk("Device: %d.%d\n", 
inode->i_rdev >> 8, inode->i_rdev & 0xFF); 

/* We dont 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. 

*In the case of processes, the danger would be 
*that one process might have check Device_Open 
*and then be replaced by the schedualer by another 
*process which runs this function. Then, when 
*the first process was back on the CPU, it would assume 
*the device is still not open. 
* However, Linux guarantees that a process wont 
* be replaced while it is running in kernel context. 

* In the case of SMP, one CPU might increment 
*Device_Open while another CPU is here, right after the check. 
*However, in version 2.0 of the kernel this is not a problem 
*because theres a lock to guarantee only one CPU will 
*be kernel module at the same time. 
*This is bad in terms of performance, so version 2.2 changed it. 
*Unfortunately, I dont have access to an SMP box 
*to check how it works with SMP. 
*/ 

Device_Open++; 

/* Initialize the message. */ 
sprintf(Message, 
"If I told you once, I told you %d times - %s", 
counter++, 
"Hello, world\n"); 
/* The only reason were allowed to do this sprintf 
* is because the maximum length of the message 
* (assuming 32 bit integers - up to 10 digits 
* with the minus sign) is less than BUF_LEN, which 
* is 80. BE CAREFUL NOT TO OVERFLOW BUFFERS, 
* ESPECIALLY IN THE KERNEL!!! 
*/ 

Message_Ptr = Message; 

/* Make sure that the module isnt removed while 
* the file is open by incrementing the usage count 
* (the number of opened references to the module, if 
* its not zero rmmod will fail) 
*/ 
MOD_INC_USE_COUNT; 

return SUCCESS; 



/* This function is called when a process closes the 
* device file. It doesnt have a return value in 
* version 2.0.x because it cant fail (you must ALWAYS 
* be able to close a device). In version 2.2.x it is 
* allowed to fail - but we wont let it. 
*/ 
#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 

/* Were now ready for our next caller */ 
Device_Open --; 

/* Decrement the usage count, otherwise once you 
* opened the file youll never get rid of the module. 
*/ 
MOD_DEC_USE_COUNT; 

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) 
return 0; 
#endif 



/* This function is called whenever a process which 
* have already opened the device file attempts to 
* read from it. */ 


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) 
static ssize_t device_read(struct file *file, 
char *buffer, /* The buffer to fill with data */ 
size_t length, /* The length of the buffer */ 
loff_t *offset) /* Our offset in the file */ 
#else 
static int device_read(struct inode *inode, 
struct file *file, 
char *buffer, /* The buffer to fill with 
* the data */ 
int length) /* The length of the buffer 
* (mustnt write beyond that!) */ 
#endif 

/* Number of bytes actually written to the buffer */ 
int bytes_read = 0; 

/* If were 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 wouldnt 
* 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 - unsupported in this example. */ 
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) 
static ssize_t device_write(struct file *file, 
const char *buffer, /* The buffer */ 
size_t length, /* The length of the buffer */ 
loff_t *offset) /* Our offset in the file */ 
#else 
static int device_write(struct inode *inode, 
struct file *file, 
const char *buffer, 
int length) 
#endif 

return -EINVAL; 





/* Module Declarations ***************************** */ 

/* The major device number for the device. This is 
* global (well, static, which in this context is global 
* within this file) because it has to be accessible 
* both for registration and for release. */ 
static int Major; 

/* 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 cant be local to 
* init_module. NULL is for unimplemented functions. */ 


struct file_operations Fops = { 
NULL, /* seek */ 
device_read, 
device_write, 
NULL, /* readdir */ 
NULL, /* select */ 
NULL, /* ioctl */ 
NULL, /* mmap */ 
device_open, 
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) 
NULL, /* flush */ 
#endif 
device_release /* a.k.a. close */ 
}; 


/* Initialize the module - Register the character device */ 
int init_module() 

/* Register the character device (atleast try) */ 
Major = module_register_chrdev(0, 
DEVICE_NAME, 
&Fops); 

/* Negative values signify an error */ 
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 ("youll 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; 



/* Cleanup - unregister the appropriate file from /proc */ 
void cleanup_module() 

int ret; 

/* Unregister the device */ 
ret = module_unregister_chrdev(Major, DEVICE_NAME); 

/* If theres an error, report it */ 
if (ret < 0) 
printk("Error in unregister_chrdev: %d\n", ret); 

2.1多内核版本源文件 
系统调用是内核出示给进程的主要接口,在不同版本中一般是相同的。可能会增加
新的系统,但是旧的系统的行为是不变的。向后兼容是必要的——新的内核版本不
能打破正常的进程规律。在大多数情况下,设备文件是不变的。然而,内核中的内
部接口是可以在不同版本间改变的。 
Linux内核的版本分为稳定版(n.<偶数>.m)和发展版(n.<奇数>.m)。发展版包
含了所有新奇的思想,包括那些在下一版中被认为是错的,或者被重新实现的。所
以,你不能相信在那些版本中这些接口是保持不变的(这就是为什么我在本书中不
厌其烦的支持不同接口。这是很大量的工作但是马上就会过时)。但是在稳定版中
我们就可以认为接口是相同的,即使在修正版中(数字m所指的)。 
MPG版本包括了对内核2.0.x和2.2.x的支持。这两种内核仍有不同之处,所以编译
时要取决于内核版本而决定。方法是使用宏LINUX_VERSION_CODE。在a.b.c版中,
这个宏的值是216a+28b+c。如果希望得到具体内核版本号,我们可以使用宏
KERNEL_VERSION。在2.0.35版中没有定义这个宏,在需要时我们可以自己定义。 




阅读:1561次

来源:Linux开发指南 
网友评论 
大 

--
                 Activity & Advanced

健康的自信,一种脱离优越感、盛气凌人、自以为是和傲慢无礼的自信。这种自信不但
让对方感到很得体,也会使与之交往的人产生一种信赖。

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 211.94.161.128]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:211.121毫秒