cover_image

Linux用户态与内核态通信方式介绍及选型参考

qiang 酷派技术团队 2022年07月20日 05:57

 本文列举了几种用户态与内核态通信的方式:Netlink通信机制,proc通信机制,基于文件的ioctl通信机制,并通过实验分析它们的耗时以及适应环境。为项目选型提供参考。

一.背景

进程内核态和用户态数据交互,已经成为linux开发中重要的需求,但网络上缺少通信耗时相关的实验数据。因此这篇文章,主要通过比较耗时,延伸出各通信机制的适用环境。

二.典型通信方式介绍

2.1 ioctl通信方式

2.1.1 概述

从ioctl这个名称上看,它是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制。例如串口的传输波特率、马达的转速等等, 但实际上ioctl所处理的对象并不限制是真正的I/O设备,还可以是其它任何一个内核设备。

ioctl以系统调用的形式提供了一条用户与内核交互的便捷途径。ioctl 这种方式的特点是用户层为主动,当用户层通过ioctl下发指令,内核给出相应的操作,具体的细节由自己实现的代码决定。

2.1.2 基本原理

内核实现ioctl()函数的是sys_ioctl().在内核中主要调用框架图如下,它清晰地给我们展示了ioctl的控制传递框架

图片

2.2 proc通信方式

2.2.1 概述

Linux用户与内核的通信,也可通过"/proc"目录的文件读写来实现。如果只是控制内核中的参数而不是传输较多数据的话,用“/proc”是很合适的。proc 这种方法标准的做法是内核单向向用户层传递数据,根据自己实现的代码来决定通过什么方式处理。

2.2.2 基本原理

proc文件系统的思路就是:在内核中构建一个虚拟文件系统/proc,内核运行时将内核中一些关键的数据结构以文件的方式呈现在/proc目录中的一些特定文件中,这样相当于将不可见的内核中的数据结构以可视化的方式呈现给内核的开发者

2.3 netlink通信方式

2.3.1 概述

netlink 这种方法是利用sock网络套接字来实现用户层和内核的双向数据传递,相对编码复杂一点,但是接口都是封装好的。这种方法的特点是用户层可以像操作网络套接字那样的轻松的操作内核传递上来的数据。可以用户层作客户端,内核作服务端;或者用户层做服务端,内核做客户端。

2.3.2 基本原理

我们通常用对应某进程的标识,即该进程的ID号,来作为Netlink套接字的通信依据。在实现netlink用于内核空间与用户空间之间的通信时,用户空间的创建方法和一般的套接字的创建使用类似,但内核的创建方法则有所不同,下图是netlink实现此类通信时的创建过程:

图片

三.典型通信方式性能实验

3.1.实验环境

VMware® Workstation 15 Pro虚拟机

操作系统 Ubuntu 20.04

CPU 8核 Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz

内存 8G

3.2.测试场景说明

因为项目的需求(内核态和用户态进行双向通信)所以只选择ioctl,netlink进行对比实验。

本实验只是测试单独的系统调用(ioctl,sendto)耗时,不统计循环等待时间。考虑系统调用时间很短,纳秒级。按照每5000次系统调用统计耗时。每次发送1024字节的数据。

3.3.测试代码准备

  ioctltest

unsigned char msg[1024];int rc;struct timespec ts_start, ts_end;
rc = clock_gettime(CLOCK_MONOTONIC, &ts_start);int i=0; for(i=0;i<5000;i++) { ret = ioctl(fd, IOCWREG, &msg); if (ret) { perror("ioctl write:"); exit(-4); } } rc = clock_gettime(CLOCK_MONOTONIC, &ts_end);
printf("CLOCK_MONOTONIC reports %ld.%09ld seconds\n",ts_end.tv_sec - ts_start.tv_sec, ts_end.tv_nsec - ts_start.tv_nsec);


  netlinktest

unsigned char msg[1024];int rc;struct timespec ts_start, ts_end;
rc = clock_gettime(CLOCK_MONOTONIC, &ts_start);int i=0; for(i=0;i<5000;i++) { ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl)); if(!ret) { perror("sendto error\n"); close(skfd); exit(-1); } //printf("send kernel:%s\n", umsg); }
rc = clock_gettime(CLOCK_MONOTONIC, &ts_end);
printf("CLOCK_MONOTONIC reports %ld.%09ld seconds\n",ts_end.tv_sec - ts_start.tv_sec, ts_end.tv_nsec - ts_start.tv_nsec);


3.4.测试脚本

  ioctl测试脚本

while true;do echo 3 > /proc/sys/vm/drop_caches; time ./ioctltest;sleep 1;done


  netlink测试脚本

while true;do echo 3 > /proc/sys/vm/drop_caches; time ./usersocket;sleep 1;done

3.5.测试结果

  ioctl测试数据

CLOCK_MONOTONIC reports 0.006016295 secondsreal    0m0.015suser    0m0.005ssys     0m0.006s
CLOCK_MONOTONIC reports 0.006374883 secondsreal 0m0.016suser 0m0.003ssys 0m0.007s
CLOCK_MONOTONIC reports 0.007895143 secondsreal 0m0.018suser 0m0.000ssys 0m0.012s
CLOCK_MONOTONIC reports 0.006377391 secondsreal 0m0.015suser 0m0.000ssys 0m0.012s
CLOCK_MONOTONIC reports 0.006373939 secondsreal 0m0.015suser 0m0.000ssys 0m0.009s


  netlink 测试数据

CLOCK_MONOTONIC reports 0.004700564 secondsreal    0m0.015suser    0m0.003ssys     0m0.006s
CLOCK_MONOTONIC reports 0.004535751 secondsreal 0m0.013suser 0m0.000ssys 0m0.008s
CLOCK_MONOTONIC reports 0.004554671 secondsreal 0m0.012suser 0m0.000ssys 0m0.008s
CLOCK_MONOTONIC reports 0.004469324 secondsreal 0m0.011suser 0m0.000ssys 0m0.007s
CLOCK_MONOTONIC reports 0.005344049 secondsreal 0m0.014suser 0m0.000ssys 0m0.009s

3.6.耗时对比

对比项
每次调用耗时
ioctl
0.0012749766毫秒
netlink0.0009401128毫秒


ioctl和netlink 耗时差距不大,netlink 略优了一点点。所以在项目选型中,不用过多的考虑ioctl和netlink 的耗时问题。更多的考虑和项目架构的贴合度。

四.选型参考

4.1 ioctl 面向硬件驱动

    • ioctl是内核比较早的一种用户态内核态的交互方式,侧重于文件系统,方便添加对硬件驱动的处理。
    • ioctl机制可以在驱动中扩展特定的ioctl消息,用于将一些状态从内核反应到用户态。ioctl有很好的数据同步保护机制,不用担心内核和用户层的数据访问冲突,但是ioctl不适合传输大量的数据。

4.2 netlink 面向通信

    相对于ioctl 优点如下:
    • 用户仅需要在 include/linux/netlink.h 中增加一个新类型的 netlink 协议定义( 如 #define NETLINK_MYTEST 17)即可使用netlink进行数据通信。而ioctl 则需要增加设备或文件
    • Netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接收队列,而不需要等待接收者收到消息,但 ioctl 则是同步通信机制,如果传递的数据太长,将影响调度粒度。
    • netlink 的内核部分可以采用模块的方式实现,而且内核与应用部分没有编译依赖关系。
    • Netlink 支持多播。内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性,任何对内核事件感兴趣的应用都能收到该子系统发送的内核事件。
    • 内核可以使用 netlink 首先发起会话,但 ioctl 只能由用户主动发起调用。

五.总结

通过ioctl和netlink耗时对比实验,发现两者性能差距不大。如果项目原生架构是通过文件系统或者设备驱动,和用户态进程进行交互,且传输少量数据,优先选择ioctl。如果是其他方式,或者有广播需求优先选择netlink方式。

微信扫一扫
关注该公众号

继续滑动看下一个
酷派技术团队
向上滑动看下一个