内核通过各种不同的接口把内部信息输出到用户空间。
(1) procfs(/proc 文件系统)
这是一个虚拟文件系统,通常是挂载(mount)在/proc,允许内核以文件的形式向用户空间输出内部信息。
这些文件并没有实际存在于磁盘中,但是可以通过cat(查看)或> shell (写入)文件。这些文件可以像真实文件一样指定其访问权限。
内核组件可以说明任何一个文件可由谁读取或写入。
目录不能被写入。(用户不能把文件或目录添加到/proc中的任何目录)
内核配置
make ARCH=arm64 menuconfig
-> File system
-> Pseudo filesystems
-> [*] /proc file system support
(2) sysctl(/proc/sys目录)
此接口允许用户空间读取或修改内核变量的值。(不能用此接口对内核每个变量进行修改操作)
在用户空间中,你可以用两种方式访问sysctl输出的变量
第一种,sysctl系统调用
第二种,procfs。当内核支持procfs时,会在/proc中添加一个特殊目录(/proc/sys)。为每个由sysctl所输出的内核变量引入一个文件。
此命令通过写入/proc/sys与内核对话
内核配置
make ARCH=arm64 menuconfig
-> File system
-> Pseudo filesystems
-> [*] Sysctl support (/proc/sys)
(3) sysfs(/sys文件系统)
sysfs用以干净并且有组织的方式输出很多信息。sysctl所输出的部分信息可以移植到sysfs。
内核配置
make ARCH=arm64 menuconfig
-> File system
-> Pseudo filesystems
-> -*- sysfs file system support
应用层使用下列接口把命令传给内核,配置某事或者去掉某些配置内容:
ioctl 系统调用
ioctl(输入/输出控制)系统调用操作的对象是一个文件,通常是用于实现特殊设备所需但标准文件系统没有提供的操作。
可以把socket系统调用的套接字描述符传给ioctl,这就是网络代码使用ioctl的方法。
使用ifconfig和route都调用这个函数访问。
Netlink 套接字(socket)
这是网络应用程序与内核通信时最新的首选机制。IPROUTE2包中大多数命令都是用此接口进行配置。
procfs和sysctl都是输出内核内部信息。procfs主要输出只读数据;sysctl信息使用超级用户可以写入的。
sysctl输出:简单的内核变量或数据结构相关联的一些文件;
procfs输出:复杂的数据结构并且需要特殊格式时使用。
1、大多数网络功能在其初始化时都会在/proc中注册一个或多个文件。用户在读取该文件时,会引起内核间接运行一组内核函数,以返回某种输出内容。
2、网络代码所注册的文件位于/proc/net/
3、/proc中的目录可以使用proc_mkdir创建。/proc/net中的文件可以使用定义在
static struct pernet_operations arp_net_ops = {
.init = arp_net_init,
.exit = arp_net_exit,
};
static int __init arp_proc_init(void)
{
return register_pernet_subsys(&arp_net_ops);
}
register_pernet_subsys会将arp_net_ops加入ops->list链表中。系统初始化的时候执行链表上结构体中的init函数
static const struct seq_operations arp_seq_ops = { // arp协议操作函数
.start = arp_seq_start,
.next = neigh_seq_next,
.stop = neigh_seq_stop,
.show = arp_seq_show,
};
// 文件路径:fs/proc/proc_net.c
// #define proc_create_net(name, mode, parent, state_size, ops) proc_create_net_data(name, mode, parent, state_size, ops, NULL)
// struct proc_dir_entry *proc_create_net_data(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct seq_operations *ops, unsigned int state_size, void *data)
// 传入参数:名字,权限,
static int __net_init arp_net_init(struct net *net) // 在proc中建立arp接口
{
if (!proc_create_net("arp", 0444, net->proc_net, &arp_seq_ops,
sizeof(struct neigh_seq_state)))
return -ENOMEM;
return 0;
}
static void __net_exit arp_net_exit(struct net *net)
{
remove_proc_entry("arp", net->proc_net); // 在proc中删除arp接口
}
用户对节点的操作相当于对arp_seq_ops结构体的调用。
1、用户在/proc/sys看到的一个文件,实际上是内核变量。
2、在/proc/sys里以组件的来划分变量的所处的位置。例如:在/proc/sys/net/ipv4中可以找见与IPv4相关的文件。
3、访问权限。例如,一个文件可以由任何人读,但只能由超级用户修改。
4、输出到/proc/sys中的变量内容可以借助相关文件进行读写,或直接用sysctl系统调用。
/proc/sys中的文件和目录都是以ctl_table结构体定义的。ctl_table结构的注册和除名是通过在
static struct ctl_table sysctl_base_table[] = {
{
.procname = "kernel",
.mode = 0555,
.child = kern_table, // 指向另一个ctl_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,
},
{ }
};
int __init sysctl_init(void)
{
struct ctl_table_header *hdr;
hdr = register_sysctl_table(sysctl_base_table);
kmemleak_not_leak(hdr);
return 0;
}
解析下ctl_table结构体成员
struct ctl_table
{
const char *procname; // 在/proc/sys中所用的文件名
void *data;
int maxlen; // 输出内核变量的尺寸大小
umode_t mode; // 分配给/proc/sys中相关联的文件或目录的访问权限
struct ctl_table *child; // 用于建立目录与文件之间的父子关系
proc_handler *proc_handler; // 当在/proc/sys中读取或写入一个文件时,完成读取或写入操作的函数。所有与文件相关联的ctl_instances都必须由pro_handler初始化
struct ctl_table_poll *poll;
// strategy 用sysctl系统调用访问/proc/sys中的文件时被调用(rk3568平台没有此成员,其他平台需注意)
void *extra1;
void *extra2; // 两个可选的参数,用于定义变量的最小值min和最大值max
} __randomize_layout;
根据与文件相关联的变量种类而定,proc_handler和strategy初始化不同。
所有变量的proc_handler和strategy初始化,定义
/proc/sys/net/ipv4/conf/eth0/accept_local文件ctl_table结构体定义在
{
.procname = "accept_local",
.data = ipv4_devconf.data + \ // 指向accept_local数值
IPV4_DEVCONF_ ## attr - 1, \
.maxlen = sizeof(int), \
.mode = 0644, \
.proc_handler = devinet_conf_proc, \
.extra1 = &ipv4_devconf, \
}
上面是一个accept_local文件的定义。文件名称为accept_local,通过accept_local文件输出的值为ipv4_devconf指针所指向的内核accept_local数值。文件的权限为0644。proc_handler初始化为devinet_conf_proc
通过register_net_sysctl创建,最总调用__register_sysctl_table
// net/ipv4/devinet.c __devinet_sysctl_register
// 每个文件下所有变量文件模块定义赋值
for (i = 0; i < ARRAY_SIZE(t->devinet_vars) - 1; i++) {
t->devinet_vars[i].data += (char *)p - (char *)&ipv4_devconf;
t->devinet_vars[i].extra1 = p;
t->devinet_vars[i].extra2 = net;
}
// 以函数传进来的dev_name创建文件,上面例子就是net/ipv4/conf/eth0/
snprintf(path, sizeof(path), "net/ipv4/conf/%s", dev_name);
t->sysctl_header = register_net_sysctl(net, path, t->devinet_vars); // t->devinet_vars为树的根
__register_sysctl_table的输入路径不包括/proc/sys。传入的参数ctl_table却会添加到/proc/sys文件下面的对应目录下?这是因为所有的插入都针对/proc/sys目录进行。
如果想要把一个文件注册到/proc/sys的子目录,就要建一棵树(多个文件可以由child字段链接的ctl_table实体)。当数的节点未存在就会被创建。
参考文献中有个实例
register_sysctl_table接收的时scsi_root_table,也就是这段代码里的根
在/proc/sys中由网络代码所使用的主要目录。
当系统管理输入像ifconfig eth0 mtu 1250这样的命令,用以改变接口eth0的MTU时,ifconfig会打开一个套接字,从系统管理员那接收的信息初始化一个本地数据结构体,然后以ioctl调用传送给内核。SIOCSIFMTU是命令标识符
struct ifreq data;
fd = socket(PF_INET,SOCK_DGRAM,0);
err = ioctl(fd,SIOCSIFMTU,&data);
内核会在几个不同地方处理ioctl命令。将由sock_ioctl分配
ioctl命令的名称解析
ADD ,表示要添加什么
RT , 表示要添加的是一条路由
G , 取得
S ,设置
例:SIOCADDRT (添加路由);SIOCSIFMTU(设定(S)接口(IF)的最大传输单元(MTU))
网络用的命令列在