在 著 手 撰 寫 Linux I/O Device Driver之 前 , 首 先 介 紹 一 些 相 關 的 觀 念 。 UNIX Device Driver是 屬 於 核 心 軟 體 (Kernel)的 一 部 份 ; UNIX作 業 系 統 主 要 分 為 Kernel和 應 用 軟 體 包 括 公 用 程 式 在 內 等 兩 大 部 份 。 然 而 Device Driver與 作 業 系 統 之 間 的 關 係 如 圖 一 所 示 。 基 本 上 , 所 有 的 UNIX系 統 架 構 都 是 以 此 為 藍 本 而 設 計 的 。 只 是 每 一 套 UNIX作 業 系 統 也 有 不 完 全 相 同 之 處 , 如 Sun Microsystem的 Solaris系 統 與 AT&T的 System V, Interactive UNIX, 雖 是 以 System V為 版 本 , 但 相 異 處 也 不 少 。 Linux與 SunOS 4.x皆 出 自 於 Berkey版 本 的 背 景 , 也 是 有 許 多 不 同 的 地 方 。 在 這 裡 就 不 針 對 這 些 問 題 作 討 論 , 只 是 提 醒 大 家 認 清 一 套 系 統 要 注 意 其 背 景 及 架 構 , 不 要 混 為 一 談 。

使 用 者 應 用 程 式
/dev  User Space
Device Driver Kernel Space
實 體 設 備

圖一、Device Driver與作業系統的關係

 

 

  Device Driver大 致 分 為 Block Device Driver和 Character Device Driver兩 類 。 而 Block Device Driver是 以 固 定 大 小 長 度 來 傳 送 轉 移 資 料 ; Character Device Driver是 以 不 定 長 度 的 字 元 傳 送 資 料 。 且 所 連 接 的 Devices也 有 所 不 同 , Block Device大 致 是 可 以 隨 機 存 取 (Random Access)資 料 的 設 備 , 如 硬 碟 機 或 光 碟 機 ; 而 Character Device剛 好 相 反 , 依 循 先 後 順 序 存 取 資 料 的 設 備 , 如 印 表 機 、 終 端 機 等 皆 是 。

  本 文 就 以 Character Device Driver發 展 的 程 序 作 說 明 , 其 他 就 不 另 行 討 論 了 。 有 興 趣 者 可 以 參 考 其 他 UNIX作 業 系 統 的 DDI/DKI(Device Driver Interface/ Driver-Kernel Interface)手 冊 ; 因 為 Linux Device Driver發 展 手 冊 或 相 關 書 籍 , 在 國 內 大 概 是 找 不 到 了 。 發 展 Device Driver有 相 當 多 的 問 題 需 要 注 意 , 其 中 最 重 要 的 就 是 備 份 你 的 程 式 和 資 料 , 還 有 一 開 機 磁 片 ; 因 為 發 展 過 程 當 中 , Driver可 能 會 產 生 Bug, 造 成 無 法 開 機 或 是 System Crash, 那 時 你 的 一 切 頃 刻 之 間 馬 上 化 為 烏 有 , 真 叫 你 傷 心 欲 絕 , 欲 哭 無 淚 , 喚 不 回 來 了 。 所 以 , 記 得 做 Backup!

  現 在 就 來 說 明 Linux Device Driver 發 展 的 步 驟 , 供 大 家 參 考 ; 但 不 一 定 要 嚴 格 遵 循 此 程 序 , 可 依 自 己 的 意 向 做 調 整 。

 

熟 悉 Linux Function Routines和 Data Structure

  Linux提 供 一 些 Routines和 Kernel使 用 的 資 料 結 構 (Data Structure)於 核 心 軟 體 (Kernel) 裡 面 , 供 大 家 參 考 使 用 。 筆 者 歸 納 了 幾 類 , 相 信 已 足 夠 應 付 Device Driver的 開 發 。

 

1.記 憶 體 配 置 、 釋 放 和 轉 移

void *kmalloc(unsigned int size, int priority)
void *kfree_s(void * obj, int size)
kfree(char *)
memcpy_fromfs(dest, src, size)
memcpy_tofs(dest, src, size)
put_fs_byte(src, dest)
byte get_fs_byte(char *s)

  上 面 列 印 的 Routines是 記 憶 體 的 配 置 、 釋 放 、 轉 移 等 Procedures。 kmalloc(), kfree()用 法 與 C Language中 使 用 於 User Program的 malloc(), free()用 法 一 樣 。 只 是 其 動 態 配 置 的 空 間 是 Kernel Space而 已 ; 而 且 因 為 Kernel的 空 間 是 有 限 制 的 , 所 以 記 得 做 資 源 回 收 不 要 浪 費 記 憶 空 間 。 memcpy _fromfs()是 把 User program寫 入 的 資 料 搬 移 到 Kernel Space的 位 置 ; memcpy_tofs()的 動 作 剛 好 相 反 , 是 從 Kernel Space的 位 置 搬 移 到 檔 案 系 統 (File System)的 空 間 , 以 便 User Program讀 取 資 料 。 這 兩 者 大 致 應 用 於 Read, Write的 動 作 程 序 當 中 。

  至 於 put_fs_byte()與 get_fs_byte()的 動 作 是 寫 入 和 讀 取 一 個 字 元 的 資 料 , 分 別 從 User program的 檔 案 系 統 空 間 到 Kernel Space, 或 是 相 反 的 動 作 。

 

2.輸 出 入 程 序

inb(port)
outb(char, port)

  此 兩 程 序 分 別 用 於 介 面 埠 (Interface I/O Port)以 讀 取 和 寫 出 一 個 字 元 , 達 到 控 制 介 面 、 讀 取 狀 態 、 輸 出 入 資 料 的 目 的 。

 

3.巨 集 指 令

cli()
sti()
minor=MINOR(inode->i_rdev)
major=MAJOR(inode->i_rdev)
boolean suser()
save_flags(long flag)
restore_flags(long flag)

  cli()與 sti()的 功 能 和 C語 言 中 的 disable(), enable()以 及 Assembly語 言 的 cli, sti是 一 樣 的 ; 分 別 用 以 清 除 插 斷 旗 幟 (Interrupt flag), 達 到 抑 制 插 斷 的 發 生 , 和 設 定 旗 幟 , 允 許 插 斷 於 下 一 個 指 令 執 行 結 束 後 可 以 回 應 任 何 型 式 插 斷 的 發 生 。 接 下 來 解 釋 major number和 minor number的 意 義 。 major number用 以 識 別 Devices的 類 別 , 而 minor number用 以 區 別 每 一 類 別 中 , 每 一 個 Device。 舉 個 例 子 來 說 明 : 你 可 以 用 ls-l command於 目 錄 /d ev中 列 印 資 料 如 下 :

crw-rw-rw- 1  bin   bin    5, 0  Oct 9 14:00  /dev/tty1a
crw-rw-rw- 1 bin bin 5, 1 Oct 9 14:02 /dev/tty1b
crw-rw-rw- 1 bin bin 5, 2 Oct 9 14:05 /dev/tty1c

  範 例 中 , 數 目 5代 表 tty Device的 major Device number; 而 其 中 0, 1, 2 就 是 代 表 minor number, 分 別 表 示 Device tty1a, tty1b, tty1c。 說 明 至 此 , 相 信 你 已 了 解 什 麼 是 major number, minor number 了 , 那 又 怎 麼 使 用 呢 ? 在 User Program 中 , 根 本 不 用 考 慮 這 個 問 題 , 僅 是 使 用 open()來 開 啟 /dev/tty1a Device, 以 得 到 一 個 file descriptor而 已 ; 然 而 , 在 Kernel Device Driver中 , open、 close、 read、 write等 函 數 就 是 利 用 minor number來 區 別 到 底 是 那 一 個 Device要 求 服 務 (Service)。 只 要 是 應 用 inode Kernel資 料 結 構 來 取 得 minor number。 用 法 如 下 :
minor= MINOR(inode->i_rdev)

  至 於 inode structure將 於 資 料 結 構 單 元 中 介 紹 。 suser()這 是 個 檢 核 使 用 者 是 否 為 supervisor privilege的 函 數 , 以 決 定 使 用 者 的 使 用 權 限 。 save_flags(), restore _flags()是 用 以 存 取 系 統 旗 幟 (System Flags)的 函 數 。

4.中斷函數

request_irq(dev_irq, handler())
irqaction(irq, (struct sigaction *))
free_irq(dev_irq)
interruptible_sleep_on(struct wait_queue *)
wake_up(struct wait_queue *)
wake_up_interruptible(struct wait_queue *)

  第 四 點 提 到 的 是 與 中斷 動 作 有 關 的 函 數 , 不 見 得 每 一 個 Device Driver皆 會 用 得 到 ; 因 為 有 些 Device Driver是 使 用 詢 問 (Polling)的 方 式 來 撰 寫 的 , 其 應 用 固 定 週 期 對 需 要 的 Device做 詢 問 , 是 否 需 要 Service, 再 做 進 一 步 處 理 ; 至 於 比 較 有 效 率 的 Interrupt方 式 還 是 要 了 解 。

  request_irq()與 irqaction()兩 者 的 功 用 是 相 同 的 ; 都 是 向 系 統 註 冊 (Register)本 身 Device Driver使 用 的 IRQ和 Interrupt Handler是 什 麼 。 其 作 用 與 MS-DOS下 寫 C語 言 使 用 的 setvect()函 數 相 類 似 , 只 不 過 在 Linux Driver就 不 需 要 自 己 enable IRQ的 宣 告 罷 了 。 free_irq()的 功 用 就 是 釋 放 (Rel ease)IRQ的 使 用 權 , 歸 還 給 系 統 。

  在 寫 Device Driver的 I/O動 作 時 , 不 能 與 在 寫 一 般 的 User Program的 I/O一 樣 , 放 任 其 不 管 。 一 定 要 注 意 其 I/O是 否 在 Busy或 Waiting狀 態 , 做 出 必 要 的 反 應 動 作 。 例 如 發 現 Device在 Busy狀 態 時 , 最 好 呼 叫 interrupt_sleep_on()去 休 息 一 下 , 把 執 行 權 (Running)交 還 給 系 統 ; 若 發 現 是 在 Waiting狀 態 時 , 就 可 借 用 wake_up()或 是 wait_up_interruptible()函 數 , 把 你 的 Sleeping Process叫 醒 來 工 作 。

5.其他

printk() 
register_chrdev(unsigned int major, const char *name, struct file_operations &fops)
unregister_chrdev(unsigned int major, const char *name)

  有 經 驗 的 軟 體 工 程 師 , 一 定 有 這 樣 的 經 驗 , 在 軟 體 開 發 的 時 候 , 早 就 事 先 埋 下 伏 筆 , 將 有 需 要 查 證 、 除 錯 (Debug)的 資 料 做 個 處 理 , 且 配 合 測 試 軟 體 做 個 安 排 , 以 便 節 省 除 錯 時 間 。 printk()的 功 能 與 printf()相 似 , 只 是 僅 能 應 用 於 Kernel的 軟 體 中 。 register_chrdev()是 Character Device Driver用 來 向 系 統 註 冊 (Register)的 函 數 , 其 參 數 包 括 Character Device的 major number, Driver名 稱 以 及 檔 案 作 業 (File Operation)指 定 的 幾 個 函 數 宣 告 的 名 稱 。 對 於 File Operation的 結 構 及 如 何 應 用 , 將 於 後 面 做 詳 細 的 討 論 。

  unregister_chrdev()顧 名 思 義 , 就 是 系 統 取 消 註 冊 名 稱 。

6.資 料 結 構

      struct file_operations  fops
struct file *filp
struct wait_queue dev_q
struct inode dev_node
struct sigaction sa

  上 式 所 列 幾 個 資 料 結 構 在 開 發 Device Driver時 , 是 比 較 常 見 的 。 像 struct
file_operations是 Device Driver的 主 要 結 構 , 其 定 義 於
/usr/src/linux/include 目 錄 下 的 fs.h檔 案 。 Driver中 使 用 到 主 要 函 數 如
xx_read、 xx_write、 xx_open、 xx_
close等 必 須 以 此 結 構 加 以 宣 稱 。 現 在 列 印 此 結 構 如 下 :
 struct file_operations {

int (*lseek)(....);

int (*read) (...);

int (*write) (...);

int (*readdir) (...);

int (*select) (...);

int (*ioctl) (...);

int (*mmap) (....);

int (*open) (...);

void (*release) (...);

int (*fsync) (....);

int (*fasync) (...);

};

  詳 細 結 構 請 參 考 fs.h, 它 的 每 一 個 element, 都 是 一 函 數 指 標 , 指 向 你 的 Device Driver所 寫 的 函 數 。 我 們 舉 個 例 子 如 下 , 或 是 參 閱 本 文 的 Listing程 式 。

static struct file_operations testty_fops = {

NULL,

testty_read,

testty_write,

NULL,

NULL,

NULL,

NULL,

testty_open,

testty_close,

NULL,

NULL,

};

  讀 者 只 要 將 此 資 料 結 構 在 Driver中 宣 告 , 然 後 應 用 register_chrdev()向 kernel system註 冊 , 宣 告 你 的 major number和 Driver名 稱 , 此 時 你 已 接 近 完 成 一 半 的 工 作 了 。 接 下 來 就 依 照 你 宣 告 的 函 數 名 稱 , 逐 一 完 成 相 關 的 程 式 , 此 刻 你 的 Device Driver 就 算 完 成 了 coding的 動 作 了 。 其 餘 的 我 們 後 面 再 慢 慢 介 紹 了 。

  struct file結 構 定 義 於 fs.h include file 中 , 假 如 你 的 Driver是 應 用 於 I/O Device 方 面 時 , 此 結 構 大 致 配 合 function的 參 數 (Parameter)來 使 用 , 其 elements的 應 用 就 少 了 ; 假 如 是 data file方 面 的 應 用 , 那 就 應 用 許 多 了 , 包 括 file mode、 file’ s offset position等 。

  struct wait_queue是 一 個 Process處 理 時 , 發 生 I/O動 作 需 要 進 入 睡 眠 (Sleep)或 甦 醒 (Wake up)狀 態 時 , 所 用 到 的 一 個 結 構 ; 用 以 記 錄 Process目 前 所 處 的 狀 況 (St- atus)。 像 interruptible_sleep_on(), wake_up()及 wake_up_interruptible()皆 以 此 結 構 為 主 , 配 合 Driver來 完 成 I/O的 動 作 , 僅 須 宣 告 一 參 數 於 這 些 函 數 中 就 可 以 了 。 接 下 來 就 繼 續 說 明 inode結 構 的 應 用 。

  inode結 構 是 組 成 file system的 元 素 , 其 內 容 為 file owner identifier、 file type 、 file access permissions, 檔 案 存 取 的 時 間 紀 錄 , 檔 案 的 大 小 , 檔 案 資 料 於 disk address的 表 格 (table), 以 及 file link的 數 目 , 還 有 許 多 結 構 的 嵌 入 , 詳 細 的 內 容 可 參 閱 fs.h中 的 定 義 。 其 中 的 i_rdev係 用 來 決 定 Device的 minor和 major number:

minor=MINOR(inode->i_rdev)
major=MAJOR(inode->i_rdev)

  當 你 取 得 minor number時 , 你 就 可 決 定 目 前 工 作 的 Device是 那 一 個 需 要 服 務 (Service), 以 便 取 得 Device Driver中 的 結 構 , 得 到 Current Device的 狀 態 。 Struct Sigaction是 用 來 向 Kernel系 統 註 冊 IRQ及 插 斷 處 理 程 式 (Interrupt Handler) 的 結 構 。 例 如 宣 告 一 變 數 為 sa: struct sigaction sa;

     sa.sa_handler = test_interrupt;

sa.sa_flags = SA_INTERRUPT;

sa.sa_mask = 0;

sa.sa_restorer = NULL;

irqaction(irq, &sa);

  應 用 上 面 幾 行 statements宣 告 你 的 Interrupt handler函 數 了 ; 亦 可 使 用 requestirq(dev_irq, handler)來 完 成 相 同 的 功 能 。

 

程 式 碼 開 發

 

  Linux Device Driver其 實 很 簡 單 , 最 難 的 仍 然 是 I/O卡 的 控 制 , 只 要 你 對 你 的 Device很 了 解 , 其 餘 的 follow或 參 考 本 文 就 可 迎 刃 而 解 了 。 首 先 就 比 較 相 關 的 函 數 著 手 , 例 如 xx_open()、 xx_close()、 xx_read()、 xx_write()等 。 當 然 名 稱 不 一 定 要 這 樣 宣 告 , 隨 你 的 意 思 , 以 清 楚 有 意 義 為 原 則 。 將 函 數 宣 告 於 struct file_operations結 構 中 , 然 後 依 照 所 定 義 的 函 數 名 稱 去 開 發 這 些 函 數 , 可 參 考 本 文 所 附 的 程 式 ; 其 中 有 一 個 做 i nitialize動 作 的 函 數 xx_init(long kmem _start), 並 沒 有 宣 告 於 file_operations當 中 。 此 函 數 的 內 容 除 了 利 用 register _chrdev()向 Kernel系 統 註 冊 之 外 , 可 以 做 一 些 Hardware或 是 Software方 面 初 值 設 定 。

  對 於 long xx_init(long kmem_start) 函 數 , 還 有 一 些 其 他 的 動 作 要 處 理 。 在 同 一 個 工 作 目 錄 /usr/src/linux/Drivers/char 下 , 找 到 mem.c程 式 , 然 後 修 改 此 程 式 中 的 chr_dev_init()函 數 , 增 加 下 列 幾 行 於 函 數 中 。 TEST_DRIVE是 自 己 宣 告 的 defined constant。

#ifdef  TEST_DRIVE

kmem_start = xx_init(kmem_start);

#endif

  因 為 此 函 數 係 用 來 安 排 各 種 Devices佔 用 記 憶 體 (Memory)的 空 間 , 包 括 記 憶 體 本 身 在 內 。 其 他 輔 助 性 的 函 數 可 依 Driver的 性 質 , 自 行 增 減 於 整 個 軟 體 的 開 發 。 假 設 Driver的 相 關 功 能 都 Coding完 畢 之 後 , 接 下 來 都 是 重 複 性 的 工 作 包 括 錯 誤 更 正 、 編 譯 、 測 試 等 等 。 現 在 我 們 繼 續 說 明 如 何 更 動 系 統 檔 案 , 達 到 編 譯 程 式 , Build Up系 統 Kernel的 目 的 。 c建 立 Makefile及 Update .config

  請 於 /usr/src/linux/Drivers/char目 錄 下 , 將 你 的 Device Driver名 稱 鍵 入 於 Makefile 中 , 以備編譯程式時使用 。

#ifdef  TEST_DRIVE

OBJS := $(OBJS) Driver_name.o

SRCS := $(SRCS) Driver_name.c

#endif

  接 著 請 於 目 錄 /usr/src/linux下 工 作 , 更 改 .config檔 案 內 容 ; 請 在 有 關 char Device 位 置 部 份 增 加
TEST_DRIVE=TEST_DRIVE

  還 有 請 於 config.in檔 案 中 , 在 適 當 的 地 方 加 入 一 行
bool ‘ Char TEST_DRIVE Driver support’ TEST_DRIVE y

  以 便 在 執 行 make config時 , 是 否 需 要 將 test_Driver一 起 包 括 進 去 做 configure的 動 作 , 其 中 字 串 內 容 依 自 己 Driver的 功 能 , 自 行 描 述 即 可 ; 再 來 就 繼 續 介 紹 下 一 步 驟 。

 

執 行 make config及 make dep

  此 動 作 係 做 Kernel各 種 設 備 (Device)的 安 排 , 例 如 有 無 印 表 機 、 光 碟 機 、 PPP、 SLIP 等 等 。 針 對 系 統 的 需 要 設 備 , 做 適 當 的 調 整 。 make dep針 對 系 統 做 一 致 性 的 調 整 , 例 如 有 那 些 檔 案 經 過 更 改 之 後 , 它 會 做 適 度 Update日 期 , 以 配 合 編 譯 程 式 處 理 。 步 驟 四 做 過 一 次 之 後 , 視 需 要 再 做 , 不 用 每 次 都 處 理 , 因 為 它 花 費 太 多 時 間 。

 

建 立 Kernel系 統

在 command prompt下 , 執 行 make zImage指 令 , 以 建 立 Kernel系 統 軟 體 ; 此 動 作 就 會 開 始 編 譯 你 開 發 的 Driver程 式 。 假 如 發 現 有 Syntax方 面 的 錯 誤 時 , 必 須 更 正 Driver程 式 , 再 重 複 make zImage的 動 作 , 一 直 到 正 確 無 誤 後 , 才 能 載 入 系 統 中 執 行 。

 

進 行 系 統 測 試

準 備 一 1.44MB的 磁 片 , 藉 著 此 指 令
  #fdformat -n /dev/fd0H1440

將 此 磁 片 格 式 化 (Format), 以 便 複 製 一 Kernel zImage到 磁 片 來 。 請 執 行
  #cp zImage /dev/fd #co zUnage /dev/fd

  指 令 就 可 以 了 。 為 什 麼 不 直 接 Update根 (Root)目 錄 下 的 檔 案 vmlinuz呢 ? 因 為 如 此 做 太 冒 險 了 , 可 能 會 毀 掉 系 統 造 成 無 法 開 機 ; 所 以 保 守 一 點 , 有 耐 心 一 點 , 一 步 一 步 來 , 用 Floopy開 機 吧 !

  如 果 順 利 的 話 , 你 可 以 在 螢 幕 上 看 到 , 藉 著 printk()於 xx_init()函 數 中 , 列 印 出 來 的 initial訊 息 ; 不 幸 的 話 , 只 好 從 Hard Drive 重 新 開 機 了 , 緊 接 著 開 始 Debug, 抓 蟲 了 。 一 切 步 驟 從 頭 開 始 , 更 改 程 式 Driver, make zImage, 製 作 Floppy, 再 開 機 吧 !

  請 你 再 寫 一 個 測 試 程 式 包 括 Read、 Write、 Ioctl等 動 作 的 程 式 以 及 準 備 各 類 的 測 試 資 料 , 以 便 進 一 步 測 試 Driver(測 試 程 式 部 份 請 見 光 碟 \AUTHOR\LINUX子 目 錄 )。 可 能 你 已 在 Driver當 中 , 早 就 安 排 加 入 了 printk(), 此 時 你 可 能 會 發 現 , 為 什 麼 沒 有 發 現 所 要 列 印 的 Message呢 ? 不 要 慌 , 因 為 它 不 會 在 螢 幕 上 出 現 , 此 時 只 有 在 /var/ adm/messages檔 案 中 , 可 以 找 到 它 。 請 你 從 尾 巴 找 起 , 可 能 會 比 較 快 一 點 ; 因 為 它 記 錄 系 統 太 多 東 西 了 。 還 有 如 果 你 的 測 試 程 式 , 發 現 Driver有 問 題 的 話 , 請 繼 續 重 複 上 面 所 提 過 的 步 驟 , 一 步 一 步 地 繼 續 抓 蟲 , 一 直 到 零 錯 誤 為 止 ; 有 一 點 要 特 別 注 意 的 是 , 在 run測 試 程 式 之 前 , 請 你 在 /dev目 錄 下 , 鍵 入 指 令
#mknod drive_name c major_no minor_no

  其 中 name, major number就 是 在 Driver中 , xx_init()用 於 註 冊 所 宣 稱 的 參 數 ; 至 於 minor number依 你 的 Driver軟 體 可 以 控 制 的 能 力 來 鍵 入 。 可 以 ls-l來 察 看 , 所 鍵 的 Device是 否 正 確 ?



crw-r--r-- 1 root root 29, 0 Nov. 21 05:14 /dev/testty0
crw-r--r-- 1 root root 29, 1 Nov. 21 05:14 /dev/testty1
crw-r--r-- 1 root root 29, 2 Nov. 21 05:14 /dev/testty2

  此 例 是 鍵 入 三 個 Devices。 Linux Device Driver的 製 作 過 程 很 簡 單 , 但 Driver程 式 的 Debug需 要 花 一 點 時 間 和 經 驗 , 希 望 本 篇 文 章 能 節 省 你 一 點 摸 索 的 時 間 , 以 及 對 你 有 點 幫 助 。

huenlil 發表在 痞客邦 PIXNET 留言(0) 人氣()