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



    概述

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


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

    (1) procfs(/proc 文件系统)
    这是一个虚拟文件系统,通常是挂载(mount)在/proc,允许内核以文件的形式向用户空间输出内部信息。
    这些文件并没有实际存在于磁盘中,但是可以通过cat(查看)或> shell (写入)文件。这些文件可以像真实文件一样指定其访问权限。
    内核组件可以说明任何一个文件可由谁读取或写入。
    目录不能被写入。(用户不能把文件或目录添加到/proc中的任何目录)

    内核配置

    make ARCH=arm64 menuconfig
        -> File system
            -> Pseudo filesystems
                -> [*] /proc file system support 
    
    • 1
    • 2
    • 3
    • 4

    (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)
    
    • 1
    • 2
    • 3
    • 4

    (3) sysfs(/sys文件系统)
    sysfs用以干净并且有组织的方式输出很多信息。sysctl所输出的部分信息可以移植到sysfs。

    内核配置

    make ARCH=arm64 menuconfig
        -> File system
            -> Pseudo filesystems
                -> -*- sysfs file system support 
    
    • 1
    • 2
    • 3
    • 4

    应用层接口

    应用层使用下列接口把命令传给内核,配置某事或者去掉某些配置内容:
    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中的文件可以使用定义在中的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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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接口
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    用户对节点的操作相当于对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结构的注册和除名是通过在中的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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    解析下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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    根据与文件相关联的变量种类而定,proc_handler和strategy初始化不同。
    所有变量的proc_handler和strategy初始化,定义
    在这里插入图片描述

    ctl_table结构体实例举例

    /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, \
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上面是一个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为树的根
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    __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);
    
    • 1
    • 2
    • 3

    内核会在几个不同地方处理ioctl命令。将由sock_ioctl分配
    在这里插入图片描述

    ioctl命令的名称解析
    ADD ,表示要添加什么
    RT , 表示要添加的是一条路由
    G , 取得
    S ,设置

    例:SIOCADDRT (添加路由);SIOCSIFMTU(设定(S)接口(IF)的最大传输单元(MTU))
    网络用的命令列在中。设备驱动程序可以用代码定义新的(私有)命令。范围介于SIOCPROTOPRIVATE和SIOCPROTOPRIVATE+15之间

  • 相关阅读:
    【数据结构】单链表的特点
    Llama2-Chinese项目:3.2-LoRA微调和模型量化
    【MindSpore易点通】Transformer的注意力机制
    在 Vue 中控制表单输入
    从产品角度分析羊了个羊为何能爆火
    【网络安全带你练爬虫-100练】第22练:数据包中参数提取与处理
    RSA加密算法
    18-基于CentOS7搭建RabbitMQ3.10.7集群镜像队列+HaProxy+Keepalived高可用架构
    猿创征文|实战开发openGauss DataStudio的sql联想结构
    【云原生】Docker的私有仓库部署——Harbor
  • 原文地址:https://blog.csdn.net/weixin_43564241/article/details/126683572