操作系统基础:用户态与内核通信的方式

上次组会老师提到了在操作系统中内核的级别更高,所以是可以实现从内核读取用户态数据的。

首先解释区分内核空间与用户空间的原因:
在 CPU 的所有指令中,有些指令是非常危险的,如果错用,将导致系统崩溃,比如清内存、设置时钟等。如果允许所有的程序都可以使用这些指令,那么系统崩溃的概率将大大增加。(本质上是要提高操作系统的稳定性及可用性)

所以,CPU 将指令分为特权指令和非特权指令,对于那些危险的指令,只允许操作系统及其相关模块使用,普通应用程序只能使用那些不会造成灾难的指令。比如 Intel 的 CPU 将特权等级分为 4 个级别:Ring0~Ring3。

其实 Linux 系统只使用了 Ring0 和 Ring3 两个运行级别(Windows 系统也是一样的)。当进程运行在 Ring3 级别时被称为运行在用户态,而运行在 Ring0 级别时被称为运行在内核态。

内核态与用户态的定义: 当进程运行在内核空间时就处于内核态,而进程运行在用户空间时则处于用户态。

从内核空间访问用户空间

将用户空间数据传到内核态,之前提到了使用伪文件系统 或者 copy_from_user,前者我比较熟悉了,这里主要调研、记录一下后者。

伪文件系统

通过自定义伪文件系统,或者使用securityfs创建相应接口,可以实现在用户态修改内核空间中的数据(需要root),基本原理是将对某文件的读、写重定向到相应函数,需要在内核中实现相应函数修改或读取相应数据。

值得一提的是,通过伪文件系统这种方式,输入一般是写一个字符串。

copy_from_user

事实上部分伪文件系统里也会用到这个函数,以将字符串copy到内核空间。

to:将数据拷贝到内核的地址
from:需要拷贝数据的地址
n:拷贝数据的长度(字节)
也就是将@form地址中的数据拷贝到@to地址中去,拷贝长度是n
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
{
  might_sleep();
  if (access_ok(VERIFY_READ, from, n))
     n = __copy_from_user(to, from, n);
  else
     memset(to, 0, n);
  return n;
}
 

内核有处理缺页异常功能。copy_from_user就是来处理用户态的指针所指向的虚拟地址没有映射到实际物理内存这种情况,这个现象在用户空间不是什么大事,缺页异常会自动提交物理内存,之后发生异常的指令正常运行;但是对于内核,如果用户空间的数据地址是个非法的地址,或是超出用户空间的范围,或是那些地址还没有被映射到,都可能对内核产生很大的影响,如oops、内核安全等。

在copy_from_user时,会去进行指针检查,然后如果没有映射,会触发page_fault,如果指针不合法,也会检查。这也正是copy_from_user的作用所在。

即使不copy_from_user, 内核也可以直接访问用户空间,例如通过memcpy( ),只是不安全:
(1)内核和用户程序之间是独立的地址空间,用户程序都是用的虚拟地址,同样的内存地址在用户空间和内核空间代表不同的意义;
(2)在内核和用户空间之间拷贝数据,是要考虑安全性的,比如要检查用户空间传入地址的有效性,不能因为用户空间传递的无效地址导致内核死机;
(3)内核对用户空间的访问是有限制的,不能让用户程序随意访问内核空间;

从用户空间访问内核空间

系统调用函数

在用户空间,通过系统调用函数来访问内核空间,这是最常用的一种用户态和内核态通信的方式。

procfs

procfs是进程文件系统的缩写,它是一个伪文件系统,不占用外部存储空间,只是占用少量的内存,通常是挂载在 /proc 目录下。

我们在该目录下看到的每个文件,都对应着一个内核变量。内核就是通过这个目录,以文件的形式展现自己的内部信息,相当于 /proc 目录为用户态和内核态之间的交互搭建了一个桥梁,用户态读写 /proc 下的文件,就是读写内核相关的配置参数。
在这里插入图片描述/proc/[pid]下存放着进程pid对应着的一系列数据:
在这里插入图片描述
比如常见的 /proc/cpuinfo、/proc/meminfo 和 /proc/net 就分别提供了 CPU、内存、网络的相关参数。除此之外,还有很多的参数,如下所示:
在这里插入图片描述

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 安全策略等等。

sysctl

sysctl 是一个 Linux 命令,主要被用来修改内核的运行时参数,即,它可以在内核运行过程中,动态修改内核参数。

本质上,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 下有什么:

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值