今天我們介紹另一種用戶內(nèi)核空間通信的方法:proc文件系統(tǒng)。
proc文件系統(tǒng)作為linux提供的一種虛擬文件系統(tǒng)并不占用實際外圍存儲空間,它僅存在于內(nèi)存中,系統(tǒng)斷電即消失。proc文件系統(tǒng)最開始的設(shè)計主要是為滿足內(nèi)核向用戶態(tài)進程報告其狀態(tài)而設(shè)計,并沒有為輸入做規(guī)定和說明。隨著發(fā)展,現(xiàn)在的proc文件系統(tǒng)已經(jīng)演變成一個“用戶-內(nèi)核”空間半雙工的通信方式了(雖然目前已經(jīng)開始有點混亂了,但某些早期開發(fā)的軟件代碼中還在繼續(xù)使用這個文件系統(tǒng))。用戶不但可以從proc文件系統(tǒng)中讀取內(nèi)核的相關(guān)狀態(tài)信息,還可以向其中寫入數(shù)據(jù)以改變內(nèi)核的某些行為狀態(tài)。
/proc目錄里主要存放由內(nèi)核控制的狀態(tài)信息,一般會動態(tài)改變。如果你對/proc目錄執(zhí)行'ls -l' 命令可以看到大部分文件都是 0 字節(jié),原因是 procfs和其他常規(guī)的文件系統(tǒng)一樣把自己注冊到虛擬文件系統(tǒng)層 (VFS)。直到當(dāng)VFS調(diào)用它,請求文件、目錄的i-node的時候,procfs才根據(jù)內(nèi)核中的信息動態(tài)地建立相應(yīng)的文件和目錄。我的系統(tǒng)中proc目錄的結(jié)構(gòu)如下,每個人可能不一樣,這取決于你編譯內(nèi)核時所打開的選項:
這些文件的解釋和意義如下:
| cmdline:系統(tǒng)啟動時輸入給內(nèi)核命令行參數(shù)? cpuinfo:CPU的硬件信息 (型號, 家族, 緩存大小等)?? devices:主設(shè)備號及設(shè)備組的列表,當(dāng)前加載的各種設(shè)備(塊設(shè)備/字符設(shè)備)? dma:使用的DMA通道? filesystems:當(dāng)前內(nèi)核支持的文件系統(tǒng),當(dāng)沒有給 mount(1) 指明哪個文件系統(tǒng)的時候, mount(1) 就依靠該文件遍歷不同的文件系統(tǒng) interrupts :中斷的使用及觸發(fā)次數(shù),調(diào)試中斷時很有用? ioports I/O:當(dāng)前在用的已注冊 I/O 端口范圍? kcore:該偽文件以 core 文件格式給出了系統(tǒng)的物理內(nèi)存映象(比較有用),可以用 GDB 查探當(dāng)前內(nèi)核的任意數(shù)據(jù)結(jié)構(gòu)。該文件的總長度是物理內(nèi)存 (RAM) 的大小再加上 4KB kmsg:可以用該文件取代系統(tǒng)調(diào)用 syslog(2) 來記錄內(nèi)核日志信息,對應(yīng)dmesg命令 kallsym:內(nèi)核符號表,該文件保存了內(nèi)核輸出的符號定義,?modules(X) 使用該文件動態(tài)地連接和捆綁可裝載的模塊 loadavg:負(fù)載均衡,平均負(fù)載數(shù)給出了在過去的 1、 5,、15 分鐘里在運行隊列里的任務(wù)數(shù)、總作業(yè)數(shù)以及正在運行的作業(yè)總數(shù)。 locks:內(nèi)核鎖?。 meminfo物理內(nèi)存、交換空間等的信息,系統(tǒng)內(nèi)存占用情況,對應(yīng)df命令。 misc:雜項?。 modules:已經(jīng)加載的模塊列表,對應(yīng)lsmod命令?。 mounts:已加載的文件系統(tǒng)的列表,對應(yīng)mount命令,無參數(shù)。 partitions:系統(tǒng)識別的分區(qū)表?。 slabinfo:sla池信息。 stat:全面統(tǒng)計狀態(tài)表,CPU內(nèi)存的利用率等都是從這里提取數(shù)據(jù)。對應(yīng)ps命令。 swaps:對換空間的利用情況。? version:指明了當(dāng)前正在運行的內(nèi)核版本。 |
/proc目錄下常見的就是上述幾個文件和目錄。需要格外注意的就是三個黃色的目錄:net、scsi和sys。sys目錄是可寫的,可以通過它來訪問或修改內(nèi)核的某些控制參數(shù),sysctl命令接口會用到/proc/sys目錄,這里我就不展開了,在介紹sysctl章節(jié)時詳細(xì)討論。而net和scsi則依賴于內(nèi)核配置,協(xié)議棧和我們前面介紹過的Netfitler都在/proc/net目錄下建立了各自的某些控制信息所對應(yīng)的文件。如果系統(tǒng)不支持scsi,則 scsi目錄就不存在。?
接下來我們就來實踐一下Linux所提供給我們的proc機制來完成用戶和內(nèi)核空間的通信。 如果要在/proc目錄下創(chuàng)建一個目錄,一般用:
struct proc_dir_entry *proc_mkdir(const char *name,?
struct proc_dir_entry *parent)
其中,name為待創(chuàng)建的目錄名;parent為待創(chuàng)建目錄的上一級目錄,如果為NULL則表示默認(rèn)創(chuàng)建到/proc目錄下。而如果要在/proc目錄下創(chuàng)建一個文件,一般用:
struct proc_dir_entry *create_proc_entry(const char *name,
mode_t mode,
struct proc_dir_entry *parent)
name和parent的意義同proc_mkdir,mode指明了待創(chuàng)建文件的權(quán)限,即是否可讀可寫,或哪些用戶可讀,哪些用戶可寫。這兩個函數(shù)均返回一個struct proc_dir_entry{}結(jié)構(gòu)體的實例,該結(jié)構(gòu)體定義在linux-2.6.21\include\linux\proc_fs.h文件中,如下:
點擊(此處)折疊或打開
struct proc_dir_entry {
????… …
????const struct inode_operations *proc_iops;
????const struct file_operations *proc_fops;
????… …
????read_proc_t *read_proc;
????write_proc_t *write_proc;
????… …
};
比較重要的兩個成員函數(shù)是read_proc和write_proc,分別指向proc目錄下待創(chuàng)建的文件被“讀”和“寫”的時候的回調(diào)處理函數(shù)(重要)。 讀函數(shù)的原型:
int read_proc(char *page, char **start, off_t off,int count, int *eof, void *data);
當(dāng)我們通過諸如“cat”之類的命令來讀取/proc目錄下的文件內(nèi)容時,內(nèi)核會分配給proc讀取程序一頁大小的內(nèi)存空間,即PAGE_SIZE大小,proc的驅(qū)動程序會自動將這塊內(nèi)存中的數(shù)據(jù)復(fù)制到用戶空間,最終待訪問的proc文件的read_proc回調(diào)函數(shù)會被調(diào)用,其中:
page:將需要傳遞給用戶的數(shù)據(jù)復(fù)制到這個緩沖區(qū)地址里。這個緩沖區(qū)是內(nèi)核空間的,不需要用戶調(diào)用copy_to_user()之類的函數(shù),系統(tǒng)會自動將page中的數(shù)據(jù)復(fù)制給用戶。
start:在page所指向的緩沖區(qū)中需要復(fù)制給用戶數(shù)據(jù)的起始地址。一般不使用。
off:用戶打算讀取文件時的偏移地址,即從這個地址開始讀取。類似用戶空間lseek移動文件指針的作用。
count:用戶要求讀取的字節(jié)數(shù)。
eof:如果讀到文件結(jié)尾,當(dāng)驅(qū)動程序的數(shù)據(jù)發(fā)送完畢時將這個值置為1,發(fā)送給用戶,我們可以通過該值判斷是否讀取到文件末尾。
data:私有數(shù)據(jù)指針,一般不用。
寫函數(shù)的原型:
int write_proc(struct file *file, const char __user *buffer,unsigned long count, void *data);
file:內(nèi)核中一個打開的文件結(jié)構(gòu),通常忽略。
buffer:用戶空間傳遞過來的數(shù)據(jù)指針,用戶待寫入文件的數(shù)據(jù)就存儲在這個值所指向的地址區(qū)域中。而這家伙實際上是一個用戶空間地址,內(nèi)核中不能直接拿來用,需要調(diào)用諸如copy_from_user()之類的函數(shù)來講用戶空間的數(shù)據(jù)復(fù)制到內(nèi)核空間來。
count:用戶待寫入文件的字節(jié)數(shù)。
data:一般不用。
刪除proc文件比較簡單,調(diào)用:
void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
即可,參數(shù)同上,其中name是要刪除的proc文件的名稱。看個proc文件的應(yīng)用示例:
點擊(此處)折疊或打開
/* myproctest.c*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/stat.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
MODULE_AUTHOR("Koorey Wung");
MODULE_DESCRIPTION("procfs test module.");
MODULE_LICENSE("GPL");
#define PROCNAME "mytest"
static struct proc_dir_entry * myproc_entry = NULL;
static char msg[512]={0};
static int my_read(char *page, char **start, off_t off,int count, int *eof, void *data)
{
????????int len = strlen(msg);
????????if(off >= len)
????????????????return 0;
????????if(count > len-off)
????????????????count = len-off;
????????memcpy(page+off,msg+off,count);
????????return off+count;
}
static int my_write(struct file *file, const char __user *buffer,unsigned long count, void *data)
{
????????unsigned long len = sizeof(msg);
????????if(count >= len)
????????????????count = len -1;
????????if(copy_from_user(msg,(void*)buffer,count))
????????????????return -EFAULT;
????????msg[count]='\0';
????????return count;
}
static int __init procTest_init(void)
{
????????myproc_entry = create_proc_entry(PROCNAME,0666,NULL);
????????if(!myproc_entry){
????????????????printk(KERN_ERR "can't create /proc/mytest \n");
????????????????return -EFAULT;
????????}
????????myproc_entry->read_proc = my_read;
????????myproc_entry->write_proc = my_write;
????????return 0;
}
static void __exit procTest_exit(void)
{
????remove_proc_entry(PROCNAME,NULL);
}
module_init(procTest_init);
module_exit(procTest_exit);編程生成myproctest.ko模塊后,測試結(jié)果如下:
在上面的例子中我們看到read_proc的回調(diào)函數(shù)中我們確實沒有調(diào)用copy_to_user()函數(shù),結(jié)果還是正確誤區(qū)地返回給用戶。procfs既然屬于一種特殊的文件系統(tǒng),那我們我們是否可以像操作普通文件那樣,對其進行擴充呢?答案是肯定,我們對上面的例子稍加改造,使用內(nèi)核提供的文件系統(tǒng)的機制來實現(xiàn)對proc文件的讀寫操作,修改后的代碼如下:
點擊(此處)折疊或打開
/* myproctest.c*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/stat.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
MODULE_AUTHOR("Koorey Wung");
MODULE_DESCRIPTION("procfs test module.");
MODULE_LICENSE("GPL");
#define PROCNAME "mytest"
static struct proc_dir_entry * myproc_entry = NULL;
static char msg[512]={0};
static int my_file_read(struct file * file,char *data,size_t len,loff_t *off)
{
????????if(*off > 0)
????????????????return 0;
????????if(copy_to_user(data,msg,strlen(msg)))
????????????????return -EFAULT;
????????*off += strlen(msg);
????????return strlen(msg);
}
static int my_file_write(struct file *file, const char *data,size_t len,loff_t *off)
{
????????if(copy_from_user(msg,(void*)data,len))
????????????????return -EFAULT;
????????msg[len]='\0';
????????return len;
}
static struct file_operations my_file_test_ops = {
???????.read = my_file_read,
???????.write = my_file_write,
};
static int __init procTest_init(void)
{
????????myproc_entry = create_proc_entry(PROCNAME,0666,NULL);
????????if(!myproc_entry){
????????????????printk(KERN_ERR "can't create /proc/mytest \n");
????????????????return -EFAULT;
????????}
????????myproc_entry->proc_fops = & my_file_test_ops;
????????return 0;
}
static void __exit procTest_exit(void)
{
????remove_proc_entry(PROCNAME,NULL);
}
module_init(procTest_init);
module_exit(procTest_exit);編譯后驗證,結(jié)果依然正確,這是我們站在文件系統(tǒng)的角度來實現(xiàn)proc目錄下的文件的讀寫操作,關(guān)于文件系統(tǒng)這里就不展開了,以后有時間再寫個它的專題。
轉(zhuǎn)載于:https://www.cnblogs.com/masterpanda/p/5700474.html
總結(jié)
以上是生活随笔為你收集整理的用户空间和内核空间通讯之【proc文件系统】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。