3.2. 主次編號(hào)

2018-02-24 15:49 更新

3.2.?主次編號(hào)

字符設(shè)備通過文件系統(tǒng)中的名子來存取. 那些名子稱為文件系統(tǒng)的特殊文件, 或者設(shè)備文件, 或者文件系統(tǒng)的簡單結(jié)點(diǎn); 慣例上它們位于 /dev 目錄. 字符驅(qū)動(dòng)的特殊文件由使用 ls -l 的輸出的第一列的"c"標(biāo)識(shí). 塊設(shè)備也出現(xiàn)在 /dev 中, 但是它們由"b"標(biāo)識(shí). 本章集中在字符設(shè)備, 但是下面的很多信息也適用于塊設(shè)備.

如果你發(fā)出 ls -l 命令, 你會(huì)看到在設(shè)備文件項(xiàng)中有 2 個(gè)數(shù)(由一個(gè)逗號(hào)分隔)在最后修改日期前面, 這里通常是文件長度出現(xiàn)的地方. 這些數(shù)字是給特殊設(shè)備的主次設(shè)備編號(hào). 下面的列表顯示了一個(gè)典型系統(tǒng)上出現(xiàn)的幾個(gè)設(shè)備. 它們的主編號(hào)是 1, 4, 7, 和 10, 而次編號(hào)是 1, 3, 5, 64, 65, 和 129.


 crw-rw-rw- 1 root  root  1,  3 Apr 11  2002 null 
 crw------- 1 root  root  10, 1 Apr 11  2002 psaux 
 crw------- 1 root  root  4,  1 Oct 28 03:04 tty1 
 crw-rw-rw- 1 root  tty   4, 64 Apr 11  2002 ttys0 
 crw-rw---- 1 root  uucp  4, 65 Apr 11  2002 ttyS1 
 crw--w---- 1 vcsa  tty   7,  1 Apr 11  2002 vcs1 
 crw--w---- 1 vcsa  tty   7,129 Apr 11  2002 vcsa1 
 crw-rw-rw- 1 root  root  1,  5 Apr 11  2002 zero  

傳統(tǒng)上, 主編號(hào)標(biāo)識(shí)設(shè)備相連的驅(qū)動(dòng). 例如, /dev/null 和 /dev/zero 都由驅(qū)動(dòng) 1 來管理, 而虛擬控制臺(tái)和串口終端都由驅(qū)動(dòng) 4 管理; 同樣, vcs1 和 vcsa1 設(shè)備都由驅(qū)動(dòng) 7 管理. 現(xiàn)代 Linux 內(nèi)核允許多個(gè)驅(qū)動(dòng)共享主編號(hào), 但是你看到的大部分設(shè)備仍然按照一個(gè)主編號(hào)一個(gè)驅(qū)動(dòng)的原則來組織.

次編號(hào)被內(nèi)核用來決定引用哪個(gè)設(shè)備. 依據(jù)你的驅(qū)動(dòng)是如何編寫的(如同我們下面見到的), 你可以從內(nèi)核得到一個(gè)你的設(shè)備的直接指針, 或者可以自己使用次編號(hào)作為本地設(shè)備數(shù)組的索引. 不論哪個(gè)方法, 內(nèi)核自己幾乎不知道次編號(hào)的任何事情, 除了它們指向你的驅(qū)動(dòng)實(shí)現(xiàn)的設(shè)備.

3.2.1.?設(shè)備編號(hào)的內(nèi)部表示

在內(nèi)核中, dev_t 類型(在 <linux/types.h>中定義)用來持有設(shè)備編號(hào) -- 主次部分都包括. 對(duì)于 2.6.0 內(nèi)核, dev_t 是 32 位的量, 12 位用作主編號(hào), 20 位用作次編號(hào). 你的代碼應(yīng)當(dāng), 當(dāng)然, 對(duì)于設(shè)備編號(hào)的內(nèi)部組織從不做任何假設(shè); 相反, 應(yīng)當(dāng)利用在 <linux/kdev_t.h>中的一套宏定義. 為獲得一個(gè) dev_t 的主或者次編號(hào), 使用:


MAJOR(dev_t dev); 
MINOR(dev_t dev);

相反, 如果你有主次編號(hào), 需要將其轉(zhuǎn)換為一個(gè) dev_t, 使用:


MKDEV(int major, int minor); 

注意, 2.6 內(nèi)核能容納有大量設(shè)備, 而以前的內(nèi)核版本限制在 255 個(gè)主編號(hào)和 255 個(gè)次編號(hào). 有人認(rèn)為這么寬的范圍在很長時(shí)間內(nèi)是足夠的, 但是計(jì)算領(lǐng)域被這個(gè)特性的錯(cuò)誤假設(shè)搞亂了. 因此你應(yīng)當(dāng)希望 dev_t 的格式將來可能再次改變; 但是, 如果你仔細(xì)編寫你的驅(qū)動(dòng), 這些變化不會(huì)是一個(gè)問題.

3.2.2.?分配和釋放設(shè)備編號(hào)

在建立一個(gè)字符驅(qū)動(dòng)時(shí)你的驅(qū)動(dòng)需要做的第一件事是獲取一個(gè)或多個(gè)設(shè)備編號(hào)來使用. 為此目的的必要的函數(shù)是 register_chrdev_region, 在 <linux/fs.h>中聲明:


int register_chrdev_region(dev_t first, unsigned int count, char *name);

這里, first 是你要分配的起始設(shè)備編號(hào). first 的次編號(hào)部分常常是 0, 但是沒有要求是那個(gè)效果. count 是你請求的連續(xù)設(shè)備編號(hào)的總數(shù). 注意, 如果 count 太大, 你要求的范圍可能溢出到下一個(gè)次編號(hào); 但是只要你要求的編號(hào)范圍可用, 一切都仍然會(huì)正確工作. 最后, name 是應(yīng)當(dāng)連接到這個(gè)編號(hào)范圍的設(shè)備的名子; 它會(huì)出現(xiàn)在 /proc/devices 和 sysfs 中.

如同大部分內(nèi)核函數(shù), 如果分配成功進(jìn)行, register_chrdev_region 的返回值是 0. 出錯(cuò)的情況下, 返回一個(gè)負(fù)的錯(cuò)誤碼, 你不能存取請求的區(qū)域.

如果你確實(shí)事先知道你需要哪個(gè)設(shè)備編號(hào), register_chrdev_region 工作得好. 然而, 你常常不會(huì)知道你的設(shè)備使用哪個(gè)主編號(hào); 在 Linux 內(nèi)核開發(fā)社團(tuán)中一直努力使用動(dòng)態(tài)分配設(shè)備編號(hào). 內(nèi)核會(huì)樂于動(dòng)態(tài)為你分配一個(gè)主編號(hào), 但是你必須使用一個(gè)不同的函數(shù)來請求這個(gè)分配.


int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);

使用這個(gè)函數(shù), dev 是一個(gè)只輸出的參數(shù), 它在函數(shù)成功完成時(shí)持有你的分配范圍的第一個(gè)數(shù). fisetminor 應(yīng)當(dāng)是請求的第一個(gè)要用的次編號(hào); 它常常是 0. count 和 name 參數(shù)如同給 request_chrdev_region 的一樣.

不管你任何分配你的設(shè)備編號(hào), 你應(yīng)當(dāng)在不再使用它們時(shí)釋放它. 設(shè)備編號(hào)的釋放使用:


void unregister_chrdev_region(dev_t first, unsigned int count); 

調(diào)用 unregister_chrdev_region 的地方常常是你的模塊的 cleanup 函數(shù).

上面的函數(shù)分配設(shè)備編號(hào)給你的驅(qū)動(dòng)使用, 但是它們不告訴內(nèi)核你實(shí)際上會(huì)對(duì)這些編號(hào)做什么. 在用戶空間程序能夠存取這些設(shè)備號(hào)中一個(gè)之前, 你的驅(qū)動(dòng)需要連接它們到它的實(shí)現(xiàn)設(shè)備操作的內(nèi)部函數(shù)上. 我們將描述如何簡短完成這個(gè)連接, 但首先顧及一些必要的枝節(jié)問題.

3.2.3.?主編號(hào)的動(dòng)態(tài)分配

一些主設(shè)備編號(hào)是靜態(tài)分派給最普通的設(shè)備的. 一個(gè)這些設(shè)備的列表在內(nèi)核源碼樹的 Documentation/devices.txt 中. 分配給你的新驅(qū)動(dòng)使用一個(gè)已經(jīng)分配的靜態(tài)編號(hào)的機(jī)會(huì)很小, 但是, 并且新編號(hào)沒在分配. 因此, 作為一個(gè)驅(qū)動(dòng)編寫者, 你有一個(gè)選擇: 你可以簡單地?fù)煲粋€(gè)看來沒有用的編號(hào), 或者你以動(dòng)態(tài)方式分配主編號(hào). 只要你是你的驅(qū)動(dòng)的唯一用戶就可以撿一個(gè)編號(hào)用; 一旦你的驅(qū)動(dòng)更廣泛的被使用了, 一個(gè)隨機(jī)撿來的主編號(hào)將導(dǎo)致沖突和麻煩.

因此, 對(duì)于新驅(qū)動(dòng), 我們強(qiáng)烈建議你使用動(dòng)態(tài)分配來獲取你的主設(shè)備編號(hào), 而不是隨機(jī)選取一個(gè)當(dāng)前空閑的編號(hào). 換句話說, 你的驅(qū)動(dòng)應(yīng)當(dāng)幾乎肯定地使用 alloc_chrdev_region, 不是 register_chrdev_region.

動(dòng)態(tài)分配的缺點(diǎn)是你無法提前創(chuàng)建設(shè)備節(jié)點(diǎn), 因?yàn)榉峙浣o你的模塊的主編號(hào)會(huì)變化. 對(duì)于驅(qū)動(dòng)的正常使用, 這不是問題, 因?yàn)橐坏┚幪?hào)分配了, 你可從 /proc/devices 中讀取它.[6]

為使用動(dòng)態(tài)主編號(hào)來加載一個(gè)驅(qū)動(dòng), 因此, 可使用一個(gè)簡單的腳本來代替調(diào)用 insmod, 在調(diào)用 insmod 后, 讀取 /proc/devices 來創(chuàng)建特殊文件.

一個(gè)典型的 /proc/devices 文件看來如下:


Character devices:
 1 mem
 2 pty
 3 ttyp
 4 ttyS
 6 lp
 7 vcs
 10 misc
 13 input
 14 sound 
 21 sg
 180 usb

Block devices:
 2 fd
 8 sd
 11 sr
 65 sd
 66 sd 

因此加載一個(gè)已經(jīng)安排了一個(gè)動(dòng)態(tài)編號(hào)的模塊的腳本, 可以使用一個(gè)工具來編寫, 如 awk , 來從 /proc/devices 獲取信息以創(chuàng)建 /dev 中的文件.

下面的腳本, snull_load, 是 scull 發(fā)布的一部分. 以模塊發(fā)布的驅(qū)動(dòng)的用戶可以從系統(tǒng)的 rc.local 文件中調(diào)用這樣一個(gè)腳本, 或者在需要模塊時(shí)手工調(diào)用它.


#!/bin/sh
module="scull"
device="scull"
mode="664"

# invoke insmod with all arguments we got
# and use a pathname, as newer modutils don't look in . by default
/sbin/insmod ./$module.ko $* || exit 1

# remove stale nodes
rm -f /dev/${device}[0-3]

major=$(awk "\\$2==\"$module\" {print \\$1}" /proc/devices) 
mknod /dev/${device}0 c $major 0
mknod /dev/${device}1 c $major 1
mknod /dev/${device}2 c $major 2
mknod /dev/${device}3 c $major 3

# give appropriate group/permissions, and change the group.
# Not all distributions have staff, some have "wheel" instead.
group="staff"
grep -q '^staff:' /etc/group || group="wheel"

chgrp $group /dev/${device}[0-3]
chmod $mode /dev/${device}[0-3]

這個(gè)腳本可以通過重定義變量和調(diào)整 mknod 行來適用于另外的驅(qū)動(dòng). 這個(gè)腳本僅僅展示了創(chuàng)建 4 個(gè)設(shè)備, 因?yàn)?4 是 scull 源碼中缺省的.

腳本的最后幾行可能有些模糊:為什么改變設(shè)備的組和模式? 理由是這個(gè)腳本必須由超級(jí)用戶運(yùn)行, 因此新建的特殊文件由 root 擁有. 許可位缺省的是只有 root 有寫權(quán)限, 而任何人可以讀. 通常, 一個(gè)設(shè)備節(jié)點(diǎn)需要一個(gè)不同的存取策略, 因此在某些方面別人的存取權(quán)限必須改變. 我們的腳本缺省是給一個(gè)用戶組存取, 但是你的需求可能不同. 在第 6 章的"設(shè)備文件的存取控制"一節(jié)中, sculluid 的代碼演示了驅(qū)動(dòng)如何能夠強(qiáng)制它自己的對(duì)設(shè)備存取的授權(quán).

還有一個(gè) scull_unload 腳本來清理 /dev 目錄并去除模塊.

作為對(duì)使用一對(duì)腳本來加載和卸載的另外選擇, 你可以編寫一個(gè) init 腳本, 準(zhǔn)備好放在你的發(fā)布使用這些腳本的目錄中. [7]作為 scull 源碼的一部分, 我們提供了一個(gè)相當(dāng)完整和可配置的 init 腳本例子, 稱為 scull.init; 它接受傳統(tǒng)的參數(shù) -- start, stop, 和 restart -- 并且完成 scull_load 和 scull_unload 的角色.

如果反復(fù)創(chuàng)建和銷毀 /dev 節(jié)點(diǎn), 聽來過分了, 有一個(gè)有用的辦法. 如果你在加載和卸載單個(gè)驅(qū)動(dòng), 你可以在你第一次使用你的腳本創(chuàng)建特殊文件之后, 只使用 rmmod 和 insmod: 這樣動(dòng)態(tài)編號(hào)不是隨機(jī)的. [8]并且你每次都可以使用所選的同一個(gè)編號(hào), 如果你不加載任何別的動(dòng)態(tài)模塊. 在開發(fā)中避免長腳本是有用的. 但是這個(gè)技巧, 顯然不能擴(kuò)展到一次多于一個(gè)驅(qū)動(dòng).

安排主編號(hào)最好的方式, 我們認(rèn)為, 是缺省使用動(dòng)態(tài)分配, 而留給自己在加載時(shí)指定主編號(hào)的選項(xiàng)權(quán), 或者甚至在編譯時(shí). scull 實(shí)現(xiàn)以這種方式工作; 它使用一個(gè)全局變量, scull_major, 來持有選定的編號(hào)(還有一個(gè) scull_minor 給次編號(hào)). 這個(gè)變量初始化為 SCULL_MAJOR, 定義在 scull.h. 發(fā)布的源碼中的 SCULL_MAJOR 的缺省值是 0, 意思是"使用動(dòng)態(tài)分配". 用戶可以接受缺省值或者選擇一個(gè)特殊主編號(hào), 或者在編譯前修改宏定義或者在 insmod 命令行指定一個(gè)值給 scull_major. 最后, 通過使用 scull_load 腳本, 用戶可以在 scull_load 的命令行傳遞參數(shù)給 insmod.[9]

這是我們用在 scull 的源碼中獲取主編號(hào)的代碼:


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;
}

本書使用的幾乎所有例子驅(qū)動(dòng)使用類似的代碼來分配它們的主編號(hào).

[6] 從 sysfs 中能獲取更好的設(shè)備信息, 在基于 2.6 的系統(tǒng)通常加載于 /sys. 但是使 scull 通過 sysfs 輸出信息超出了本章的范圍; 我們在 14 章中回到這個(gè)主題.

[7] Linux Standard Base 指出 init 腳本應(yīng)當(dāng)放在 /etc/init.d, 但是一些發(fā)布仍然放在別處. 另外, 如果你的腳本在啟動(dòng)時(shí)運(yùn)行, 你需要從合適的運(yùn)行級(jí)別目錄做一個(gè)連接給它(也就是, .../rc3.d).

[8] 盡管某些內(nèi)核開發(fā)者已警告說將來就會(huì)這樣做.

[9] init 腳本 scull.init 不在命令行中接受驅(qū)動(dòng)選項(xiàng), 但是它支持一個(gè)配置文件, 因?yàn)樗辉O(shè)計(jì)來在啟動(dòng)和關(guān)機(jī)時(shí)自動(dòng)使用.

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)