《linux系统内核设计与实现》-内核空间和用户空间的概念以及内核空间和用户空间的数据拷贝

news2024/11/28 14:44:32

1 内核空间和用户空间的概念

用户空间:0-3G

内核空间:3-4G

PAGE_OFFSET配置用户空间和内核空间的界限

分离的原因:

(1)处理器模式不同,权限不同

对于x86体系的cpu,用户空间代码运行在Ring3模式,内核空间代码运行Ring o模式;

对于arm体系的cpu,用户空间代码运行在usr模式,内核空间代码运行在svc模式;

(2)安全考量

整个系统中有各种资源,比如计算资源、内存资源和外设资源,而linux是多用户、多进程系统,所以,这些资源必须在受限的、被管理的状态下使用,要不然就陷入了混乱。至间隔离可以保证即便是单个应用程序出现错误也不会影响到操作系统的稳定性

(3)从软件设计思想来看,解除了核心代码和业务逻辑代码的耦合内核代码偏重于系统管理;而用户空间代码(也即应用程序)偏重于业务逻辑代码的实现。两者分工不同,隔离也是解耦。

用户空间的代码如何调用到内核空间中:

基于最早的字符设备代码修改: 《linux系统内核设计与实现》-实现最简单的字符设备驱动-CSDN博客

除了两个函数需要修改外,变量也需要修改

#define BUFFER_MAX (32) // buff大小

char buffer[BUFFER_MAX]; // 缓冲区

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>

#define BUFFER_MAX  (32)             // buff大小
#define OK          (0)
#define ERROR       (-1)

struct cdev *gDev;                   // 字符设备结构体指针
struct file_operations *gFile;       // 文件操作结构体指针
dev_t devNum;                        // 设备号
unsigned int subDevNum = 1;          // 注册设备的数量

int reg_major  = 232;                // 主设备号
int reg_minor =  0;                  // 次设备号
char buffer[BUFFER_MAX];             // 缓冲区

/**
 * printk 是内核中用于输出调试信息、错误信息和其他日志信息的函数。
 * 它将消息输出到内核日志缓冲区,这些日志可以通过 dmesg 命令查看
 * KERN_INFO 是一个宏:信息性消息
*/

// hello_open函数:打开文件
int hello_open(struct inode *p, struct file *f)
{
    printk(KERN_INFO "hello_open\r\n");
    return 0;
}

// hello_write函数
ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)
{
    printk(KERN_INFO "hello_write\r\n");
    int writelen = 0;
    writelen = BUFFER_MAX > s ? s : BUFFER_MAX;
    // 把用户空间数据拷贝到内核空间。把u拷到buffer,长度为writelen
    // 返回值是拷贝失败的字节的个数
    if (copy_from_user(buffer, u, writelen)) {
        return -EFAULT;
    }
    return writelen;
}

// hello_read函数
ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l)
{
    printk(KERN_INFO "hello_read\r\n");
    int readlen;
    readlen = BUFFER_MAX > s ? s : BUFFER_MAX;
    // 把内核空间数据拷贝到用户空间。把u拷到buffer,长度为readlen
    if (copy_to_user(u, buffer, readlen)) {
        return -EFAULT;
    }
    return readlen;
}

int hello_init(void)
{
    devNum = MKDEV(reg_major, reg_minor);                   // 根据主次设备号,生成devNum,唯一标识设备
    // 把设备号注册到内核中。从devNum开始注册subDevNum个设备。
    if (OK == register_chrdev_region(devNum, subDevNum, "helloworld")) {
        printk(KERN_INFO "register_chrdev_region ok \n");
    } else {
        printk(KERN_INFO "register_chrdev_region error n");
        return ERROR;
    }
    
    printk(KERN_INFO " hello driver init \n");
    // 内核模块成功分配并初始化了一个字符设备结构
    gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);
    // 文件操作
    gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
    // 赋值。回调函数
    gFile->open = hello_open;
    gFile->read = hello_read;
    gFile->write = hello_write;
    gFile->owner = THIS_MODULE;
    // 建立联系:通过这两行代码,驱动程序完成了字符设备的初始化和注册,使得字符设备可以被用户进程打开、读取和写入。
    cdev_init(gDev, gFile);             // 初始化字符设备结构体gDev,并将其与文件操作结构体gFile关联起来。
    cdev_add(gDev, devNum, 1);          // 将初始化好的字符设备gDev 添加到内核字符设备层,使其成为一个有效的字符设备,可以被用户空间访问和操作
    return 0;
}

// 驱动退出函数
void __exit hello_exit(void)
{
    printk(KERN_INFO " hello driver exit \n");
    cdev_del(gDev);                                         // 删除字符设备
    kfree(gFile);                                           // 释放内存
    kfree(gDev);                                            // 释放内存
    unregister_chrdev_region(devNum, subDevNum);            // 注销设备号
    return;
}

module_init(hello_init);        // 声明驱动的入口函数。执行insmod的时候调用
module_exit(hello_exit);        // 声明驱动的退出函数。执行rmmod的时候调用
MODULE_LICENSE("GPL");          // 指定模块的许可证

记得先把原来的驱动卸载

dmesg -c			# 清零内核日志
insmod helloDev.ko  # 插入(加载)Linux内核模块的命令
dmesg				# dmesg 命令用于查看和管理 Linux 内核的环形缓冲区中的消息
lsmod				# 查看驱动
rmmod helloDev.ko   # 卸载驱动

# 创建设备文件:名字 类型 【主设备号  次设备号】
mknod [OPTION]... NAME TYPE [MAJOR MINOR]
mknod /dev/hello c 232 0

write.c:将用户数据拷贝到内核空间

/*************************************************************************
 > File Name: write.c
 > Author: Winter
 > Created Time: 2024年05月18日 星期六 18时58分19秒
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/select.h>
#include <fcntl.h>

#define DATA_NUM         (32)
int main(int argc, char* argv[])
{
     int fd, i;
     int w_len;
     fd_set fdset;
     char buf[DATA_NUM] = "hello world";
     fd = open("/dev/hello", O_RDWR);
     if (-1 == fd) {
          perror("open file error\n");
          return -1;
     } else {
          printf("open successe\n");
     }

     w_len = write(fd, buf, DATA_NUM);
     if (w_len == -1) {
          perror("write error\n");
          return -1;
     }
     
     printf("write len: %d\n", w_len);
     close(fd);
     return 0;
}

read.c:把内核空间的数据拷贝到内核空间

/*************************************************************************
 > File Name: read.c
 > Author: Winter
 > Created Time: 2024年05月18日 星期六 18时58分26秒
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/select.h>
#include <fcntl.h>

#define DATA_NUM         (32)
int main(int argc, char* argv[])
{
     int fd, i;
     int r_len;
     fd_set fdset;
     char buf[DATA_NUM];
     memset(buf, 0, DATA_NUM);
     fd = open("/dev/hello", O_RDWR);
     if (-1 == fd) {
          perror("open file error\n");
          return -1;
     } else {
          printf ("open successe\n");
     }

     r_len = read(fd, buf, DATA_NUM);
     if (r_len == -1) {
          perror("write error\n");
          return -1;
     }
     
     printf("read len: %d\n", r_len);
     close(fd);
     return 0;
}

先执行write,再执行read

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1902928.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

时间、查找、打包、行过滤与指令的运行——linux指令学习(二)

前言&#xff1a;本节内容标题虽然为指令&#xff0c;但是并不只是讲指令&#xff0c; 更多的是和指令相关的一些原理性的东西。 如果友友只想要查一查某个指令的用法&#xff0c; 很抱歉&#xff0c; 本节不是那种带有字典性质的文章。但是如果友友是想要来学习的&#xff0c;…

如何创建和规划 PMO 团队,并发挥其最大实际价值

在当今复杂多变的商业环境中&#xff0c;项目管理办公室&#xff08;PMO&#xff09;已成为许多组织中不可或缺的一部分。PMO 团队能够为企业的项目管理提供战略指导、资源协调、流程优化等支持&#xff0c;从而提升项目的成功率和组织的整体绩效。那么&#xff0c;如何创建和规…

【React】React18 Hooks 之 useReducer

目录 useReducer案例1&#xff1a;useReducer不带初始化函数案例2&#xff1a;useReducer带初始化函数注意事项1&#xff1a;dispatch函数不会改变正在运行的代码的状态注意事项2&#xff1a;获取dispatch函数触发后 JavaScript 变量的值注意事项3&#xff1a;触发了reducer&am…

【沐风老师】3DMAX样条线增强工具SplinePro使用方法详解

3DMAX样条线增强工具SplinePro使用教程 3DMAX样条线增强工具SplinePro&#xff0c;允许创建选定的多条样条曲线形状的轮廓并删除交叉点。 【适用版本】 3dMax2019 - 2025 【安装方法】 1.解压缩后&#xff0c;确认SplinePro-0.2.0.mse和logo.png两个文件在同一文件夹中。 2.…

移动校园(4):数据处理(sql server数据库)

昨天写入数据库后的数据 可以看到classname和timeandlocation有多个值&#xff0c;所以需要进行数据处理 let reawait req.app.locals.db.query(select distinct classname from courses)let data[]re.recordset.map((value)>{let namesvalue.classname.split(,)names.map(…

那你真的了解方法调用吗?

方法调用是不是很熟悉&#xff1f;那你真的了解它吗&#xff1f;今天就让我们来盘一下它。 首先大家要明确一个概念&#xff0c;此处的方法调用并不是方法中的代码被执行&#xff0c;而是要确定被调用方法的版本&#xff0c;即最终会调用哪一个方法。 之前我们了解到&#xff…

【Android】自定义换肤框架05之Skinner框架集成

引入依赖 api("io.github.hellogoogle2000:android-skinner:1.0.0")初始化Skinner 在所有功能前调用即可&#xff0c;建议在Application中初始化 SkinnerKit.init(application)安装皮肤包 在应用该皮肤包前安装即可&#xff0c;建议预安装&#xff0c;或应用皮肤…

【反悔堆 反悔贪心】2813. 子序列最大优雅度

本文涉及知识点 反悔堆 反悔贪心 LeetCode 2813. 子序列最大优雅度 给你一个长度为 n 的二维整数数组 items 和一个整数 k 。 items[i] [profiti, categoryi]&#xff0c;其中 profiti 和 categoryi 分别表示第 i 个项目的利润和类别。 现定义 items 的 子序列 的 优雅度 可…

如何在 PostgreSQL 中实现数据的增量备份和恢复?

文章目录 一、增量备份的原理二、准备工作&#xff08;一&#xff09;环境配置&#xff08;二&#xff09;创建测试数据库和表&#xff08;三&#xff09;插入初始数据 三、全量备份四、基于时间点的增量备份&#xff08;一&#xff09;开启 WAL 归档&#xff08;二&#xff09…

网页封装APP:让您的网站变身移动应用

网页封装APP&#xff1a;让您的网站变身移动应用 随着移动设备的普及&#xff0c;越来越多的人开始使用移动设备浏览网站。但是&#xff0c;传统的网站设计并不适合移动设备的屏幕尺寸和交互方式&#xff0c;这导致了用户体验不佳和流失。 有没有办法让您的网站变身移动应用&…

TXT文本处理新篇章:告别繁琐,一键批量删除单号间空白行,引领高效管理新潮流!

在繁忙的商务环境中&#xff0c;文本处理往往占据了大量的时间和精力。特别是那些充斥着订单、单号等关键信息的TXT文本文件&#xff0c;一旦处理不当&#xff0c;就可能引发一系列问题。空白行&#xff0c;这个看似微不足道的小细节&#xff0c;却常常成为我们高效处理文本的绊…

C++ | Leetcode C++题解之第220题存在重复元素III

题目&#xff1a; 题解&#xff1a; class Solution { public:int getID(int x, long w) {return x < 0 ? (x 1ll) / w - 1 : x / w;}bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {unordered_map<int, int> mp;int n nums.si…

python根据父母身高预测儿子身高

题目 从键盘输入父母的升高&#xff0c;并使用eval()或float()转换输入的数据类型。计算公式&#xff1a;儿子身高&#xff08;父亲身高母亲身高&#xff09;*0.54. father_heighteval(input(请输入爸爸的身高&#xff1a;)) mother_heighteval(input(请输入妈妈的身高&#…

普通Java工程如何在代码中引用docker-compose.yml中的environment值

文章目录 一、概述二、常规做法1. 数据库配置分离2. 代码引用配置3. 编写启动类4. 支持打包成可执行包5. 支持可执行包打包成docker镜像6. docker运行 三、存在问题分析四、改进措施1. 包含environment 变量的编排文件2. 修改读取配置文件方式3. 为什么可以这样做 五、运行效果…

【项目日记(一)】梦幻笔耕-数据层实现

❣博主主页: 33的博客❣ ▶️文章专栏分类:项目日记◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多项目内容 目录 1.前言2.后端模块3数据库设计4.mapper实现4.1UserInfoMapper4.2BlogMapper 5.总结 1.…

20240707 每日AI必读资讯

&#x1f9e0;中国生成式AI专利数量超过美国 6 倍 - 中国在2014年至2023年期间申请的生成式AI专利数量达到38210个&#xff0c;超过了美国的6倍。 - 腾讯、平安保险集团和百度是GenAI专利数量最多的中国公司。 - 中国的顶级学术机构和技术生态为生成式AI的发展提供了强大支持…

初学嵌入式是弄linux还是单片机?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「单片机的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“666”之后私信回复“666”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;1、先入门了51先学了89c52…

Ubuntu 22.04 LTS 上安装 MySQL8.0.23(在线安装)

目录 在线安装MySQL 步骤1&#xff1a;更新软件包列表 步骤2&#xff1a;安装MySQL服务器 步骤3&#xff1a;启动MySQL服务 步骤4&#xff1a;检查MySQL状态 步骤5&#xff1a;修改密码、权限 在线安装MySQL 步骤1&#xff1a;更新软件包列表 在进行任何软件安装之前&a…

绘唐3最新版本哪里下载

绘唐3最新版本哪里下载 绘唐最新版本下载地址 推文视频创作设计是一种通过视频和文字的形式来进行推广的方式&#xff0c;可以通过一些专业的工具来进行制作。 以下是一些常用的小说推文视频创作设计工具&#xff1a; 视频剪辑软件&#xff1a;如Adobe Premiere Pro、Fina…

Postman深度解析:打造高效接口测试自动化流程

《Postman深度解析&#xff1a;打造高效接口测试自动化流程》 一、概述与Postman核心优势 1. 接口测试的重要性与挑战 接口测试是确保软件系统各组成部分能够正确交互的关键环节。随着现代软件系统的复杂性增加&#xff0c;接口的数量和类型也在不断增长&#xff0c;这给接口测…