Linux内核模块指南(第四章===>第五章完)。。。翻译完。。。
2015-10-04 17:03
916 查看
第四章,字符设备文件
4.1 字符设备驱动
4.1.1 文件操作结构
文件操作结构定义在linux/fs.h中,然后它包含函数指针的定义,可由驱动在设备上执行各种操作。结构的每一块对应到一些由驱动定义的函数地址来处理对应的操作。
例如,每个字符驱动需要定义一个函数,来从设备读操作。文件操作结构给了模块函数的地址,来执行那个操作。下面是对于内核2.6.5类似的定义:
struct file_operations { struct module *owner; loff_t(*llseek) (struct file *, loff_t, int); ssize_t(*read) (struct file *, char __user *, size_t, loff_t *); ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t); ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *); ssize_t(*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area) (struct file *, unsigned long, unsigned long, unsigned long, unsigned long); };
一些操作并没有被驱动所实现。例如一个驱动操作显卡不需要从目录结构中读。在文件操作结构对应的入口就应该设定为NULL。
这里有个gcc的拓展,它让结构更方便做了个分配。你将看到它在现代驱动中,且让你大吃一惊。这是对于结构的新式分配方法:
struct file_operations fops = { read: device_read, write: device_write, open: device_open, release: device_release };
然而也有一个C99的方法,它用结构元素来分配,这个绝对是更好的使用GNU的拓展的。这个版本的gcc的作者在写2.95的时候使用了,支持新的C99句法。你应该使用这个句法以防万一一些人想要移植你的驱动。这将在兼容性很有帮助:
truct file_operations fops = { .read = device_read, .write = device_write, .open = device_open, .release = device_release };
这个意思就很清楚了,你应该知道你没有特意分配的结构的任意成员将会被gcc初始化为空。
结构化文件操作的例子包含到函数的指针,一般用read,write,open,…来实现,系统调用通常命名为fops。
4.1.2文件结构
每一个设备在内核中都是用一个文件结构来代表的。定义在linux/fs.h中。要知道那个文件是内核等级结构且不会出现在用户空间程序中。这与一般的FILE不是一样的东西。FILE时由装置(glibc)定义的并且永远不会出现在内核空间函数中。同样,它的名字也有一定的误导性;它代表一个抽象的开放‘文件’,不是在磁盘上的文件,在磁盘上的文件是由索引节点结构来表示的。
struct file的例子通常被称作‘filp’,你也看见它提到过用‘struct file file’。抵制了这个诱惑的名字。
接着看看file的定义,你看到的大多数如果,像结构目录项,不是由设备驱动使用的,你可以忽略他们。这是因为驱动不直接装填文件,他们只是使用结构包含在可以创建在任何地方的文件中。
4.1.3注册一个设备
这个前面讨论过了,字符设备通过设备文件来接入,通常在/dev中,主号(设备驱动关联码)会告诉你哪个驱动处理哪个设备文件。辅号(设备号)仅仅是驱动自己用来区分应该操作哪个设备。仅仅是以防万一驱动需要操作多个设备的时候。
加入一个驱动到你的系统意味着用内核注册一个驱动。这个和在内核初始化的时候分配一个主号是同样的意义。你也可以用定义在
linux/fs.h的
register_chrdev函数。
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
其中
unsigned int major是你想要请求的主号,
const char *name是设备的名字,它会显示在
/proc/devices中。
struct file_operations *fops是一个指针,指向对于你驱动的
file_operations表。
如果返回的是一个负数表示注册失败。注意的是我们并不会传设备号(辅号)到
register_chrdev。那时因为内核并不关心设备号,只有驱动会用它。
现在,问题来了,你在没有劫持一个正在使用的设备的时候,如何得到一个主号?最容易的方法就是通过看
Documentation/devices.txt文件然后选出正在使用的那一个。那是个比较笨的方法,因为你永远不能确定是否这个你选择的号码时候后面会被分配。回答就是你能问内核来分配一个动态的主号。
如果你传递了一个主号0到
register_chrdev,然后返回的值将会是动态分配的主号。不好的方面是你不能提前造一个设备文件。因为你不知道主号将会是什么。这里就有一些的方法来做。第一种,驱动本身能打印新分配的数,并且我们能手动的生产设备文件。第二种,新注册的设备将会有一个入口在
/proc/devices然后我们要不就手动生产一个设备文件或者写一个shell脚本来读文件然后生成设备文件。第三种方法是我们能让我们驱动生产设备文件通过使用
mknod在一次成功注册后系统调用后,然后在调用
cleanup_module的时候用rm命令。
4.1.4撤销一个设备
我们不能允许内核模块让root按照他想的那样随时rmmod。如果设备正在由一个进程打开然后我们移除内核模块,正在使用的file就会造成请求的原来恰好是函数(读/写)的内存定位不存在。如果幸运,没有其他的代码装载到那,那么我们就会得到一些错误信息。如果我们不是太幸运,另一个内核模块被装载到了同样的位置,那就意味着跳入了内核的另一个函数的中间位置。这个结果就不能预测了,但是我们不能这样积极的想问题。通常情况下,当我们不想允许什么事,你从应该处理的函数返回错误代码(一个负数)。通过用
cleanup_module是不可能的,因为它是一个无返回值的函数。然而,有一个计数器,用来存有多少进程正在使用你的内核的追踪。你能在
/proc/modules的第三个文件看见这个值。如果这个值不是0,
rmmod命令将会失败。注意的是,你没必要在
cleanup_module的时候检查计数器,因为这个检查会通过系统调用’sys_delete_module’来执行,它的定义在
linux/module.c。你不应该直接的使用计数器,但是在
linux/module.h中有很多的函数定义,它们能让你增加,减少和现实这个计数器(counter):
try_module_get(THIS_MODULE): Increment the use count.
module_put(THIS_MODULE): Decrement the use count.
保证counter精确的值是很重要的;如果你曾今在用count的时候确定丢失正确的追踪,你就永远不能卸载这个模块了;各位,那就只能重启了。这个在模块的开发过程中迟早会发生的。
4.1.5 hardev.c
接下来的代码样例命名为chardev,你能cat它的设备文件(或者用程序打开
open)并且这个驱动将这个设备文件独到的数字放到文件中去。我们不支持写入文件(像
echo "hi" > /dev/hello),但是抓住这些意图然后告诉用户这个操作不支持。如果看不见我们对数据的操作没关系,我们读到缓存中;我们不会做太多,我们简单的读数据然后打印成消息承认我们收到了。
例子4-1. chardev.c
/*
* chardev.c: Creates a read-only char device that says how many times
* you've read from the dev file
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h> /* for put_user */
/*
* Prototypes - this would normally go in a .h file
*/
int init_module(void);
void cleanup_module(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
#define SUCCESS 0
#define DEVICE_NAME "chardev" /* Dev name as it appears in /proc/devices */
#define BUF_LEN 80 /* Max length of the message from the device */
/*
* Global variables are declared as static, so are global within the file.
*/
static int Major; /* Major number assigned to our device driver */
static int Device_Open = 0; /* Is device open?
* Used to prevent multiple access to device */
static char msg[BUF_LEN]; /* The msg the device will give when asked */
static char *msg_Ptr;
static struct file_operations fops = { .read = device_read, .write = device_write, .open = device_open, .release = device_release };
/*
* This function is called when the module is loaded
*/
int init_module(void)
{
Major = register_chrdev(0, DEVICE_NAME, &fops);
if (Major < 0) {
printk(KERN_ALERT "Registering char device failed with %d\n", Major);
return Major;
}
printk(KERN_INFO "I was assigned major number %d. To talk to\n", Major);
printk(KERN_INFO "the driver, create a dev file with\n");
printk(KERN_INFO "'mknod /dev/%s c %d 0'.\n", DEVICE_NAME, Major);
printk(KERN_INFO "Try various minor numbers. Try to cat and echo to\n");
printk(KERN_INFO "the device file.\n");
printk(KERN_INFO "Remove the device file and module when done.\n");
return SUCCESS;
}
/*
* This function is called when the module is unloaded
*/
void cleanup_module(void)
{
/*
* Unregister the device
*/
int ret = unregister_chrdev(Major, DEVICE_NAME);
if (ret < 0)
printk(KERN_ALERT "Error in unregister_chrdev: %d\n", ret);
}
/*
* Methods
*/
/*
* Called when a process tries to open the device file, like
* "cat /dev/mycharfile"
*/
static int device_open(struct inode *inode, struct file *file)
{
static int counter = 0;
if (Device_Open)
return -EBUSY;
Device_Open++;
sprintf(msg, "I already told you %d times Hello world!\n", counter++);
msg_Ptr = msg;
try_module_get(THIS_MODULE);
return SUCCESS;
}
/*
* Called when a process closes the device file.
*/
static int device_release(struct inode *inode, struct file *file)
{
Device_Open--; /* We're now ready for our next caller */
/*
* Decrement the usage count, or else once you opened the file, you'll
* never get get rid of the module.
*/
module_put(THIS_MODULE);
return 0;
}
/*
* Called when a process, which already opened the dev file, attempts to
* read from it.
*/
static ssize_t device_read(struct file *filp, /* see include/linux/fs.h */
char *buffer, /* buffer to fill with data */
size_t length, /* length of the buffer */
loff_t * offset)
{
/*
* Number of bytes actually written to the buffer
*/
int bytes_read = 0;
/*
* If we're at the end of the message,
* return 0 signifying end of file
*/
if (*msg_Ptr == 0)
return 0;
/*
* Actually put the data into the buffer
*/
while (length && *msg_Ptr) {
/*
* The buffer is in the user data segment, not the kernel
* segment so "*" assignment won't work. We have to use
* put_user which copies data from the kernel data segment to
* the user data segment.
*/
put_user(*(msg_Ptr++), buffer++);
length--;
bytes_read++;
}
/*
* Most read functions return the number of bytes put into the buffer
*/
return bytes_read;
}
/*
* Called when a process writes to dev file: echo "hi" > /dev/hello
*/
static ssize_t
device_write(struct file *filp, const char *buff, size_t len, loff_t * off)
{
printk(KERN_ALERT "Sorry, this operation isn't supported.\n");
return -EINVAL;
}
译注:
ssize_t是有符号整型,在32位机器上等同int,在64位下等同与long int,size_t 就是无符号的ssize_t,32位下为unsigned int ,size_t 在64位下是unsigned long size_t。
其主要目的就是为了适应于不同的机器的存储。
loff_t 为long long行,在32位机器上相当于一个64位数据,占用两个存储单元。
我的fs.h里面unregister_chrdev为void的返回函数.
但是我网上看的unregister_chrdev是有返回值的。
我能编译通过,并且能显示值。
4.1.6为多内核版本写模块
系统调用是内核展示給进程的主要接口,通常待在不同版本是一样的。一个新的系统调用可能被加进去,但是通常旧的那一个会表现的恰如其原来的那样。这个必须是与旧版本相兼容的--一个新的内核版本是不应该打破常规进程的。在大多数情况下,设备文件将保持一致。另一方面,内核的内部接口却可以因版本不同而发生改变。Llinux内核版本被分成稳定版(n.<even number>.m)和开发版(n.<odd number>.m)开发版包含所有的新奇的想法,包含那些在下一个版本会被认为是错误的或者重实现的。结果就是,你不能相信在那些版本中接口都是一样的(那就是为什么我不想惹麻烦在书中支持他们,那有太多的工作要做并且还可能因为太快而过时)。在稳定版本中,另一方面,我们能期待不管bug还是修复的版本中(m数字)的接口仍然是一样的。
在不同内核中有很多的不同,且如果你想要支持多内核版本的话,你要找出自己代码让其拥有一个有条件的编译指令。做法就是把
LINUX_VERSION_CODE宏指令与
KERNEL_VERSION宏指令做对比,在版本内核a.b.c版本中,宏指令的值将会是
$2^{16}a+2^{8}b+c$.
然而这个导论的前一个版本展示了你如何用这样的构造写一个旧版本兼容的代码的细节,但是我决定打破这个传统。人们对于用LKMPG来匹配内核版本来做这件事更有兴趣。我决定用版本LKMPG来匹配内核,至少是在主辅号方面是这样的。我们用补丁级别来为我们的版本更新,以此用LKMPG版本2.4来对应内核版本2.4.x,用LKMPG版本2.6.x对应内核版本2.6.x以此类推。同样确认你总是使用将两者的版本同时更新,内核和导论。。。
更新:我上述所说对于内核2.6.10以上的都是真实的。你可能已经意识到最近的内核看起来不同。以防万一你使他们现在看起来不像2.6.x.y。头3个保持一支,但是子补丁级别已经加进去了,且将表示安全修复直到下一个稳定的补丁级别出来。所以人们就能选择在稳定版本树中用安全更新,且在开发者树中用最新的内核。如果你对于所有的故事感兴趣,搜索内核邮件列表档案。
第5章/proc
文件系统
5.1 /proc
文件系统
译注:这章的例子都有问题,对于新的内核,用的不一样了,沿用老的例子会造成错误。在后面我会加上一些自己写的代码,作为新的例子。但是旧的例子也是可以看的。
我在5.2后面加了译注,是5.1和5.2的例子在新版本内核中用的
在linux中,对于内核和内核模块有一种额外的机制来发送信息到进程那就是
/proc文件系统。原本设计的目的是允让进程获取信息更简单(hence the name),现在是被内核报告信息所用。例如
/proc/modules就提供了一个模块列表,还有
/proc/meminfo就说明了内存使用情况的数据。
用proc文件系统的方法与使用设备驱动差不多——创建一个结构,然后在
/proc文件中包含所有需要的信息,包括指向的任何处理函数(在我们的例子中,仅有一个例子:是当某人企图从
/proc文件中读数据时被调用)。接着
init_module模块在内核注册这个结构,用
cleanup_module取消注册。
我们使用
proc_register_dynamic的原因是因为我们不想要提起决定对于我们文件的索引节点的号码,但是允许内核决定它是为了避免冲突。通常文件系统都在磁盘上,而不是仅仅在内存里(就是
proc在的地方),在那种情况下,索引节点号是一个指针指向磁盘的位置,也就是文件的索引节点(inode是缩写)的位置。索引节点包含关于文件的信息,例如文件的权限,还有指向磁盘位置的指针或者说哪能找到文件的数据。
因为当文件时关闭或者打开状态时候,我们不能得到调用函数,我们没有地方将
try_module_get和
try_module_put放到模块中去,然后如果文件是打开的,然后模块又被移除了,那就不能避免这种结果了。
下面是一个例子展现如何使用
/proc文件。这是一个对于
/proc文件系统的HelloWorld。一共有3个部分:在
init_module函数中创建文件于
/proc/helloworld,然后当文件
proc/helloworld在回调函数
procfs_read中读到东西就返回一个值(和一个缓冲),最后在
cleanup_module函数中删除
/proc/helloworld文件。
当模块用函数
create_proc_entry函数装入内核的时候,
/proc/helloworld文件被创建。返回值是一个结构
struct proc_dir_entry *。它用来配置文件
/proc/helloworld(例如,文件的所有者),一个空指针返回值表示创建失败。
每次,文件
proc/helloworld被读到,函数
procfs_read就会被调用。这个函数的两个参数就很重要了:buffer(第一个参数),还有offset(第三个参数)。buffer里面的内容将会被返回給读它的应用(例如cat命令)。offset是当前在文件中的位置。如果函数的返回值不是空,那么这个函数会在调用一次。所以请谨慎使用这个函数,如果永远不返回0,那么这个函数就会无止尽的读。。
% cat /proc/helloworld HelloWorld!
例子5-1.procfs1.c
/* * procfs1.c - create a "file" in /proc * */ #include <linux/module.h> /* Specifically, a module */ #include <linux/kernel.h> /* We're doing kernel work */ #include <linux/proc_fs.h> /* Necessary because we use the proc fs */ #define procfs_name "helloworld" /** * This structure hold information about the /proc file * */ struct proc_dir_entry *Our_Proc_File; /* Put data into the proc fs file. * * Arguments * ========= * 1. The buffer where the data is to be inserted, if * you decide to use it. * 2. A pointer to a pointer to characters. This is * useful if you don't want to use the buffer * allocated by the kernel. * 3. The current position in the file * 4. The size of the buffer in the first argument. * 5. Write a "1" here to indicate EOF. * 6. A pointer to data (useful in case one common * read for multiple /proc/... entries) * * Usage and Return Value * ====================== * A return value of zero means you have no further * information at this time (end of file). A negative * return value is an error condition. * * For More Information * ==================== * The way I discovered what to do with this function * wasn't by reading documentation, but by reading the * code which used it. I just looked to see what uses * the get_info field of proc_dir_entry struct (I used a * combination of find and grep, if you're interested), * and I saw that it is used in <kernel source * directory>/fs/proc/array.c. * * If something is unknown about the kernel, this is * usually the way to go. In Linux we have the great * advantage of having the kernel source code for * free - use it. */ int procfile_read(char *buffer, char **buffer_location, off_t offset, int buffer_length, int *eof, void *data) { int ret; printk(KERN_INFO "procfile_read (/proc/%s) called\n", procfs_name); /* * We give all of our information in one go, so if the * user asks us if we have more information the * answer should always be no. * * This is important because the standard read * function from the library would continue to issue * the read system call until the kernel replies * that it has no more information, or until its * buffer is filled. */ if (offset > 0) { /* we have finished to read, return 0 */ ret = 0; } else { /* fill the buffer, return the buffer size */ ret = sprintf(buffer, "HelloWorld!\n"); } return ret; } int init_module() { Our_Proc_File = create_proc_entry(procfs_name, 0644, NULL); if (Our_Proc_File == NULL) { remove_proc_entry(procfs_name, &proc_root); printk(KERN_ALERT "Error: Could not initialize /proc/%s\n", procfs_name); return -ENOMEM; } Our_Proc_File->read_proc = procfile_read; Our_Proc_File->owner = THIS_MODULE; Our_Proc_File->mode = S_IFREG | S_IRUGO; Our_Proc_File->uid = 0; Our_Proc_File->gid = 0; Our_Proc_File->size = 37; printk(KERN_INFO "/proc/%s created\n", procfs_name); return 0; /* everything is ok */ } void cleanup_module() { remove_proc_entry(procfs_name, &proc_root); printk(KERN_INFO "/proc/%s removed\n", procfs_name); }
5.2读和写一个/poc文件
我们已经看到了一个对于proc文件非常简单的例子,就是我们仅仅读文件
/proc/helloworld.对于写一个
/proc文件也是可能的。它和读是一样的方式。当
/proc文件被写的时候,一个函数调用。但是这里又和读的方法有一点点的不一样,数据来自读者,所以你必须从用户空间引入数据到内核空间(用函数:
copy_from_useror
get_user)
对于
copy_from_user or get_user的理由是Linux内存是段式的(在intel结构中,它在一些其他处理器中可能不同)。这个就意味着有一个指针,由他自己,不指向一个内存的单独位置,仅仅是内存的段中的位置,所以你需要知道能使用的是哪个内存段。这里有一个内核的内存段,一个对于每个进程的内存段。
这个内存段仅仅能被自己拥有的那个进程进入,所以当写常规程序来跑进程的时候,我们没必要担心段。当你写一个内核模块的时候,通常不能进入到内核段,这个一般是由系统自动处理的。然而,当内存buffer的内容需要在当前运行的进程和内核传递的时候,内核函数收到一个指针指向内存buffer,这个buffer救在进程段中。那么宏定义的
put_user和
get_user允许你进入到内存。这些函数方法仅仅有一个性状(caracter是什么),你能用
copy_to_user和
copy_from_user处理几个性状(caracter是什么)。当buffer在内核空间时(在 read 或 write 函数中),对于写函数因为数据来自于用户空间,你需要引入数据但是对于读函数就不需要了,因为数据已经在内核空间了。
例子5-2 procfs.c
/** * procfs2.c - create a "file" in /proc * */ #include <linux/module.h> /* Specifically, a module */ #include <linux/kernel.h> /* We're doing kernel work */ #include <linux/proc_fs.h> /* Necessary because we use the proc fs */ #include <asm/uaccess.h> /* for copy_from_user */ #define PROCFS_MAX_SIZE 1024 #define PROCFS_NAME "buffer1k" /** * This structure hold information about the /proc file * */ static struct proc_dir_entry *Our_Proc_File; /** * The buffer used to store character for this module * */ static char procfs_buffer[PROCFS_MAX_SIZE]; /** * The size of the buffer * */ static unsigned long procfs_buffer_size = 0; /** * This function is called then the /proc file is read * */ int procfile_read(char *buffer, char **buffer_location, off_t offset, int buffer_length, int *eof, void *data) { int ret; printk(KERN_INFO "procfile_read (/proc/%s) called\n", PROCFS_NAME); if (offset > 0) { /* we have finished to read, return 0 */ ret = 0; } else { /* fill the buffer, return the buffer size */ memcpy(buffer, procfs_buffer, procfs_buffer_size); ret = procfs_buffer_size; } return ret; } /** * This function is called with the /proc file is written * */ int procfile_write(struct file *file, const char *buffer, unsigned long count, void *data) { /* get buffer size */ procfs_buffer_size = count; if (procfs_buffer_size > PROCFS_MAX_SIZE ) { procfs_buffer_size = PROCFS_MAX_SIZE; } /* write data to the buffer */ if ( copy_from_user(procfs_buffer, buffer, procfs_buffer_size) ) { return -EFAULT; } return procfs_buffer_size; } /** *This function is called when the module is loaded * */ int init_module() { /* create the /proc file */ Our_Proc_File = create_proc_entry(PROCFS_NAME, 0644, NULL); if (Our_Proc_File == NULL) { remove_proc_entry(PROCFS_NAME, &proc_root); printk(KERN_ALERT "Error: Could not initialize /proc/%s\n", PROCFS_NAME); return -ENOMEM; } Our_Proc_File->read_proc = procfile_read; Our_Proc_File->write_proc = procfile_write; Our_Proc_File->owner = THIS_MODULE; Our_Proc_File->mode = S_IFREG | S_IRUGO; Our_Proc_File->uid = 0; Our_Proc_File->gid = 0; Our_Proc_File->size = 37; printk(KERN_INFO "/proc/%s created\n", PROCFS_NAME); return 0; /* everything is ok */ } /** *This function is called when the module is unloaded * */ void cleanup_module() { remove_proc_entry(PROCFS_NAME, &proc_root); printk(KERN_INFO "/proc/%s removed\n", PROCFS_NAME); }
hello_proc 内核模块,加入了
/proc文件系统的入口,Ref
目前,要把接收串文件作为数据buffers,要有5个值:
1. 头文件,
linux/module.h作为模块宏定义和函数是必须的。
linux/proc_fs.h对于函数
proc_create()和
remove_proc_entry()也是必须的,他们用来在proc文件系统中创建一个“假”文件.还有一个
linux/seq_file.h这个后面再说
2.
hello_proc_show()是定义的输出
3.
hello_proc_open()用来打开回调函数,当文件被打开的时候调用。这里有
single_open()表示所有的数据是一次输出。更多的在讨论sequence文件的时候说
4.
hello_proc_fops()是文件操作结构,用来为我们的文件定义文件操作回调函数
5. 文件事实上用
proc_create(file_name,permission_bits,parent_dir,file_operations)来创建的,我们的参数中”
hello_proc“是名字,0在权限参数上代表着默认值0444权限于文件,第三个参数NULL代表我们的文件直接产生在
/proc。
6. 这个函数
remove_proc_entry(name, parent_dir)是用来移除我们“假”文件的
proc文件系统在linux实现上有限制,我们的模块不能一次输出多于一页的数据。一页是内存中预定义的,(这里的一页是指内存分页算法中的那个页不是看到的页面)一般是4096字节。可以在宏定义的PAGE_SIZE中找到。这个限制在用sequence文件的时候绕开了,提供了一个接口来使得打印多页输出更容易。这个接口很方便,使用也像单一页输出一样简单。
#include<linux/module.h> #include<linux/proc_fs.h> #include<linux/seq_file.h> static int hello_proc_show(struct seq_file *m,void *v){ seq_printf(m,"Hello proc!\n"); return 0; } static int hello_proc_open(struct inode *inode,struct file *file){ //这里输出不能多余一页 return single_open(file,hello_proc_show,NULL); } static const struct file_operations hello_proc_fops={ .owner = THIS_MODULE, .open = hello_proc_open, .read = seq_read, .llseek = seq_lseek, .release= single_release, }; static int __init hello_proc_init(void){ proc_create("hello_proc",0,NULL,&hello_proc_fops); return 0; } static void __exit hello_proc_exit(void){ remove_proc_entry("hello_proc",NULL); } MODULE_LICENSE("GPL"); module_init(hello_proc_init); module_exit(hello_proc_exit);
>
$ sudo insmod hello_proc.ko $ cat /proc/hello_proc Hello proc! $ ls -l /proc/hello_proc -r--r--r-- 1 root root 0 2013-09-18 21:32 /proc/hello_proc $ sudo rmmod hello_proc
5.3用标准文件系统管理/proc
文件
我们已经看到了如何用/proc接口读和写一个
/proc文件。但是我们也能用索引节点来管理
/proc文件。最有趣的就是使用高级函数,就像permissions.
在Linux,对于文件系统的注册登记有标准机制。因为每一个文件系统必须有它自己拥有函数来处理索引节点和文件操作,这里就有一个特殊结构来对于所有这些函数来保存指针,
struct inode_operations,这个结构包含了一个指针指向
struct file_operations。在
/proc中,无论何时我们注册一个新的文件,我们被允许指定一个
struct inode_operations并用这个结构来进入文件。我们使用的这个机制是一个包括了指针指向我们的
procfs_read和
procfs_write的
struct file_operations结构,然后
struct file_operations结构又被一个指针指向的
struct inode_operations结构所包括。(译注:
struct inode_operations->
struct file_operations->
procfs_read和
procfs_write)
另一个有趣的点是
module_permission函数。这个函数不管何时一个进程尝试对
/proc文件做一些操作的时候就被调用了,这个函数能决定是否允许这个进程进入
/proc文件进行操作。现在仅仅建立在操作上和当前使用者的uid(可以在
current中得到,一个指针指向一个包含了当前正在运行进程信息的结构体),但是这个将会基于我们想要的任何事,例如其他进程正在对同样的文件做了什么事,或者上次我们收到的输入是什么等。。。
需要注意的是标准的读和写角色在内核是相反的。读函数是用来输出的,然后写函数是用来输入的。原因就是读和写代表的是的用户的观点————如果一个进程从内核读东西,那么内核就需要输出它,如果一个进程写一个东西到内核,那么内核就应该把收到的作为收入。
例子 5-3.procfs3.c
/* * procfs3.c - create a "file" in /proc, use the file_operation way * to manage the file. */ #include <linux/kernel.h> /* We're doing kernel work */ #include <linux/module.h> /* Specifically, a module */ #include <linux/proc_fs.h> /* Necessary because we use proc fs */ #include <asm/uaccess.h> /* for copy_*_user */ #define PROC_ENTRY_FILENAME "buffer2k" #define PROCFS_MAX_SIZE 2048 /** * The buffer (2k) for this module * */ static char procfs_buffer[PROCFS_MAX_SIZE]; /** * The size of the data hold in the buffer * */ static unsigned long procfs_buffer_size = 0; /** * The structure keeping information about the /proc file * */ static struct proc_dir_entry *Our_Proc_File; /** * This funtion is called when the /proc file is read * */ static ssize_t procfs_read(struct file *filp, /* see include/linux/fs.h */ char *buffer, /* buffer to fill with data */ size_t length, /* length of the buffer */ loff_t * offset) { static int finished = 0; /* * We return 0 to indicate end of file, that we have * no more information. Otherwise, processes will * continue to read from us in an endless loop. */ if ( finished ) { printk(KERN_INFO "procfs_read: END\n"); finished = 0; return 0; } finished = 1; /* * We use put_to_user to copy the string from the kernel's * memory segment to the memory segment of the process * that called us. get_from_user, BTW, is * used for the reverse. */ if ( copy_to_user(buffer, procfs_buffer, procfs_buffer_size) ) { return -EFAULT; } printk(KERN_INFO "procfs_read: read %lu bytes\n", procfs_buffer_size); return procfs_buffer_size; /* Return the number of bytes "read" */ } /* * This function is called when /proc is written */ static ssize_t procfs_write(struct file *file, const char *buffer, size_t len, loff_t * off) { if ( len > PROCFS_MAX_SIZE ) { procfs_buffer_size = PROCFS_MAX_SIZE; } else { procfs_buffer_size = len; } if ( copy_from_user(procfs_buffer, buffer, procfs_buffer_size) ) { return -EFAULT; } printk(KERN_INFO "procfs_write: write %lu bytes\n", procfs_buffer_size); return procfs_buffer_size; } /* * This function decides whether to allow an operation * (return zero) or not allow it (return a non-zero * which indicates why it is not allowed). * * The operation can be one of the following values: * 0 - Execute (run the "file" - meaningless in our case) * 2 - Write (input to the kernel module) * 4 - Read (output from the kernel module) * * This is the real function that checks file * permissions. The permissions returned by ls -l are * for referece only, and can be overridden here. */ static int module_permission(struct inode *inode, int op, struct nameidata *foo) { /* * We allow everybody to read from our module, but * only root (uid 0) may write to it */ if (op == 4 || (op == 2 && current->euid == 0)) return 0; /* * If it's anything else, access is denied */ return -EACCES; } /* * The file is opened - we don't really care about * that, but it does mean we need to increment the * module's reference count. */ int procfs_open(struct inode *inode, struct file *file) { try_module_get(THIS_MODULE); return 0; } /* * The file is closed - again, interesting only because * of the reference count. */ int procfs_close(struct inode *inode, struct file *file) { module_put(THIS_MODULE); return 0; /* success */ } static struct file_operations File_Ops_4_Our_Proc_File = { .read = procfs_read, .write = procfs_write, .open = procfs_open, .release = procfs_close, }; /* * Inode operations for our proc file. We need it so * we'll have some place to specify the file operations * structure we want to use, and the function we use for * permissions. It's also possible to specify functions * to be called for anything else which could be done to * an inode (although we don't bother, we just put * NULL). */ static struct inode_operations Inode_Ops_4_Our_Proc_File = { .permission = module_permission, /* check for permissions */ }; /* * Module initialization and cleanup */ int init_module() { /* create the /proc file */ Our_Proc_File = create_proc_entry(PROC_ENTRY_FILENAME, 0644, NULL); /* check if the /proc file was created successfuly */ if (Our_Proc_File == NULL){ printk(KERN_ALERT "Error: Could not initialize /proc/%s\n", PROC_ENTRY_FILENAME); return -ENOMEM; } Our_Proc_File->owner = THIS_MODULE; Our_Proc_File->proc_iops = &Inode_Ops_4_Our_Proc_File; Our_Proc_File->proc_fops = &File_Ops_4_Our_Proc_File; Our_Proc_File->mode = S_IFREG | S_IRUGO | S_IWUSR; Our_Proc_File->uid = 0; Our_Proc_File->gid = 0; Our_Proc_File->size = 80; printk(KERN_INFO "/proc/%s created\n", PROC_ENTRY_FILENAME); return 0; /* success */ } void cleanup_module() { remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root); printk(KERN_INFO "/proc/%s removed\n", PROC_ENTRY_FILENAME); }
对于个procfs例子觉得太少了?好吧,第一要记住,有些传闻说,procfs正要推出舞台了,考虑使用
sysfs来代替。第二个,如果你真的觉得不够,那么就给你个福利吧:
linux/Documentation/DocBook里面。在内核文件的顶层使用make help指令。例如:make htmldocs。这个能转换成你最喜欢的格式。考虑到用这个机制,万一你自己想要了解一些关于内核相关的文件。
5.4用seq_file
管理/proc
文件
如我们看到的,写一个/proc文件可能很复杂,那么帮助人们写
/proc文件,这里有一个api叫做
seq_file能帮助格式化的输出到一个
/proc文件。它基于sequence(时序?)。它由3个函数组成:
start(),next(),stop()。这个
seq_fileAPI当一个用户读一个
/proc文件的时候开启一个sequence(时序?)。
一个序列开始于调用函数
start()。如果返回一个非NULL的值,那么这个函数就会调用
next()。这个函数是一个游标(iterator),它的目标是审查所有的数据。每次调用
next()时候,这个函数同时会调用
show()函数。它写入数据到buffer,然后給用户读,直到
next()调用的时候返回NULL结束。这个序列也会在
next()返回NULL的时候结束,然后调用函数
stop()
注意:当一个序列完成了,另一个就会开始。那就意味着,在
stop()结束的时候,
start()会再一次调用。这个循环终止于
start()函数的返回值为NULL。你能在下面表:
"How seq_file works"看见这个结构解释。
seq_file提供了对于
file_operations的基础函数,例如
seq_read,
seq_lseek等等。但是不会有写文件的操作。当然,你能使用如前面的例子中的方法。
例子5-4.procfs4.c
/**
* procfs4.c - create a “file” in /proc
* This program uses the seq_file library to manage the /proc file.
*
*/
#include <linux/kernel.h> /* We're doing kernel work */ #include <linux/module.h> /* Specifically, a module */ #include <linux/proc_fs.h> /* Necessary because we use proc fs */ #include <linux/seq_file.h> /* for seq_file */ #define PROC_NAME "iter" MODULE_AUTHOR("Philippe Reynes"); MODULE_LICENSE("GPL"); /** * This function is called at the beginning of a sequence. * ie, when: * - the /proc file is read (first time) * - after the function stop (end of sequence) * */ static void *my_seq_start(struct seq_file *s, loff_t *pos) { static unsigned long counter = 0; /* beginning a new sequence ? */ if ( *pos == 0 ) { /* yes => return a non null value to begin the sequence */ return &counter; } else { /* no => it's the end of the sequence, return end to stop reading */ *pos = 0; return NULL; } } /** * This function is called after the beginning of a sequence. * It's called untill the return is NULL (this ends the sequence). * */ static void *my_seq_next(struct seq_file *s, void *v, loff_t *pos) { unsigned long *tmp_v = (unsigned long *)v; (*tmp_v)++; (*pos)++; return NULL; } /** * This function is called at the end of a sequence * */ static void my_seq_stop(struct seq_file *s, void *v) { /* nothing to do, we use a static value in start() */ } /** * This function is called for each "step" of a sequence * */ static int my_seq_show(struct seq_file *s, void *v) { loff_t *spos = (loff_t *) v; seq_printf(s, "%Ld\n", *spos); return 0; } /** * This structure gather "function" to manage the sequence * */ static struct seq_operations my_seq_ops = { .start = my_seq_start, .next = my_seq_next, .stop = my_seq_stop, .show = my_seq_show }; /** * This function is called when the /proc file is open. * */ static int my_open(struct inode *inode, struct file *file) { return seq_open(file, &my_seq_ops); }; /** * This structure gather "function" that manage the /proc file * */ static struct file_operations my_file_ops = { .owner = THIS_MODULE, .open = my_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release }; /** * This function is called when the module is loaded * */ int init_module(void) { struct proc_dir_entry *entry; entry = create_proc_entry(PROC_NAME, 0, NULL); if (entry) { entry->proc_fops = &my_file_ops; } return 0; } /** * This function is called when the module is unloaded. * */ void cleanup_module(void) { remove_proc_entry(PROC_NAME, NULL); }
如果你想要更多信息,你能阅读下面的网页:
1. http://lwn.net/Articles/22355/
2. http://www.kernelnewbies.org/documents/seq_file_howto.txt
你也可以读linux内核中的文件:
fs/seq_file.c
相关文章推荐
- Linux socket 初步
- linux lsof详解
- linux 文件权限
- 10 篇对初学者和专家都有用的 Linux 命令教程
- Linux 与 Windows 对UNICODE 的处理方式
- Ubuntu12.04下QQ完美走起啊!走起啊!有木有啊!
- 解決Linux下Android开发真机调试设备不被识别问题
- 运维入门
- 运维提升
- Linux 自检和 SystemTap
- Ubuntu Linux使用体验
- c语言实现hashmap(转载)
- Linux 信号signal处理机制
- linux下mysql添加用户
- Scientific Linux 5.5 图形安装教程
- 基于 Linux 集群环境上 GPFS 的问题诊断
- 谁是桌面王者?Win PK Linux三大镇山之宝
- vivi下重新调整分区
- Linux VS Unix:Linux欲一统天下 Unix不死