日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux 字符设备驱动开发基础(三)—— read()、write() 相关函数解析

發布時間:2023/12/9 linux 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux 字符设备驱动开发基础(三)—— read()、write() 相关函数解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

我們在前面講到了file_operations,其是一個函數指針的集合,用于存放我們定義的用于操作設備的函數的指針,如果我們不定義,它默認保留為NULL。其中有最重要的幾個函數,分別是open()、read()、write()、ioctl(),下面分別對其進行解析

? ? ?

一、 打開和關閉設備函數

a -- 打開設備

? ????int (*open) (struct inode *, struct file *);

在操作設備前必須先調用open函數打開文件,可以干一些需要的初始化操作。當然,如果不實現這個函數的話,驅動會默認設備的打開永遠成功。打開成功時open返回0。

b -- 關閉設備 ?????

? ? ??int (*release) (struct inode *, struct file *);

當設備文件被關閉時內核會調用這個操作,當然這也可以不實現,函數默認為NULL。關閉設備永遠成功。

這兩個函數已經講過,這里不再贅述,主要看下面幾個函數


二、read()、write() 函數

? ? ? ? 現在把 read()、write() 兩個函數放一起講,因為兩個函數非密不可分的,先看一下兩個函數的定義?

a -- read() 函數

? ?函數原型? ? ? ???ssize_t (*read) (struct file * filp, char __user * buffer, size_t ? ?size , loff_t * p);?
? ?參數含義

?filp ? ? ? :為進行讀取信息的目標文件,

?buffer ?:為對應放置信息的緩沖區(即用戶空間內存地址);

?size ? ? :為要讀取的信息長度;

?p ? ? ? ? ?:為讀的位置相對于文件開頭的偏移,在讀取信息后,這個指針一般都會移動,

? ? ? ? ? ? ? ? ?移動的值為要讀取信息的長度值

b -- write() 函數

? ?函數原型? ? ? ???ssize_t (*write) (struct file * filp, const char __user * ? buffer, size_t count, loff_t * ppos);?
? ?參數含義

filp ? ? ?:為目標文件結構體指針;

buffer :為要寫入文件的信息緩沖區;

count ?:為要寫入信息的長度;

ppos ? :為當前的偏移位置,這個值通常是用來判斷寫文件是否越界

? ? ? ? ?

? ? ? ?兩個函數的作用分別是?從設備中獲取數據發送數據給設備,應用程序中與之對應的也有 write() 函數及 read() 函數:

? ? len = read(fd,buf,len)
? ? len = write(fd,buf,size)

static?ssize_t?hello_read(struct?file?*filep,?char?__user?*buf,?size_t?len,?loff_t?*pos)
static?ssize_t?hello_write(struct?file?*filep,?const?char?__user?*buf,?size_t?len,?loff_t?*pos)


? ? ? ?我們知道,應用程序工作在用戶空間,而驅動工作在內核空間,二者不能直接通信的,那我們用何種方法進行通信呢?下面介紹一下內核中的memcpy---copy_from_user和copy_to_user雖然說內核中不能使用C庫提供的函數,但是內核也有一個memcpy的函數,用法跟C庫中的一樣。

? ? ? ? 下面看一下copy_from_user() 及 copy_to_user() 函數的定義:

[cpp]?view plaincopy
  • static?inline?int?copy_from_user(void?*to,?const?void?__user?volatile?*from,??
  • ?????????????????unsigned?long?n)??
  • {??
  • ????__chk_user_ptr(from,?n);??
  • ????volatile_memcpy(to,?from,?n);??
  • ????return?0;??
  • }??
  • ??
  • static?inline?int?copy_to_user(void?__user?volatile?*to,?const?void?*from,??
  • ???????????????????unsigned?long?n)??
  • {??
  • ????__chk_user_ptr(to,?n);??
  • ????volatile_memcpy(to,?from,?n);??
  • ????return?0;??
  • }??
  • 可以看到兩個函數均是調用了 _memcpy() 函數

    [cpp]?view plaincopy
  • static?void?volatile_memcpy(volatile?char?*to,?const?volatile?char?*from,???
  • ????????????????unsigned?long?n)??
  • {??
  • ????while?(n--)??
  • ????????*(to++)?=?*(from++);??
  • }??
  • ? ? ? ?其實在這里,我們可以思考,既然拷貝的功能上面的_memcpy() 函數就可以實現,為什么還要封裝成 copy_to_user()和copy_from_user()呢?答案是_memcpy() 函數是有缺陷的,譬如我們在用戶層調用函數時傳入的不是字符串,而是一個不能訪問或修改的地址,那樣就會造成系統崩潰。

    ? ? ? 出于上面的原因, 內核和用戶態之間交互的數據時必須要先對數據進行檢測 ,如果數據是安全的,才可以進行數據交互。上面的函數就是memcpy的改進版,在memcpy功能的基礎上加上的檢查傳入參數的功能,防止有些人有意或者無意的傳入無效的參數。

    ? ? ?

    ? ? ? 現在我們可以審視一下這兩個函數了:

    [cpp]?view plaincopy
  • static?inline?unsigned?long?__must_check?copy_to_user(void?__user?*to,?const?void?*from,?unsigned?long?n)??
  • ??
  • static?inline?unsigned?long?__must_check?copy_from_user(void?*to,?const?void?__user?*from,?unsigned?long?n)??
  • 用法:

    和memcpy的參數一樣,但它根據傳參方向的不同分開了兩個函數。

    "to"是相對于內核態來說的。所以,to函數的意思是從from指針指向的數據將n個字節的數據傳到to指針指向的數據。

    "from"也是相對于內核來說的。所以,from函數的意思是從from指針指向的數據將n個字節的數據傳到to指針指向的數據

    返回值:函數的返回值是指定要讀取的n個字節中還剩下多少字節還沒有被拷貝。

    注意:

    一般的,如果返回值不為0時,調用copy_to_user的函數會返回錯誤號-EFAULT表示操作出錯。當然也可以自己決定。


    又到了擺實例的時候了,這里只列出部分代碼,看看這兩個函數的用法:

    [cpp]?view plaincopy
  • static?ssize_t?hello_read(struct?file?*filep,?char?__user?*buf,?size_t?len,?loff_t?*pos)??
  • {??
  • ????if(len>64)??
  • ????{??
  • ????????len?=64;??
  • ????}??
  • ????if(copy_to_user(buf,temp,len))??
  • ????{??
  • ????????return?-EFAULT;??
  • ????}?????
  • ????return?len;??
  • }??
  • static?ssize_t?hello_write(struct?file?*filep,?const?char?__user?*buf,?size_t?len,?loff_t?*pos)??
  • {??
  • ????if(len>64)??
  • ????{??
  • ????????len?=?64;??
  • ????}??
  • ??
  • ????if(copy_from_user(temp,buf,len))??
  • ????{??
  • ????????return?-EFAULT;??
  • ????}??
  • ????printk("write?%s\n",temp);??
  • ????return?len;??
  • }??
  • 測試程序:

    [cpp]?view plaincopy
  • #include?<sys/types.h>??
  • #include?<sys/stat.h>??
  • #include?<fcntl.h>??
  • #include?<stdio.h>??
  • ??
  • ??
  • char?buf[]="111232342342342";??
  • char?temp[64]={0};??
  • main()??
  • {??
  • ????int?fd,len;??
  • ??
  • ????fd?=?open("/dev/hello",O_RDWR);??
  • ????if(fd<0)??
  • ????{??
  • ????????perror("open?fail?\n");??
  • ????????return?;??
  • ????}??
  • ??
  • ????write(fd,buf,strlen(buf));??
  • ??
  • ????len=read(fd,temp,sizeof(temp));??
  • ??
  • ????printf("len=%d,%s?\n",len,temp);??
  • ??
  • ????close(fd);??
  • }??


  • ? ? ? 到這里open、close、read、write四個函數已經學完,下面我們來看一下四個函數使用時,到底經歷了一個怎樣的過程:

    注:箭頭方向是從調用的一方指向受作用的一方

    ?? ?

    ? ??

    ? ?

    總結

    以上是生活随笔為你收集整理的Linux 字符设备驱动开发基础(三)—— read()、write() 相关函数解析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。