Linux内核——内核空间与用户空间的数据拷贝

跟着简叔学的,可以B站搜索简说linux

1.32位系统内核空间和用户空间的默认大小

1678066147846

内核空间运行在高地址空间,用户空间在低地址空间。之所以要做这样的划分。出于安全考量,内核需要更高的权限,已屏蔽用户区的不安全操作。从软件设计思想来开,内核代码偏重于系统管理;用户空间的代码偏重于业务逻辑代码的实现。 注意这只是逻辑地址,并不是物理地址。这中间有一个映射的过程。

陷入内核态一般有三种情况:

系统调用,定时器中断,外设中断 处理完中断再返回用户空间的应用程序

2.x86段页式内存管理荷叶表映射机制

linux内核页表映射机制:线性地址如何转为物理地址? - 哔哩哔哩 (bilibili.com)

两步走,逻辑地址转化成线性地址,再转化成物理地址。部分架构逻辑地址就是线性地址,基址+offset 找到线性地址空间,通过把基址设为0来实现。那么从线性地址转化成逻辑地址就是我们说的页表映射。。

1678079204392

不同进程有不同的页目录表,所以他们可以不冲突的访问相同的逻辑地址(在他们各自的视角里),因为他们的页目录表不一样。 线性地址 总共32位(页表目录索引找到页表10位,页表索引找到物理地址基址10位,偏移12位)

3.对之前写的hello驱动的读写函数进行简单修改

  1. 代码改动如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //
    ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)
    {
    printk(KERN_EMERG"hello_write\r\n");
    int writenlen = 0;
    writenlen = BUFFER_MAX>s?s:BUFFER_MAX;
    if(copy_from_user(buffer,u,writenlen))
    {
    return -EFAULT;
    }
    return writelen;
    }
    ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l)
    {
    printk(KERN_EMERG"hello_read\r\n");
    int readlen;
    readlen = BUFFER_MAX>s?s:BUFFER_MAX;
    if(copy_to_user(u,buffer,readlen))
    {
    return -EFAULT;
    }
    return readlen;
    }

    其中,copy_from_usercopy_to_user实现了用户空间和内核空间的数据拷贝。驱动程序肯定是在内核空间 const char __user *u则是用户空间的地址。所以看起来可能是反的,因为以前我们的视角是用户程序在用户空间。而且要注意,copy_from_usercopy_to_user他们的参数顺序哦。

    重新对驱动代码进行编译,插入。然后编译测试代码,进行测试,产生了段错误。

    1
    2
    3
    3
    open successe
    Segmentation fault

    写入操作发生段错误,经过检查发现,原来驱动代码中,定义了内核空间的buffer指针,却没有开辟空间。

    1
    char * buffer;

    替换为

    1
    char buffer[BUFFER_MAX];

    重新测试。

    1678070706739

    发现success拼写错了,无伤大雅无伤大雅。。。。。

    代码表示两个用户程序,读写内核空间数据。

1678070706739|1678070706739
—- | —-