1. 程式人生 > >【Linux】Linux裝置驅動開發詳解:基於最新的Linux 4.0核心

【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