【Linux】Linux設備驅動開發詳解:基於最新的Linux 4.0內核

1 Linux設備驅動概述及開發環境構建

1.1 設備驅動的作用

  • 驅使硬件設備行動

1.2 無操作系統時的設備驅動

  • 典型架構:一個無限循環中夾雜着對設備中斷的檢測或者對設備的輪詢
    無標題.png

1.3 有操作系統時的設備驅動

  • 併發 、內存管理

    無標題.png

1.4 Linux 設備驅動

1.4.1 設備的分類及特點

● 字符設備。
● 塊設備。
● 網絡設備。

1.4.2 Linux 設備驅動與整個軟硬件系統的關係

捕獲.PNG

1.4.3 Linux 設備驅動的重點、難點

● 編寫 Linux 設備驅動要求工程師有非常好的硬件基礎,懂得 SRAM、 Flash、 SDRAM、磁盤的讀寫方式,UART、 I2C、 USB 等設備的接口以及輪詢、中斷、 DMA 的原理,PCI 總線的工作方式以及 CPU 的內存管理單元(MMU)等。
● 編寫 Linux 設備驅動要求工程師有非常好的 C 語言基礎,能靈活地運用 C 語言的結構體、指針、函數指針及內存動態申請和釋放等。
● 編寫 Linux 設備驅動要求工程師有一定的 Linux 內核基礎,雖然並不要求工程師對內核各個部分有深入的研究,但至少要明白驅動與內核的接口。尤其是對於塊設備、網絡設備、 Flash 設備、串口設備等複雜設備,內核定義的驅動體系結構本身就非常複雜。
● 編寫 Linux 設備驅動要求工程師有非常好的多任務併發控制和同步的基礎,因爲在驅動中會大量使用自旋鎖、互斥、信號量、等待隊列等併發與同步機制。

2 驅動設計的硬件基礎

2.1 處理器

2.1.1 通用處理器

2.1.2 數字信號處理器

捕獲.PNG

2.2 存儲器

捕獲.PNG

捕獲.PNG

2.3 接口與總線

串口 、I2C 、SPI 、USB、以太網 、PCI 和 PCI-E 、SD 和 SDIO

捕獲.PNG

捕獲.PNG

2.4 CPLD 和 FPGA

2.5 原理圖分析

  • 符號 、網絡 、描述

2.6 硬件時序分析

  • 時序分析的意思是讓芯片之間的訪問滿足芯片數據手冊中時序圖信號有效的先後順序、採樣建立時間(Setup Time)和保持時間(Hold Time)的要求

2.7 芯片數據手冊閱讀方法

2.8 儀器儀表使用

  • 萬用表 、示波器 、邏輯分析儀

3 Linux 內核及內核編程

3.1 Linux 內核的發展與演變

  • 表 3.1 Linux 操作系統版本的歷史及特點
版 本 時 間 特 點
Linux 0.1 1991 年 10 月 最初的原型
Linux 1.0 1994 年 3 月 包含了 386 的官方支持,僅支持單 CPU 系統
Linux 1.2 1995 年 3 月 第一個包含多平臺(Alpha、 Sparc、 MIPS 等)支持的官方版本
Linux 2.0 1996 年 6 月 包含很多新的平臺支持,最重要的是,它是第一個支持 SMP(對稱多處理器)體系的內核版本
Linux 2.2 1999 年 1 月 極大提升 SMP 系統上 Linux 的性能,並支持更多的硬件
Linux 2.4 2001 年 1 月 進一步提升了 SMP 系統的擴展性,同時也集成了很多用於支持桌面系統的特性: USB、 PC 卡(PCMCIA)的支持,內置的即插即用等
Linux 2.6.0 ~ 2.6.39 2003 年 12 月~2011 年 5 月 無論是對於企業服務器還是對於嵌入式系統, Linux 2.6 都是一個巨大的進步。對高端機器來說,新特性針對的是性能改進、可擴展性、吞吐率,以及對 SMP 機器 NUMA 的支持。對於嵌入式領域,添加了新的體系結構和處理器類型。包括對那些沒有硬件控制的內存管理方案的無MMU 系統的支持。同樣,爲了滿足桌面用戶羣的需要,添加了一整套新的音頻和多媒體驅動程序
Linux 3.0 ~ 3.19、Linux 4.0-rc1 至今 2011 年 7 月至今 性能優化等 開發熱點聚焦於虛擬化、新文件系統、 Android、新體系結構支持以及

3.2 內核組件

捕獲.PNG

1. 進程調度

捕獲.PNG

2. 內存管理

捕獲.PNG

3. 虛擬文件系統

捕獲.PNG

4. 網絡接口

捕獲.PNG

5. 進程間通信

  • 進程間通信支持進程之間的通信, Linux 支持進程間的多種通信機制,包含信號量、共享內存、消息隊列、管道、 UNIX 域套接字等,這些機制可協助多個進程、多資源的互斥訪問、進程間的同步和消息傳遞。在實際的 Linux 應用中,人們更多地趨向於使用 UNIX 域套接字,而不是 System V IPC 中的消息隊列等機制。 Android 內核則新增了 Binder 進程間通信方式。

4 內核模塊

4.1 模塊簡介

insmod ./hello.ko
rmmod hello

lsmod
/proc/modules
/sys/module

4.2 模塊結構

4.2.1 加載函數

static int __init hello_init(void)
{
    ...

    return 0;
}

module_init(hello_init);

4.2.2 卸載函數

static void __exit hello_exit(void)
{
    ...
}

module_exit(hello_exit);

4.2.3 許可聲明

MODULE_AUTHOR("lin");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("A simple param Module");
MODULE_ALIAS("a simplest module");
  • 模塊參數module_param(var, int, S_IRUGO);
  • 導出符號EXPORT_SYMBOL_GPL(func); (proc/kallsyms)

5 文件系統與設備文件

捕獲.PNG

捕獲.PNG

6 字符設備驅動

6.1 驅動結構

6.1.1 cdev結構體

捕獲.PNG

//生成dev
MKDEV(int major, int minor);    //major:0-19 minor:20-31
//獲取設備號
MAJOR(dev_t dev)
MINOR(dev_t dev)
//cdev操作
void cdev_init(struct cdev *, struct file_operations *);
struct cdev* cdev_alloc(void);
void cdev_put(struct cdev *);
int  cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);

6.1.2 設備號分配

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);

int unregister_chrdev_region(dev_t from, unsigned count);

6.1.3 file_operations結構體

捕獲.PNG

7 設備驅動中的併發控制

7.1 併發與競態

  • 臨界區:訪問共享資源的代碼段
  • 互斥:中斷屏蔽、原子操作、自旋鎖、信號量、互斥體

7.2 編譯亂序和執行亂序

  • 表 隔離指令
指令名 功能描述
DMB 數據存儲器隔離。DMB 指令保證: 僅當所有在它前面的存儲器訪問操作都執行完畢後,才提交(commit)在它後面的存儲器訪問操作。
DSB 數據同步隔離。比 DMB 嚴格: 僅當所有在它前面的存儲器訪問操作都執行完畢後,才執行在它後面的指令(亦即任何指令都要等待存儲器訪 問操作——譯者注)
ISB 指令同步隔離。最嚴格:它會清洗流水線,以保證所有它前面的指令都執行完畢之後,才執行它後面的指令。

7.3 中斷屏蔽

local_irq_disable() local_irq_enable() //與自旋鎖聯合使用
local_irq_save(flags) local_irq_restore(flags)
local_bh_disable() local_bh_enable()

7.4 原子操作

7.4.1 整型原子操作

  • 設置

    void atomic_set(atomic_t *v, int i);
    atomic_t ATOMIC_INIT(int i);
  • 獲取

    int atomic_read(atomic_t *v);
  • 加減

    void atomic_add(int i, atomic_t *v);
    void atomic_sub(int i, atomic_t *v);
    
    void atomic_inc(atomic_t *v);
    void atomic_dec(atomic_t *v);
  • 操作後測試(爲0返回true,非0返回false)

    int atomic_inc_and_test(atomic_t *v);
    int atomic_dec_and_test(atomic_t *v);
    int atomic_sub_and_test(int i, atomic_t *v);
  • 操作後返回新值

    int atomic_add_return(int i, atomic_t *v);
    int atomic_sub_return(int i, atomic_t *v);
    
    int atomic_inc_return(atomic_t *v);
    int atomic_dec_return(atomic_t *v);

7.4.2 位原子操作

捕獲.PNG

7.5 自旋鎖

7.5.1 自旋鎖

spinlock_t lock;
spin_lock_init(lock);
spin_lock(lock);
spin_trylock(lock);
spin_unlock(lock);


spin_lock_irq(lock); spin_unlock_irq(lock);
spin_lock__irqsave(lock); spin_unlock_irqrestore(lock);
spin_lock_bh(lock); spin_unlock_bh(lock);

無標題.png

7.5.2 讀寫鎖

無標題.png

7.5.3 順序鎖

  • 讀執行單元不會被寫執行單元阻塞;但寫執行單元進行寫操作時,其他寫執行單元就會自旋。

無標題.png

7.5.4 讀-複製-更新

  • RCU: Read-Copy-Update

    捕獲.PNG

    無標題.png

    7.6 信號量

    無標題.png

    7.7 互斥體

    無標題.png

    7.8 完成量

    無標題.png

8 阻塞I/O和非阻塞I/O

8.1 阻塞I/O和非阻塞I/O

fd= open("/dev/ttyS1", O_RDWR | O_NONBLOCK);
fcntl(fd, F_SETFL, O_NONBLOCK);

8.1.1 等待隊列

//定義
wait_queue_head_t queue_head;
//初始化
init_waitqueue_head(&queue_head);
//定義及初始化
DECLARE_WAIT_QUEUE_HEAD(name)
//隊列等待元素
DECLARE_WAITQUEUE(name, tsk)
//操作
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
//等待事件
wait_event(queue, condition)
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
//喚醒隊列
void wake_up(wait_queue_head_t *q);
void wake_up_interruptible(wait_queue_head_t *q);
//睡眠
sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
static ssize_t xxx_write(struct file *file, const char *buffer, size_t count, loff_t *ppos)
{
    ...
    DECLARE_WAITQUEUE(wait, current);
    add_wait_queue(&xxx_wait, &wait);

    /*等待設備緩衝區可寫*/
    do {
        avail = device_writable();
        if (avail < 0) {
            if (file->f_flags & O_NONBLOCK) {
                ret = -EAGAIN;
                goto out;
            }
            __set_current_state(TASK_INTERRUPTIBLE);
            schedule();
            if (signal_pending(current)) {
                ret = -ERESTARTSYS;
                goto out;
            }
        }
    } while (avail < 0);

    device_write();
out:
    remove_wait_queue(&xxx_wait, &wait);
    set_current_state(TASK_RUNNING);

    reutrn ret;
}

捕獲.PNG

8.1.2 支持等待隊列的globalfifo

無標題.png

8.2 輪詢操作

8.2.1 輪詢的概念與作用

9.2.3 信號的釋放

  1. 異步通知結構體

    struct xxx_dev{
        struct cdev cdev;
        ...
        struct fasync_struct *async_queue;
    }
    1. xxx_fasync
    static int xxx_fasync(int fd, struct file *filp, int mode)
    {
        struct xxx_dev *dev=file->private_data;
        return fasync_helper(fd, filp, mode, &dev->async_queue);
    }
    1. 釋放讀信號
    //xxx_write
    if(dev->async_queue)
      kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
    1. 從異步通知列表刪除filp
    //xxx_release
    xxx_fasync(-1, filp, 0);

9.4 Linux異步I/O

9.4.1 AIO

struct aiocb {
 
  int aio_fildes;               // File Descriptor
  int aio_lio_opcode;           // Valid only for lio_listio (r/w/nop)
  volatile void *aio_buf;       // Data Buffer
  size_t aio_nbytes;            // Number of Bytes in Data Buffer
  struct sigevent aio_sigevent; // Notification Structure
 
  /* Internal fields */
  ...
 
};
API 函數 說明
aio_read int aio_read( struct aiocb *aiocbp ); 請求異步讀操作
aio_error int aio_error( struct aiocb *aiocbp ); 檢查異步請求的狀態
aio_return ssize_t aio_return( struct aiocb *aiocbp ); 獲得完成的異步請求的返回狀態
aio_write int aio_write( struct aiocb *aiocbp ); 請求異步寫操作
aio_suspend int aio_suspend( const struct aiocb *const cblist[], int n, const struct timespec *timeout ); 掛起調用進程,直到一個或多個異步請求已經完成(或失敗)
aio_cancel int aio_cancel( int fd, struct aiocb *aiocbp ); 取消異步 I/O 請求
lio_listio int lio_listio( int mode, struct aiocb *list[], int nent, struct sigevent *sig ); 發起一系列 I/O 操作

9.4.2 內核AIO與libaio

10 中斷與時鐘

10.1 中斷與定時器

11 內存與I/O訪問

17 I2C、SPI、USB驅動架構類比

無標題.png

18 ARM Linux設備樹

18.1 ARM設備樹起源

  • 可描述的信息:
    • CPU的數量和類別
    • 內存基地址和大小
    • 總線和橋
    • 外設連接
    • 中斷控制器和中斷使用情況
    • GPIO控制器和GPIO使用情況
    • 時鐘控制器和時鐘使用情況

18.2 設備樹的組成和結構

18.2.1 DTS、DTC和DTB

  1. .dts:device tree source

    1.1 Soc共用部分:.dtsi (/include/ “s3c24440.dtsi”)

    1.2 模板

    /* root節點 */
    / {
        node1 {
            a-string-property = "A string";
            a-string-list-property = "first string", "second string";
            a-byte-data-property = [0x01 0x23 0x34 0x56];
            child-node1 {
                first-child-property;
                second-child-property = <1>;
                a-string-property = "Hello, world";
            };
            child-node2 {
            };
        };
        node2 {
            an-empty-property;
            a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
            child-node1 {
            };
        };
    };
  2. .dtc:device tree compiler

  3. .dtb:Device Tree Blob

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章