內核通過不同的接口將信息輸出到用戶空間,除了用於請求特定信息的系統調用(system call)外,還有三個特殊接口:
-
procfs(/proc文件系統):這是一個虛擬文件系統,掛載在/proc目錄下。允許內核以文件的形式向用戶空間輸出內部信息。這些文件並不實際存在於磁盤上,但可以通過cat或more以及‘>’shell重定向字符寫入。也可以設置文件的訪問權限。目錄不能被寫入,即用戶不能把文件或目錄添加到/proc中的任何目錄或刪除文件和目錄。procfs不能編譯成為一個模塊。配置菜單中的相關內核選項為“Filesystems->pseudo filesystems->/proc file system support”
-
sysctl(/proc/sys目錄): 此接口允許用戶讀取或修改內核變量的值。不能用此接口對每個內核變量進行操作:內核應明確指出哪些變量從此接口是可見的。從用戶空間可以用兩種方式訪問sysctl輸出的變量,一是sysctl系統調用,另一種是procfs。當內核支持procfs時,會在/proc中添加一個特殊目錄(/proc/sys),為每個由sysctl所輸出的內核變量引入一個文件。procfs包隨附的sysctl命令可用於配置由sysctl接口輸出的變量,此命令通過寫入/proc/sys與內核通信。sysctl不能編譯成為一個模塊。內核配置選項為“General setup –>sysctl support”。
-
sysfs:以非常干淨而有組織的方式輸出很多信息。由sysctl所輸出的部分信息可以移植到sysfs上。配置菜單中的內核選項為“Filesystem->pseudo filesystems->sysfs filesystem support(NEW)”,只有想開啟“General setup->Configure standard kernel features(for small system)”選項後,才能看見上述選項。
-
ioctl系統調用:ioctl系統調用操作的對象時一個文件,通常用於實現特殊設備需要而標准文件系統沒有提供的操作。
-
Netlink套接字:這是網絡應用程序和內核通信時最新的首選機制。多數的網絡內核功能都可以用Netlink或ioctl接口進行配置。
Procfs與sysctl
procfs和sysctl都輸出內核內部信息,但procfs主要是輸出只讀數據,而大多數sysctl信息是可寫入的,但只要超級用戶能寫入。與一個簡單的內核變量或數據結構相關聯的一些文件,可以用sysctl輸出。其他涉及更為復雜的數據結構而需要特殊格式時,就以procfs輸出,如緩存和統計數據。
Procfs
大多數網絡功能會在引導時或在模塊加載時其初始化期間在/proc中注冊一個或多個文件。當用戶讀取該文件時,會引起內核間接運行一組內核函數,返回某種輸出內容。網絡代碼所注冊的文件位於/proc/net。
/proc中的目錄可以使用proc_mkdir創建。/proc/net中的文件可以使用定義在include/linux/proc_fs.h中的proc_net_fops_create和proc_net_remove予以注冊和注銷。
還有兩個通用的API函數create_proc_entry和remove_proc_entry。proc_net_fops_create負責調用proc_net_create創建文檔,然後初始化器文件操作處理函數。
如ARP協議在/proc/net中注冊其arp文件:
static const struct file_operations arp_seq_fops = {
.owner = THIS_MODULE,
.open = arp_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_net,
};
static int __net_init arp_net_init(struct net *net)
{
if (!proc_net_fops_create(net, "arp", S_IRUGO, &arp_seq_fops))
return -ENOMEM;
return 0;
}
static void __net_exit arp_net_exit(struct net *net)
{
proc_net_remove(net, "arp");
}
proc_net_fops_create參數中權限必須指定為只讀。當用戶讀取該文件時,使用file_operations數據結構,返回數據給用戶。open所初始化的例程如前例中的qrp_seq_open會做另一次重要的初始化:注冊一個函數指針數組,包括procfs用於變量要傳回給用戶數據的所有例程:一個例程啟動dump復制數據,另一個索引到下一條記錄,在dump該條記錄,繼續後移。這些例程的內部保存必要的環境信息,就是已經dump了多少信息。這些信息是dump點已經到正確位置繼續dump所必需的。
static const struct seq_operations arp_seq_ops = {
.start = arp_seq_start,
.next = neigh_seq_next,
.s
top = neigh_seq_stop,
.show = arp_seq_show,
};
static int arp_seq_open(struct
Linux/1672.html' target='_blank'>
inode *inode, struct file *file)
{
return seq_open_net(inode, file, &arp_seq_ops,
sizeof(struct neigh_seq_state));
}
Sysctl:目錄/proc/sys
在/proc/sys下看到的一個文件,實際都是一個內核變量。對於每個變量,內核可以將其放在/proc/sys的位置(與相同內核組件或功能相關聯的變量通常都位於同一個目錄中,如/proc/sys/net/ipv4目錄中都是與ipv4相關的文件)、命名(多數時候文件名都是和相關聯的內核變量相同的名字)、訪問權限(如一個文件可以由任何人讀,但只能由超級用戶修改)。
輸出到/proc/sys中的變量內容可借助相關聯的文件進行讀寫或直接用sysctl系統調用。有些目錄和文件在引導期間靜態定義,而其他則是在允許期間添加的。當一個內核模塊實現一項新功能或一個協議被加載或卸載時;當一個新的網絡設備被注冊或注銷時。都有可能創建目錄或文件。
/proc/sys中的文件和目錄都是以ctl_table結構定義的。ctl_table結構的注冊和注銷是通過調用kernel/sysctl.c中定義的register_sysctl_table和unregister_sysctl_table:
struct ctl_table_header *register_sysctl_table(struct ctl_table *table);
void unregister_sysctl_table(struct ctl_table_header * header);
struct ctl_table
{
const char *procname; /* Text ID for /proc/sys, or zero */
void *data;
int maxlen;
mode_t mode;
struct ctl_table *child;
struct ctl_table *parent; /* Automatically set */
proc_handler *proc_handler; /* Callback for text formatting */
void *extra1;
void *extra2;
};
ctl_table的關鍵成員:
procname:在/proc/sys中所使用的文件名
maxlen:輸出的內核變量的大小
mode:創建的/proc/sys豬相關文件或目錄的訪問權限
child:用於建立目錄和文件直接的父子關系
proc_handler:當在/proc/sys中讀取或寫入一個文件時,完成讀取或寫入操作的函數。所有和文件相關聯的ctl_instance都必須有proc_handler初始化,內內核會給目錄分派一個默認值
extra1,extra2:可選參數,通常用於定義變量的最小值和最大值
根據與文件相關聯的變量類型的不同,proc_handler所指的函數也不相同。
proc_dostring:讀/寫一個字符串
proc_dointvec:讀寫一個包含一個或多個整數的數組
proc_dointvec_minmax:同上,但是要確定輸入數據在min/max范圍內,不在該范圍內的值會被拒絕
proc_dointvec_jiffies:讀寫一個整數數組,但此內核變量以jiffies為單位表示,在返回用戶前會轉化為秒數,寫入前轉化為jiffies
proc_dointvec_ms_jiffies:同上,只是這裡是轉化為毫秒數
proc_doulongvec_minmax:類似proc_dointvec_minmax,但其值為長整數。
proc_doulongvec_ms_jiffies_minmax:讀取一個長整數數組。此內核變量以jiffies為單位,而用戶空間已毫秒為單位。此值也必須指定min和max區間
ctl_table注冊文件的結構實例:
static struct ctl_table ctl_forward_entry[] = {
{
.procname = "ip_forward", //文件名
.data = &ipv4_devconf.data[
IPV4_DEVCONF_FORWARDING - 1], //輸出參數
.maxlen = sizeof(int), //參數大小
.mode = 0644, //文件權限
.proc_handler = devinet_sysctl_forward, //指定函數
.extra1 = &ipv4_devconf,
.extra2 = &init_net,
},
{ },
};
在這裡還看不出這個文件在/proc/sys下的那個目錄中。
這裡是/proc/sys目錄下建的默認目錄:
static struct ctl_table root_table[] = {
{
.procname = "kernel",
.mode = 0555,
.child = kern_table,
},
{
.procname = "vm",
.mode = 0555,
.child = vm_table,
},
{
.procname = "fs",
.mode = 0555,
.child = fs_table,
},
{
.procname = "debug",
.mode = 0555,
.child = debug_table,
},
{
.procname = "dev",
.mode = 0555,
.child = dev_table,
},
{ }
};
目錄不需要proc_handler函數指針但卻有一個child字段。child是一個指針,指向另一個ctl_table結構,這個地址是ctl_table結構列表的頭元素地址
在/proc/sys中注冊文件
函數register_sysctl_table的輸入並不包括輸入參數ctl_table應該被添加到/proc/sys文件目錄的那個子目錄中。原因在於所有的插入都是針對/proc/sys目錄進行的,所以若想將一個文件注冊到/proc/sys的子目錄中,就必須建立一棵樹由多個child字段鏈接成ctl_table實體以提供完整路徑,然後將代表剛健的樹根的ctl_table傳遞給register_sysctl_table函數。
drivers/scsi/scsi_sysctl.c顯示了文件logging_level的定義已經放置到/proc/sys/dev/scsi/目錄下的方法:
static ctl_table scsi_table[] = { //logging_level文件
.procname = "logging_level",
.data = &scsi_logging_level,
.maxlen = sizeof(scsi_logging_level),
.mode = 0644,
.proc_handler = proc_dointvec },
{ }
};
static ctl_table scsi_dir_table[] = { //scsi目錄
{ .procname = "scsi",
.mode = 0555,
.child = scsi_table },
{ }
};
static ctl_table scsi_root_table[] = { //dev目錄
{ .procname = "dev",
.mode = 0555,
.child = scsi_dir_table },
{ }
};
static struct ctl_table_header *scsi_table_header;
int __init scsi_init_sysctl(void) //注冊
{
scsi_table_header = register_sysctl_table(scsi_root_table);
if (!scsi_table_header)
return -ENOMEM;
return 0;
}
void scsi_exit_sysctl(void) //注銷
{
unregister_sysctl_table(scsi_table_header);
}
若需要添加多個文件到同一個目錄下,可以定義一個模板,每次有新文件要添加到同一個目錄下就重用。使用模板的好處是ctl_table結構只需要初始化一個便可貫穿整個目錄。