---
title: '硬體 - 設備管理'
disqus: kyleAlien
---
硬體 - 設備管理
===
## Overview of Content
電腦的基礎就是硬體設備(硬碟、記憶體、CPU... 等等),而 Linux 對於硬體的呈現方式也有一定的規則,這個章節就是要說明 Linux 如何管理硬體設備
:::success
* 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
本篇文章對應的是 [**理解 Unix/Linux 設備偵測、建立 | 認識與使用 udevd | SCSI 與 Linux 核心**](https://devtechascendancy.com/unix-linux-dev_udevd_scsi/)
:::
[TOC]
## Unix 設備節點概念
Unix 系統中,核心會將 I/O 介面以 **檔案的形式** 型態呈現給使用者看,這些設備有為 **==設備節點==**
> 開發者可以使用 `cat`、`less`、`vim`、重新導向... 等等命令來存取設備
:::info
但請注意,**並非** 所有的設備節點都可以使用 I/O 的方式來存取
:::
### 設備取名規範
* Linux 如 Unix 一樣,會將硬體設備當作檔案,**儲存在 `/dev` 目錄下**,而這個 `/dev` 目錄是提供 **使用者程序** 使用的,使用者程序可以對做讀寫
> 就像是直接對裝置存取一樣
以下使用 `ls` 查看 `/dev` 目錄
```shell=
# 顯示前 30 筆
ls -laF /dev | head -n 30
```
並且這些設備顯示有一個簡易的符號可以讓我們快速了解(顯示的最前方)
| 檔案類型 | 符號 | 概述 |
| -------- | -------- | - |
| 區塊設備 | b | 固定的區塊大小 |
| 字元設備 | c | 沒有固定容量(核心不會對字元設備 備份、驗證) |
| 管道設備 | p | 與字元類似,**不過輸入的數據不一定從核心而來** |
| Socket 設備 | c | 跨進程、遠端常用到 |
> ![](https://hackmd.io/_uploads/ry4gDI4vn.png)
:::success
* **設備編號** 是甚麽?
編號有分為,**主編號、副編號**;相同類型的設備會有相同的主編號(副編號則沒有硬性規定)
:::
:::warning
* **所有設備都有對應的檔案**?
NO~ 像是網路介面就沒有設備檔案
:::
### Linux 改造 `/dev` 目錄:sysfs 介面
* 傳統的 Unix `/dev` 目錄會將所有(使用者程序、核心程序)的設備檔案放至此,沒有詳細分類,而這會造成一個問題…
**Unix 系統每次開機後,同樣設備儲存在 `/dev` 資料夾內的裝置名稱也會不同**
> `/dev` 目錄中的裝置名稱通常是由系統動態生成的,並且可能根據裝置的探測順序或其他因素而變化
* 為此 Linux 核心透過一個檔案目錄系統提供 sysfs 介面,它是 **基於硬體性** 統一顯示設備的相關資訊,這就讓 **每次開機時,同樣設備都會顯示與上次相同的資料**
> sysfs 提供的是系統裝置(devices)的相關資訊,而不僅僅是硬體性質;它包括有關系統組件、裝置、驅動程式和其他資源的詳細信息
**sysfs 提供的目錄為 `/sys/devices`**,sysfs 介面會以 `/sys/devices` 下的路徑「作為硬體根路徑」,並呈現相對資訊,範例如下
```shell=
cd /sys/devices
# 標準 PC 搜尋 sd.*
find . -name sd.*
# 嵌入式的話就要搜尋 eMMC 裝置
find . -name mmcblk0*
```
從下圖中,我們可以看到主硬碟在 `/sys/devices` 目錄下也有對應的資訊(是個目錄)
> ![image](https://hackmd.io/_uploads/rJCAYF5-C.png)
:::info
* **`/dev` 目錄跟 `/sys/devices` 目錄差在哪** ?
兩者都可以查看硬體訊息,但是 **差異在這兩者的服務目標不同**
| `/dev` | `/sys/devices` |
| - | - |
| 提供程序使用設備 | 用來 **查找設備資訊、管理設備** |
1. **查找主、副設備號**:**`/sys/devies` 下查找主、副設備號**
```shell=
# 打印 dev 檔案資訊
cat ./platform/emmc2bus/fe340000.mmc/mmc_host/mmc0/mmc0:59b4/block/mmcblk0/dev
```
> 從下圖中(最下面那行),我們可以看到當前裝置主硬碟的主設備號為 179、副設備號為 0
>
> ![image](https://hackmd.io/_uploads/BkJeoKqWC.png)
2. **查找區「塊」設備**:在 `/sys/block` 目錄下的裝置都以連結的方式存在,都會連接到 `../device/virtual/block` 目錄下
```shell=
cd /sys
ls -laF block/
```
> ![](https://hackmd.io/_uploads/S11uxw4wn.png)
:::
### 查找設備的方法
* Linux 中有多種方法可以查看當前系統所裝配的設備、裝置,常見的方式如下…
1. **使用 `udevadm` 命令** 查看設備
2. 手動 **去 `/sys` 目錄** 下尋找設備
```shell=
ls -laF /sys/
```
> ![](https://hackmd.io/_uploads/HkiZIjUPn.png)
2. 查看系統日誌,**使用 `demsg` 命令** 輸出核心日誌
3. **使用 `mount` 命令** 查看當前已掛載的設備
```shell=
mount
```
> ![](https://hackmd.io/_uploads/rkHSIi8D2.png)
4. **查看 `/proc/devices` 文本**:該文本內容顯示系統 ++已經配置驅動++ 的 **字元、區塊** 設備
```shell=
cat /proc/devices
```
> ![](https://hackmd.io/_uploads/HkjiUi8Pn.png)
## 認識 udev & udevd
| 關鍵字 | 簡介 | 補充 |
| -------- | -------- | -------- |
| udev(`Userspace Device`) | Linux 設備管理工具,負責動態管理、識別設備,在找到設備後,自動加入 `/dev` 目錄下 | `udev` 會與內核中的 `sysfs`, `devtmpfs` 交互獲取設備訊息 |
| udevd | 在背景守護 `udev` 進程,有(硬體)事件發生時會先通知 `udevd` 處理,再將處理結果傳遞給 `dev` | 通過監視 `sysfs`, `uevent` 介面來獲取事件通知 |
:::warning
* **udevd 注意事項**:
`udevd` 作為守護進程,系統啟動前期就需要設備檔案,也就是說 `udevd` 應該儘早的啟動
> 並且 **`udevd` 不能依賴任何設備來建立設備檔**
:::
### 內核的 devtmpfs 檔案系統:通知 udevd
* **內核中的 `devtmpfs` 檔案系統 類似於舊的 `devfs` 系統,但它更加簡單**
`devtmpfs` 是一種臨時的虛擬檔案系統,用於在 Linux 內核中管理和展示裝置(device)
* **`devtmpfs` 發現設備後,處理順序如下**
1. **`devtmpfs` 在發現新設時通知 `udevd`**
> 當 Linux 內核發現新的裝置時,devtmpfs 會通知 udevd 進行後續處理
2. `udevd` 在收到消息後,它不會直接建立裝置檔案(device file),而是進行初始化和後續處理
3. 接著 `udevd` 會在 `/dev` 中建立符號連結(`ln`)
```shell=
# 可在 `/dev/disk/by-id` 發現 `udevd` 建立的連結
ls -laF /dev/disk/by-id
```
:::info
* udevd 會使用介面類型名稱、廠商、型號、序列號、分區… 等等的組合方式來為符號連結命名
:::
> ![](https://hackmd.io/_uploads/HJX2joSw3.png)
### udevd 操作、設定:uevent 事件
* 這個小節來看 `udevd` 的工作:它作為守護進程,當有一個新硬體後,會執行以下操作(上至下,按照順序來看)
1. 核心透過內部網路 **向 `udevd` 發送 `uevent` 事件**,以下我們來看看當前裝置的主硬碟(`mmcblk0`)收到的 uevent 事件
事件會紀錄在,`/sys/`目錄下硬體的對應資料夾中的
```shell=
cat ./platform/emmc2bus/fe340000.mmc/mmc_host/mmc0/mmc0:59b4/block/mmcblk0/uevent
```
> ![image](https://hackmd.io/_uploads/r1N13K5Z0.png)
:::info
* 以下是一段簡單的 shell,用來判斷 `/sys/devices/` 目錄下的 `uevent` 檔案
```shell=
#!/bin/bash
list=$(find /sys/devices/ -name "uevent")
IFS=$'\n'
for tmp in $list; do
#echo "file:$tmp"
if [ -n "$(cat $tmp)" ] ; then
echo "The file=($tmp) has content."
cat $tmp
fi
done
```
:::
2. **`udevd` 讀取 `uevent` 中所有屬性資訊**
以下我們來看看更多 uevent 的中可能會紀錄的屬性訊息
```shell=
# `udevd` 收到的事件內容
# 透過以下屬性,可以獲取 `sysfs` 的設備路徑... 等等訊息
ACTION=change
DEVNAME=sde
DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host4/target4:0:0/4:0:0:3/block/sde
DEVTYPE=disk
DISK_MEDIA_CHANGE=1
MAJOR=8
MINOR=64
SEQNUM=2752
SUBSYSTEM=block
UDEV_LOG=3
```
3. `udevd` **透過 ++規則++ 解析來決定執行哪些操作**,**`udevd` 的事件規則**:
`udevd` 解析這些事件則是 **透過規則**,這些 **規則可能放置在 ^1.^ `/lib/udev/rules.d`、^2.^ `/etc/udev/rules.d` 目錄下**
:::success
* 如果有重名的規則,那則以 `/etc/udev/rules.d` 目錄下的規則為準
* 規則有相當多,詳細規則內容則可以看 `man 7 udev`
:::
以下以 `60-persistent-storage.rules` 規則來說明
* **`IMPORT` 導入參數**:執行 `ata_id` 命令
```shell=
# 指令
cat -n /lib/udev/rules.d/60-persistent-storage.rules
# 輸出部分內容
# ATA
KERNEL=="sd*[!0-9]|sr*", # 配對以 sd* or sr* 開頭的硬體
# 要求變數 `ID_SERIAL`
ENV{ID_SERIAL}!="?*",
# 子系統為 scsi
SUBSYSTEMS=="scsi",
ATTRS{vendor}=="ATA",
# 上述條件都滿足,則執行以下命令
IMPORT{program}="ata_id --export $devnode"
```
> ![](https://hackmd.io/_uploads/S1los2BPh.png)
:::info
* **`ID_SERIAL` 設定值**?
**`ENV{ID_SERIAL}!="?*"` 是一個條件句**,`?*` 代表的是一個隨機值,加上 `!=` 判斷後最終的意思就是
如果 `ENV{ID_SERIAL}` 沒有設定(空),則回傳 true 繼續往下執行,否則返回 false
> true: 往下執行判斷 或 指令
>
> false: 跳過這個規則,往下執行其他規則
:::
其中 `IMPORT{program}="ata_id --export $devnode"` 最終執行的是一個 **`ata_id` 命令**;透過這個命令可以將 rules 過濾完的數據結果寫入到檔案中
```shell=
sudo /lib/udev/ata_id --export /dev/sda
```
* **`SYMLINK` ==建立符號連結==**:建立設備符號連結的命名規則也可以在這裡找到,如下可以看出,它命名的規範
```shell=
# 指令
cat -n /lib/udev/rules.d/60-persistent-storage.rules
# 輸出部分內容
# sd* / sr* / cciss* 設備使用以下規則
KERNEL=="sd*|sr*|cciss*",
# 類行為 disk
ENV{DEVTYPE}=="disk",
# 並且沒有 Serial id
ENV{ID_SERIAL}=="?*",
# 執行以下指令
SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
```
在這裡可以看到 `ENV{ID_SERIAL}=="?*"` 規則的改變!變成如果有設定 `ID_SERIAL` 則往下執行,也就是執行 `SYMLINK` 指令
```shell=
# 執行以下指令,建立 ln 連結
SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
```
## udevadm 命令:管理 udevd
udevadm 是管理 `udevd` 的一個強大工具,主要用來搜尋、瀏覽、監控 **`udevd`**
### 查看 udevd 訊息
* 透過 `udevadm` 瀏覽 **`udevd` 從核心接收到的訊息**
```shell=
udevadm info --query=all --name=/dev/sda
```
其中首符號代表的意義如下表
| 首符號 | 說明 |
| - | - |
| `P` | 代表 `sysfs` 放置的路徑 (`/sys/` 目錄下) |
| `N` | 設備節點 `/dev` 目錄下的設備名 |
| `S` | 設備符號連結 |
| `E` | **從 ++udev 規則++ 中獲得的額外資訊** |
> ![](https://hackmd.io/_uploads/r1NT1jLvh.png)
### udevadm 監控設備
* 透過 `udevadm` 監控設備:以下監控所有的訊息
```shell=
udevadm monitor
```
> ![](https://hackmd.io/_uploads/Sk1sZsLPn.png)
命令 `udevadm monitor` 還有其他 Option 可以過濾所需訊息
> 詳細訊息設定可以查看 `man 8 udevadm`
| `udevadm monitor` Option | 功能 |
| -------- | -------- |
| `--kernal` | 只查看從 Kernel 獲取的訊息 |
| `--udev` | 只查看 udev 發送的訊息 |
| `--property` | 只查看設備屬性 |
### udevadm 命令:查找設備
* Udev manager tool 可以使用 udevadm 指令查看路徑 & 其他屬性
```shell=
# 查找所有 name 為 /dev/sda 的設備
udevadm info --query=all --name=/dev/sda
```
> ![](https://i.imgur.com/QmWQJYx.png)
## SCSI 與 Linux 核心
介紹 Linux 核心對於 SCSI 的支援方式(以下偏理論)
### 認識、理解 SCSI
* 傳統的 SCSI 硬體設定:由兩個大元素組成,透過 **^1.^ ++SCSI 匯流排++ 連接到 ^2.^++主機配接器++**;其種其種每個設備都會有 `SCSI ID` (主機 Adapter, 設備)
> SCSI 匯流排: 可能會有 8~16 不等的 `SCSI ID`;匯流排中會有 硬碟、USB、光碟機... 等等硬體
而電腦並不直接與每個設備通訊,電腦只與「**SCSI 主機 Adapter**」通訊,而 **通訊的語言則是透過 SCSI 命令集**
> ![](https://hackmd.io/_uploads/HJtzooLw3.png)
:::warning
* 新版的的 SAS (`Serial Attach SCSI`) 效能更加出色~
:::
:::info
* **設備都是 SCSI 裝置**?
而現在並沒有很多的 SCSI 設備,但是卻有很多使用 SCSI 指令集通訊的設備(模擬 SCSI 設備),像是 USB, CD, DVD ... 等等
* **STAT 設備也可以是 SCSI 設備**
透過轉換機制(使用 `libata` 庫),對上層表示為 SCSI 裝置(像是 USB 就可作為 SCSI 設備)
:::success
* 而高效能的 **RAID 控制器,使用硬體來實現 SCSI 轉換**
:::
:::
* **我們再重新檢視一次 `lsscsi` 命令**
```shell=
lsscsi
```
其中括號內代表的意義分別是:`SCSI 主機 Adapter`、`SCSI 匯流排編號`、`設備 SCSI ID`、`LUN 邏輯單元編號`
> ![](https://hackmd.io/_uploads/rJjYaoIv2.png)
### Linux 核心處理 SCSI
* 按照我們對於 SCSI 的了解後,下圖表示的是從硬體到核心是如何透過 `SCIS 子系統` 處理 SCSI 裝置
> ![](https://hackmd.io/_uploads/rkGTtn8vh.png)
* 接著我們區塊快來看
1. **SCSI 子系統**:主要分為 3 層
* **最頂層**:按照類型設備類型做不同處理;sd 驅動(或其他驅動) **負責將來自核心區塊設備介面的請求翻譯成 SCSI 協定**
* **中間層**:調控和分流 SCSI 訊息,負責 **管理系統中所有的 SCSI 匯流排和設備**
* **最下層**:該層中的驅動會向 **特定主機配接器發送 SCSI 協定訊息**,並擷取硬體發送的訊息
> 與頂層的分別:雖然 SCSI 協定對於某類設備是統一的,但不同類型的主機配接器處理 SCSI 的方式不同
> ![](https://hackmd.io/_uploads/rktD8nLv2.png)
:::info
* 通常下層驅動會與頂層驅動一對一配對(eg. `sd` 配對 `ATA Bridge`)
:::
2. **SCSI & USB 設備**:USB 也有一個子系統,當有 USB 裝置插入時,核心會通知 USB 子系統;與 SCSI 相同分為三層
* **最頂層**:將 USB 協定翻譯成 SCSI 協定(像是一個指令 Map,幫我們做翻譯)
* **中間層**:匯流排管理
* **最下層**:主機控制驅動
:::info
* 其種以一個類似 lsscsi 的命令 `lsusb`
```shell=
lsusb
```
> ![](https://hackmd.io/_uploads/rkAeOhIDn.png)
:::
> ![](https://hackmd.io/_uploads/HJMJv2LPh.png)
3. **SCSI & ATA 設備**:與 USB 驅動相同,需要一個 **橋接驅動將 STAT 驅動連接到 SCSI 子系統**
而這裡 **透過 `libata` 庫來達到 ATA 裝置對 SCSI 協定的翻譯工作**
> 不然會很複雜,因為 SATA, CD/DVD 的 通訊都不同
> ![](https://hackmd.io/_uploads/H1T8t3Uvh.png)
### 通用設備
* 使用者空間(User space)通常是與 SCSI 子系統的最頂層 (各種區塊驅動) 來通訊,所以大多數時間是不需了解 SCSI 通訊協議
* **通用設備**:然而我們也可以繞過 核心提供給使用者的區塊驅動,可以直接與 SCSI 設備交互
這種交互的管道就是 **通用設備**;可以透過以下指令查看 SCSI 設備對應的通用設備
```shell=
lsscsi -g
```
> ![](https://hackmd.io/_uploads/S1Mxo2LPn.png)
:::info
* 通常在考慮到核心複雜度,才會使用到直接操控 **通用設備**
:::
### 存取設備:`/dev`
* 通常我們使用者空間在存取設備時,**並不是直接讀取驅動,而是透過 ++區塊設備介面++ 來存取**(也就是我們常見的 `/dev` 目錄下的設備文件)
> ![](https://hackmd.io/_uploads/r1MG2n8D2.png)
## dev 常見設備名
Linux 會依照設備的類型給予一定規則的命名方式 (命名規範)
### 硬碟裝置:`sd*`、`hd*`
* **`sd*`、`hd*` 命名**
| /dev 中取名 | 說明 |
| -------- | -------- |
| `/hd*` | PATA 硬碟,舊版本的 Linux 會將 SATA 辨認為 PATA 設備 |
| `/sd*` | 大部分是指硬碟,現在大部分 Linux 系統中的硬碟都用 `sd<順序>` 開頭 |
:::success
* PATA 設備:
代表舊的區塊設備,這些設備有時候也會被認成 SATA 設備
* **sd 設備:該命名代表的是 ++SCSI dick++**
小型電腦介面 (`Small Computer System Interface`) 最初是作為設備之間通訊的硬體協定標準 (也就是有一個共同的抽象介面)
雖然現在已經沒有 `SCSI` 硬體,不過 `SCSI` 的規範介面卻很廣泛 !
> e.g: Usb 設備就可以實作這個協定
:::
* **使用 `lsscsi 指令`**:查看當前設備中所有的 SCSI 裝置,但若是硬碟故障就必須移除,而原來對硬碟的標記也就不一定能使用
```shell=
# 安裝套件
sudo apt install lsscsi
lsscsi
```
| 前到後 | 數據 | 說明 |
| - | - | - |
| 1 | `[0:0:0:0]` | 設備在系統中的位置 |
| 2 | `dick` | 設備描述 |
| 3 | `/dev/sda` | 設備檔案的路徑 |
> ![](https://hackmd.io/_uploads/rkAkPPVvn.png)
### 光碟裝置:`sg*`、`sr*`
* `sr*`、`sg*` 命名:光碟,Linux 會將大部分 **光學儲存設備視為 `SCSI` 設備**,但若是使用舊介面的話,可能被識別為 **PATA 設備**
:::info
* **光碟可寫不可寫** ?
不可寫設備被取名為 `sr*` (read),可寫設備會被取名為 `sg*` (generic)
:::
> ![](https://i.imgur.com/qKbPGu6.png)
### 終端設備裝置:`tty/*`、`pts`、`tty`
* `tty/*`、`pts/*`、`tty`:終端設備,這種設備就是典型的非硬體設備,是透過記憶體模擬出來的設備
> 像是 shell 視窗就是偽終端
| 終端設備 | 功能 | 補充 |
| -------- | -------- | - |
| tty1 | 第一虛擬控制台 (`X Window`) | - |
| pts/0 | 第一虛擬終端 (`純文字終端`),可開 0~7 個 | - |
| pts | 該目錄中有一個專門的檔案系統 | - |
### 連接埠裝置:`ttyS*`、`lp*`
* 連接埠有分為 ++串列++、++並列++ 連接埠
| 連接埠 | 功能 | 補充 |
| -------- | -------- | - |
| (串)`ttyS*` | 主要在處理 `Baud rate`, `flow control`,像是 Rs-232 | 在 window 上是 `COM*` |
| (並)`lp*` | 單向並行連接埠設備 | 在 window 上是 `LPT1`、`LPT2` |
:::info
* 可插拔 USB 串列 Adapter 和 ACM (`Abstract control module`) 模式下,就是以 **串列連接埠表示**:
其取名是 `/dev/USB*` or `/dev/ACM*`
:::
### 聲音裝置:`snd*`
* `snd*`、`dsp`、`audio`:聲音設備,Linux 有兩組聲音設備,分別如下
1. `Advanced Linux Sound Architecture` (ALSA) 高級聲音架構:放置在 `/dev/snd`
> ![](https://hackmd.io/_uploads/BkguXjrv2.png)
2. `Open Sorund System` (OSS) 開放聲音架構:ALSA 可向後支援 OSS
> eg. 可以將 WAV 檔發送給 `/dev/dsp` 來播放
## 建立設備
設備通常都需要建立一個相對的文件,而這件事通常由 Linux 設備的底層服務自動幫我們完成,但這並不代表我們不能自己建立
### 建立假設備:`dd` 指令
* 在 Linux 中,並非每個設備都是硬體元件,我們可以 **透過記憶體來模擬硬體設備**
* **使用 `dd` 指令**:
該指令可以讀取、寫入到指定檔案 (可以透過 flag 控制直接寫入 or 寫入核心分頁檔案)
| dd option | 簡述 |
| -------- | -------- |
| if=<file\> | 輸入檔案 |
| of=<file\> | 輸出檔案 |
| bs=<size\> | 一次讀取、寫入的區塊大小(一起設定) |
| ibs=<size\> | 輸入的區塊大小 |
| obs=<size\> | 輸出的區塊大小 |
| count=<num\> | 複製區塊的總數 |
| skip=<num\> | 跳過前面多少的區塊,不將他們複製到輸出 |
```shell=
# 讀取 /dev/zero 區塊的資料到當前資料夾中的 testfile 檔案
# (當前是寫入核心分頁)
# 一次寫 1024
# 寫 1K 的數量,最終就是 1M 的大小
dd if=/dev/zero of=test_file bs=1024 count=1k
```
> ![](https://hackmd.io/_uploads/ryER-wNvn.png)
### 手動建立 dev 設備檔案:`mknod` 指令
* 每個外部硬體裝置通常都會在 `/dev` 目錄下建立自身的目錄
而我們插入硬體裝置後,這工作是通常 **由 `devtmpfs`、`udev` 自動幫我們完成**…
若要手動建立就要 **使用 `mknod` 指令** (並設定設備主、次號)
> mknod 也說明了,設備在 Linux 中就代表了一個節點
```shell=
# 主設備號 10
# 副設備號 20
# 建立塊設備,在 /dev/testFile 目錄下
sudo mknod /dev/testFile b 10 20
ls -laF /dev | grep testFile
```
> ![](https://hackmd.io/_uploads/BkEwHsSw3.png)
## Appendix & FAQ
:::info
:::
###### tags: `Linux Shell`