驅動-內核空間和用戶空間數據交換

內核空間與用戶控件數據交換
前面了解的字符設備中對 file_operations 結構體的進行了填充, 該
結構體的每一個成員都對應著一個系統調用, 例如 read、 write 等, 在字符設備相關的文章中有實驗過對
調用函數進行了標志打印, 并沒有真正實現設備的讀寫功能。 這里需要實現的功能其實就是內核空間和用戶控件之間的數據交換

文章目錄

  • 理解概念:內核空間與用戶空間
  • 參考資料
  • 用戶空間和內核空間數據交換
    • 用戶空間數據復制到內核空間 copy_from_user
    • 數據從內核空間拷貝到用戶空間copy_to_user
  • 實驗
    • 源碼程序 file.c
      • 方法 copy_to_user
      • 方法 copy_from_user
    • 編譯文件 Makefile
    • 測試程序
        • 源碼程序分析
    • 加載驅動 insmod file.ko
    • 執行程序 ./appfile
  • 總結


理解概念:內核空間與用戶空間

Linux 系統將可訪問的內存空間分為了兩個部分, 一部分是內核空間, 一部分是用戶空間。
操作系統和驅動程序運行在內核空間(內核態) , 應用程序運行在用戶空間(用戶態) 。
那么為什么要區分用戶空間和內核空間呢?
(1) 內核空間中的代碼控制了硬件資源, 用戶空間中的代碼只能通過內核暴露的系統調
用接口來使用系統中的硬件資源, 這樣的設計可以保證操作系統自身的安全性和穩定性。
(2) 從另一方面來說, 內核空間的代碼更偏向于系統管理, 而用戶空間中的代碼更偏重
業務邏輯實現, 倆者的分工不同。
硬件資源管理都是在內核空間完成的, 應用程序無法直接對硬件進行操作, 只能通過調用
相應的內核接口來完成相應的操作。 比如應用程序要對磁盤上的一個文件進行讀取, 應用程序
可以向內核發起一個“系統調用” 申請——我要讀取磁盤上的文件。 這個過程其實是通過一個
特殊的指令讓進程從用戶態進入到了內核態。 在內核空間中, CPU 可以執行任何命令, 包括
從磁盤上讀取數據, 具體過程是先把數據讀取到內核空間中, 然后再把數據拷貝到用戶空間并
從內核態切換到用戶態。 此時應用程序已經從系統調用中返回并拿到了想要的數據, 可以繼續
往下執行了。
進程只有從用戶空間切換到內核空間才可以使用系統的硬件資源, 切換的方式有三種: 系
統調用, 軟中斷, 硬中斷, 如下圖:
在這里插入圖片描述

參考資料

字符設備的基礎知識一定要了解,都是關聯的知識點
申請字符設備號
注冊字符設備
創建字符設備節點
字符設備驅動框架
雜項設備

用戶空間和內核空間數據交換

內核空間和用戶空間的內存是不能互相訪問的。 但是很多應用程序都需要和內核進行數據
的交換, 例如應用程序使用 read 函數從驅動中讀取數據, 使用 write 函數向驅動中寫數據, 上
述功能就需要使用 copy_from_user 和 copy_to_user 倆個函數來完成。 copy_from_user 函數是將
用戶空間的數據拷貝到內核空間。 copy_to_user 函數是將內核空間的數據拷貝到用戶空間。
這倆個函數定義在了 kernel/include/linux/uaccess.h 文件下

用戶空間數據復制到內核空間 copy_from_user

copy_from_user 用于將數據從用戶空間復制到內核空間。

unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);

參數說明

  • to: 內核空間的目標地址
  • from: 用戶空間的源地址
  • n: 要復制的字節

使用示例

char kernel_buf[BUFSIZE];
if (copy_from_user(kernel_buf, user_buf, count)) {// 處理錯誤return -EFAULT;
}

數據從內核空間拷貝到用戶空間copy_to_user

copy_to_user 用于將數據從內核空間復制到用戶空間。 函數原型如下:

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
  • to: 用戶空間的目標地址
  • from: 內核空間的源地址
  • n: 要復制的字節數

實驗

源碼程序 file.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>static dev_t dev_num;//定義dev_t類型(32位大小)的變量dev_num
static int major;  //定義int類型的主設備號major
static int minor;//定義int類型的 次設備號minor
static struct cdev cdev_test;  //定義struct cdev 類型結構體變量cdev_test,表示要注冊的字符設備
struct class *class_test;//定于struct class *類型結構體變量class_test,表示要創建的類
struct device *device;    //設備/*打開設備函數*/
static int chrdev_open(struct inode *inode, struct file *file)
{printk("This is chrdev_open \n");return 0;
}static ssize_t chrdev_read(struct file *file,char __user *buf, size_t size, loff_t *off)
{/*本章實驗重點******/char kbuf[32] = "This is read from kernel";//定義內核空間數據// copy_to_user:內核空間向用戶空間傳數據if (copy_to_user(buf, kbuf, strlen(kbuf)) != 0)     {printk("copy_to_user error\r\n"); //打印copy_to_user函數執行失敗return -1;}printk("This is cdev_test_read\r\n");return 0;
}/*向設備寫入數據函數*/
static ssize_t chrdev_write(struct file *file,const char __user *buf,size_t size,loff_t *off)
{/*本章實驗重點******/char kbuf[32] = {0};   //定義寫入緩存區kbufif (copy_from_user(kbuf, buf, size) != 0) // copy_from_user:用戶空間向內核空間傳數據{printk("copy_from_user error\r\n");//打印copy_from_user函數執行失敗return -1;}printk("This is cdev_test_write\r\n");printk("kbuf is %s\r\n", kbuf);return 0;
}
static int chrdev_release(struct inode *inode, struct file *file)
{return 0;
}static struct file_operations cdev_test_ops = {.owner=THIS_MODULE,//將owner字段指向本模塊,可以避免在模塊的操作正在被使用時卸載該模塊.open = chrdev_open,//將open字段指向chrdev_open(...)函數.read = chrdev_read,//將open字段指向chrdev_read(...)函數.write = chrdev_write,//將open字段指向chrdev_write(...)函數.release = chrdev_release,//將open字段指向chrdev_release(...)函數
};//定義file_operations結構體類型的變量cdev_test_opsstatic int __init chrdev_fops_init(void)//驅動入口函數
{int ret;//定義int類型的變量ret,用來判斷函數返回值ret=alloc_chrdev_region(&dev_num,0,1,"chardev_num"); //通過動態方式進行設備號注冊if(ret < 0){printk("alloc_chrdev_region is error\n");}   printk("alloc_chrdev_region is ok\n");major=MAJOR(dev_num);//通過MAJOR()函數進行主設備號獲取minor=MINOR(dev_num);//通過MINOR()函數進行次設備號獲取printk("major is %d\n",major);printk("minor is %d\n",minor);使用cdev_init()函數初始化cdev_test結構體,并鏈接到cdev_test_ops結構體cdev_init(&cdev_test,&cdev_test_ops);cdev_test.owner = THIS_MODULE;//將owner字段指向本模塊,可以避免在模塊的操作正在被使用時卸載該模塊 ret= cdev_add(&cdev_test,dev_num,1);if(ret < 0 ){printk("cdev_add is error\n");}printk("cdev_add is ok\n");class_test  = class_create(THIS_MODULE,"class_test");//使用class_create進行類的創建,類名稱為class_testdevice_create(class_test,NULL,dev_num,NULL,"device_test");//使用device_create進行設備的創建,設備名稱為device_testreturn 0;
}
static void __exit chrdev_fops_exit(void)//驅動出口函數
{cdev_del(&cdev_test);//使用cdev_del()函數進行字符設備的刪除unregister_chrdev_region(dev_num,1);//釋放字符驅動設備號 device_destroy(class_test,dev_num);//刪除創建的設備class_destroy(class_test);//刪除創建的類printk("module exit \n");}
module_init(chrdev_fops_init);//注冊入口函數
module_exit(chrdev_fops_exit);//注冊出口函數
MODULE_LICENSE("GPL v2");//同意GPL開源協議
MODULE_AUTHOR("wang fang chen "); //作者信息

源碼分析:
這里省略之前內容關聯知識的內容,重點看 內核空間和用戶控件數據交換部分內容。

方法 copy_to_user

讀取內核空間數據到用戶空間,定義了內核空間數據,其實就是驅動程序里面定義了字節數組
char kbuf[32] = “This is read from kernel”;//定義內核空間數據

copy_to_user 方法調用后, copy kbuf 數據到 buf里面去,buf 在方法里面的參數是一個用戶空間態指針,這樣就傳遞到用戶空間了。

static ssize_t chrdev_read(struct file *file,char __user *buf, size_t size, loff_t *off)
{/*本章實驗重點******/char kbuf[32] = "This is read from kernel";//定義內核空間數據// copy_to_user:內核空間向用戶空間傳數據if (copy_to_user(buf, kbuf, strlen(kbuf)) != 0)     {printk("copy_to_user error\r\n"); //打印copy_to_user函數執行失敗return -1;}printk("This is cdev_test_read\r\n");return 0;
}

方法 copy_from_user

從用戶空間數據copy 數據到內核空間
buf 數據是用戶態指針類型,調用 copy_from_user 方法后 kbuf 就有新的數據了,其實其實驅動程序里面的數據就是內核態數據,然后將其打印出來。

/*向設備寫入數據函數*/
static ssize_t chrdev_write(struct file *file,const char __user *buf,size_t size,loff_t *off)
{/*本章實驗重點******/char kbuf[32] = {0};   //定義寫入緩存區kbufif (copy_from_user(kbuf, buf, size) != 0) // copy_from_user:用戶空間向內核空間傳數據{printk("copy_from_user error\r\n");//打印copy_from_user函數執行失敗return -1;}printk("This is cdev_test_write\r\n");printk("kbuf is %s\r\n", kbuf);return 0;
}

編譯文件 Makefile

#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += file.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:make -C $(KDIR) M=$(PWD) modulesclean:make -C $(KDIR) M=$(PWD) clean

測試程序

appfile.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]) // 主函數
{int fd;                         // 定義 int 類型的文件描述符char buf1[32] = {0};            // 定義讀取緩存區 buf1char buf2[32] = "nihao";        // 定義寫入緩存區 buf2fd = open("/dev/test", O_RDWR); // 打開字符設備驅動if (fd < 0){perror("open error \n");return fd;}read(fd, buf1, sizeof(buf1));    // 從/dev/test 文件讀取數據printf("buf1 is %s \r\n", buf1); // 打印讀取的數據write(fd, buf2, sizeof(buf2));   // 向/dev/test 文件寫入數據close(fd);return 0;
}
源碼程序分析

這里關注read 方法,buf1 用戶態數據,讀取后 經過系統調用
copy_to_user(buf, kbuf, strlen(kbuf)),內核態數據copy 了一份到用戶態 buf1 中,然后打印出來。

需要 aarch64-linux-gnu-gcc 來編譯, 輸入以下命令, 編譯完成以后會生成一個可執行程序

aarch64-linux-gnu-gcc appfile.c -o appfile

生成可執行程序 appfile
在這里插入圖片描述

加載驅動 insmod file.ko

這里都是以前的基本知識,暫不擴展
在這里插入圖片描述

執行程序 ./appfile

結果如下: 不正是內核和用戶態數據交互結果嗎?
在這里插入圖片描述

總結

  • copy_from_user 和 copy_to_user 是 Linux 內核中用于在用戶空間和內核空間之間安全傳輸數據的兩個重要函數。
  • 內核和用戶態數據傳遞就是通過兩個方法調用來實現,回調到用戶態其實就是指針傳遞。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/75389.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/75389.shtml
英文地址,請注明出處:http://en.pswp.cn/web/75389.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

5G_WiFi_CE_DFS

目錄 一、規范要求 1、法規目錄 2、定義 3、運行模式 4、主/從設備相關的運行行為及具體的動態頻率選擇&#xff08;DFS&#xff09;要求 5、產品角色確定測試項目 6、測試項目 測試項1&#xff1a;信道可用性檢查&#xff08;Channel Availability Check&#xff09; …

Devops之GitOps:什么是Gitops,以及它有什么優勢

GitOps 定義 GitOps 是一種基于版本控制系統&#xff08;如 Git&#xff09;的運維實踐&#xff0c;將 Git 作為基礎設施和應用程序的唯一事實來源。通過聲明式配置&#xff0c;系統自動同步 Git 倉庫中的期望狀態到實際運行環境&#xff0c;實現持續交付和自動化運維。其核心…

【藍橋杯】單片機設計與開發,第十二屆

/*頭文件聲明區*/ #include <STC15F2K60S2.H>//單片機寄存器頭文件 #include <init.h>//初始化底層驅動頭文件 #include <led.h>//led,蜂鳴器,繼電器底層驅動頭文件 #include <key.h>//按鍵底層驅動頭文件 #include <seg.h>//數碼管底層驅動頭…

Vue3連接MQTT作為客戶端

先下載依賴 npx --yes --registry https://registry.npmmirror.com npm install mqtt 在src的api創建 mes.js // 導入axios import axios from axios;// 定義一個變量,記錄公共的前綴, baseURL const baseURL http://localhost:8080; const instance axios.create({ base…

主服務器和子服務器之間通過NFS實現文件夾共享

背景&#xff1a; 子服務器想做一個備份服務器 但是之前有很多文件是上傳到本地的&#xff0c;于是服務要從本地讀取文件 但是在不在同一臺服務器中&#xff0c;讀取就會有問題&#xff0c;想 實現在兩者之間創建一個共享文件夾 一 NFS掛載步驟&#xff1a; 在主服務器&#…

LeetCode算法題(Go語言實現)_39

題目 給定一個二叉樹的根節點 root&#xff0c;想象自己站在它的右側&#xff0c;按照從頂部到底部的順序&#xff0c;返回從右側所能看到的節點值。 一、代碼實現 type TreeNode struct {Val intLeft *TreeNodeRight *TreeNode }func rightSideView(root *TreeNode) []int {i…

【AI提示詞】長期主義助手提供規劃支持

提示說明 長期主義是一種關注長期利益和持續學習的思維模式&#xff0c;幫助個人和組織在快速變化的環境中保持耐心和系統性思考。 提示詞 # Role: Long-termist Assistant## Profile - language: 中文 - description: 長期主義是一種關注長期利益和持續學習的思維模式&…

數組 array

1、數組定義 是一種用于存儲多個相同類型數據的存儲模型。 2、數組格式 &#xff08;1&#xff09;數據類型[ ] 變量名&#xff08;比較常見這種格式&#xff09; 例如&#xff1a; int [ ] arr0&#xff0c;定義了一個int類型的數組&#xff0c;數組名是arr0&#xff1b; &am…

基于JavaAPIforKml實現Kml 2.2版本的全量解析實踐-以兩步路網站為例

目錄 前言 一、關于兩步路網站 1、相關功能 2、數據結構介紹 二、JAK的集成與實現 1、JAK類圖簡介 2、解析最外層數據 3、解析擴展元數據和樣式 4、遞歸循環解析Feature 5、解析具體的數據 三、結論 前言 隨著地理信息技術的快速發展&#xff0c;地理空間數據的共享…

腦科學與人工智能的交叉:未來智能科技的前沿與機遇

引言 隨著科技的迅猛發展&#xff0c;腦科學與人工智能&#xff08;AI&#xff09;這兩個看似獨立的領域正在發生深刻的交匯。腦機接口、神經網絡模型、智能機器人等前沿技術&#xff0c;正帶來一場跨學科的革命。這種結合不僅推動了科技進步&#xff0c;也在醫療、教育、娛樂等…

3.1.3.2 Spring Boot使用Servlet組件

在Spring Boot應用中使用Servlet組件&#xff0c;可以通過注解和配置類兩種方式注冊Servlet。首先&#xff0c;通過WebServlet注解直接在Servlet類上定義URL模式&#xff0c;Spring Boot會自動注冊該Servlet。其次&#xff0c;通過創建配置類&#xff0c;使用ServletRegistrati…

《AI大模型應知應會100篇》第10篇:大模型的涌現能力:為什么規模如此重要

第10篇&#xff1a;大模型的涌現能力&#xff1a;為什么規模如此重要 摘要 在人工智能領域&#xff0c;“規模"始終是大模型發展的核心關鍵詞。隨著參數量從百萬級躍升至萬億級&#xff0c;大模型展現出令人驚嘆的"涌現能力”&#xff1a;這些能力在小模型中幾乎不可…

安寶特案例 | Fundació Puigvert 醫院應用AR技術開創尿石癥治療新紀元

案例介紹 在醫療科技不斷進步的今天&#xff0c;Fundaci Puigvert 醫院邁出了重要一步&#xff0c;成功應用AR技術進行了全球首例同時使用兩臺內窺鏡的ECIRS手術&#xff08;內鏡腎內聯合手術&#xff09;&#xff0c;由Esteban Emiliani M.D. PhD F.E.B.U 博士主刀。這標志著…

從數據海洋中“淘金”——數據挖掘的魔法與實踐

從數據海洋中“淘金”——數據挖掘的魔法與實踐 在這個數據飛速膨脹的時代&#xff0c;每天產生的數據量可以用“天文數字”來形容。如果將數據比作金礦&#xff0c;那么數據挖掘&#xff08;Data Mining&#xff09;就是在數據的海洋中挖掘黃金的技術。作為一門結合統計學、機…

kotlin的takeIf使用

takeIf用于判斷指定對象是否滿足條件&#xff0c;滿足就返回該對象自身&#xff0c;不滿足返回null。因為可以返回對象自身&#xff0c;所以可以用作鏈式調用&#xff0c;以簡化代碼&#xff0c;又因takeIf可能返回空&#xff0c;所以常常和let結合使用&#xff0c;示例如下&am…

[定位器]晶藝LA1823,4.5V~100V, 3.5A,替換MP9487,MP9486A,啟燁科技

Features ? 4.5V to 100V Wide Input Range ? 3.5A Typical Peak Current Limit ? Integrated 500mΩ low resistance high side power MOS. ? Constant On Time Control with Constant Switching Frequency. ? 180μA Low Quiescent Current ? 150kHz/240kHz/420kHz Swi…

火山RTC 4 音視頻引擎 IRTCVideo,及 音視頻引擎事件回調接口 IRTCVideoEventHandler

一、IRTCVideo、IRTCVideoEventHandler 音視頻引擎 IRTCVideo&#xff0c;及 音視頻引擎事件回調接口 IRTCVideoEventHandler 負責音視頻管理、創建房間/獲得房間實例 1、創建引擎、及事件回調示例 如&#xff1a; void VideoConfigWidget::initRTCVideo() {m_handler.res…

前端獲取不到后端新加的字段 解決方案

前端獲取不到后端新加的字段 解決方案 sql 返回的是 FileInfo 對象 private String lastUpdateTimeStr;// 自定義 setLastUpdateTime 方法&#xff0c;確保在設置 lastUpdateTime 時自動格式化為字符串public void setLastUpdateTime(LocalDateTime lastUpdateTime) {this.las…

30天學Java第九天——線程

并行與并發的區別 并行是多核 CPU 上的多任務處理&#xff0c;多個任務在同一時間真正的同時執行并發是單核 CPU 上的多任務處理&#xff0c;多個任務在同一時間段內交替執行&#xff0c;通過時間片輪轉實現交替執行&#xff0c;用于解決 IO 密集型任務的瓶頸 線程的創建方式…

論壇系統(測試報告)

文章目錄 一、項目介紹二、設計測試用例三、自動化測試用例的部分展示用戶名或密碼錯誤登錄成功編輯自己的帖子成功修改個人信息成功回復帖子信息成功 四、性能測試總結 一、項目介紹 本平臺是用Java開發&#xff0c;基于SpringBoot、SpringMVC、MyBatis框架搭建的小型論壇系統…