---
title: Driver (驅動) - 從零開始的開源地下城
tags: Linux, Linux讀書會, Kernel, 從零開始的開源地下城, COMBO-tw
description: 介紹與Linux Kernel相關基本知識
lang: zh-Hant
GA: G-2QY5YFX2BV
---
# Driver (驅動)
###### tags: `Linux`
## 目錄
[TOC]
## Topic
Block Device
Character Device
Ref: [LDD3](https://lwn.net/Kernel/LDD3/)
## Introduction
在我們的裝置上,不論是電腦、手機甚至是網通設備這樣無法與人直接互動的設備等,皆需要 Driver 的存在,常見的裝置有"藍芽"、"網路卡"或是"鍵盤滑鼠"...等,然而這些裝置如果沒有像 Driver 這樣偏向硬體底層的軟體,那使用者將會需要耗費需多資源來進行程式的開發。
![](https://hackmd.io/_uploads/rycTWoWU3.png)
上圖為 Linux Kernel 的拆解分視圖,在一部嵌入式裝置上會提供許多功能,例如 行程管理 (Process management)、記憶體管理 (Memory management)、檔案系統格式驅動 (Filesystems)、裝置設備控制 (Device control)及網路驅動控制 (Networking)等。大致上分為以下幾種**Block devices**、**Character devices**,在後面將會詳細介紹這幾種驅動型態。
在本章節將介紹如何建立一個驅動程式來讓 User Space 程式來進行使用,並且完成 Block Device 及 Character Device 兩種類型的實作,。
## 驅動程式類型介紹
### Block devices
Block devices 可透過 **/dev** 中的 Filesystem nodes 來 Accessed。一個 Block 只能處理一到多個完整 Block 的 I/O 操作,大小為 512 bytes (或是更大的2的次方)。Linux 可以讓 User Space 讀取 Block device 如同操作File 一樣,而不用一次讀一整個 Block,在 User Space 中使用與 Char 基本上無差別。有差別的是在 Kernel & Driver 這一層完全不同。
### Character devices
Character devices 可以被當作 **A stream of bytes** 來被 Accessed,就像 File 一樣。因此 Char dirver 就要負責至少 open, close, read, and write 的 system calls 操作,有的還有提供 ioctl 的操作方式。常見的例子有 Text Console (/dev/console) and the Serial Ports (/dev/ttyS0),都是 Streaming 結構。Char devices 由 Filesystem nodes 來 accessed, such as /dev/tty1 and /dev/lp0。但跟一般 File 不同的是,一般 File 可以把指標往前往後來操作檔案,但是多數 Char device 只是 Data Channels,只能依序 Access。當然也有例外,而該例外多出現在 Frame Grabbers,就會像 Data Areas 一樣,可以在之中隨意前後操作,多用 mmap or lseek 來配合使用。
### Block 與 Character 的差異
Block devices 是以固定大小長度來傳送轉移資料,而 Character devices 是以不定長度的字元傳送資料,而其操作方式在 User Spcae 則大同小異。
## 實際動手做做看吧
### Block devices
那麼首先就是來建立一個空的 Block devices 吧,而我們要如何建立一支 Driver 呢?
那就必須要有他的主程式,在此我們命名為`my_block.c`,而內容如下:
```clike=
#include <linux/init.h>
#include <linux/module.h>
#include <linux/blkdev.h>
MODULE_LICENSE("Dual BSD/GPL");
#define MY_BLOCK_MAJOR 240
#define MY_BLKDEV_NAME "my_block"
static __init int my_block_init(void)
{
int status;
status = register_blkdev(MY_BLOCK_MAJOR, MY_BLKDEV_NAME);
if (status < 0) {
printk(KERN_ERR "unable to register mybdev block device\n");
return -EBUSY;
}
printk(KERN_INFO "Hello kernel\n");
return 0;
}
static void __exit my_block_exit(void)
{
printk(KERN_INFO "Goodbye\n");
unregister_blkdev(MY_BLOCK_MAJOR, MY_BLKDEV_NAME);
}
module_init(my_block_init);
module_exit(my_block_exit);
```
而每一隻 Driver 都有共通的函式,`init`及`exit`,分別式掛載時用與卸載時用,然而 Block driver 需要向 Kernel 註冊自己,需要使用到 `<linux/blkdev.h>` 中的 `register_blkdev` 及 `unregister_blkdev` 來進行註冊與註銷的動作。
而每個 Block Device 都必須有個 Major Number ,該編號在 Kernel 中可使用的範圍定義於 `linux/kdev_t.h` 當中。掛載的方式為 `insmod <DRIVER_NAME.ko>` ,掛在上去後可用 `sed -n '/^Block/, /^$/ { /^$/ !p }' /proc/devices` 來查看裝置是否存在。
相關的 Source Code 請參考 Github [04-driver](https://github.com/combo-tw/LinuxBookClub/tree/master/chapter/04-driver)
### Character devices
在前一段中,我們說明了 Block Devices Driver 的建立以及 Driver 基本所需的函式,而在此將說明如何建立一個簡單的 Character Devices Driver,在此我們命名為`my_char.c`,而內容如下:
```clike=
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/ioctl.h>
MODULE_LICENSE("Dual BSD/GPL");
#define MY_CDEB_MAJOR 42
#define MY_CDEV_NAME "my_char"
#define MY_MAX_MINORS 5
struct my_device_data {
struct cdev cdev;
};
struct my_device_data devs[MY_MAX_MINORS];
const int arr[5];
static int my_open(struct inode *inode, struct file *file)
{
return 0;
}
static int my_read(struct file *file, char __user *user_buffer,
size_t size, loff_t *offset)
{
return 0;
}
static int my_write(struct file *file, const char __user *user_buffer,
size_t size, loff_t * offset)
{
return 0;
}
static int my_release(struct inode *inode , struct file *filp)
{
return 0;
}
static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
return 0;
}
const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.read = my_read,
.write = my_write,
.release = my_release,
.unlocked_ioctl = my_ioctl
};
static __init int my_char_init(void)
{
int i, err;
err = register_chrdev_region(MKDEV(MY_CDEB_MAJOR, 0), MY_MAX_MINORS,
MY_CDEV_NAME);
if (err != 0) {
/* report error */
return err;
}
for(i = 0; i < MY_MAX_MINORS; i++) {
/* initialize devs[i] fields */
cdev_init(&devs[i].cdev, &my_fops);
cdev_add(&devs[i].cdev, MKDEV(MY_CDEB_MAJOR, i), 1);
}
printk(KERN_INFO "Hello kernel\n");
return 0;
}
static void __exit my_char_exit(void)
{
int i;
printk(KERN_INFO "Goodbye\n");
for(i = 0; i < MY_MAX_MINORS; i++) {
/* release devs[i] fields */
cdev_del(&devs[i].cdev);
}
unregister_chrdev_region(MKDEV(MY_CDEB_MAJOR, 0), MY_MAX_MINORS);
}
module_init(my_char_init);
module_exit(my_char_exit);
```
為了方便說明以下簡稱`Block`與`Char`,而與 Block 不同的是, Char 具有 read, write 等功能,使得使用者可以透過 Driver 來對 Devices 進行 Register 操作,進而做到 User Space 使用通用 Interface 即可使用多種多樣化的 Devices 。
可透過 `sed -n '/^Character/, /^$/ { /^$/ !p }' /proc/devices` 來查看裝置是否存在。
相關的 Source Code 請參考 Github [04-driver](https://github.com/combo-tw/LinuxBookClub/tree/master/chapter/04-driver)
在此我們將特別針對兩個常用函式進行說明,首先是 `read()`:
```clike=
static int my_read(struct file *file, char __user *user_buffer,
size_t size, loff_t *offset)
{
return 0;
}
```
此函數是使用者透過開啟 `file descriptor` 或是稱 `fd` 時,進行 read 操作時會調用之函式,而第一個引數 `sturct file *file` 為該 `fd` 資訊結構體,而第二個引數 `const char __user *user_buffer` 為該次 read 操作時可使用的 User Space 變數指標,第三個引數 `size_t size` 為欲讀取的資料筆數,最後一個引數 `loff_t *offset` 為該 `fd` 對於基準點的偏移量。
![](https://hackmd.io/_uploads/HkuAbjWU3.png)
```clike=
static int my_write(struct file *file, const char __user *user_buffer,
size_t size, loff_t * offset)
{
return 0;
}
```
此函數是使用者透過開啟 `file descriptor` 或是稱 `fd` 時,進行 write 操作時會調用之函式,而第一個引數 `sturct file *file` 為該 `fd` 資訊結構體,而第二個引數 `const char __user *user_buffer` 為該次 write 操作時可使用的 User Space 變數指標,第三個引數 `size_t size` 為欲寫入的資料筆數,最後一個引數 `loff_t *offset` 為該 `fd` 對於基準點的偏移量。
![](https://hackmd.io/_uploads/H1ACWob82.png)
## 本章節練習與反思
* 請依照 Github 所提供的 Source Code 為範本,設計一個 Character Driver ,讓使用者可以透過 `read()` 及 `write()` 來操作 Driver 內的 `arr` 變數,也就是,每開啟一個 `fd` 就可以操作對應的 `arr` 變數 address,舉例來說,第一個 fd 可以操作 `arr[0]`,第二個 fd 可以操作 `arr[1]` 以此類推。
> 測試程式位於[此](https://github.com/combo-tw/LinuxBookClub/blob/master/chapter/04-driver/test/io_ut.c)
> 測試方法為當 QEMU 啟動後,進入 `/home/pi/test` 資料夾,並執行 `make check` 即可
>> Hints: 不可以讓 fd 操作不屬於該 fd 的 Register。
* 思考為什麼 Driver 要設計成這個樣子,需要有註冊、註銷、ioctl、read 及 write 等函式架構?並紀錄於筆記當中。
* 還有試著去了解,當我們使用 `read` 及 `write` 等 System Call 時,它的程式流程,並紀錄於筆記當中。
## 參考資料
* [LDD3](https://lwn.net/Kernel/LDD3/)
* [Linux Device Driver 3 心得整理 (1)](https://medium.com/@wbobw951/linux-device-driver-3-%E5%BF%83%E5%BE%97%E6%95%B4%E7%90%86-ceb91196fa3f)
* [[linux]Block Device Driver和 Character Device Driver](https://pcnews.pixnet.net/blog/post/30896162)
* [一個簡單的 Linux Kernel Module](https://wwssllabcd.github.io/blog/2012/11/13/how-to-make-linux-module/)
* [如何查看 Char and Block Devices](https://askubuntu.com/questions/1068747/how-to-display-list-of-character-devices-and-block-devices-separately)
* [Block Device Drivers](https://linux-kernel-labs.github.io/refs/heads/master/labs/block_device_drivers.html)
* [Character device drivers](https://linux-kernel-labs.github.io/refs/heads/master/labs/device_drivers.html)