Linux字元設備驅動框架

来源:http://www.cnblogs.com/xiaojiang1025/archive/2016/12/15/6181833.html
-Advertisement-
Play Games

字元設備是Linux三大設備之一(另外兩種是塊設備,網路設備),字元設備就是位元組流形式通訊的I/O設備,絕大部分設備都是字元設備,常見的字元設備包括滑鼠、鍵盤、顯示器、串口等等,當我們執行 ls l /dev 的時候,就能看到大量的設備文件, c 就是字元設備, b 就是塊設備,網路設備沒有對應的設 ...


字元設備是Linux三大設備之一(另外兩種是塊設備,網路設備),字元設備就是位元組流形式通訊的I/O設備,絕大部分設備都是字元設備,常見的字元設備包括滑鼠、鍵盤、顯示器、串口等等,當我們執行ls -l /dev的時候,就能看到大量的設備文件,c就是字元設備,b就是塊設備,網路設備沒有對應的設備文件。編寫一個外部模塊的字元設備驅動,除了要實現編寫一個模塊所需要的代碼之外,還需要編寫作為一個字元設備的代碼。

驅動模型

Linux一切皆文件,那麼作為一個設備文件,它的操作方法介面封裝在struct file_operations,當我們寫一個驅動的時候,一定要實現相應的介面,這樣才能使這個驅動可用,Linux的內核中大量使用"註冊+回調"機制進行驅動程式的編寫,所謂註冊回調,簡單的理解,就是當我們open一個設備文件的時候,其實是通過VFS找到相應的inode,並執行此前創建這個設備文件時註冊在inode中的open函數,其他函數也是如此,所以,為了讓我們寫的驅動能夠正常的被應用程式操作,首先要做的就是實現相應的方法,然後再創建相應的設備文件。

#include <linux/cdev.h> //for struct cdev
#include <linux/fs.h>   //for struct file
#include <asm-generic/uaccess.h>    //for copy_to_user
#include <linux/errno.h>            //for error number


/* 準備操作方法集 */
/* 
struct file_operations {
    struct module *owner;   //THIS_MODULE
    
    //讀設備
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    //寫設備
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

    //映射內核空間到用戶空間
    int (*mmap) (struct file *, struct vm_area_struct *);

    //讀寫設備參數、讀設備狀態、控制設備
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

    //打開設備
    int (*open) (struct inode *, struct file *);
    //關閉設備
    int (*release) (struct inode *, struct file *);

    //刷新設備
    int (*flush) (struct file *, fl_owner_t id);

    //文件定位
    loff_t (*llseek) (struct file *, loff_t, int);

    //非同步通知
    int (*fasync) (int, struct file *, int);
    //POLL機制
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    。。。
};
*/

ssize_t myread(struct file *filep, char __user * user_buf, size_t size, loff_t* offset)
{
    return 0;
}

struct file fops = {
    .owner = THIS_MODULE,
    .read = myread,
    ...
};


/* 字元設備對象類型 */
struct cdev {
    //public    
    struct module *owner;               //模塊所有者(THIS_MODULE),用於模塊計數
    const struct file_operations *ops;  //操作方法集(分工:打開、關閉、讀/寫、...)
    dev_t dev;                          //設備號(第一個)
    unsigned int count;                 //設備數量
    //private
    ...
};

static int __init chrdev_init(void)
{
    ...
    /* 構造cdev設備對象 */
    struct cdev *cdev_alloc(void);

    /* 初始化cdev設備對象 */
    void cdev_init(struct cdev*, const struct file_opeartions*);

    /* 為字元設備靜態申請設備號 */
    int register_chrdev_region(dev_t from, unsigned count, const char* name);

    /* 為字元設備動態申請主設備號 */
    int alloc_chrdev_region(dev_t* dev, unsigned baseminor, unsigned count, const char* name);

    MKDEV(ma,mi)    //將主設備號和次設備號組合成設備號
    MAJOR(dev)      //從dev_t數據中得到主設備號
    MINOR(dev)      //從dev_t數據中得到次設備號

    /* 註冊字元設備對象cdev到內核 */
    int cdev_add(struct cdev* , dev_t, unsigned);
    ...
}

static void __exit chrdev_exit(void)
{
    ...
    /* 從內核註銷cdev設備對象 */
    void cdev_del(struct cdev* );

    /* 從內核註銷cdev設備對象 */
    void cdev_put(stuct cdev *);

    /* 回收設備號 */
    void unregister_chrdev_region(dev_t from, unsigned count);
    ...
}

實現read,write

Linux下各個進程都有自己獨立的進程空間,即使是將內核的數據映射到用戶進程,該數據的PID也會自動轉變為該用戶進程的PID,由於這種機制的存在,我們不能直接將數據從內核空間和用戶空間進行拷貝,而需要專門的拷貝數據函數/巨集:

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

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

這兩個函數可以將內核空間的數據拷貝到回調該函數的用戶進程的用戶進程空間,有了這兩個函數,內核中的read,write就可以實現內核空間和用戶空間的數據拷貝。

ssize_t myread(struct file *filep, char __user * user_buf, size_t size, loff_t* offset)
{
    long ret = 0;
    size = size > MAX_KBUF?MAX_KBUF:size;
    if(copy_to_user(user_buf, kbuf,size)
        return -EAGAIN;
    }
    return 0;
}

實現ioctl

ioctl是Linux專門為用戶層控制設備設計的系統調用介面,這個介面具有極大的靈活性,我們的設備打算讓用戶通過哪些命令實現哪些功能,都可以通過它來實現,ioctl在操作方法集中對應的函數指針是long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);,其中的命令和參數完全由驅動指定,Linux建議如圖所示的方式定義ioctl()命令

設備類型    序列號     方向      數據尺寸
8bit        8bit    2bit    13/14bit

這裡,設備類型欄位為一個幻數,可以是0~0xff之間的數,內核中的"ioctl-number.txt"給出了一個推薦的和已經被使用的幻數(但是已經好久沒人維護了),新設備驅動定義幻數的時候要避免與其衝突。命令碼的方向欄位為2bit,表示數據的傳輸方向,可能的值是:_IOC_NONE_IOC_READ_IOC_WRITE_IOC_READ|_IOC_WRITE。命令碼的數據欄位表示涉及的用戶數據的大小,這個成員的寬度依賴於體繫結構,通常是13或14位。內核還定義了_IO()_IOR()_IOW()_IOWR()這4個巨集來輔助生成這種格式的命令。這幾個巨集的作用是根據傳入的type(設備類型欄位),nr(序列號欄位)和size(數據長度欄位)和巨集名銀行的方向欄位移位組合生成命令碼。內核中還預定義了一些I/O控制命令,如果某設備驅動中包含了與預定義命令一樣的命令碼,這些命令會被當做預定義命令被內核處理而不是被設備驅動處理,有如下4種:

  • FIOCLEX:即file ioctl close on exec 對文件設置專用的標誌,通知內核當exec()系統帶哦用發生時自動關閉打開的文件
  • FIONCLEX:即file ioctl not close on exec,清除由FIOCLEX設置的標誌
  • FIOQSIZE:獲得一個文件或目錄的大小,當用於設備文件時,返回一個ENOTTY錯誤
  • FIONBIO:即file ioctl non-blocking I/O 這個調用修改flip->f_flags中的O_NONBLOCK標誌

我們可以將驅動設計的命令包含在一個頭文件中,記錄用戶程式和驅動程式的命令約定,下麵是一個簡單的例子

//mycmd.h
...
#include <asm/ioctl.h>
#define CMDT 'A'
#define KARG_SIZE 36
struct karg{
    int kval;
    char kbuf[KARG_SIZE];
};
#define CMD_OFF _IO(CMDT,0)
#define CMD_ON  _IO(CMDT,1)
#define CMD_R   _IOR(CMDT,2,struct karg)
#define CMD_W   _IOW(CMDT,3,struct karg)
...
//chrdev.c
static long demo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    static struct karg karg = {
        .kval = 0,
        .kbuf = {0},
    };
    struct karg *usr_arg;

    switch(cmd){
    case CMD_ON:
        /* 開燈 */
        break;
    case CMD_OFF:
        /* 關燈 */
        break;
    case CMD_R:
        if(_IOC_SIZE(cmd) != sizeof(karg)){
            return -EINVAL;
        }
        usr_arg = (struct karg *)arg;
        if(copy_to_user(usr_arg, &karg, sizeof(karg))){
            return -EAGAIN;
        }
        break;
    case CMD_W:     
        if(_IOC_SIZE(cmd) != sizeof(karg)){
            return -EINVAL;
        }
        usr_arg = (struct karg *)arg;
        if(copy_from_user(&karg, usr_arg, sizeof(karg))){
            return -EAGAIN;
        }
        break;
    default:
        ;
    };
    return 0;
}

創建設備文件

插入的設備模塊,我們就可以使用cat /proc/devices命令查看當前系統註冊的設備,但是我們還沒有創建相應的設備文件,用戶也就不能通過文件訪問這個設備。設備文件的inode應該是包含了這個設備的設備號,操作方法集指針等信息,這樣我們就可以通過設備文件找到相應的inode進而訪問設備。創建設備文件的方法有兩種,手動創建自動創建手動創建設備文件就是使用mknod /dev/xxx 設備類型 主設備號 次設備號的命令創建,所以首先需要使用cat /proc/devices查看設備的主設備號並通過源碼找到設備的次設備號,需要註意的是,理論上設備文件可以放置在任何文件加夾,但是放到"/dev"才符合Linux的設備管理機制,這裡面的devtmpfs是專門設計用來管理設備文件的文件系統。設備文件創建好之後就會和創建時指定的設備綁定,即使設備已經被卸載了,如要刪除設備文件,只需要像刪除普通文件一樣rm即可。理論上模塊名(lsmod),設備名(/proc/devices),設備文件名(/dev)並沒有什麼關係,完全可以不一樣,但是原則上還是建議將三者進行統一,便於管理。

除了使用蹩腳的手動創建設備節點的方式,我們還可以在設備源碼中使用相應的措施使設備一旦被載入就自動創建設備文件,自動創建設備文件需要我們在編譯內核的時候或製作根文件系統的時候就好相應的配置:

Device Drivers --->
        Generic Driver Options --->
            [*]Maintain a devtmpfs filesystem to mount at /dev
            [*] Automount devtmpfs at /dev,after the kernel mounted the rootfs

OR
製作根文件系統的啟動腳本寫入

mount -t sysfs none sysfs /sys
mdev -s //udev也行

有了這些準備,只需要導出相應的設備信息到"/sys"就可以按照我們的要求自動創建設備文件。內核給我們提供了相關的API

class_create(owner,name);
struct device *device_create_vargs(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt, va_list vargs);

void class_destroy(struct class *cls);   
void device_destroy(struct class *cls, dev_t devt);

有了這幾個函數,我們就可以在設備的xxx_init()xxx_exit()中分別填寫以下的代碼就可以實現自動的創建刪除設備文件

    /* 在/sys中導出設備類信息 */
    cls = class_create(THIS_MODULE,DEV_NAME);

    /* 在cls指向的類中創建一組(個)設備文件 */
    for(i= minor;i<(minor+cnt);i++){
        devp = device_create(cls,NULL,MKDEV(major,i),NULL,"%s%d",DEV_NAME,i);
    }  
    /* 在cls指向的類中刪除一組(個)設備文件 */
    for(i= minor;i<(minor+cnt);i++){
        device_destroy(cls,MKDEV(major,i));
    }

    /* 在/sys中刪除設備類信息 */
    class_destroy(cls);             //一定要先卸載device再卸載class

完成了這些工作,一個簡單的字元設備驅動就搭建完成了,現在就可以寫一個用戶程式進行測試了^ - ^



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 'hello world' > 'Hello world' update tableName set columnName=CHAR(ASCII(SUBSTRING(columnName, 1, 1))-32)+SUBSTRING(columnName, 2, len(columnName)) WH ...
  • T-sql是對SQL(structure query language )的升級。可以加函數。 系統資料庫:master管理資料庫。model模版資料庫,msdb備份等操作需要用到的資料庫,tempdb臨時資料庫。 用戶資料庫:用戶自己創建。實際上用戶創建資料庫就是想master這個資料庫下麵去註冊... ...
  • 問題描述:不知道誤操作了什麼,導致cmd視窗的滑鼠顯示位置出現錯位,如下: 現在要將滑鼠位置調整回來。 使用工具:cmd。 操作步驟: 1、查看cmd屬性可以看到 可以看到是UTF-8編碼格式的,我們需要修改其為GBK編碼。 2、在cmd下輸入命令:chcp 936 即可修改為GBK編碼。修改後的結 ...
  • 換了一臺新電腦, 在使用 ant 拷貝大量文件的時候 cmd 視窗過了很久沒有繼續輸出新的內容,遠遠超過平時的耗時, 以為已經卡死 按下 ctrl + c 取消, 這時並沒有取消, 而是輸出了新內容,顯示整個過程已經完成 build success了 。 執行其他耗時命令也是這樣,不繼續輸出。 go ...
  • 一、μCos-ii _概述 網上關於μCosii的文章多不勝數,本人學習的過程中也參考了很多人的理解和想法,看的是盧有亮老師的《嵌入式實時操作系統-μC/OS原理與實踐》(第2版),同時也參考了邵貝貝老師的《嵌入式實時操作系統μCOS-II》,斷斷續續一個月看了幾遍書,也在stm32上簡單移植了μC ...
  • 一、LAMP環境的介紹 1.LAMP環境的重要性 思索許久,最終還是決定寫一篇詳細的LAMP的源碼編譯安裝的實驗文檔,一來是為了給自己一個交代,把技術進行系統的歸納,將技術以極致的形式呈現出來,做為一個做技術的應該有的態度要通過這篇文檔展現,做為以後二來也是為了給那些一直在尋找詳細文檔來細緻學習的人 ...
  • 這種情況有兩種情況,一種是遠程伺服器出現故障。另一種是自己的電腦出現問題,具體原因我還沒有找到,但是可以肯定的是註冊表除了問題,一個終極的解決辦法就是把註冊表替換了。先將HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Rasman 路徑的註冊 ...
  • 在Win10系統里右鍵開始菜單,選擇彈出菜單里的命令提示符,如下圖所示:然後複製要粘貼的文字,例如:echo hovertree.com把上面的文字複製後,點擊命令提示符視窗,然後在命令提示符視窗的標題欄上右鍵,選擇彈出菜單的編輯-->粘貼,也可以直接在游標的位置點擊滑鼠右鍵,如下圖所示。效果如下: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...