--- 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`