---
title: LDD 3e 閱讀筆記
tags: security
lang: zh_tw
---
# LDD 3e 閱讀筆記
[TOC]
# Intro
- Linux Device Drivers 3rd Edition, O'REILLY 閱讀筆記

# Driver & Device 概念
1. Driver 跟 Device 的關係有點像是 Class 跟 Object
2. Driver 的流程如下
- 申請 Device numbers
- 實作 File Operation, 像是 read, write 實際上怎麼做
- 申請 Device 編號
3. Linux 中**所有東西(Process, Device, Driver)都是檔案**, 或說, 都像是檔案
Device 會出現在 `/dev` 底下, 可以像是對待一個 File 般的對待此 Device
4. Driver 分成三種
- Char Driver
以 `ls -al /dev` 觀察會看到屬於此類 Driver 的 Device 的權限第一個字會是 `c`
例如以下
```
crw-rw-rw- 1 root tty 5, 0 Mar 15 14:55 tty
```
- Block Driver
以 `ls -al /dev` 觀察會看到屬於此類 Driver 的 Device 的權限第一個字會是 `b`
例如以下
```
brw-rw---- 1 root disk 8, 0 Mar 15 00:18 sda
```
- Network Driver
## Major/Minor Numbers 概念
一個 Device 會有 Major/Minor Numbers
以下是一小段 `ls -al /dev` 的輸出
```
# ls -al /dev
...
crw-rw-rw- 1 root root 5, 0 Mar 14 23:55 tty
crw-rw-rw- 1 root root 1, 9 Mar 14 23:55 urandom
crw-rw-rw- 1 root root 1, 5 Mar 14 23:55 zero
```
`tty` 是一個 **Device**, 由 **Driver** 5 管理
`urandom` `zero` 是兩個不同的 **Device**, 都由 **Driver** 1 管理, 可以再用 9 跟 5 這兩個 minor numbers 來區分這兩個 **Device**
- Major Number: Driver 編號
- Minor Number: 用來分辨 Device 的編號
## Important Data Structures
### File Operations
```c
struct file_operations
```
- 定義於 `<linux/fs.h>`
這邊紀錄幾個 File Operations, 不會全部列出
- asynchronous
function return 後, read/write 還沒完成
- `poll`
`poll` 用來查詢 I/O 會不會暫停
若設定 `poll` 為 NULL, 表示此 device 不會被暫停
`poll` 相關 syscalls:
- poll
- epoll
- select
- `ioctl` 像是執行 device 自訂的行為, 而不僅僅只是 I/O
- `mmap` 是要求把 device memory mapping 到 process's address space
- 若 `open` 為 NULL, 則 `open` call 會成功, 但 driver 不會知道被 open
- `flush` 發生在 close 此 device 的 fd 的複製品時, 極少 driver 使用到此 File Operation
若 `flush` 為 NULL, kernel 就只是 ignore 掉此呼叫, 而不是回傳任何錯誤代碼
- `release` 是發生在 close 此 device 的 fd 時, 跟 open 一樣可以是 NULL
- File Operation `fsync` 是與 system call fsync 有關, 若此為 NULL, 則 system call fsync 會 return -EINVAL
- `fasync` 是發生在 FASYNC flag 改變時
- `lock` 是 file 不能缺少的 File Operation, 但 driver 不用實作這個
- 若 char device 指定 File Operation `readdir` 會?
### The file Structure
```c
struct file
```
- 定義於 `<linux/fs.h>`
- 注意這個 struct 跟 C library 中的 `FILE` 無關
- C lib 中的 `FILE` 只出現在 user program
- `<linux/fs.h>` 中的 `file` 只出現在 kernel
- kernel 用此結構去代表一個 open file, 呼叫到某 file 的 file operations 時, 會將此代表此 file 的 **struct file 指標**傳進對應的 file operation 中
以下簡單介紹幾個欄位
- `f_mode`
此檔案的讀寫權限設定, 但你不需要在 File Operations `read` `write` 中添加判斷 `f_mode` 的 code, kernel 會先自行判斷可不可讀/寫後才呼叫 `read`/`write`
- `f_pos`
目前讀/寫到的位置
- `f_op`
此為 `struct file_operations *`, 且可以 overwrite, 這就表示了可以達到**同屬於一個 Driver 的 Devices 們行為卻不同**
### The inode Structure
- `inode` 是用來表示 file 的結構, 跟 `file` 不同, `file` 是用來表示已開啟的 file
- 一個 file 可以被多重 open, 此時會有多個 `file` 結構都代表此 open file
- 但只有一個 `inode` 結構代表此 file
以下簡單介紹幾個欄位
- `i_rdev`
表示實際的 device number
- `i_cdev`
是一個 `struct cdev *`, `cdev` 用來表示 char devices 結構
# Char Device Driver
可以配合 [書中 scull 範例 code](https://github.com/starpos/scull/tree/master/scull) 觀賞
以下是寫 Char Device Driver 的幾個步驟
## 申請 Device Numbers
使用 `register_chrdev_region()` 或 `alloc_chrdev_region()` 來取得可用的 device numbers
- `register_chrdev_region()`
- 是直接指定 device number, 但若指定的編號已經被占用, 就會失敗
- `alloc_chrdev_region()`
- 則是交給 kernel 決定 device number
在範例 code 中就是 `main.c` 的
```c
dev_t dev = 0;
/*
* Get a range of minor numbers to work with, asking for a dynamic
* major unless directed otherwise at load time.
*/
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
} else {
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
"scull");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return result;
}
```
## 實作 File Operations
將此 Char Device Driver 要實現的功能做好 e.g. `read` `write` `open` `release`
在範例 code 中就是 `main.c` 的
```c
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.unlocked_ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};
```
以及 `scull_llseek` `scull_read` ... 這些 function 的實作
這邊可以觀察到, 其實這就是物件導向的寫法
## 註冊 Char Device
使用 `cdev_init()` `cdev_add()` 來向 kernel 註冊 Char Device
在範例 code 中就是 `main.c` 的
```c
/*
* Set up the char_dev structure for this device.
*/
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
int err, devno = MKDEV(scull_major, scull_minor + index);
cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;
err = cdev_add (&dev->cdev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}
```
## 註銷 Char Device
最後如果此 Driver 要移除了, 就是跟創造的過程反過來
- 註銷 Char Device
- 註銷 Device Numbers
使用 `cdev_del()` 來註銷 Char Device
範例 code
```c
int i;
dev_t devno = MKDEV(scull_major, scull_minor);
/* Get rid of our char dev entries */
if (scull_devices) {
for (i = 0; i < scull_nr_devs; i++) {
scull_trim(scull_devices + i);
cdev_del(&scull_devices[i].cdev);
}
kfree(scull_devices);
}
```
## 註銷 Device Numbers
使用 `unregister_chrdev_region()` 來註銷 Device Numbers
範例 code
```c
/* cleanup_module is never called if registering failed */
unregister_chrdev_region(devno, scull_nr_devs);
```
# Remapping RAM
- p.430