/proc文件系統是Linux內核的一個虛擬文件系統,說它虛擬,是因爲和磁盤分區上的文件系統不同,它只有在內核啓動以後,工作起來的時候纔會被動態創建。
/proc下面的內容會隨着內核的配置和工作的狀態在變化,比如/proc/meminfo這個文件,裏面的內容是內存信息,不同的機器自然不同;還有就是/proc目錄下面用數字命名的子目錄,每個子目錄的名字也就是數字,對應當前系統正在運行的進程,而子目錄裏面的內容就是對應進程的信息。
/proc裏面還有其他許多信息,這裏就不一一描述了,有興趣可以谷歌一下。
Linux內核經過多年發展,/proc目錄下的林林總總,已經是非常多了,雖然已經開發出了新的/sys文件系統替代/proc,但是,許多傳統的程序仍然使用/proc文件系統作爲內核與用戶程序的接口。
如何讓內核更高效,那是內核開發人員的事情。作爲一個使用內核的開發人員,掌握/proc文件系統的使用方法也是很有用的,和/sys文件系統組織嚴密的結構相比,用/proc有時候還是很方便的。
不多說廢話了,先入個門,介紹/proc的最基本操作。
這一集我們就介紹操作/proc文件系統的三個最基本函數:
代碼片段1 定義在內核代碼的<linux/proc_fs.h>
- struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,
- struct proc_dir_entry *parent);
- struct proc_dir_entry *proc_mkdir(const char *name,
- struct proc_dir_entry *parent);
- void remove_proc_entry(const char *name, struct proc_dir_entry *parent);
先插一句,本例所有代碼使用內核版本2.6.38.2,系統是ubuntu 11.04 amd64,要注意哦。
這三個函數,從函數名稱就可以理解出來意思,第一個create_proc_entry()函數用做創建一個節點,也就是創建一個文件;
第二個proc_mkdir()函數建立一個目錄;
第三個remove_proc_entry()函數是刪除一個節點,實際上,不僅能刪除節點,也可以刪除proc_mkdir()函數創建的目錄。
需要注意的是,proc相關的函數,操作的目標都是在/proc目錄下。好,繼續看函數參數:
create_proc_entry()函數:
name 是節點的名稱,一個字符串;
mode 訪問權限,可以使用八進制表示,chmod命令使用的權限一樣;
parent 父目錄,注意是一個struct proc_dir_entry結構,如果寫NULL表示/proc目錄。
proc_mkdir()函數:
name 子目錄名稱,一個字符串;
parent 父目錄,和上面解釋一樣。
remove_proc_entry()函數:
參數同上面一樣。
函數介紹完畢。有這三個函數就可以開工了。廢話少說,貼代碼:
代碼片段2 自己寫的小模塊,演示如何建立刪除/proc節點
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/version.h>
- #include <linux/proc_fs.h>
- MODULE_LICENSE("Dual BSD/GPL");
- MODULE_AUTHOR("<[email protected]>");
- #define USER_ROOT_DIR "pt"
- #define USER_ENTRY1 "pt_entry1"
- // user defined directory
- static struct proc_dir_entry *pt_root;
- static struct proc_dir_entry *pt_entry1;
- static int proc_test_init(void)
- {
- // Create user root dir under /proc
- pt_root = proc_mkdir(USER_ROOT_DIR, NULL);
- if (NULL==pt_root)
- {
- printk(KERN_ALERT "Create dir /proc/%s error!\n",
- USER_ROOT_DIR);
- return -1;
- }
- printk(KERN_INFO "Create dir /proc/%s\n", USER_ROOT_DIR);
- // Create a test entry under USER_ROOT_DIR
- pt_entry1 = create_proc_entry(USER_ENTRY1,
- 0666, pt_root);
- if (NULL == pt_entry1)
- {
- printk(KERN_ALERT "Create entry %s under /proc/%s error!\n",
- USER_ENTRY1, USER_ROOT_DIR);
- goto err_out;
- }
- printk(KERN_INFO "Create /proc/%s/%s\n",
- USER_ROOT_DIR, USER_ENTRY1);
- pt_entry1->read_proc = NULL;
- pt_entry1->write_proc = NULL;
- return 0;
- err_out:
- remove_proc_entry(USER_ROOT_DIR, pt_root);
- return -1;
- }
- static void proc_test_exit(void)
- {
- // Remove all entries
- remove_proc_entry(USER_ENTRY1, pt_root);
- remove_proc_entry(USER_ROOT_DIR, NULL);
- printk(KERN_INFO "All Proc Entry Removed!\n");
- }
- module_init(proc_test_init);
- module_exit(proc_test_exit);
一個標準的2.6內核模塊寫法。proc_test_init()函數是入口,proc_test_exit()函數是出口。
最開始引入了4個頭文件,其中3個是內核模塊標配的頭文件,第4個是主角,proc_fs.h,前面說了,操作/proc的函數就定義在裏面。
第9行,USER_ROOT_DIR定義了我們要在/proc目錄下建立的子目錄名稱,也是用戶的根目錄;第10行,USER_ENTRY1定義了欲在USER_ROOT_DIR下面建立的節點名稱。
第13、14行分別定義了USER_ROOT_DIR和USER_ENTRY1對應的目錄結構pt_root和pt_entry1。
操作的數據介紹好了,說下流程吧。
proc_test_init()函數:
此函數是加載模塊的時候執行的入口函數,會被內核自動調用。第19行,函數一開始就調用proc_mkdir()函數,在/proc目錄下建立一個子目錄,注意proc_mkdir()函數的第二個參數,NULL表示以/proc作爲根目錄。
子目錄USER_ROOT_DIR建立成功後,緊接着調用create_proc_entry()函數建立一個文件節點,注意函數的第三個參數parent,不是NULL,而是pt_root,表示以USER_ROOT_DIR作爲根目錄。
文件建立成功後,需要設置pt_entry1的讀寫回調函數爲NULL,這樣做的目的是該文件節點讀寫操作都不處理。
proc_test_exit()函數:
用戶調用rmmod卸載內核模塊的時候被內核調用。此函數刪除已經建立的節點,要注意的是remove_proc_entry()函數的parent實參,這裏容易出錯,導致內核報錯。
最後貼一下Makefile:
- obj-m += proc_test1.o
- all:
- make -C /usr/src/linux-headers-2.6.38-8-generic M=`pwd` modules
- clean:
- make -C /usr/src/linux-headers-2.6.38-8-generic M=`pwd` clean
看看執行的效果:
下面是加載模塊後列出文件節點
- root@iscsia:~/proc_test# ll /proc/pt/
- total 0
- -rw-rw-rw- 1 root root 0 2011-07-01 15:04 pt_entry1
下面是讀文件節點/proc/pt/pt_entry1
- root@iscsia:~/proc_test# cat /proc/pt/pt_entry1
- root@iscsia:~/proc_test#
什麼結果都沒有,正常,因爲回調函數都給設置爲NULL了。
下面是寫文件節點/proc/pt/pt_entry1
- root@iscsia:~/proc_test# echo "hello,entry" > /proc/pt/pt_entry1
- -bash: echo: write error: Input/output error
看來寫操作不是很理想,報錯了,pt_entry1不是很合作啊,也是,前面都給人家設置成NULL了,也不能全怪人家了。哈哈。下次繼續介紹如何進行自己的讀寫操作。