/proc文件系统是一个特殊的软件创建的文件系统, 内核用来输出消息到外界. /proc 下的每个文件都绑到一个内核函数上,
当文件被读的时候即时产生文件内容. 我们已经见到一些这样的文件起作用; 例如, /proc/modules, 常常返回当前已加载的模块列表.
/proc 在 Linux 系统中非常多地应用. 很多现代 Linux 发布中的工具, 例如 ps, top, 以及 uptime, 从
/proc 中获取它们的信息. 一些设备驱动也通过 /proc 输出信息. /proc 文件系统是动态的,
因此你的模块可以在任何时候添加或去除条目.
完全特性的 /proc 条目可能是复杂的; 另外, 它们可写也可读, 但是, 大部分时间, /proc 条目是只读的文件.
要创建一个只读 /proc 文件, 你的驱动必须实现一个函数来在文件被读时产生数据. 当某个进程读文件时(使用 read 系统调用), 这个请求通过这个函数到达你的模块.
当一个进程读你的 /proc 文件, 内核分配了一页内存(就是说, PAGE_SIZE 字节), 驱动可以写入数据来返回给用户空间. 那个缓存区传递给你的函数, 是一个称为 read_proc 的方法:
int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data);
page 指针是你写你的数据的缓存区; start 是这个函数用来说有关的数据写在页中哪里(下面更多关于这个); offset 和
count 对于 read 方法有同样的含义. eof 参数指向一个整数, 必须由驱动设置来指示它不再有数据返回, data
是驱动特定的数据指针, 你可以用做内部用途.
这个函数应当返回实际摆放于 page 缓存区的数据的字节数, 就象 read 方法对别的文件所作一样. 别的输出值是 *eof 和
*start. eof 是一个简单的标志, 但是 start 值的使用有些复杂; 它的目的是帮助实现大的(超过一页) /proc 文件.
start 参数有些非传统的用法. 它的目的是指示哪里(哪一页)找到返回给用户的数据. 当调用你的 proc_read 方法,
*start 将会是 NULL. 如果你保持它为 NULL, 内核假定数据已放进 page 偏移是 0; 换句话说, 它假定一个头脑简单的
proc_read 版本, 它安放虚拟文件的整个内容到 page, 没有注意 offset 参数. 如果, 相反, 你设置 *start 为一个
非NULL 值, 内核认为由 *start 指向的数据考虑了 offset, 并且准备好直接返回给用户. 通常, 返回少量数据的简单
proc_read 方法只是忽略 start. 更复杂的方法设置 *start 为 page 并且只从请求的 offset 那里开始安放数据.
还有一段距离到 /proc 文件的另一个主要问题, 它也打算解答 start. 有时内核数据结构的 ASCII 表示在连续的 read
调用中改变, 因此读进程可能发现从一个调用到下一个有不一致的数据. 如果 *start 设成一个小的整数值, 调用者用它来递增 filp-
注意, 有更好的方法实现大的 /proc 文件; 它称为 seq_file, 我们很快会讨论它. 首先, 然而, 是时间举个例子了. 下面是一个简单的(有点丑陋) read_proc 实现, 为 scull 设备:
int scull_read_procmem(char *buf, char **start, off_t offset,int count, int *eof, void *data)
{
int len = 0;
SCULL_DEV *pscull = pscull_dev;
if (down_interruptible(&pscull->sem))
return -ERESTARTSYS;
len += sprintf(buf+len,"\nScull Device: qset %i, quantum %i, size %li\n",pscull->qset, pscull->quantum, pscull->size);
up(&pscull->sem);
*eof = 1;
return len;
}
如果你阅览内核源码, 你会遇到使用老接口实现 /proc 的代码:
int (*get_info)(char *page, char **start, off_t offset, int count);
所有的参数的含义同 read_proc 的相同, 但是没有 eof 和 data 参数. 这个接口仍然支持, 但是将来会消失; 新代码应当使用 read_proc 接口来代替.
一旦你有一个定义好的 read_proc 函数, 你应当连接它到 /proc 层次中的一个入口项. 使用一个 creat_proc_read_entry 调用:
struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode, struct proc_dir_entry *base, read_proc_t *read_proc, void *data);
这里, name 是要创建的文件名子, mod 是文件的保护掩码(缺省系统范围时可以作为 0 传递), base 指出要创建的文件的目录(
如果 base 是 NULL, 文件在 /proc 根下创建 ), read_proc 是实现文件的 read_proc 函数, data
被内核忽略( 但是传递给 read_proc). 这就是 scull 使用的调用, 来使它的 /proc 函数可用做
/proc/scullmem:
create_proc_read_entry("scullmem", 0 /* default mode */,
NULL /* parent dir */, scull_read_procmem,
NULL /* client data */);
这里, 我们创建了一个名为 scullmem 的文件, 直接在 /proc 下, 带有缺省的, 全局可读的保护.
目录入口指针可用来在 /proc 下创建整个目录层次. 但是, 注意, 一个入口放在 /proc 的子目录下会更容易,
通过简单地给出目录名子作为这个入口名子的一部分 -- 只要这个目录自身已经存在. 例如, 一个(常常被忽略)传统的是 /proc
中与设备驱动相连的入口应当在 driver/ 子目录下; scull 能够安放它的入口在那里, 简单地通过指定它为名子
driver/scullmem.
/proc 中的入口, 当然, 应当在模块卸载后去除. remove_proc_entry 是恢复 create_proc_read_entry 所做的事情的函数:
remove_proc_entry("scullmem", NULL /* parent dir */);
- #ifndef __KERNEL__
-
# define __KERNEL__
-
#endif
-
#ifndef MODULE
-
# define MODULE
-
#endif
-
-
#include <linux/init.h>
-
#include <linux/module.h>
-
#include <linux/cdev.h>
-
#include <linux/slab.h>
-
#include <linux/kernel.h> /* printk() */
-
#include <linux/fs.h> /* everything... */
-
#include <linux/errno.h> /* error codes */
-
#include <linux/types.h> /* size_t */
-
#include <linux/proc_fs.h>
-
#include <linux/fcntl.h> /* O_ACCMODE */
-
#include <asm/system.h> /* cli(), *_flags */
-
#include <asm/uaccess.h>
-
-
#include "scull.h" /* local definitions */
-
-
-
int scull_major = SCULL_MAJOR;
-
int scull_minor = SCULL_MINOR;
-
int scull_mn_dev = 1;
-
int scull_quantum = 4000; /*每个指针所指向存储单元的大小*/
-
int scull_qset = 4000; /*指针数组的大小*/
-
static pSCULL_DEV *pscull_dev = NULL;
-
-
-
-
int scull_read_procmem(char *buf, char **start, off_t offset,int count, int *eof, void *data)
-
{
-
int len = 0;
-
-
SCULL_DEV *pscull = pscull_dev;
-
-
if (down_interruptible(&pscull->sem))
-
return -ERESTARTSYS;
-
-
len += sprintf(buf+len,"\nScull Device: qset %i, quantum %i, size %li\n",pscull->qset, pscull->quantum, pscull->size);
-
-
up(&pscull->sem);
-
-
*eof = 1;
-
-
return len;
-
}
-
-
static void scull_create_proc(void)
-
{
-
create_proc_read_entry("scullmem", 0,NULL, scull_read_procmem,NULL);
-
}
-
-
static void scull_remove_proc(void)
-
{
-
remove_proc_entry("scullmem", NULL);
-
}
-
-
int scull_trim(SCULL_DEV *pscull)
-
{
-
int i = 0;
-
int qset = pscull->qset;
-
-
if (pscull->data)
-
{
-
for(i = 0; i < qset; i++)
-
{
-
if (pscull->data[i])
-
kfree(pscull->data[i]);
-
}
-
-
kfree(pscull->data);
-
pscull->data=NULL;
-
}
-
-
pscull->size = 0;
-
pscull->quantum = scull_quantum;
-
pscull->qset = scull_qset;
-
-
return 0;
-
}
-
-
/*open方法提供给驱动程序以初始化的能力。它应完成如下工作:1、检查设备特定的错误;2、如果设备是首次打开,则对其进行初始化;3、如有必要,更新f_op指针;4、分配并填写置于filp->private_data里的数据结构。*/
-
int scull_open(struct inode *inode, struct file *filp)
-
{
-
SCULL_DEV *pscull = NULL;
-
-
/*获取我们自定义结构的指针*/
-
pscull = container_of(inode->i_cdev,pSCULL_DEV,cdev);
-
/*将自定义结构的指针保存到file结构的private_data字段中,这样可以方便以后对该指针的访问*/
-
filp->private_data = pscull;
-
if ( (filp->f_flags & O_ACCMODE) == O_WRONLY)
-
{
-
scull_trim(pscull);
-
}
-
-
return 0;
-
}
-
-
/*release主要的工作是:1、释放由open分配的、保存在filp->private_data中的所有内容;2、在最后一次关闭操作时关闭设备。*/
-
int scull_release(struct inode *inode, struct file *filp)
-
{
-
return 0;
-
}
-
-
/*
-
*调用程序对read的返回值解释如下:
-
*1、如果返回值等于传递给read系统调用的count参数,则说明所请求的字节数传输成功完成了。
-
*2、如果返回值是正的,但是比count小,则说明只有部分数据成功传送。这种情况因为设备的不同可能有许多原因。大部分情况下,程序会重新读数据。例如,如果用fread函数读数据,这个库函数就会不断调用系统调用,直至所请求的数据传输完毕为止。
-
*3、如果返回值为0,则表示已经到达了文件尾。
-
*4、负值意味着发生了错误,该值指明了发生什么错误,错误码在<linux/errno.h>中定义
-
*/
-
ssize_t scull_read(struct file *filp, char *buf, size_t count,loff_t *f_pos)
-
{
-
SCULL_DEV *pscull = filp->private_data;
-
int quantum = pscull->quantum;
-
int qset = pscull->qset;
-
int s_pos = 0, q_pos = 0;
-
ssize_t ret = 0;
-
-
if(down_interruptible(&pscull->sem))
-
return -ERESTARTSYS;
-
-
if(*f_pos >= pscull->size)
-
goto out;
-
-
if(*f_pos + count > pscull->size)
-
count = pscull->size - *f_pos;
-
-
s_pos = (long)*f_pos/quantum;
-
q_pos = (long)*f_pos%quantum;
-
-
if(s_pos > qset)
-
goto out;
-
-
if(!pscull->data)
-
goto out;
-
-
if(!pscull->data[s_pos])
-
goto out;
-
-
if(count > quantum-q_pos)
-
count = quantum-q_pos;
-
-
/*将内核空间的数据拷贝到用户空间*/
-
if(copy_to_user(buf, pscull->data[s_pos]+q_pos, count))
-
{
-
ret = -EFAULT;
-
goto out;
-
}
-
-
*f_pos += count;
-
ret = count;
-
-
out:
-
up(&pscull->sem);
-
-
return ret;
-
}
-
-
/*
-
*调用程序对write的返回值解释如下:
-
*1、如果返回值等于count,则完成了所请求数目的字节传送。
-
*2、如果返回值是正的,但小于count,则只传送了部分数据。程序很可能再次试图写入余下的数据。
-
*3、如果值为0,意味着什么也没有写入。这个结果不是错误,而且也没有理由返回一个错误码。
-
*4、负值意味着发生了错误。
-
*/
-
ssize_t scull_write(struct file *filp, const char *buf, size_t count,loff_t *f_pos)
-
{
-
SCULL_DEV *pscull = filp->private_data;
-
int quantum = pscull->quantum;
-
int qset = pscull->qset;
-
int s_pos,q_pos;
-
ssize_t ret = -ENOMEM;
-
-
if(down_interruptible(&pscull->sem))
-
return -ERESTARTSYS;
-
-
s_pos = (long)*f_pos/quantum;
-
q_pos = (long)*f_pos%quantum;
-
-
if(!pscull->data)
-
{
-
pscull->data = kmalloc(qset*sizeof(char *), GFP_KERNEL);
-
if (!pscull->data)
-
goto out;
-
memset(pscull->data, 0, qset*sizeof(char *));
-
}
-
-
if(!pscull->data[s_pos])
-
{
-
pscull->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
-
if (!pscull->data[s_pos])
-
goto out;
-
memset(pscull->data[s_pos],0,quantum);
-
}
-
-
if(count > quantum-q_pos)
-
count = quantum-q_pos;
-
-
/*将用户空间的数据拷贝到内核空间*/
-
if(copy_from_user(pscull->data[s_pos]+q_pos, buf, count))
-
{
-
ret = -EFAULT;
-
goto out;
-
}
-
-
*f_pos += count;
-
ret = count;
-
-
if(pscull->size < *f_pos)
-
pscull->size = *f_pos;
-
-
out:
-
up(&pscull->sem);
-
-
return ret;
-
}
-
-
int scull_ioctl(struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg)
-
{
-
return 0;
-
}
-
-
loff_t scull_llseek(struct file *filp, loff_t off, int whence)
-
{
-
return 0;
-
}
-
-
struct file_operations scull_fops = {
-
.owner = THIS_MODULE,
-
.llseek = scull_llseek,
-
.read = scull_read,
-
.write = scull_write,
-
.ioctl = scull_ioctl,
-
.open = scull_open,
-
.release = scull_release,
-
};
-
-
/*模块的清除函数*/
-
static void scull_cleanup_module(void)
-
{
-
dev_t dev;
-
-
scull_remove_proc();
-
/*获取起始设备号*/
-
dev = MKDEV(scull_major,scull_minor);
-
/*从系统中移除一个字符设备*/
-
cdev_del(&pscull_dev->cdev);
-
/*释放不再使用的设备编号,第一个参数是起始设备编号,第二个参数是需要释放的连续编号数*/
-
unregister_chrdev_region(dev,scull_mn_dev);
-
scull_trim(pscull_dev);
-
kfree(pscull_dev);
-
pscull_dev = NULL;
-
-
return;
-
}
-
-
/*模块的初始化函数*/
-
static int scull_init_module(void)
-
{
-
/*在内核的2.6.0版本中,dev_t是一个32位的数,其中12位用来表示主设备号,而其余20位用来表示次设备号*/
-
dev_t dev;
-
int result = -1;
-
-
pscull_dev = kmalloc(sizeof(SCULL_DEV),GFP_KERNEL);
-
if(pscull_dev == NULL)
-
{
-
printk(KERN_WARNING"scull: can't kmalloc memory\n");
-
result = -ENOMEM;
-
return result;
-
}
-
memset(pscull_dev,0,sizeof(SCULL_DEV));
-
pscull_dev->data = NULL;
-
pscull_dev->quantum = scull_quantum;
-
pscull_dev->qset = scull_qset;
-
-
/*创建信号量,并将信号量的初始值赋值为1*/
-
sema_init(&pscull_dev->sem,1);
-
-
/*用来获取主设备号,可由程序指定(或在insmod的时候指定相应值)或采用内核动态分配的方式*/
-
if(scull_major)
-
{
-
/*将主设备号和次设备号转换成dev_t*/
-
dev = MKDEV(scull_major,scull_minor);
-
/*获得设备的一个或多个编号,该种方式是由程序指定设备号,函数的第一个参数是设备号的起始值(通常次设备号都被设置成0),第二个参数表示所请求的连续设备编号的个数,第三个参数是和该编号范围关联的设备名称,它将出现在/proc/devices和sysfs中*/
-
result = register_chrdev_region(dev,scull_mn_dev,"scull");
-
}
-
else
-
{
-
/*通常情况下,我们都不知道使用哪个设备号。使用以下的函数,内核将恰当的为我们分配所需的主设备号,第一个参数仅用于输出的参数,在成功完成调用后将保存已分配范围的第一个编号,第二个参数是要使用的被请求的第一个次设备号,第三个参数表示所请求的连续设备编号的个数,第四个参数是和该编号范围关联的设备名称,它将出现在/proc/devices和sysfs中*/
-
result = alloc_chrdev_region(&dev,scull_minor,scull_mn_dev,"scull");
-
/*获取主设备号,采用MINOR(dev)可以获取到次设备号*/
-
scull_major = MAJOR(dev);
-
}
-
-
if(result < 0)
-
{
-
printk(KERN_WARNING"scull: can't get major %d\n",scull_major);
-
return result;
-
}
-
-
/*在内核内部,使用struct cdev结构来表示字符设备,以下函数可以初始化已分配到的cdev结构。因为本例中我们将cdev结构嵌入到自己的特定设备结构中,所以采用下面的函数。也可以使用cdev_alloc()来分配一个cdev字符设备结构*/
-
cdev_init(&pscull_dev->cdev,&scull_fops);
-
/*初始化cdev结构的所有者字段*/
-
pscull_dev->cdev.owner = THIS_MODULE;
-
/*初始化设备的文件操作接口结构的指针*/
-
pscull_dev->cdev.ops = &scull_fops;
-
/*将cdev结构告诉内核,第一个参数是struct cdev机构,第二个是该设备对应的第一个设备编号,第三个参数是和该设备关联的设备编号的数量。在使用cdev_add时,需要注意:首先,这个调用可能会失败。如果它返回一个负的错误码,则设备不会被添加到系统中。但这个调用总是会成功返回。只要cdev_add成功返回了,设备就启动了,它的操作就会被内核调用。因此,在驱动程序还没有完全准备好处理设备上的操作时,就不能调用cdev_add。*/
-
result = cdev_add(&pscull_dev->cdev,dev,1);
-
if(result)
-
{
-
printk(KERN_NOTICE"Error %d add scull device\n",result);
-
goto fail;
-
}
-
-
scull_create_proc();
-
-
return 0;
-
-
fail:
-
scull_cleanup_module();
-
-
return result;
-
}
-
-
-
-
/*用于指定模块的初始化和清除函数的宏*/
-
module_init(scull_init_module);
-
module_exit(scull_cleanup_module);
-
-
/*在目标文件中添加关于模块的文档信息*/
-
MODULE_AUTHOR("txgcwm");
-
MODULE_VERSION("scull_v1.0");
-
MODULE_LICENSE("GPL");
当运行我们之前写的测试程序的时候,可以从proc目录下抓取到如下的信息:
- Scull Device: qset 4000, q 4000, sz 0
-
root@txgcwm:/proc# cat scullmem
-
-
Scull Device: qset 4000, q 4000, sz 216
-
root@txgcwm:/proc# cat scullmem
-
-
Scull Device: qset 4000, q 4000, sz 312
-
root@txgcwm:/proc# cat scullmem
-
-
Scull Device: qset 4000, q 4000, sz 360
-
root@txgcwm:/proc# cat scullmem
-
-
Scull Device: qset 4000, q 4000, sz 408
-
root@txgcwm:/proc# cat scullmem
-
-
Scull Device: qset 4000, q 4000, sz 456
-
root@txgcwm:/proc# cat scullmem
-
-
Scull Device: qset 4000, q 4000, sz 576
阅读(2248) | 评论(1) | 转发(1) |