Linux 用户态和内核态由于 CPU 权限的限制,通信并不像想象中的使用进程间通信方式那么简单,今天这篇文章就来看看 Linux 用户态和内核态究竟有哪些通信方式。
我们平常在写代码时,一般是在用户空间,通过系统调用函数来访问内核空间,这是最常用的一种用户态和内核态通信的方式。(关于 Linux 用户态和内核态可以参考 xx)
除此之外,还有以下四种方式:
- procfs(/proc)
- sysctl(/proc/sys)
- sysfs(/sys)
- netlink 套接口
- 下面是新增加
- UIO
procfs(/proc)
procfs
是 进程文件系统 的缩写,它本质上是一个伪文件系统,为什么说是 伪 文件系统呢?因为它不占用外部存储空间,只是占用少量的内存,通常是挂载在 /proc
目录下。
我们在该目录下看到的一个文件,实际上是一个内核变量。内核就是通过这个目录,以文件的形式展现自己的内部信息,相当于 /proc
目录为用户态和内核态之间的交互搭建了一个桥梁,用户态读写 /proc
下的文件,就是读写内核相关的配置参数。
比如常见的 /proc/cpuinfo
、/proc/meminfo
和 /proc/net
就分别提供了 CPU、内存、网络的相关参数。除此之外,还有很多的参数,如下所示:
root@ubuntu:~# ls /proc/
1 1143 1345 1447 2 2292 29 331 393 44 63 70 76 acpi diskstats irq locks sched_debug sysvipc zoneinfo
10 1145 1357 148 20 23 290 332 396 442 64 7019 77 asound dma kallsyms mdstat schedstat thread-self
1042 1149 1361 149 2084 2425 291 34 398 45 65 7029 8 buddyinfo driver kcore meminfo scsi timer_list
1044 1150 1363 15 2087 25 3 3455 413 46 66 7079 83 bus execdomains keys misc self timer_stats
1046 1151 1371 16 2090 256 30 35 418 47 6600 7080 884 cgroups fb key-users modules slabinfo tty
1048 1153 1372 17 21 26 302 36 419 5 67 71 9 cmdline filesystems kmsg mounts softirqs uptime
11 1190 1390 18 22 27 31 37 420 518 6749 72 96 consoles fs kpagecgroup mtrr stat version
1126 12 143 182 2214 28 32 373 421 524 68 73 97 cpuinfo interrupts kpagecount net swaps version_signature
1137 1252 1434 184 2215 280 327 38 422 525 69 74 98 crypto iomem kpageflags pagetypeinfo sys vmallocinfo
1141 13 144 190 2262 281 33 39 425 5940 7 75 985 devices ioports loadavg partitions sysrq-trigger vmstat
复制
可以看到,这里面有很多的数字表示的文件,这些其实是当前系统运行的进程文件,数字表示进程号(PID),每个文件包含该进程所有的配置信息,包括进程状态、文件描述符、内存映射等等,我们可以看下:
root@ubuntu:~# ls /proc/1/
attr/ cmdline environ io mem ns/ pagemap schedstat stat timers
autogroup comm exe limits mountinfo numa_maps personality sessionid statm uid_map
auxv coredump_filter fd/ loginuid mounts oom_adj projid_map setgroups status wchan
cgroup cpuset fdinfo/ map_files/ mountstats oom_score root/ smaps syscall
clear_refs cwd/ gid_map maps net/ oom_score_adj sched stack task/
复制
综上,内核通过一个个的文件来暴露自己的系统配置信息,这些文件,有些是只读的,有些是可写的,有些是动态变化的,比如进程文件,当应用程序读取某个 /proc/
文件时,内核才会去注册这个文件,然后再调用一组内核函数来处理,将相应的内核参数拷贝到用户态空间,这样用户读这个文件就可以获取到内核的信息。一个大概的图示如下所示:
sysctl
我们熟悉的 sysctl 是一个 Linux 命令,man sysctl
可以看到它的功能和用法。它主要是被用来修改内核的运行时参数,换句话说,它可以在内核运行过程中,动态修改内核参数。
它本质上还是用到了文件的读写操作,来完成用户态和内核态的通信。它使用的是 /proc
的一个子目录 /proc/sys
。和 procfs 的区别在于:
procfs 主要是输出只读数据,而 sysctl 输出的大部分信息是可写的。
例如,我们比较常见的是通过 cat /proc/sys/net/ipv4/ip_forward
来获取内核网络层是否允许转发 IP 数据包,通过 echo 1 > /proc/sys/net/ipv4/ip_forward
或者 sysctl -w net.ipv4.ip_forward=1
来设置内核网络层允许转发 IP 数据包。
同样的操作,Linux 也提供了文件 /etc/sysctl.conf
来让你进行批量修改。
sysfs
sysfs 是 Linux 2.6 才引入的一种虚拟文件系统,它的做法也是通过文件 /sys
来完成用户态和内核的通信。和 procfs 不同的是,sysfs 是将一些原本在 procfs 中的,关于设备和驱动的部分,独立出来,以 “设备树” 的形式呈现给用户。
sysfs 不仅可以从内核空间读取设备和驱动程序的信息,也可以对设备和驱动进行配置。
我们看下 /sys
下有什么:
# ls /sys
block bus class dev devices firmware fs hypervisor kernel module power
复制
可以看到这些文件基本上都跟计算机的设备和驱动等息息相关的。更多关于这些文件的解释大家可以自行了解,这里就不过多展开了。
netlink
netlink 是 Linux 用户态与内核态通信最常用的一种方式。Linux kernel 2.6.14 版本才开始支持。它本质上是一种 socket,常规 socket 使用的标准 API,在它身上同样适用。比如创建一个 netlink socket,可以调用如下的 socket 函数:
#include <asm/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
netlink_socket = socket(AF_NETLINK, socket_type, netlink_family);
复制
netlink 这种灵活的方式,使得它可以用于内核与多种用户进程之间的消息传递系统,比如路由子系统,防火墙(Netfilter),ipsec 安全策略等等。
引申:
net-tools
工具通过 procfs(/proc) 和 ioctl 系统调用去访问和改变内核网络参数配置,而 iproute2
则通过 netlink 套接字接口与内核通信,前者已经被淘汰了,后者逐步成为标准。
Netlink
Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是应用程序与内核通信的最常用的接口方式之一。
优势:
1、 基于套接字方式,对于熟悉网络编程的开发者来说相对简单,内核态代码也只需要做简单初始化就能完成通信。
2、 用户态可以使用epoll 形式,不过多占用CPU资源
限制:
1、Netlink 目前只支持32个通道,其中内核自己就用了20个通道左右,也就是剩余通道数有限,不能复用。
2、对于网络编程不熟悉的开发者,如果这个通信过程中出问题,或者出现丢包,但是应用中不允许出现丢包问题时,比较难定位
3、仅限于通信,如果需要操作硬件还需要在内核态代码中增加相应处理
UIO
UIO主要包含两个功能:一是映射物理内存空间到用户态,使用户态程序能直接操作硬件寄存器或者部分内核申请的内存;另一个是通过设备状态的变化通知用户态程序有中断产生。
优势:
1、 用户态程序能感知到中断状态
2、 用户态程序可以直接操作内存或者寄存器
3、 可以通过直接操作内存进行交互,少了拷贝的动作,效率较高
劣势:
1、相对于netlink来说内核代码稍微复杂一些,涉及到一些中断处理以及设备创建
2、一个UIO设备只能映射4个地址,如果设备的寄存器多且地址分散,可能需要开多个UIO设备才能满足操作需求。
3、如果通过内存方式交互,竞争处理方式比较复杂,需要开发者用户态和内核态程序操作同一份内存,而不会相互覆盖与竞争。
IOCTL
Ioctl 是内核比较早的一种用户态内核态的交互方式,用户态程序通过命令的方式调用ioctl函数,然后内核态分发到对应驱动处理,最后将处理结果返回到用户态。
优势:
1、IOCTL支持的版本较多,资料非常详细
2、用户态编码简单
劣势:
1、 IOCTL由于支持的版本较老,新内核有可能不再支持
2、 IOCTL内核态是根据传入的cmd命令字来解析,如果涉及到多个命令需要编写多个函数,代码量相对较多
3、 相对于内存交互,IOCTL需要解析命令字,才能跳转对应处理函数,同时也有拷贝动作
效率较低
总结
Linux 用户态和内核态通信主要的四种方式,其中 netlink 和 procfs 是最常见的方式。