论文部分内容阅读
摘要:/proc文件系统是一个特殊的、由软件创建的文件系统,内核使用此文件系统可以向外部输出信息。在进行Linux和实时应用程序开发时,用户经常会使用内核变量,获取内核信息。文章分析了利用/proe文件系统获取内核信息的方法以及/proc文件系统在实时系统RTAI中的应用。
关键词:/proc文件系统;Linux;内核信息;RTAI
0 引言
/proc文件系统是Linux向用户提供系统内部参数的一个伪文件系统,它包含许多文件,可用于监视、调试或更改运行中内核的参数。对于内核信息的获取,一般仅使用其中的一部分文件。对这些文件执行的读操作,会被,proc文件系统定位到一个内核函数。本文通过对Linux系统内核现有通信机制的研究,介绍利用/proc文件系统输出内核信息的方法以及在实时系统RTAI中利用/proc文件系统控制实时任务的方法。
1/proc文件系统概述
Linux系统中的/proc文件系统是进程文件系统和内核文件系统组成的复合体,它将内核数据对象化为文件形式进行存取,通过文件系统接口实现,用于输出系统运行状态;以文件系统的形式为操作系统和应用进程之间的通信提供了一个界面,使应用程序能够安全、方便地获得系统当前的运行状态和内核的数据信息,并且可以修改某些系统的配置信息。
/proc文件系统主要分成两个部分:一部分是和进程相关的目录部分,在实现时将这部分称为base部分;另一部分是把/proc根下面的其他目录和文件,又分为两部分,一是/proc下的子目录,另一是/proc下的文件,如cpuinfo等。这三部分是通过不同的初始化函数完成初始化的。/proe目录下的每一个文件都与内核函数紧密相连,当文件被读取时内核函数生成文件的“内容”。/proe文件系统是动态的,可以在其中创建自己的/proc文件,获取所需内核信息。
Linux系统启动后,创建了由proc_dir_entry{}结构形成的文件系统树。在/proc文件系统中,代表各个文件节点的结构是proc_dir_entry{}结构,它描述一个/proc文件系统中目录结构节点。每个节点在整个目录结构中或是一个文件,或是一个目录,通过指针将大量的proc_dir_entry{}节点组成树状结构。和一般文件系统不同的是,它修改的并不是硬盘上的文件,而是在系统启动之后内存中由内核动态创建的文件。因此在系统关闭之后,/proc文件系统中的文件就不存在了。proc_dir_entry{}结构提供了对文件内容的读写所需要的函数指针,即管理着从操作系统的用户空间到核心空间对文件读写的驱动。每当从用户空间读取,proc目录下的文件时,内核根据读取的文件映射到对应的驱动函数,动态地获取内核数据。除了提供读的功能,/proe文件系统的部分文件还提供写的功能,在Linux2.4系统中主要是针对/proc/sys目录而做的。对/proc文件系统的写操作并不是写硬盘等硬件设备,而是动态更改内核中的数据,达到监视内核运行状态的目的。
2 /proc目录下文件节点的读写操作流程(以读为例)
读/proc文件系统中的文件,是通过层层调用,最终操作内核变量的读函数,将内核信息收集到字符型指针所指向的缓冲区中。也就是说,用户进程通过系统调用read(读文件)进入系统态,执行sys_read内核函数;
asmtinkage ssize_t sys_read(unsigned int fd,char*buf,
size_t count)
{…
ssize_t ret;
struct file*file;
file=get(fd);
ret=file->read(file,buf,count,&file->f_pos);
}
可见,文件读操作进入到核心态后,执行的是file结构中函数跳转表f_op中的read指针所指的函数。
file->f_op函数跳转表值来源于inode结构中的i_fop;
在访问文件的过程中,dentry_open函数将文件索引节点中函数跳转表i_fop值赋给file结构中的f_op。
struct file *dentry_open(struct dentry*dentry,
struct vfsmount *mnt,int flags)
{struct file *f;
struct inode *inode;
inode=dentry->d_inode;
f->f_op=fops_get(jnode->ffop);
return f;
}
inode结构中的i_fop值来源于proc_dir_entry中的proc_fops;
在创建/proc文件系统中文件的inode节点时,函数proc_get_inode将文件对应的proc_dir_entry中的proc_fops值赋给inode结构中的i_fop。
struct inode *proc_get_inode(struct super_block *sb,int ino,
struct proc_dir_entry*de)
{struct inode *inode;
inode=iget(sb,ino):
inode->u.generic_ip=(void*)de;
if(de->proc_fops)
inode->i fop=de->proc_fops;
return inode;
}
proc_dir_entry结构中的proc_fops是proc_file_operations;
在文件./linux/fs/proc/generic.c,proc_file_operations赋值为:
struct file_operations proc_file_operations=
{Ilseek:proc_file_lseek,
read::proc_file_read,
write:proc file write,
};
在创建文件的proc_dir_entry结构时,函数proc_register将proc_file_operations值赋给proc_dir_entry结构中的proc_fops。
static int proc_register(struct proc_dir_entry*dir,
struct proc_dir_entry*dp)
{…
if(dp->proc_fops==NULL)
dp->proc_fops=&proc__file_operations; }
proc_file_read函数执行的是proc_dir_entry结构中函数指针所指的函数。
static ssize_t proc_file_read(struct file*file,
const char*buffer,size_t count,Ioff_t *ppos)
{struct inode *inode=file->f_dentry->d_inode;
struct proc dir entry *dp;
dp=(struct proc_dir_entry*)->inode->u.generic_ip;
if(!dp->write_proc)
return EIO;
return dp->read_proc(file,buffer,count,dp->data);
}
proc_dir_entry结构中proc_read函数指针赋值是在创建文件的proc_dir_entry结构时进行的。
文件./include/linux/proc_fs.h
static inline 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)
{struct proc_dir_entry*res=create_proc_entry(name,mode,base);
if(res)
{res->read_proc=read_proc;
res->data=data;
}
return res;
}
函数create_proc_read_entry参数表中的参数read_proc是对应内核变量的读函数。
3 使用/proc文件系统获取内核信息的方法
3.1修改/proc文件系统中原有的文件
/proc文件系统中,用户进程是直接打开/proc文件来实现自动调用相应内核函数的。当查看/proc目录下的文件时,这些文件会显示出Linux系统内部的一些信息。其实对它们进行读操作是调用了操作系统内核中的一些对应的函数,这些函数及时将所取的信息反馈给用户或应用程序。因此在内核中改造读文件函数,在读/proc目录下的对应文件时,可以得到所需的数据。
例如对于/proc/loadavg文件来说,读文件指针指向loadavg_read_proc()函数。该函数的作用就是当用户读/proc/loadavg文件时,将数组avenrum[]中积累的,即在过去1分钟、5分钟以及15分钟的系统平均CPU负荷等统计信息通过spfintf()“打印”到缓冲区页面中。我们可以把代码加到诸如此类的文件中,由这些文件帮我们把所需的数据输出来。修改完代码后,重新编译内核即可。在这里,内核到用户的信息通过/proc文件系统来传递,但没有在/proc目录下创建新文件,而是借用Linux内核代码,从而得到所需的数据。这样做的好处是减少对内核的修改量,缺点是需重新编译内核。
3.2在/proc目录下新建文件
3.2.1创建新/proc文件,利用read_proc操作
新创建的/proc文件要想被访问到,首先要在,proc文件系统中创建一个入口,这可以通过函数creat_proc_read_entry()来实现。
从上面分析可以看出,/proc目录下的文件只提供了读写操作,即只实现了proc dir entry{}结构中对应的read_proc操作,而没有提供该结构中的文件操作函数集,此时使用的是缺省的文件操作函数集。文件操作函数集struct file_operationsproc_file_operations中read对应函数proc_file_read(),该函数的实现依赖于proc_dir_entry{}结构中的read_proc函数。因此若注册自己的/proc文件,在没有设置proc_fops文件操作函数集时,需实现read_proc(),否则缺省的proc file read()函数将做不了任何工作。所以,当创建只读的/proc文件时,程序中必须实现proc_dir_entry{}结构中的read_proc()函数。
read_proc()函数的特点是:/proc文件系统首先通过sprintf()函数将要获取的参数转换成字符串写入内核中临时分配的一个空页,然后再将该页用copy_to_user()送到用户空间,最后释放该页。实际上,大部分内核参数都是整型或长整型的。/proc文件系统将内核数据转换成字符串是为了方便用户阅读。
3.2.2创建新/proc文件,使用文件操作函数集
创建/proc文件的方法与创建字符设备文件的方法非常相似。首先创建一个proc_dir_entry{}结构,该结构包含了/proc文件需要的所有信息。然后通过create_proc_entry()函数向内核注册这个结构,而remove_proc_entry()函数将取消它的注册。proc_dir_entry{)结构中描述了/proc文件的全部信息。这里最重要的是该结构中的proc_fops项,这是一个指向file_operations结构的指针,可以把对/proc文件的读写等操作放在这个结构中,用以实现对/proc文件的读写等功能。代码如下:
int ssize_t procread(struct file*file,char*buf,aize_t len,loff_t*offset); static ssize_t procwrite(struct file*file,char*buf,aize_t Ien,
loff_t *offset);
static struct file_operations procfop=
{read:procread,
write:procwrite,
};
static int init_routine(void)
{struct proc_dir_entry *entry;
entry=create_proc_entry("test",S_IRUSRIS_IwUSR,NULL);
entry->proc_fops=&procfop;
rutern;
}
4 /proc文件系统在RTAI中的应用
RTAI是实时应用接口Real-Time Application Interface的缩写,提供了一个基于Linux的实时方案。RTAI利用Linux提 供的可加载内核模块机制来提供服务,完成实时功能。模块经过加载,成为内核的一部分,但是并没有被编译到内核里面去。它们被分别编译并连接成一组目标文件,这些文件能被插入到正在运行的内核,或从正在运行的内核中移走。RTAI提供的主要模块有RTAI主模块、RTAI调度器模块、RTAI命名管道fifo模块及RTAI共享内存模块等。
4.1利用/proc文件系统用户获取实时内核的信息
RTAI主模块是RTAI的核心模块,它是其他模块的基础。要加载其他模块,首先必须加载这个模块,没有它,任何实时任务都不能完成。当这个模块被加载后,在/proc目录下会自动生成一个rtai子目录,在其他模块如rtai_sched、rtai_fifos等模块加载后,rtai子目录下又有rtai、scheduler、fifos等文件。可以通过命令cat读取实时系统RTAI的有关信息,主要由…/rtai-x.x.x/arch/i386/rtai.c文件中的函数实现其功能:
struct proc dir entry *rtai_proc_root=NULL;
static int rtai_proc_register(void)
{…
rtai_proc_root=create_proc_entry(“rtai”,S_IFDIR,0);
ent=create_proc_entry(“rtai”,S_IFREGIS_IRUGOIS_IwUSR,
rtai_proc_root);
ent->read proc=rtai_read_rtai;
}
4.2 利用/proc文件系统控制实时任务
利用/proc文件系统write_proc函数可以修改内核数据,控制实时任务。首先使用create_proc_entry函数在/proc文件系统中创建一个虚拟文件,这个函数可以接收一个文件名、一组权限和这个文件在,proc文件系统中出现的位置。create_proc_entry的返回值是一个proc_dir_entry指针(或者为NULL,说明在create时发生了错误)。然后就可以使用这个返回的指针来配置这个虚拟文件的其他参数,例如在对该虚拟文件执行写操作时应该调用的write_proc函数。通过/proc文件的write_proc函数,修改内核中可以控制实时任务执行的变量,从而达到控制实时任务的目的。
5 结束语
Linux、RTAI都是开放源代码的,我们可以采取任何能想到的方法对它进行分析和改进,这是开放源代码提供给我们研究和学习的便利。但也应注意避免向内核中带入一些难以发现的错误。本文所提出的利用/proc文件实现实时系统监控和实时任务控制的方法,简单易行,用于开发应用程序非常方便。
注:本文中所涉及到的图表、注解、公式等内容请以PDF格式阅读原文。
关键词:/proc文件系统;Linux;内核信息;RTAI
0 引言
/proc文件系统是Linux向用户提供系统内部参数的一个伪文件系统,它包含许多文件,可用于监视、调试或更改运行中内核的参数。对于内核信息的获取,一般仅使用其中的一部分文件。对这些文件执行的读操作,会被,proc文件系统定位到一个内核函数。本文通过对Linux系统内核现有通信机制的研究,介绍利用/proc文件系统输出内核信息的方法以及在实时系统RTAI中利用/proc文件系统控制实时任务的方法。
1/proc文件系统概述
Linux系统中的/proc文件系统是进程文件系统和内核文件系统组成的复合体,它将内核数据对象化为文件形式进行存取,通过文件系统接口实现,用于输出系统运行状态;以文件系统的形式为操作系统和应用进程之间的通信提供了一个界面,使应用程序能够安全、方便地获得系统当前的运行状态和内核的数据信息,并且可以修改某些系统的配置信息。
/proc文件系统主要分成两个部分:一部分是和进程相关的目录部分,在实现时将这部分称为base部分;另一部分是把/proc根下面的其他目录和文件,又分为两部分,一是/proc下的子目录,另一是/proc下的文件,如cpuinfo等。这三部分是通过不同的初始化函数完成初始化的。/proe目录下的每一个文件都与内核函数紧密相连,当文件被读取时内核函数生成文件的“内容”。/proe文件系统是动态的,可以在其中创建自己的/proc文件,获取所需内核信息。
Linux系统启动后,创建了由proc_dir_entry{}结构形成的文件系统树。在/proc文件系统中,代表各个文件节点的结构是proc_dir_entry{}结构,它描述一个/proc文件系统中目录结构节点。每个节点在整个目录结构中或是一个文件,或是一个目录,通过指针将大量的proc_dir_entry{}节点组成树状结构。和一般文件系统不同的是,它修改的并不是硬盘上的文件,而是在系统启动之后内存中由内核动态创建的文件。因此在系统关闭之后,/proc文件系统中的文件就不存在了。proc_dir_entry{}结构提供了对文件内容的读写所需要的函数指针,即管理着从操作系统的用户空间到核心空间对文件读写的驱动。每当从用户空间读取,proc目录下的文件时,内核根据读取的文件映射到对应的驱动函数,动态地获取内核数据。除了提供读的功能,/proe文件系统的部分文件还提供写的功能,在Linux2.4系统中主要是针对/proc/sys目录而做的。对/proc文件系统的写操作并不是写硬盘等硬件设备,而是动态更改内核中的数据,达到监视内核运行状态的目的。
2 /proc目录下文件节点的读写操作流程(以读为例)
读/proc文件系统中的文件,是通过层层调用,最终操作内核变量的读函数,将内核信息收集到字符型指针所指向的缓冲区中。也就是说,用户进程通过系统调用read(读文件)进入系统态,执行sys_read内核函数;
asmtinkage ssize_t sys_read(unsigned int fd,char*buf,
size_t count)
{…
ssize_t ret;
struct file*file;
file=get(fd);
ret=file->read(file,buf,count,&file->f_pos);
}
可见,文件读操作进入到核心态后,执行的是file结构中函数跳转表f_op中的read指针所指的函数。
file->f_op函数跳转表值来源于inode结构中的i_fop;
在访问文件的过程中,dentry_open函数将文件索引节点中函数跳转表i_fop值赋给file结构中的f_op。
struct file *dentry_open(struct dentry*dentry,
struct vfsmount *mnt,int flags)
{struct file *f;
struct inode *inode;
inode=dentry->d_inode;
f->f_op=fops_get(jnode->ffop);
return f;
}
inode结构中的i_fop值来源于proc_dir_entry中的proc_fops;
在创建/proc文件系统中文件的inode节点时,函数proc_get_inode将文件对应的proc_dir_entry中的proc_fops值赋给inode结构中的i_fop。
struct inode *proc_get_inode(struct super_block *sb,int ino,
struct proc_dir_entry*de)
{struct inode *inode;
inode=iget(sb,ino):
inode->u.generic_ip=(void*)de;
if(de->proc_fops)
inode->i fop=de->proc_fops;
return inode;
}
proc_dir_entry结构中的proc_fops是proc_file_operations;
在文件./linux/fs/proc/generic.c,proc_file_operations赋值为:
struct file_operations proc_file_operations=
{Ilseek:proc_file_lseek,
read::proc_file_read,
write:proc file write,
};
在创建文件的proc_dir_entry结构时,函数proc_register将proc_file_operations值赋给proc_dir_entry结构中的proc_fops。
static int proc_register(struct proc_dir_entry*dir,
struct proc_dir_entry*dp)
{…
if(dp->proc_fops==NULL)
dp->proc_fops=&proc__file_operations; }
proc_file_read函数执行的是proc_dir_entry结构中函数指针所指的函数。
static ssize_t proc_file_read(struct file*file,
const char*buffer,size_t count,Ioff_t *ppos)
{struct inode *inode=file->f_dentry->d_inode;
struct proc dir entry *dp;
dp=(struct proc_dir_entry*)->inode->u.generic_ip;
if(!dp->write_proc)
return EIO;
return dp->read_proc(file,buffer,count,dp->data);
}
proc_dir_entry结构中proc_read函数指针赋值是在创建文件的proc_dir_entry结构时进行的。
文件./include/linux/proc_fs.h
static inline 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)
{struct proc_dir_entry*res=create_proc_entry(name,mode,base);
if(res)
{res->read_proc=read_proc;
res->data=data;
}
return res;
}
函数create_proc_read_entry参数表中的参数read_proc是对应内核变量的读函数。
3 使用/proc文件系统获取内核信息的方法
3.1修改/proc文件系统中原有的文件
/proc文件系统中,用户进程是直接打开/proc文件来实现自动调用相应内核函数的。当查看/proc目录下的文件时,这些文件会显示出Linux系统内部的一些信息。其实对它们进行读操作是调用了操作系统内核中的一些对应的函数,这些函数及时将所取的信息反馈给用户或应用程序。因此在内核中改造读文件函数,在读/proc目录下的对应文件时,可以得到所需的数据。
例如对于/proc/loadavg文件来说,读文件指针指向loadavg_read_proc()函数。该函数的作用就是当用户读/proc/loadavg文件时,将数组avenrum[]中积累的,即在过去1分钟、5分钟以及15分钟的系统平均CPU负荷等统计信息通过spfintf()“打印”到缓冲区页面中。我们可以把代码加到诸如此类的文件中,由这些文件帮我们把所需的数据输出来。修改完代码后,重新编译内核即可。在这里,内核到用户的信息通过/proc文件系统来传递,但没有在/proc目录下创建新文件,而是借用Linux内核代码,从而得到所需的数据。这样做的好处是减少对内核的修改量,缺点是需重新编译内核。
3.2在/proc目录下新建文件
3.2.1创建新/proc文件,利用read_proc操作
新创建的/proc文件要想被访问到,首先要在,proc文件系统中创建一个入口,这可以通过函数creat_proc_read_entry()来实现。
从上面分析可以看出,/proc目录下的文件只提供了读写操作,即只实现了proc dir entry{}结构中对应的read_proc操作,而没有提供该结构中的文件操作函数集,此时使用的是缺省的文件操作函数集。文件操作函数集struct file_operationsproc_file_operations中read对应函数proc_file_read(),该函数的实现依赖于proc_dir_entry{}结构中的read_proc函数。因此若注册自己的/proc文件,在没有设置proc_fops文件操作函数集时,需实现read_proc(),否则缺省的proc file read()函数将做不了任何工作。所以,当创建只读的/proc文件时,程序中必须实现proc_dir_entry{}结构中的read_proc()函数。
read_proc()函数的特点是:/proc文件系统首先通过sprintf()函数将要获取的参数转换成字符串写入内核中临时分配的一个空页,然后再将该页用copy_to_user()送到用户空间,最后释放该页。实际上,大部分内核参数都是整型或长整型的。/proc文件系统将内核数据转换成字符串是为了方便用户阅读。
3.2.2创建新/proc文件,使用文件操作函数集
创建/proc文件的方法与创建字符设备文件的方法非常相似。首先创建一个proc_dir_entry{}结构,该结构包含了/proc文件需要的所有信息。然后通过create_proc_entry()函数向内核注册这个结构,而remove_proc_entry()函数将取消它的注册。proc_dir_entry{)结构中描述了/proc文件的全部信息。这里最重要的是该结构中的proc_fops项,这是一个指向file_operations结构的指针,可以把对/proc文件的读写等操作放在这个结构中,用以实现对/proc文件的读写等功能。代码如下:
int ssize_t procread(struct file*file,char*buf,aize_t len,loff_t*offset); static ssize_t procwrite(struct file*file,char*buf,aize_t Ien,
loff_t *offset);
static struct file_operations procfop=
{read:procread,
write:procwrite,
};
static int init_routine(void)
{struct proc_dir_entry *entry;
entry=create_proc_entry("test",S_IRUSRIS_IwUSR,NULL);
entry->proc_fops=&procfop;
rutern;
}
4 /proc文件系统在RTAI中的应用
RTAI是实时应用接口Real-Time Application Interface的缩写,提供了一个基于Linux的实时方案。RTAI利用Linux提 供的可加载内核模块机制来提供服务,完成实时功能。模块经过加载,成为内核的一部分,但是并没有被编译到内核里面去。它们被分别编译并连接成一组目标文件,这些文件能被插入到正在运行的内核,或从正在运行的内核中移走。RTAI提供的主要模块有RTAI主模块、RTAI调度器模块、RTAI命名管道fifo模块及RTAI共享内存模块等。
4.1利用/proc文件系统用户获取实时内核的信息
RTAI主模块是RTAI的核心模块,它是其他模块的基础。要加载其他模块,首先必须加载这个模块,没有它,任何实时任务都不能完成。当这个模块被加载后,在/proc目录下会自动生成一个rtai子目录,在其他模块如rtai_sched、rtai_fifos等模块加载后,rtai子目录下又有rtai、scheduler、fifos等文件。可以通过命令cat读取实时系统RTAI的有关信息,主要由…/rtai-x.x.x/arch/i386/rtai.c文件中的函数实现其功能:
struct proc dir entry *rtai_proc_root=NULL;
static int rtai_proc_register(void)
{…
rtai_proc_root=create_proc_entry(“rtai”,S_IFDIR,0);
ent=create_proc_entry(“rtai”,S_IFREGIS_IRUGOIS_IwUSR,
rtai_proc_root);
ent->read proc=rtai_read_rtai;
}
4.2 利用/proc文件系统控制实时任务
利用/proc文件系统write_proc函数可以修改内核数据,控制实时任务。首先使用create_proc_entry函数在/proc文件系统中创建一个虚拟文件,这个函数可以接收一个文件名、一组权限和这个文件在,proc文件系统中出现的位置。create_proc_entry的返回值是一个proc_dir_entry指针(或者为NULL,说明在create时发生了错误)。然后就可以使用这个返回的指针来配置这个虚拟文件的其他参数,例如在对该虚拟文件执行写操作时应该调用的write_proc函数。通过/proc文件的write_proc函数,修改内核中可以控制实时任务执行的变量,从而达到控制实时任务的目的。
5 结束语
Linux、RTAI都是开放源代码的,我们可以采取任何能想到的方法对它进行分析和改进,这是开放源代码提供给我们研究和学习的便利。但也应注意避免向内核中带入一些难以发现的错误。本文所提出的利用/proc文件实现实时系统监控和实时任务控制的方法,简单易行,用于开发应用程序非常方便。
注:本文中所涉及到的图表、注解、公式等内容请以PDF格式阅读原文。