摘要:/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格式閱讀原文。