原文鏈接:https://www.cnblogs.com/sammyliu/p/5729026.html
1. Linux 上的設備 (device)
Linux 操作系統中,各種設備驅動(device driver)通過設備控制器(device controller)來管理各種設備(device),其關系如下圖所示:
這些設備之中,
- 受同一個 device driver 管理的設備都有相同的 major number,這個數字可以看作設備的類別號碼,被內核用於識別一類設備
- 受同一個 device driver 管理的同一類設備中的每一個設備都有不同的 minor number,這個數字可以看作設備編號,被設備驅動用來識別每個設備
設備驅動主要有三大類:
- 面向包的網絡設備驅動(package oriented network device driver)
- 面向塊的存儲設備驅動(block oriented storage device driver),提供緩沖式(buffered)的設備訪問。
- 面向字節的字符設備驅動 (byte oriented char device driver),有時也稱為裸設備(raw devices),提供非緩沖的直接的設備訪問(unbuffered direct access),比如串口設備,攝像頭,聲音設備等。實際上,除了網絡設備和存儲設備以外的其它設備都是某種字符設備。
除此以外,還有一類設備,稱為偽設備(pseudo device),它們是軟件設備。Linux 上的 device 不一定要有硬件設備,比如 /dev/null, /dev/zero 等。
關於字符設備和塊設備的更多區別:
- 塊設備只能以塊為單位接受輸入和返回輸出,而字符設備則以字節為單位。大多數設備是字符設備,因為它們不需要緩沖而且不以固定塊大小進行操作。
- 塊設備對於I/O 請求有對應的緩沖區,因此它們可以選擇以什么順序進行響應,字符設備無須緩沖且被直接讀寫。對於存儲設備而言調 讀寫的順序作用巨大,因為在讀寫連續的扇區比分離的扇區更快。
- 字符設備只能被順序讀寫,而塊設備可以隨機訪問。雖然塊設備可隨機訪問,但是對於磁盤這類機械設備而言,順序地組織塊設備的訪問可以提高性能。
用戶空間的各種應用是通過 device driver 來操作設備的:
如果再詳細一些就是這樣的:
(圖片來源)
從這個圖上可以看出:
- 網絡設備驅動之上,分別有包調度器(packet scheduler),網絡協議層(network protocols),NetFilter (防火牆)和 scoket 層,其中,網絡設備驅動以 socket 作為應用層的接口
- 塊設備驅動之上,分別有 I/O Scheduler,通用塊層(generic block layer)和文件系統,其中,塊設備驅動以設備文件 (device file)作為應用層的接訪問口
- 字符設備驅動之上,分別有 Line discipline 和 terminals,其中,terminals 作為和應用的訪問接口
Linux 系統中“一切皆文件”。每個設備,在 /dev 目錄中都有對一個設備文件(device file),比如 /dev/sda 表示第一個 SCSI/IDE 盤,/dev/vda 表示第一個 virtio 磁盤。應用程序通過訪問這些設備文件像操作文件一樣來訪問這些設備,可以使用的接口包括:
- int open(const char *path, int oflag, ... )
- int close(int fd);
- ssize_t write(int fd, const void *buf, size_t nbyte)
- ssize_t read(int fd, void *buf, size_t nbyte)
- int ioctl(int d, int request, ...)
在 Linux 系統上,設備驅動可以被動態加載和刪除
- lsmod - 列出當前已經被加載的模塊
- insmod <module_file> - insert/load 指定的模塊文件
- modprobe <module> - insert/load 指定的 module,以及所有依賴
- rmmod <module> - remove/unload 指定的module
2. Linux 設備的 major 和 minor number
2.1 用 ls 獲取
上文談到了 major 和 number。簡單地,可以從 ls 命令的輸出中看出 device 的這兩個numbers:
root@controller:/home/sammy# cd /dev
root@controller:/dev# ls -l total 0 crw------- 1 root root 10, 175 Jul 18 15:24 agpgart crw------- 1 root root 10, 235 Jul 18 15:24 autofs brw-rw---- 1 root disk 7, 5 Jul 18 15:24 loop5 brw-rw---- 1 root disk 7, 6 Jul 18 15:24 loop6 brw-rw---- 1 root disk 8, 0 Jul 18 15:24 sda brw-rw---- 1 root disk 8, 1 Jul 18 15:24 sda1 brw-rw---- 1 root disk 8, 2 Jul 18 15:24 sda2 brw-rw---- 1 root disk 8, 5 Jul 18 15:24 sda5 crw--w---- 1 root tty 4, 10 Jul 18 15:24 tty10 crw--w---- 1 root tty 4, 11 Jul 18 15:24 tty11
- 以 'c' 開頭的一行表示該設備是一個字符設備,以 'b' 開頭的行表示這是一個塊設備。
- 10,175 這兩個數字中,前面的 10 表示 major number,后面的 175 表示 minor number。
2.2 major 和 minor 值的設置
歷史上,設備的 major number 采用的是注冊制,各設備廠家在 http://www.lanana.org/ 中注冊他們的設備所使用的 major number。從 http://www.lanana.org/docs/device-list/devices-2.6+.txt 中還可以看出來 linux 2.6 內核中所分配的靜態major numbers。
但是,現在,這個注冊網站已經沒有人維護了,取而代之的是動態分配制度。分配者是linux 內核的 udev 模塊。它將保證在本系統中,<major number>:<minor number>的組合是唯一的,而在這個范圍之外,它不會保證其惟一性。一旦分配好了后,你就可以從 /proc/device 文件中讀出所分配的 major numbers,比如:
2 pty 3 ttyp 4 ttyS 6 lp 7 vcs 10 misc 13 input 14 sound 21 sg 180 usbBlock devices: 2 fd 8 sd 11 sr 65 sd 66 sd
3. 根據 major 和 minior numbers 識別磁盤類型
3.1 識別過程
根據以下步驟來識別磁盤類型:
(1)使用 stat 命令獲取設備文件的 major 和 minor numbers。注意結果是16進制。
root@u1:/dev# stat -c %T /dev/vda #minor number 0 root@u1:/dev# stat -c %T /dev/vdb 10 root@u1:/dev# stat -c %T /dev/sda 0 root@u1:/dev# stat -c %t /dev/vda #major number fd root@u1:/dev# stat -c %t /dev/vdb fd root@u1:/dev# stat -c %t /dev/sda 8
(2)將16進制數字轉化為10進制,並拼接字符串 /sys/dev/block/$decmajor:$minor/device/driver
/sys/dev/block/253:0/device/driver /sys/dev/block/253:16/device/driver /sys/dev/block/8:0/device/driver
(3)調用 readlink -f 命令,獲取 device driver
root@u1:/dev# readlink -f /sys/dev/block/253:0/device/driver /sys/bus/virtio/drivers/virtio_blk
root@u1:/dev# readlink -f /sys/dev/block/253:16/device/driver
/sys/bus/virtio/drivers/virtio_blk
root@u1:/dev# readlink -f /sys/dev/block/8:0/device/driver /sys/bus/scsi/drivers/sd
從輸出可以看出,/dev/vda 和 /dev/vdb 都是 virtio-block 類型的設備,而 /dev/sda 是 sd 即 SCSI 類型的設備。
常見的命名:
- fd:軟驅
- hd:IDE 磁盤
- sd:SCSI 磁盤
- tty:terminals
- vd:virtio 磁盤
3.2 virtio block driver 的實現
virtio-blk 驅動的實現代碼在 https://github.com/spotify/linux/blob/master/drivers/block/virtio_blk.c。從中可以看出 major 和 minor number 分配,以及設備命名的方法。
(1)設備命名方法
if (index < 26) { sprintf(vblk->disk->disk_name, "vd%c", 'a' + index % 26); } else if (index < (26 + 1) * 26) { sprintf(vblk->disk->disk_name, "vd%c%c", 'a' + index / 26 - 1, 'a' + index % 26); } else { const unsigned int m1 = (index / 26 - 1) / 26 - 1; const unsigned int m2 = (index / 26 - 1) % 26; const unsigned int m3 = index % 26; sprintf(vblk->disk->disk_name, "vd%c%c%c", 'a' + m1, 'a' + m2, 'a' + m3); }
可見:
- virtio-blk 設備的名稱以 ‘vd’ 開頭。從 ‘vda’ 開始遞增,數目在 26 個以內時,增長至 ‘vdz’;如果超過 26,則從 ’vdaa‘ 一直增長至 ’vdzz‘;最高可以增長到 ’vdzzz‘。
- 名稱在設備被加載后被確定,在重新加載或者系統重啟后會重新生成,因此對同一個設備其名稱可能會發生變化。我的另一篇文章 理解 QEMU/KVM 和 Ceph(3):存儲卷掛接和設備名稱 談到了這種變化導致的問題。
(2)major number 是通過向內核注冊來獲取的
static int __init init(void) { major = register_blkdev(0, "virtblk"); if (major < 0) return major; return register_virtio_driver(&virtio_blk); }
register_blkdev 這個方法是內核系統調用,用於注冊一個塊設備,需要指定主設備號。如果指定的設備號為0,則會由系統自動分配一個。該方法調用之后,就可以在/proc/devices文件中看到該塊設備以及它的 major number。
(3)minor number 是由設備的 index (索引)轉化而來的
vblk->disk->first_minor = index_to_minor(index);