Linux网络技术学习(四)—— 用户空间与内核的接口


概述

内核通过各种不同的接口把内部信息输出到用户空间。


两个虚拟文件系统:procfs和sys

(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都是输出内核内部信息。procfs主要输出只读数据;sysctl信息使用超级用户可以写入的。
sysctl输出:简单的内核变量或数据结构相关联的一些文件;
procfs输出:复杂的数据结构并且需要特殊格式时使用。

procfs

1、大多数网络功能在其初始化时都会在/proc中注册一个或多个文件。用户在读取该文件时,会引起内核间接运行一组内核函数,以返回某种输出内容。
2、网络代码所注册的文件位于/proc/net/
3、/proc中的目录可以使用proc_mkdir创建。/proc/net中的文件可以使用定义在<include/linux/proc_fs.h>中的proc_create_net和remove_proc_entry注册和除名。

以ARP协议注册为例
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结构体的调用。

systcl:目录/proc/sys

1、用户在/proc/sys看到的一个文件,实际上是内核变量。
2、在/proc/sys里以组件的来划分变量的所处的位置。例如:在/proc/sys/net/ipv4中可以找见与IPv4相关的文件。
3、访问权限。例如,一个文件可以由任何人读,但只能由超级用户修改。
4、输出到/proc/sys中的变量内容可以借助相关文件进行读写,或直接用sysctl系统调用。

/proc/sys中的文件和目录都是以ctl_table结构体定义的。ctl_table结构的注册和除名是通过在<kernel/sysctl.c>中的register_sysctl_table和unregister_sysctl_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初始化,定义<kernel/sysctl.c>中
在这里插入图片描述

ctl_table结构体实例举例

/proc/sys/net/ipv4/conf/eth0/accept_local文件ctl_table结构体定义在<net/ipv4/devinet.c>

    { 
        .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命令使用ioctl与内核通信。

当系统管理输入像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))
网络用的命令列在<include/uapi/linux/sockios.h>中。设备驱动程序可以用代码定义新的(私有)命令。范围介于SIOCPROTOPRIVATE和SIOCPROTOPRIVATE+15之间

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bazinga bingo

您的鼓励就是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值