はじめに
後輩になんとなく伝えたい知識を纏めた備忘録。
思いつきで書いたので誤植多いかもしれません。
Prerequisites
Linuxについて多少知っている事を前提にしています。カーネルとか、簡単なコマンドとか。
Everything is a file
直訳すると、「すべてはファイルである」。Linux、というかUnix系OSの大原則です。これは最初に頭に入れておきましょう。Linuxではハードウェア、ソケット、プロセス、全てがファイルとして扱われます。要するに、open(), read(), write(), close()のシステムコールで全て制御が可能であるということです。
一部例をあげると、
- HDD
/dev/sda - SSD
/dev/nvme0n1p1 - マイコン
/dev/ttyACM0 - デバイス情報
/proc
このように、様々なものがファイルシステムにファイルとして認識されます。
ファイルとして認識されるということは、catで中身を確認したり、>で書き込んだりできるわけです。もちろんC言語のようなプログラムからでも操作できます。
# シリアルポートから値を読み取る
$ cat /dev/ttyUSB0
# /dev/nullはゴミ箱みたいなもの。出力がいらない時によく`>`と組み合わせる
$ echo hello
hello
$ echo hello > /dev/null # 出力なし
# LED仮想デバイス制御
$ echo 1 > /sys/class/leds/input0::capslock/brightness
File types
ファイルにも種類があり、ls -lコマンドで出てくる一番左の文字で判別することができます。余談ですが、Ubuntu等だとllで全く同じ結果を得ることができます。(~/.bashrcのaliasを確認)シンタックスシュガー(糖衣構文)などと呼ばれます。
キャラクタデバイスとブロックデバイスはデバイスを理解する上で重要です。
| 記号 | 種類 | 説明 |
|---|---|---|
- |
通常ファイル | テキストやバイナリなど、いわゆる「ふつうのファイル」。プログラム、ドキュメント、画像など。 |
d |
ディレクトリ | ファイルをまとめるフォルダ的存在。中身は「ファイル名とinodeの対応表」。 |
c |
キャラクタデバイス | 1文字単位でデータをやり取りするデバイス。例:端末(/dev/tty)、マウス、シリアル通信。 |
b |
ブロックデバイス | 一定サイズのブロック単位でデータを扱うデバイス。例:HDD、SSD、USBメモリなど。 |
l |
シンボリックリンク | 他のファイルやディレクトリへのショートカット。パスを指し示すだけ。 |
p |
パイプ (FIFO) | プロセス間通信のための一時的な通路。mkfifo で作成できる。 |
s |
ソケット | ネットワークやプロセス間通信に使う。例:/var/run/docker.sock
|
私の環境での例です
$ ls -l /dev/input/mice
crw-rw---- 1 root input 13, 63 Oct 31 23:06 /dev/input/mice
# マウスはキャラクタデバイス
$ ls -l /usr/bin/cc
lrwxrwxrwx 1 root root 3 Aug 13 23:30 /usr/bin/cc -> gcc*
# ccコマンドはgccコマンドへのシンボリックリンク
$ ls -l ~/Downloads
drwxr-xr-x 17 tatsv tatsv 36864 Oct 31 23:12 Downloads
# Downloadsはディレクトリ
FS (Filesystem)
ストレージ(HDD, SSD等)というのはブロックの集合でしかなく、Linuxはデータがどこに存在するのか全く分かりません。そこで、そのブロックと名前(ラベル)や権限を紐付け、人間やプログラムが管理しやすいようにしたものがファイルシステムです。データに秩序を与えるもの、と言ったら良いでしょうか。
ファイルシステムには種類があり、それぞれ用途が違います
| ファイルシステム | 主な用途 | 特徴 |
|---|---|---|
| ext4 | Linux標準 | 安定・信頼性高・汎用 |
| xfs | サーバ向け | 高速・大容量に強い |
| btrfs | 先進型 | スナップショット・自己修復機能あり |
| vfat / exfat | 互換用 | Windowsとのやり取り用 |
| ntfs | Windows標準 | Linuxでも読み書き可(ntfs-3g) |
ここで最も重要なのはext4でしょう。主要Linuxを多く含むDebian系(UbuntuやMintなど)Linuxは基本的にext4を採用しています。サーバなどで用いられるRedHat系はXFS、Fedoraは最近btrfsですかね。Archだとext4か、ローリングリリースとの相性でbtrfsを選択している人もいる印象です。Linuxカーネルを採用しているAndroidやChromeOSもext4ですかね。
とにかく、ここからの話はext4を前提とします。
Mount
あるファイルシステムを、別のファイルシステムの一部に組み込んで認識させることをマウントと呼びます。mountコマンドを用いてマウントすることができます。
# マウント状況確認
$ mount
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
sys on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
dev on /dev type devtmpfs (rw,nosuid,relatime,size=8047276k,nr_inodes=2011819,mode=755,inode64)
run on /run type tmpfs (rw,nosuid,nodev,relatime,mode=755,inode64)
efivarfs on /sys/firmware/efi/efivars type efivarfs (rw,nosuid,nodev,noexec,relatime)
/dev/nvme0n1p2 on / type ext4 (rw,relatime)
securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev,inode64,usrquota)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=600,ptmxmode=000)
...
# /dev/sdb1を/mntにマウント。/mntはマウントポイントと呼ばれ、root直下の空ディレクトリ
$ sudo mount /dev/sdb1 /mnt
# マウント解除
$ sudo umount /mnt
# ファイルシステムを明示的に指定
$ sudo mount -t ext4 /dev/sdb2 /mnt
毎回マウントするのは非常に面倒なので、/etc/fstabというファイルに設定を書くと自動マウントしてくれます。
$ cat /etc/fstab
# UUID=71155e31-8a22-4e46-8748-ef78c0817540
/dev/nvme0n1p2 / ext4 rw,relatime 0 1
# UUID=007C-DD66
/dev/nvme0n1p1 /boot/efi vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro 0 2
# UUID=e2f717d6-b5cb-49a1-ae87-aa0c88e76f48
/dev/nvme0n1p3 none swap defaults 0 0
FHS(Filesystem Hierarchy Standard)
すべてのLinuxディストリビューションで、同じ場所に同じ種類のファイルがあるようにするための基準です。ここでまず覚えるべきことは、/直下にあるディレクトリの意味を知ることです。/etcに設定ファイルがあるとか、/binに基本的なコマンドが格納されてるとか、そういうのです。
今回はデバイス周りの話ですから、/dev以下にデバイスファイルが存在するということは覚えていただきたい。ここで全部は解説できないので、他は自分で調べてください。
/dev (Device)
/dev以下にあるのはデバイスファイルと呼ばれる特殊ファイルで、実質的にハードウェア操作用のインターフェースとなっています。
デバイスファイルにも種類があります。ブロックデバイスとキャラクタデバイスはファイルタイプの所ででてきましたね。もちろんls -lコマンドでファイルの種類を判別できます。
| デバイス種別 | 例 | 内容 |
|---|---|---|
| ブロックデバイス |
/dev/sda, /dev/mmcblk0
|
ディスクやSDカードなど、ブロック単位でアクセス |
| キャラクタデバイス |
/dev/tty0, /dev/ttyUSB0, /dev/random
|
バイト単位で読み書き(シリアル通信や擬似端末など) |
| 仮想デバイス |
/dev/null, /dev/zero, /dev/urandom
|
ハードじゃないけど便利な疑似デバイス |
これらの/dev以下のファイルは基本的に自動生成されます。Arduinoをシリアルポートに接続すれば、/dev/ttyACM0のような名前で出るでしょうし、PS4のコントローラをBluetoothで繋げば/dev/input/js0のような名前のファイルが生成されるでしょう。
このように、デバイスファイルを生成しているのがudevというデバイス管理デーモンです。昔はmknodのようなコマンドで全て手動作成していたそうですが、今は違います。/devの番人ことudevがデバイスが抜き差しされた瞬間に /dev 配下のデバイスファイルを作ったり消したり、名前を整理したりしてくれます。
Udev
役割:
- /dev のデバイスファイルを自動生成・削除
- デバイス名の整理(例:/dev/ttyUSB0, /dev/sda1)
- デバイスにルールを当てて特定の権限やシンボリックリンクを作れる
動作プロセス:
- ハードウェアが接続される
- カーネルがデバイスを認識
- udev がイベントをキャッチ(
udevddaemon) - /dev に対応するファイルを作る
- ルールに応じて名前や権限を整える
デーモンであるため、systemctlが使える
# 起動状態確認
$ systemctl status systemd-udevd
# 起動
$ sudo systemctl start systemd-udevd
# 常に有効化(起動時に立ち上がる)
$ sudo systemctl enable systemd-udevd
# 再起動(デバイス追加後とか)
$ sudo systemctl restart systemd-udevd
ルールは基本的に/etc/udev/rules.d/や/lib/udev/rules.dディレクトリに置きます。
ルール例: 以下のルールの場合、USB が追加された時、ベンダーID 2341 / プロダクトID 0043 の場合、/dev/myarduino というシンボリックリンクが作られます。
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="2341", ATTR{idProduct}=="0043", SYMLINK+="myarduino"
これをすることで、USBを挿す度に/dev/ttyACM0から/dev/ttyACM1に変わったりといった問題を解決することができます。他にも、権限の設定等も可能です。ファイル名に決まりがあり、NN-name.rulesの形式でなければなりません。NNは読み込む順番、名前は特に決まりはありません。
ルール有効化:
# ルールの再読み込み
$ sudo udevadm control --reload
# デバイスイベントを再適用
$ sudo udevadm trigger
シンボリックリンクの場合、ls -lコマンドでちゃんとリンクが張られているか確認しましょう。
Major / Minor
実は、さっきまで話していた/dev/sdaのようなファイル名はあまり本質ではありません。そもそも、Linuxカーネルはデバイスドライバを通してハードウェアにアクセスするわけですが、カーネルはファイル名を見て判断しているわけではありません。
その代わりにmajor/minor番号という二つの番号で判別しています。major番号とは、どのドライバがそのデバイスを扱うかを示す番号であり、minor番号とは、そのドライバが扱う個別のデバイス番号のことです。
この番号もまたls -lコマンドで確認できます
$ ls -l /dev/nvme0n1p2
brw-rw---- 1 root disk 259, 2 Oct 31 23:06 /dev/nvme0n1p2
# 259がmajor番号、2がminor番号
udevが動く前の時代はmknodで作っていたと言いましたが、こういうことです。今はudevが自動でやってくれます。
$ mknod /dev/nvme0n1p2 b 259 2
どのドライバがどのmajor番号に対応してるかは、/proc/devicesを見ればわかります
$ cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
...
Block devices:
7 loop
8 sd
11 sr
65 sd
...
Device Driver
デバイスドライバというのは、言うなればハードウェアとカーネルの間の通訳です。デバイスドライバはLinuxにおいてカーネルモジュールとして存在します(拡張子は.ko)。Linuxはモノリシックカーネルを採用していますが、モジュール拡張することができます。デバイスドライバはGitHubから取ってきたり、自分でC言語等で書くこともできます(!)
いまカーネルに読み込まれてるモジュール(ドライバ)を一覧表示するには、lsmodコマンドを使います。
$ lsmod | grep usb
btusb 86016 0
btrtl 32768 1 btusb
btintel 69632 1 btusb
btbcm 24576 1 btusb
btmtk 36864 1 btusb
bluetooth 1167360 6 btrtl,btmtk,btintel,btbcm,btusb
lsmodは実は/proc/modulesを読み取っているだけです。ですから、catでそのまま見にいけます。(普通はしません)
$ cat /proc/modules | grep usb
btusb 86016 0 - Live 0x0000000000000000
btrtl 32768 1 btusb, Live 0x0000000000000000
btintel 69632 1 btusb, Live 0x0000000000000000
btbcm 24576 1 btusb, Live 0x0000000000000000
btmtk 36864 1 btusb, Live 0x0000000000000000
bluetooth 1167360 6 btusb,btrtl,btintel,btbcm,btmtk, Live 0x0000000000000000
手動でドライバを入れるときはmodprobeコマンドを使います。insmodというコマンドもありますが、modprobeは自動で依存関係も解決してくれるので通常はそっちを使います。新しい機器を追加したり、トラブルシューティングしたりするときに使います。
$ sudo modprobe uvcvideo
アンロードする時はrmmod, 詳細を見るときはmodinfoコマンドが使えます。
$ modinfo bluetooth
filename: /lib/modules/6.17.5-zen1-1-zen/kernel/net/bluetooth/bluetooth.ko.zst
alias: net-pf-31
license: GPL
version: 2.22
description: Bluetooth Core ver 2.22
author: Marcel Holtmann <marcel@holtmann.org>
srcversion: F02CB826454C08B5988CE22
depends: rfkill
intree: Y
name: bluetooth
...
Device Check Commands
lsusb
USBバスに接続された機器一覧を表示するコマンド。
$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 2357:0604 TP-Link TP-Link Bluetooth USB Adapter
Bus 001 Device 003: ID 1a40:0101 Terminus Technology Inc. Hub
Bus 001 Device 004: ID 0b05:19af ASUSTek Computer, Inc. AURA LED Controller
Bus 001 Device 005: ID 05ac:024f Apple, Inc. Aluminium Keyboard (ANSI)
Bus 001 Device 006: ID 05e3:0608 Genesys Logic, Inc. Hub
Bus 001 Device 007: ID 04f3:0235 Elan Microelectronics Corp. Optical Mouse
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
ls /dev/ttyUSB*とは何が違う?
USBデバイスの確認というだけなら、どっちでもよさそうですが、/dev以下のファイルが生成されるプロセスを思い出してください。カーネルがドライバを当ててデバイスが正常認識されている時にしか使えません。比べて、lsusbは刺さっているデバイス自体をモジュールがロードされている、されていないに関わらず表示します。
lsblk
ディスクやパーティションを一覧表示するコマンド。lsblk -fでファイルシステムの情報も追加で表示できます。下の例だと、/dev/nvme0n1p2がext4パーティションであることがわかりますね。
$ lsblk -f
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
loop0 squashfs 4.0 0 100% /var/lib/snapd/snap/core/17200
loop1 squashfs 4.0 0 100% /var/lib/snapd/snap/core/17212
loop2 squashfs 4.0 0 100% /var/lib/snapd/snap/core22/1380
loop3 squashfs 4.0 0 100% /var/lib/snapd/snap/core22/2045
loop4 squashfs 4.0 0 100% /var/lib/snapd/snap/snapd/21759
loop5 squashfs 4.0 0 100% /var/lib/snapd/snap/snapd/24792
nvme0n1
├─nvme0n1p1 vfat FAT32 5683-ACF5 1021.8M 0% /boot/efi
├─nvme0n1p2 ext4 1.0 71155e31-8a22-4e46-8748-ef78c0817540 455.5G 46% /
└─nvme0n1p3 swap 1 e2f717d6-b5cb-49a1-ae87-aa0c88e76f48 [SWAP]
lspci
PCI/PCIe バスに接続された機器を一覧表示するコマンド。要するに、マザーボードのスロットに刺さっているカードの情報を見るコマンド。GPUとか見るときによく使います。
$ lspci
00:00.0 Host bridge: Intel Corporation Device 4c43 (rev 01)
00:02.0 VGA compatible controller: Intel Corporation RocketLake-S GT1 [UHD Graphics 750] (rev 04)
00:14.0 USB controller: Intel Corporation Tiger Lake-H USB 3.2 Gen 2x1 xHCI Host Controller (rev 11)
00:14.2 RAM memory: Intel Corporation Tiger Lake-H Shared SRAM (rev 11)
00:15.0 Serial bus controller: Intel Corporation Tiger Lake-H Serial IO I2C Controller #0 (rev 11)
00:16.0 Communication controller: Intel Corporation Tiger Lake-H Management Engine Interface (rev 11)
00:17.0 SATA controller: Intel Corporation Device 43d2 (rev 11)
00:1c.0 PCI bridge: Intel Corporation Tiger Lake-H PCI Express Root Port #5 (rev 11)
00:1f.0 ISA bridge: Intel Corporation H510 LPC/eSPI Controller (rev 11)
00:1f.3 Audio device: Intel Corporation Tiger Lake-H HD Audio Controller (rev 11)
00:1f.4 SMBus: Intel Corporation Tiger Lake-H SMBus Controller (rev 11)
00:1f.5 Serial bus controller: Intel Corporation Tiger Lake-H SPI Controller (rev 11)
00:1f.6 Ethernet controller: Intel Corporation Ethernet Connection (14) I219-V (rev 11)
01:00.0 Non-Volatile memory controller: Intel Corporation SSD 660P Series (rev 03)
dmesg
カーネルが今まで記録してきたログを見るためのコマンドです。デバイス接続やドライバロードの状況を確認したりするのに使います。下の例では、キーボードのUSBを抜き差ししたのがわかりますね。(Keychronのキーボードおすすめです笑)
$ sudo dmesg | tail -15
[ 60.635439] rfkill: input handler enabled
[ 61.509744] rfkill: input handler disabled
[ 8323.502444] usb 1-3.2: USB disconnect, device number 5
[ 8324.094136] usb 1-3.2: new full-speed USB device number 8 using xhci_hcd
[ 8324.283294] usb 1-3.2: New USB device found, idVendor=05ac, idProduct=024f, bcdDevice= 1.03
[ 8324.283307] usb 1-3.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 8324.283312] usb 1-3.2: Product: Keychron C1
[ 8324.283316] usb 1-3.2: Manufacturer: Keychron
[ 8324.290021] apple 0003:05AC:024F.0005: Non-apple keyboard detected; function keys will default to fnmode=2 behavior
[ 8324.290136] input: Keychron Keychron C1 as /devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3.2/1-3.2:1.0/0003:05AC:024F.0005/input/input18
[ 8324.318566] apple 0003:05AC:024F.0005: input,hidraw1: USB HID v1.11 Keyboard [Keychron Keychron C1] on usb-0000:00:14.0-3.2/input0
[ 8324.320870] apple 0003:05AC:024F.0006: Fn key not found (Apple Wireless Keyboard clone?), disabling Fn key handling
[ 8324.320881] apple 0003:05AC:024F.0006: Non-apple keyboard detected; function keys will default to fnmode=2 behavior
[ 8324.321048] input: Keychron Keychron C1 as /devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3.2/1-3.2:1.1/0003:05AC:024F.0006/input/input19
[ 8324.322134] apple 0003:05AC:024F.0006: input,hidraw2: USB HID v1.11 Keyboard [Keychron Keychron C1] on usb-0000:00:14.0-3.2/input1
lsof (list open files)
ファイルが誰に開かれてるか、要するに誰に占領されてるか確認するコマンド。よくあるのは、誰がそのシリアルポートを開いてるか確認して、PIDを特定し、kill -9でそのプロセスを殺す。ネットワークポートの確認にも使えますし、かなり幅広く有効なコマンドだと思います。
$ lsof /dev/ttyUSB0
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
minicom 1234 tatsv 3u CHR 188,0 0t0 4 /dev/ttyUSB0
python3 2345 tatsv 4u CHR 188,0 0t0 4 /dev/ttyUSB0