簡介: /proc 文件系統是一個虛擬文件系統,經過它可使用一種新的方法在 Linux® 內核空間和用戶空間之間進行通訊。在 /proc 文件系統中,咱們能夠將對虛擬文件的讀寫做爲與內核中實體進行通訊的一種手段,可是與普通文件不一樣的是,這些虛擬文件的內容都是動態建立的。本文對 /proc 虛擬文件系統進行了介紹,並展現了它的用法。 html
最初開發 /proc 文件系統是爲了提供有關係統中進程的信息。可是因爲這個文件系統很是有用,所以內核中的不少元素也開始使用它來報告信息,或啓用動態運行時配置。 node
/proc 文件系統包含了一些目錄(用做組織信息的方式)和虛擬文件。虛擬文件能夠向用戶呈現內核中的一些信息,也能夠用做一種從用戶空間向內核發送信息的手段。實際上咱們並不會同時須要實現這兩點,可是本文將向您展現如何配置這個文件系統進行輸入和輸出。 linux
儘管像本文這樣短小的一篇文章沒法詳細介紹 /proc 的全部用法,可是它依然對這兩種用法進行了展現,從而可讓咱們體會一下 /proc 是多麼強大。清單 1 是對 /proc 中部分元素進行一次交互查詢的結果。它顯示的是 /proc 文件系統的根目錄中的內容。注意,在左邊是一系列數字編號的文件。每一個實際上都是一個目錄,表示系統中的一個進程。因爲在 GNU/Linux 中建立的第一個進程是 init進程,所以它的 process-id 爲 1。而後對這個目錄執行一個 ls 命令,這會顯示不少文件。每一個文件都提供了有關這個特殊進程的詳細信息。例如,要查看 init 的 command-line 項的內容,只需對 cmdline 文件執行 cat 命令。 程序員
/proc 中另一些有趣的文件有:cpuinfo,它標識了處理器的類型和速度;pci,顯示在 PCI 總線上找到的設備;modules,標識了當前加載到內核中的模塊。 編程
[root@plato]# ls /proc 1 2040 2347 2874 474 fb mdstat sys 104 2061 2356 2930 9 filesystems meminfo sysrq-trigger 113 2073 2375 2933 acpi fs misc sysvipc 1375 21 2409 2934 buddyinfo ide modules tty 1395 2189 2445 2935 bus interrupts mounts uptime 1706 2201 2514 2938 cmdline iomem mtrr version 179 2211 2515 2947 cpuinfo ioports net vmstat 180 2223 2607 3 crypto irq partitions 181 2278 2608 3004 devices kallsyms pci 182 2291 2609 3008 diskstats kcore self 2 2301 263 3056 dma kmsg slabinfo 2015 2311 2805 394 driver loadavg stat 2019 2337 2821 4 execdomains locks swaps [root@plato 1]# ls /proc/1 auxv cwd exe loginuid mem oom_adj root statm task cmdline environ fd maps mounts oom_score stat status wchan [root@plato]# cat /proc/1/cmdline init [5] [root@plato]# |
清單 2 展現了對 /proc 中的一個虛擬文件進行讀寫的過程。這個例子首先檢查內核的 TCP/IP 棧中的 IP 轉發的目前設置,而後再啓用這種功能。 cookie
[root@plato]# cat /proc/sys/net/ipv4/ip_forward 0 [root@plato]# echo "1" > /proc/sys/net/ipv4/ip_forward [root@plato]# cat /proc/sys/net/ipv4/ip_forward 1 [root@plato]# |
另外,咱們還可使用 sysctl 來配置這些內核條目。有關這個問題的更多信息,請參閱 參考資料 一節的內容。 網絡
順便說一下,/proc 文件系統並非 GNU/Linux 系統中的唯一一個虛擬文件系統。在這種系統上,sysfs 是一個與 /proc 相似的文件系統,可是它的組織更好(從 /proc 中學習了不少教訓)。不過 /proc 已經確立了本身的地位,所以即便 sysfs 與 /proc 相比有一些優勢,/proc 也依然會存在。還有一個 debugfs 文件系統,不過(顧名思義)它提供的更可能是調試接口。debugfs 的一個優勢是它將一個值導出給用戶空間很是簡單(實際上這不過是一個調用而已)。 app
可加載內核模塊(LKM)是用來展現 /proc 文件系統的一種簡單方法,這是由於這是一種用來動態地向 Linux 內核添加或刪除代碼的新方法。LKM 也是 Linux 內核中爲設備驅動程序和文件系統使用的一種流行機制。 dom
若是您曾經從新編譯過 Linux 內核,就可能會發如今內核的配置過程當中,有不少設備驅動程序和其餘內核元素都被編譯成了模塊。若是一個驅動程序被直接編譯到了內核中,那麼即便這個驅動程序沒有運行,它的代碼和靜態數據也會佔據一部分空間。可是若是這個驅動程序被編譯成一個模塊,就只有在須要內存並將其加載到內核時纔會真正佔用內存空間。有趣的是,對於 LKM 來講,咱們不會注意到有什麼性能方面的差別,所以這對於建立一個適應於本身環境的內核來講是一種功能強大的手段,這樣能夠根據可用硬件和鏈接的設備來加載對應的模塊。
下面是一個簡單的 LKM,能夠幫助您理解它與在 Linux 內核中看到的標準(非動態可加載的)代碼之間的區別。清單 3 給出了一個最簡單的 LKM。(能夠從本文的 下載 一節中下載這個代碼)。
清單 3 包括了必須的模塊頭(它定義了模塊的 API、類型和宏)。而後使用 MODULE_LICENSE 定義了這個模塊使用的許可證。此處,咱們定義的是 GPL,從而防止會污染到內核。
清單 3 而後又定義了這個模塊的 init 和 cleanup 函數。my_module_init 函數是在加載這個模塊時被調用的,它用來進行一些初始化方面的工做。my_module_cleanup 函數是在卸載這個模塊時被調用的,它用來釋放內存並清除這個模塊的蹤影。注意此處 printk 的用法:這是內核的 printf 函數。KERN_INFO 符號是一個字符串,能夠用來對進入內核迴環緩衝區的信息進行過濾(很是相似於syslog)。
最後,清單 3 使用 module_init 和 module_exit 宏聲明瞭入口函數和出口函數。這樣咱們就能夠按照本身的意願來對這個模塊的 init和 cleanup 函數進行命名了,不過咱們最終要告訴內核維護函數就是這些函數。
#include <linux/module.h> /* Defines the license for this LKM */ MODULE_LICENSE("GPL"); /* Init function called on module entry */ int my_module_init( void ) { printk(KERN_INFO "my_module_init called. Module is now loaded.\n"); return 0; } /* Cleanup function called on module exit */ void my_module_cleanup( void ) { printk(KERN_INFO "my_module_cleanup called. Module is now unloaded.\n"); return; } /* Declare entry and exit functions */ module_init( my_module_init ); module_exit( my_module_cleanup ); |
清單 3 儘管很是簡單,但它倒是一個真正的 LKM。如今讓咱們對其進行編譯並在一個 2.6 版本的內核上進行測試。2.6 版本的內核爲內核模塊的編譯引入了一種新方法,我發現這種方法比原來的方法簡單了不少。對於文件 simple-lkm.c,咱們能夠建立一個 makefile,其唯一內容以下:
obj-m += simple-lkm.o |
要編譯 LKM,請使用 make 命令,如清單 4 所示。
[root@plato]# make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules make: Entering directory `/usr/src/linux-2.6.11' CC [M] /root/projects/misc/module2.6/simple/simple-lkm.o Building modules, stage 2. MODPOST CC /root/projects/misc/module2.6/simple/simple-lkm.mod.o LD [M] /root/projects/misc/module2.6/simple/simple-lkm.ko make: Leaving directory `/usr/src/linux-2.6.11' [root@plato]# |
結果會生成一個 simple-lkm.ko 文件。這個新的命名約定能夠幫助將這些內核對象(LKM)與標準對象區分開來。如今能夠加載或卸載這個模塊了,而後能夠查看它的輸出。要加載這個模塊,請使用 insmod 命令;反之,要卸載這個模塊,請使用 rmmod 命令。lsmod能夠顯示當前加載的 LKM(參見清單 5)。
[root@plato]# insmod simple-lkm.ko [root@plato]# lsmod Module Size Used by simple_lkm 1536 0 autofs4 26244 0 video 13956 0 button 5264 0 battery 7684 0 ac 3716 0 yenta_socket 18952 3 rsrc_nonstatic 9472 1 yenta_socket uhci_hcd 32144 0 i2c_piix4 7824 0 dm_mod 56468 3 [root@plato]# rmmod simple-lkm [root@plato]# |
注意,內核的輸出進到了內核迴環緩衝區中,而不是打印到 stdout 上,這是由於 stdout 是進程特有的環境。要查看內核迴環緩衝區中的消息,可使用 dmesg 工具(或者經過 /proc 自己使用 cat /proc/kmsg 命令)。清單 6 給出了 dmesg 顯示的最後幾條消息。
[root@plato]# dmesg | tail -5 cs: IO port probe 0xa00-0xaff: clean. eth0: Link is down eth0: Link is up, running at 100Mbit half-duplex my_module_init called. Module is now loaded. my_module_cleanup called. Module is now unloaded. [root@plato]# |
能夠在內核輸出中看到這個模塊的消息。如今讓咱們暫時離開這個簡單的例子,來看幾個能夠用來開發有用 LKM 的內核 API。
內核程序員可使用的標準 API,LKM 程序員也可使用。LKM 甚至能夠導出內核使用的新變量和函數。有關 API 的完整介紹已經超出了本文的範圍,所以咱們在這裏只是簡單地介紹後面在展現一個更有用的 LKM 時所使用的幾個元素。
要在 /proc 文件系統中建立一個虛擬文件,請使用 create_proc_entry 函數。這個函數能夠接收一個文件名、一組權限和這個文件在 /proc 文件系統中出現的位置。create_proc_entry 的返回值是一個 proc_dir_entry 指針(或者爲 NULL,說明在 create 時發生了錯誤)。而後就可使用這個返回的指針來配置這個虛擬文件的其餘參數,例如在對該文件執行讀操做時應該調用的函數。create_proc_entry 的原型和 proc_dir_entry 結構中的一部分如清單 7 所示。
struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode, struct proc_dir_entry *parent ); struct proc_dir_entry { const char *name; // virtual file name mode_t mode; // mode permissions uid_t uid; // File's user id gid_t gid; // File's group id struct inode_operations *proc_iops; // Inode operations functions struct file_operations *proc_fops; // File operations functions struct proc_dir_entry *parent; // Parent directory ... read_proc_t *read_proc; // /proc read function write_proc_t *write_proc; // /proc write function void *data; // Pointer to private data atomic_t count; // use count ... }; void remove_proc_entry( const char *name, struct proc_dir_entry *parent ); |
稍後咱們就能夠看到如何使用 read_proc 和 write_proc 命令來插入對這個虛擬文件進行讀寫的函數。
要從 /proc 中刪除一個文件,可使用 remove_proc_entry 函數。要使用這個函數,咱們須要提供文件名字符串,以及這個文件在 /proc 文件系統中的位置(parent)。這個函數原型如清單 7 所示。
parent 參數能夠爲 NULL(表示 /proc 根目錄),也能夠是不少其餘值,這取決於咱們但願將這個文件放到什麼地方。表 1 列出了可使用的其餘一些父 proc_dir_entry,以及它們在這個文件系統中的位置。
proc_dir_entry | 在文件系統中的位置 |
---|---|
proc_root_fs | /proc |
proc_net | /proc/net |
proc_bus | /proc/bus |
proc_root_driver | /proc/driver |
咱們可使用 write_proc 函數向 /proc 中寫入一項。這個函數的原型以下:
int mod_write( struct file *filp, const char __user *buff, unsigned long len, void *data ); |
filp 參數其實是一個打開文件結構(咱們能夠忽略這個參數)。buff 參數是傳遞給您的字符串數據。緩衝區地址其實是一個用戶空間的緩衝區,所以咱們不能直接讀取它。len 參數定義了在 buff 中有多少數據要被寫入。data 參數是一個指向私有數據的指針(參見 清單 7)。在這個模塊中,咱們聲明瞭一個這種類型的函數來處理到達的數據。
Linux 提供了一組 API 來在用戶空間和內核空間之間移動數據。對於 write_proc 的狀況來講,咱們使用了 copy_from_user 函數來維護用戶空間的數據。
咱們可使用 read_proc 函數從一個 /proc 項中讀取數據(從內核空間到用戶空間)。這個函數的原型以下:
int mod_read( char *page, char **start, off_t off, int count, int *eof, void *data ); |
page 參數是這些數據寫入到的位置,其中 count 定義了能夠寫入的最大字符數。在返回多頁數據(一般一頁是 4KB)時,咱們須要使用 start 和 off 參數。當全部數據所有寫入以後,就須要設置 eof(文件結束參數)。與 write 相似,data 表示的也是私有數據。此處提供的 page 緩衝區在內核空間中。所以,咱們能夠直接寫入,而不用調用 copy_to_user。
咱們還可使用 proc_mkdir、symlinks 以及 proc_symlink 在 /proc 文件系統中建立目錄。對於只須要一個 read 函數的簡單 /proc 項來講,可使用 create_proc_read_entry,這會建立一個 /proc 項,並在一個調用中對 read_proc 函數進行初始化。這些函數的原型如清單 8 所示。
/* Create a directory in the proc filesystem */ struct proc_dir_entry *proc_mkdir( const char *name, struct proc_dir_entry *parent ); /* Create a symlink in the proc filesystem */ struct proc_dir_entry *proc_symlink( const char *name, struct proc_dir_entry *parent, const char *dest ); /* Create a proc_dir_entry with a read_proc_t in one call */ 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 ); /* Copy buffer to user-space from kernel-space */ unsigned long copy_to_user( void __user *to, const void *from, unsigned long n ); /* Copy buffer to kernel-space from user-space */ unsigned long copy_from_user( void *to, const void __user *from, unsigned long n ); /* Allocate a 'virtually' contiguous block of memory */ void *vmalloc( unsigned long size ); /* Free a vmalloc'd block of memory */ void vfree( void *addr ); /* Export a symbol to the kernel (make it visible to the kernel) */ EXPORT_SYMBOL( symbol ); /* Export all symbols in a file to the kernel (declare before module.h) */ EXPORT_SYMTAB |
下面是一個能夠支持讀寫的 LKM。這個簡單的程序提供了一個財富甜點分發。在加載這個模塊以後,用戶就可使用 echo 命令向其中導入文本財富,而後再使用 cat 命令逐一讀出。
清單 9 給出了基本的模塊函數和變量。init 函數(init_fortune_module)負責使用 vmalloc 來爲這個點心罐分配空間,而後使用memset 將其所有清零。使用所分配並已經清空的 cookie_pot 內存,咱們在 /proc 中建立了一個 proc_dir_entry 項,並將其稱爲fortune。當 proc_entry 成功建立以後,對本身的本地變量和 proc_entry 結構進行了初始化。咱們加載了 /proc read 和 write 函數(如清單 9 和清單 10 所示),並肯定這個模塊的全部者。cleanup 函數簡單地從 /proc 文件系統中刪除這一項,而後釋放cookie_pot 所佔據的內存。
cookie_pot 是一個固定大小(4KB)的頁,它使用兩個索引進行管理。第一個是 cookie_index,標識了要將下一個 cookie 寫到哪裏去。變量 next_fortune 標識了下一個 cookie 應該從哪裏讀取以便進行輸出。在全部的 fortune 項都讀取以後,咱們簡單地回到了next_fortune。
#include <linux/module.h> #include <linux/kernel.h> #include <linux/proc_fs.h> #include <linux/string.h> #include <linux/vmalloc.h> #include <asm/uaccess.h> MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Fortune Cookie Kernel Module"); MODULE_AUTHOR("M. Tim Jones"); #define MAX_COOKIE_LENGTH PAGE_SIZE static struct proc_dir_entry *proc_entry; static char *cookie_pot; // Space for fortune strings static int cookie_index; // Index to write next fortune static int next_fortune; // Index to read next fortune int init_fortune_module( void ) { int ret = 0; cookie_pot = (char *)vmalloc( MAX_COOKIE_LENGTH ); if (!cookie_pot) { ret = -ENOMEM; } else { memset( cookie_pot, 0, MAX_COOKIE_LENGTH ); proc_entry = create_proc_entry( "fortune", 0644, NULL ); if (proc_entry == NULL) { ret = -ENOMEM; vfree(cookie_pot); printk(KERN_INFO "fortune: Couldn't create proc entry\n"); } else { cookie_index = 0; next_fortune = 0; proc_entry->read_proc = fortune_read; proc_entry->write_proc = fortune_write; proc_entry->owner = THIS_MODULE; printk(KERN_INFO "fortune: Module loaded.\n"); } } return ret; } void cleanup_fortune_module( void ) { remove_proc_entry("fortune", &proc_root); vfree(cookie_pot); printk(KERN_INFO "fortune: Module unloaded.\n"); } module_init( init_fortune_module ); module_exit( cleanup_fortune_module ); |
向這個罐中新寫入一個 cookie 很是簡單(如清單 10 所示)。使用這個寫入 cookie 的長度,咱們能夠檢查是否有這麼多空間可用。若是沒有,就返回 -ENOSPC,它會返回給用戶空間。不然,就說明空間存在,咱們使用 copy_from_user 將用戶緩衝區中的數據直接拷貝到 cookie_pot 中。而後增大 cookie_index(基於用戶緩衝區的長度)並使用 NULL 來結束這個字符串。最後,返回實際寫入cookie_pot 的字符的個數,它會返回到用戶進程。
ssize_t fortune_write( struct file *filp, const char __user *buff, unsigned long len, void *data ) { int space_available = (MAX_COOKIE_LENGTH-cookie_index)+1; if (len > space_available) { printk(KERN_INFO "fortune: cookie pot is full!\n"); return -ENOSPC; } if (copy_from_user( &cookie_pot[cookie_index], buff, len )) { return -EFAULT; } cookie_index += len; cookie_pot[cookie_index-1] = 0; return len; } |
對 fortune 進行讀取也很是簡單,如清單 11 所示。因爲咱們剛纔寫入數據的緩衝區(page)已經在內核空間中了,所以能夠直接對其進行操做,並使用 sprintf 來寫入下一個 fortune。若是 next_fortune 索引大於 cookie_index(要寫入的下一個位置),那麼咱們就將 next_fortune 返回爲 0,這是第一個 fortune 的索引。在將這個 fortune 寫入用戶緩衝區以後,在 next_fortune 索引上增長剛纔寫入的 fortune 的長度。這樣就變成了下一個可用 fortune 的索引。這個 fortune 的長度會被返回並傳遞給用戶。
int fortune_read( char *page, char **start, off_t off, int count, int *eof, void *data ) { int len; if (off > 0) { *eof = 1; return 0; } /* Wrap-around */ if (next_fortune >= cookie_index) next_fortune = 0; len = sprintf(page, "%s\n", &cookie_pot[next_fortune]); next_fortune += len; return len; } |
從這個簡單的例子中,咱們能夠看出經過 /proc 文件系統與內核進行通訊其實是件很是簡單的事情。如今讓咱們來看一下這個 fortune 模塊的用法(參見清單 12)。
[root@plato]# insmod fortune.ko [root@plato]# echo "Success is an individual proposition. Thomas Watson" > /proc/fortune [root@plato]# echo "If a man does his best, what else is there? Gen. Patton" > /proc/fortune [root@plato]# echo "Cats: All your base are belong to us. Zero Wing" > /proc/fortune [root@plato]# cat /proc/fortune Success is an individual proposition. Thomas Watson [root@plato]# cat /proc/fortune If a man does his best, what else is there? General Patton [root@plato]# |
/proc 虛擬文件系統能夠普遍地用來報告內核的信息,也能夠用來進行動態配置。咱們會發現它對於驅動程序和模塊編程來講都是很是完整的。在下面的 參考資料 中,咱們能夠學習到更多相關知識。
下載
描述 | 名字 | 大小 | 下載方法 |
---|---|---|---|
Linux kernel module source | l-proc-lkm.zip | 2KB | HTTP |
學習
得到產品和技術
討論