Linux的内核空间和用户空间是怎么回事儿?驱动程序位于内核空间中,与用户空间的数据传递通过copy_to_user和copy_from_user进行;Ring 0和Ring 3是啥意思?

01-内核空间与用户空间是怎么回事儿

  1. 内核空间(Kernel Space)

    • 是操作系统的核心部分,负责管理硬件资源和提供系统服务。
    • 驱动程序作为内核的一部分运行在内核空间。
    • 在内核空间中,代码可以直接访问硬件设备和系统资源(如内存、I/O 端口等)。
  2. 用户空间(User Space)

    • 是普通应用程序运行的地方,与内核空间隔离。
    • 应用程序通过系统调用(如 read, write, ioctl 等)与内核交互,但不能直接访问内核空间中的资源。
  3. 内核空间的特点

    • 特权模式:内核代码运行在 CPU 的高特权级(通常是 Ring 0),可以直接访问硬件。
    • 无内存保护:内核代码可以访问任意内存地址,因此需要开发者小心避免越界访问。
    • 直接硬件访问:可以操作硬件设备的寄存器和内存,但需要遵循硬件协议。
    • 实时性更高:由于内核空间代码直接运行在内核中,响应时间比用户空间应用更短。
    • 注意:内核空间具有高特权,但也需要更加谨慎的编程,以避免影响系统稳定性。

02-内核空间和用户空间分别有自己的堆、栈、数据段、代码段

关于这个问题的详细说明见我的另一博文: https://blog.youkuaiyun.com/wenhao_ir/article/details/144826850

03-Linux系统中的Ring 0和Ring 3是啥意思?

可简单的理解为内核空间就是Ring 0,用户空间就是Ring 3,下面是详细解释:

在Linux系统中,Ring 0Ring 3是与CPU的特权级别(Privilege Level)相关的概念,来源于x86架构的硬件设计。它们是CPU保护模式下的不同运行级别,用于区分操作系统和应用程序的权限,以确保系统的安全性和稳定性。

特权级别(Rings)的基本概念

x86架构定义了4个特权级别,从Ring 0Ring 3

  • Ring 0:最高权限级别(内核态,Kernel Mode)。
  • Ring 1 和 Ring 2:中间权限级别(很少使用)。
  • Ring 3:最低权限级别(用户态,User Mode)。

在实际使用中,现代操作系统(包括Linux)通常只使用Ring 0Ring 3


Ring 0(内核态,Kernel Mode)

  1. 作用

    • 用于运行操作系统的内核代码和驱动程序。
    • 拥有对硬件资源的完全访问权限(包括CPU指令集、内存、I/O设备等)。
    • 可以执行特权指令(例如访问硬件寄存器、控制中断等)。
  2. 特性

    • 没有任何限制,可以直接操作硬件和管理资源。
    • 一旦出现错误(例如非法访问内存),可能导致整个系统崩溃。
  3. 典型例子

    • Linux内核代码。
    • 驱动程序代码。

Ring 3(用户态,User Mode)

  1. 作用

    • 用于运行用户程序(应用程序),例如Web浏览器、文本编辑器等。
    • 通过系统调用(System Call)与内核交互,获取服务(如文件读写、网络通信)。
  2. 特性

    • 权限受限,无法直接访问硬件或执行特权指令。
    • 任何试图越权操作都会被CPU拦截,并可能触发异常。
    • 提高系统的稳定性和安全性:用户程序崩溃不会影响系统内核。
  3. 典型例子

    • 用户空间程序,例如lscat等命令。
    • 应用程序和库(如glibc)。

两者的区别和联系

特性Ring 0 (内核态)Ring 3 (用户态)
权限最高权限,无限制最低权限,受严格限制
运行位置操作系统内核及驱动程序用户空间程序及库
硬件访问可直接访问硬件资源需通过内核提供的接口
指令执行可执行所有指令只能执行非特权指令
稳定性和安全性易受错误影响(整个系统)崩溃只影响当前程序

Linux系统的设计

  1. Linux将内核运行在Ring 0,用户进程运行在Ring 3
  2. 用户进程通过系统调用进入内核模式(Ring 0),请求内核服务。
  3. 通过这种机制,Linux系统在性能与安全性之间达到了良好的平衡:
    • 核心功能运行在高权限模式,效率高。
    • 应用程序运行在低权限模式,减少错误和恶意代码的风险。

04-驱动程序运行于内核空间

当驱动程序通过 insmodmodprobe 加载到内核中后,便成为内核的一部分,并运行在内核空间。当用户程序通过系统调用(如 open, read, write)访问驱动程序时,内核会切换到内核空间执行驱动程序中的相应函数(如 file_operations 中的 read, write)。

05-请给出一段典型的运行于用户空间的程序

下面这段代码就是典型的运行于用户空间的程序


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 * ./hello_drv_test -w abc
 * ./hello_drv_test -r
 */
int main(int argc, char **argv)
{
	int fd;
	char buf[1024];
	int len;
	
	/* 1. 判断参数 */
	if (argc < 2) 
	{
		printf("Usage: %s -w <string>\n", argv[0]);
		printf("       %s -r\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open("/dev/hello", O_RDWR);
	if (fd == -1)
	{
		printf("can not open file /dev/hello\n");
		return -1;
	}

	/* 3. 写文件或读文件 */
	if ((0 == strcmp(argv[1], "-w")) && (argc == 3))
	{
		len = strlen(argv[2]) + 1;
		len = len < 1024 ? len : 1024;
		write(fd, argv[2], len);
	}
	else
	{
		len = read(fd, buf, 1024);		
		buf[1023] = '\0';
		printf("APP read : %s\n", buf);
	}
	
	close(fd);
	
	return 0;
}

06-内核空间和用户空间怎么样实现数据传递

驱动程序中所有的变量(包括全局变量和局部变量)都属于内核空间中的数据,假设在假如在驱动程序中定义了一个全局变量型的字符数组:

static char kernel_buf[1024];

由于在Linux中,内核空间和用户空间是隔离的,所以用户空间的程序是不能直接访问或修改这个数组的数据的。

通常用内核函数copy_to_usercopy_from_user实现数据传递,下面介绍这两个函数。
备注:也可以使用用户空间中的内核函数mmap调用驱动程序中的mmap函数将驱动程序中的数据对应的内存映射到用户空间中,关于这一点见下面的博文:
https://blog.youkuaiyun.com/wenhao_ir/article/details/145328555
https://blog.youkuaiyun.com/wenhao_ir/article/details/144489705

07-函数copy_to_usercopy_from_user

copy_to_usercopy_from_user 是 Linux 内核中专门用于在用户空间和内核空间之间交换数据的媒介函数。它们是实现数据传递的核心 API,能够在内核和用户空间之间安全地复制数据,同时避免直接访问用户空间内存可能引发的安全和稳定性问题。


为什么需要这些函数?

在 Linux 中,用户空间和内核空间是分离的:

  1. 用户空间(User Space)
    • 普通程序运行的空间。
    • 对内核空间没有直接访问权限。
  2. 内核空间(Kernel Space)
    • 负责底层操作系统功能。
    • 必须对用户空间进行严格保护,避免恶意或意外行为影响系统稳定性。

由于用户空间与内核空间是隔离的,直接访问对方内存可能导致非法内存访问。因此,Linux 提供了安全的 API,比如 copy_to_usercopy_from_user,以确保数据的安全传递。


两个函数的作用

copy_to_user
  • 功能:将内核空间的数据复制到用户空间。
  • 使用场景:用户调用 read 系统调用时,驱动程序通过该函数将数据传递给用户程序。
copy_from_user
  • 功能:从用户空间复制数据到内核空间。
  • 使用场景:用户调用 write 系统调用时,驱动程序通过该函数接收用户程序的数据。

两个函数的参数和返回值

copy_to_user 原型
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
  • to: 用户空间的目标地址(缓冲区)。
  • from: 内核空间的源地址(缓冲区)。
  • n: 要复制的字节数。
copy_from_user 原型
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
  • to: 内核空间的目标地址(缓冲区)。
  • from: 用户空间的源地址(缓冲区)。
  • n: 要复制的字节数。
返回值
  • 如果复制成功,返回 0
  • 如果复制失败,返回未成功复制的字节数(通常是因为地址无效或权限问题)。

工作流程

write 数据流:用户空间 → 内核空间
  1. 用户空间程序调用 write(fd, user_buf, size),将数据写入设备文件。
  2. 内核将 user_buf 地址和大小传递到驱动程序的 write 函数。
  3. 驱动程序通过 copy_from_user 从用户空间的 user_buf 复制数据到内核空间缓冲区。
read 数据流:内核空间 → 用户空间
  1. 用户空间程序调用 read(fd, user_buf, size),从设备文件读取数据。
  2. 驱动程序通过 copy_to_user 将内核空间缓冲区中的数据复制到用户空间的 user_buf
  3. 用户程序接收数据。

安全性保障

  1. 地址合法性检查

    • 这两个函数会检查用户空间地址是否有效,防止非法内存访问。
    • 如果地址越界或权限不足,会返回未成功复制的字节数。
  2. 数据隔离

    • 避免直接访问用户空间地址引发的错误或安全漏洞。
    • 通过受控的方式完成数据交换。
  3. 内存保护

    • 确保内核无法被用户空间直接操作,防止系统崩溃或数据泄露。

示例

用户空间程序
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main() {
    char write_data[] = "Hello, Kernel!";
    char read_data[128] = {0};
    int fd = open("/dev/hello", O_RDWR);

    if (fd < 0) {
        perror("Failed to open device");
        return -1;
    }

    // 写数据到设备
    write(fd, write_data, strlen(write_data));

    // 从设备读取数据
    read(fd, read_data, sizeof(read_data));
    printf("Data from kernel: %s\n", read_data);

    close(fd);
    return 0;
}
驱动程序(运行于内核空间)
// kernel_buf由于是驱动程序中的数组,所以属于内核空间中的数组,它里面存储的数据属于内核空间中的数据
static char kernel_buf[128];

static ssize_t hello_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    size_t to_copy = min(sizeof(kernel_buf), size);
    size_t not_copied = copy_from_user(kernel_buf, buf, to_copy);

    if (not_copied)
        return -EFAULT;

    printk("Kernel received: %s\n", kernel_buf);
    return to_copy;
}

static ssize_t hello_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
    size_t to_copy = min(sizeof(kernel_buf), size);
    size_t not_copied = copy_to_user(buf, kernel_buf, to_copy);

    if (not_copied)
        return -EFAULT;

    printk("Kernel sent: %s\n", kernel_buf);
    return to_copy;
}

小结

  • copy_to_usercopy_from_user 是用户数据与内核数据交换的媒介函数。
  • 它们通过安全的方式完成用户空间和内核空间之间的数据传递,避免直接访问带来的风险。
  • 这些函数广泛用于 Linux 驱动程序中,确保内核和用户程序的可靠通信。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

昊虹AI笔记

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值