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