書名: Linux設(shè)備驅(qū)動開發(fā)詳解(第2版)作者名: 華清遠(yuǎn)見嵌入式培訓(xùn)中心 宋寶華編著本章字?jǐn)?shù): 2030字更新時(shí)間: 2018-12-27 10:06:13
第5章 Linux文件系統(tǒng)與設(shè)備文件系統(tǒng)
本章導(dǎo)讀
由于字符設(shè)備和塊設(shè)備都良好地體現(xiàn)了“一切都是文件”的設(shè)計(jì)思想,掌握Linux文件系統(tǒng)、設(shè)備文件系統(tǒng)的知識就顯得相當(dāng)重要了。
首先,驅(qū)動工程師編寫的驅(qū)動最終通過操作系統(tǒng)的文件操作系統(tǒng)調(diào)用或C庫函數(shù)(本質(zhì)也基于系統(tǒng)調(diào)用)被訪問,而設(shè)備驅(qū)動的結(jié)構(gòu)最終也是為了迎合提供給應(yīng)用程序員的API。
其次,驅(qū)動工程師在設(shè)備驅(qū)動中不可避免地會與設(shè)備文件系統(tǒng)打交道,從Linux2.4內(nèi)核的devfs文件系統(tǒng)到目前Linux2.6基于sysfs的udev文件系統(tǒng)。
5.1節(jié)講解了通過Linux API和C庫函數(shù)在用戶空間進(jìn)行Linux文件操作的編程方法。
5.2節(jié)分析了Linux文件系統(tǒng)的目錄結(jié)構(gòu),簡單介紹了Linux內(nèi)核中文件系統(tǒng)的實(shí)現(xiàn),并給出了文件系統(tǒng)與設(shè)備驅(qū)動的關(guān)系。
5.3節(jié)和5.4節(jié)分別講解Linux2.4內(nèi)核的devfs和Linux2.6所采用的udev設(shè)備文件系統(tǒng),并分析了兩者的區(qū)別。
5.5節(jié)講解了LDD6410的SD卡和NAND分區(qū)和文件系統(tǒng)的使用情況。
5.1 Linux文件操作
5.1.1 文件操作系統(tǒng)調(diào)用
Linux的文件操作系統(tǒng)調(diào)用(在Windows編程領(lǐng)域,習(xí)慣稱操作系統(tǒng)提供的接口為API)涉及創(chuàng)建、打開、讀寫和關(guān)閉文件。
1.創(chuàng)建
int creat(const char *filename, mode_t mode);
參數(shù)mode指定新建文件的存取權(quán)限,它同umask一起決定文件的最終權(quán)限(mode&umask),其中umask代表了文件在創(chuàng)建時(shí)需要去掉的一些存取權(quán)限。umask可通過系統(tǒng)調(diào)用umask()來改變:
int umask(int newmask);
該調(diào)用將umask設(shè)置為newmask,然后返回舊的umask,它只影響讀、寫和執(zhí)行權(quán)限。
2.打開
int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);
open()函數(shù)有兩個(gè)形式,其中pathname是我們要打開的文件名(包含路徑名稱,缺省是認(rèn)為在當(dāng)前路徑下面),flags可以是如表5.1所示的一個(gè)值或者是幾個(gè)值的組合。
表5.1 文件打開標(biāo)志

O_RDONLY、O_WRONLY、O_RDWR三個(gè)標(biāo)志只能使用任意的一個(gè)。
如果使用了O_CREATE標(biāo)志,則使用的函數(shù)是int open(const char *pathname,int flags,mode_t mode); 這個(gè)時(shí)候我們還要指定mode標(biāo)志,用來表示文件的訪問權(quán)限。mode可以是如表5.2所列值的組合。
表5.2 文件訪問權(quán)限

除了可以通過上述宏進(jìn)行“或”邏輯產(chǎn)生標(biāo)志以外,我們也可以自己用數(shù)字來表示,Linux用5個(gè)數(shù)字來表示文件的各種權(quán)限:第一位表示設(shè)置用戶ID;第二位表示設(shè)置組ID;第三位表示用戶自己的權(quán)限位;第四位表示組的權(quán)限;最后一位表示其他人的權(quán)限。每個(gè)數(shù)字可以取1(執(zhí)行權(quán)限)、2(寫權(quán)限)、4(讀權(quán)限)、0(無)或者是這些值的和。例如,要創(chuàng)建一個(gè)用戶可讀、可寫、可執(zhí)行,但是組沒有權(quán)限,其他人可以讀、可以執(zhí)行的文件,并設(shè)置用戶 ID 位。那么,我們應(yīng)該使用的模式是1(設(shè)置用戶ID)、0(不設(shè)置組ID)、7(1+2+4,讀、寫、執(zhí)行)、0(沒有權(quán)限)、5(1+4,讀、執(zhí)行)即10705:
open("test", O_CREAT, 10 705);
上述語句等價(jià)于:
open("test", O_CREAT, S_IRWXU | S_IROTH | S_IXOTH | S_ISUID );
如果文件打開成功,open函數(shù)會返回一個(gè)文件描述符,以后對該文件的所有操作就可以通過對這個(gè)文件描述符進(jìn)行操作來實(shí)現(xiàn)。
3.讀寫
在文件打開以后,我們才可對文件進(jìn)行讀寫,Linux中提供文件讀寫的系統(tǒng)調(diào)用是read、write函數(shù):
int read(int fd, const void *buf, size_t length); int write(int fd, const void *buf, size_t length);
其中參數(shù)buf為指向緩沖區(qū)的指針,length為緩沖區(qū)的大小(以字節(jié)為單位)。函數(shù)read()實(shí)現(xiàn)從文件描述符fd所指定的文件中讀取length個(gè)字節(jié)到buf所指向的緩沖區(qū)中,返回值為實(shí)際讀取的字節(jié)數(shù)。函數(shù)write實(shí)現(xiàn)將把length個(gè)字節(jié)從buf指向的緩沖區(qū)中寫到文件描述符fd所指向的文件中,返回值為實(shí)際寫入的字節(jié)數(shù)。
以O(shè)_CREAT為標(biāo)志的open實(shí)際上實(shí)現(xiàn)了文件創(chuàng)建的功能,因此,下面的函數(shù)等同creat()函數(shù):
int open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
4.定位
對于隨機(jī)文件,我們可以隨機(jī)地指定位置讀寫,使用如下函數(shù)進(jìn)行定位:
int lseek(int fd, offset_t offset, int whence);
lseek()將文件讀寫指針相對whence移動offset個(gè)字節(jié)。操作成功時(shí),返回文件指針相對于文件頭的位置。參數(shù)whence可使用下述值:
SEEK_SET:相對文件開頭
SEEK_CUR:相對文件讀寫指針的當(dāng)前位置
SEEK_END:相對文件末尾
offset可取負(fù)值,例如下述調(diào)用可將文件指針相對當(dāng)前位置向前移動5個(gè)字節(jié):
lseek(fd, -5, SEEK_CUR);
由于 lseek 函數(shù)的返回值為文件指針相對于文件頭的位置,因此下列調(diào)用的返回值就是文件的長度:
lseek(fd, 0, SEEK_END);
5.關(guān)閉
當(dāng)我們操作完成以后,我們要關(guān)閉文件了,只要調(diào)用close就可以了,其中fd是我們要關(guān)閉的文件描述符:
int close(int fd);
例程:編寫一個(gè)程序,在當(dāng)前目錄下創(chuàng)建用戶可讀寫文件hello.txt,在其中寫入“Hello, software weekly”,關(guān)閉該文件。再次打開該文件,讀取其中的內(nèi)容并輸出在屏幕上。
解答如代碼清單5.1。
代碼清單5.1 Linux文件操作用戶空間編程(使用系統(tǒng)調(diào)用)
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <stdio.h> 5 #define LENGTH 100 6 main() 7 { 8 int fd, len; 9 char str[LENGTH]; 10 11 fd = open("hello.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); /* 12 創(chuàng)建并打開文件 */ 13 if (fd) { 14 write(fd, "Hello World", strlen("Hello World")); /* 15 寫入字符串 */ 16 close(fd); 17 } 18 19 fd = open("hello.txt", O_RDWR); 20 len = read(fd, str, LENGTH); /* 讀取文件內(nèi)容 */ 21 str[len] = '\0'; 22 printf("%s\n", str); 23 close(fd); 24 }
編譯并運(yùn)行,執(zhí)行結(jié)果為輸出“Hello World”。
5.1.2 C庫文件操作
C庫函數(shù)的文件操作實(shí)際上是獨(dú)立于具體的操作系統(tǒng)平臺的,不管是在DOS、Windows、Linux還是在VxWorks中都是這些函數(shù):
1.創(chuàng)建和打開
FILE *fopen(const char *path, const char *mode);
fopen()實(shí)現(xiàn)打開指定文件filename,其中的mode為打開模式,C庫函數(shù)中支持的打開模式如表5.3所示。
表5.3 C庫函數(shù)文件打開標(biāo)志

其中 b 用于區(qū)分二進(jìn)制文件和文本文件,這一點(diǎn)在 DOS、Windows 系統(tǒng)中是有區(qū)分的,但Linux不區(qū)分二進(jìn)制文件和文本文件。
2.讀寫
C庫函數(shù)支持以字符、字符串等為單位,支持按照某種格式進(jìn)行文件的讀寫,這一組函數(shù)為:
int fgetc(FILE *stream); int fputc(int c, FILE *stream); char *fgets(char *s, int n, FILE *stream); int fputs(const char *s, FILE *stream); int fprintf(FILE *stream, const char *format, ...); int fscanf (FILE *stream, const char *format, ...); size_t fread(void *ptr, size_t size, size_t n, FILE *stream); size_t fwrite (const void *ptr, size_t size, size_t n, FILE *stream);
fread()實(shí)現(xiàn)從流stream中讀取加n個(gè)字段,每個(gè)字段為size字節(jié),并將讀取的字段放入ptr所指的字符數(shù)組中,返回實(shí)際已讀取的字段數(shù)。在讀取的字段數(shù)小于num時(shí),可能是在函數(shù)調(diào)用時(shí)出現(xiàn)錯(cuò)誤,也可能是讀到文件的結(jié)尾。所以要通過調(diào)用feof()和ferror()來判斷。
write()實(shí)現(xiàn)從緩沖區(qū)ptr所指的數(shù)組中把n個(gè)字段寫到流stream中,每個(gè)字段長為size個(gè)字節(jié),返回實(shí)際寫入的字段數(shù)。
另外,C庫函數(shù)還提供了讀寫過程中的定位能力,這些函數(shù)包括:
int fgetpos(FILE *stream, fpos_t *pos); int fsetpos(FILE *stream, const fpos_t *pos); int fseek(FILE *stream, long offset, int whence);
3.關(guān)閉
利用C庫函數(shù)關(guān)閉文件依然是很簡單的操作:
int fclose (FILE *stream);
例程:將第5.1.1節(jié)中的例程用C庫函數(shù)來實(shí)現(xiàn),如代碼清單5-2所示。
代碼清單5.2 Linux文件操作用戶空間編程(使用C庫函數(shù))
1 #include <stdio.h> 2 #define LENGTH 100 3 main() 4 { 5 FILE *fd; 6 char str[LENGTH]; 7 8 fd = fopen("hello.txt", "w+"); /* 創(chuàng)建并打開文件 */ 9 if (fd) { 10 fputs("Hello World", fd); /* 寫入字符串 */ 11 fclose(fd); 12 } 13 14 fd = fopen("hello.txt", "r"); 15 fgets(str, LENGTH, fd); /* 讀取文件內(nèi)容 */ 16 printf("%s\n", str); 17 fclose(fd); 18 }
5.2 Linux文件系統(tǒng)
5.2.1 Linux文件系統(tǒng)目錄結(jié)構(gòu)
進(jìn)入Linux根目錄(即“/”,Linux文件系統(tǒng)的入口,也是處于最高一級的目錄),運(yùn)行“l(fā)s -l”命令,看到Linux包含以下目錄。
1./bin
包含基本命令,如 ls、cp、mkdir等,這個(gè)目錄中的文件都是可執(zhí)行的。
2./sbin
包含系統(tǒng)命令,如modprobe、hwclock、ifconfig等,大多是涉及系統(tǒng)管理的命令,這個(gè)目錄中的文件都是可執(zhí)行的。
3./dev
設(shè)備文件存儲目錄,應(yīng)用程序通過對這些文件的讀寫和控制就可以訪問實(shí)際的設(shè)備。
4./etc
系統(tǒng)配置文件的所在地,一些服務(wù)器的配置文件也在這里,如用戶賬號及密碼配置文件。busybox的啟動腳本也存放在該目錄。
5./lib
系統(tǒng)庫文件存放目錄,如LDD6410包含libc-2.6.1.so、libpthread-2.6.1.so、libthread_db-1.0.so等。
6./mnt
/mnt這個(gè)目錄一般是用于存放掛載儲存設(shè)備的掛載目錄的,比如有cdrom 等目錄。可以參看/etc/fstab 的定義。有時(shí)我們可以把讓系統(tǒng)開機(jī)自動掛載文件系統(tǒng),把掛載點(diǎn)放在這里也是可以的。
7./opt
opt 是“可選”的意思,有些軟件包會被安裝在這里,例如,在LDD6410的文件系統(tǒng)中, Qt/Embedded就存放在該目錄。
8./proc
操作系統(tǒng)運(yùn)行時(shí),進(jìn)程及內(nèi)核信息(比如CPU、硬盤分區(qū)、內(nèi)存信息等)存放在這里。/proc目錄為偽文件系統(tǒng)proc的掛載目錄,proc并不是真正的文件系統(tǒng),它存在于內(nèi)存之中。
9./tmp
有時(shí)用戶運(yùn)行程序的時(shí)候,會產(chǎn)生臨時(shí)文件,/tmp就用來存放臨時(shí)文件的。
10./usr
這個(gè)是系統(tǒng)存放程序的目錄,比如用戶命令、用戶庫等。LDD6410的usr包括bin、sbin、lib三個(gè)子目錄。usr/bin中包含diff、which、who、rx、cmp等,usr/sbin中包含chroot、flash_eraseall、inetd等,usr/lib中包含libjpeg.so.62.0.0等。
11./var
var 表示的是變化的意思,這個(gè)目錄的內(nèi)容經(jīng)常變動,如/var的/var/log 目錄被用來存放系統(tǒng)日志。
12./sys
Linux2.6內(nèi)核所支持的sysfs文件系統(tǒng)被映射在此目錄。Linux設(shè)備驅(qū)動模型中的總線、驅(qū)動和設(shè)備都可以在 sysfs 文件系統(tǒng)中找到對應(yīng)的節(jié)點(diǎn)。當(dāng)內(nèi)核檢測到在系統(tǒng)中出現(xiàn)了新設(shè)備后,內(nèi)核會在sysfs文件系統(tǒng)中為該新設(shè)備生成一項(xiàng)新的記錄。
5.2.2 Linux文件系統(tǒng)與設(shè)備驅(qū)動
圖5.1所示為Linux中虛擬文件系統(tǒng)、磁盤文件(存放于Ramdisk、Flash、ROM、SD卡、U盤等文件系統(tǒng)中的文件也屬于此列)及一般的設(shè)備文件與設(shè)備驅(qū)動程序之間的關(guān)系。

圖5.1 文件系統(tǒng)與設(shè)備驅(qū)動
應(yīng)用程序和VFS之間的接口是系統(tǒng)調(diào)用,而VFS與磁盤文件系統(tǒng)以及普通設(shè)備之間的接口是 file_operations 結(jié)構(gòu)體成員函數(shù),這個(gè)結(jié)構(gòu)體包含對文件進(jìn)行打開、關(guān)閉、讀寫、控制的一系列成員函數(shù)。
由于字符設(shè)備的上層沒有磁盤文件系統(tǒng),所以字符設(shè)備的file_operations 成員函數(shù)就直接由設(shè)備驅(qū)動提供了,在稍后的第6章,將會看到file_operations正是字符設(shè)備驅(qū)動的核心。
而對于塊存儲設(shè)備而言,ext2、fat、jffs2等文件系統(tǒng)中會實(shí)現(xiàn)針對VFS的file_operations成員函數(shù),設(shè)備驅(qū)動層將看不到 file_operations 的存在。磁盤文件系統(tǒng)和設(shè)備驅(qū)動會將對磁盤上文件的訪問最終轉(zhuǎn)換成對磁盤上柱面和扇區(qū)的訪問。
在設(shè)備驅(qū)動程序的設(shè)計(jì)中,一般而言,會關(guān)心file和inode這兩個(gè)結(jié)構(gòu)體。
1.file結(jié)構(gòu)體
file結(jié)構(gòu)體代表一個(gè)打開的文件(設(shè)備對應(yīng)于設(shè)備文件),系統(tǒng)中每個(gè)打開的文件在內(nèi)核空間都有一個(gè)關(guān)聯(lián)的struct file。它由內(nèi)核在打開文件時(shí)創(chuàng)建,并傳遞給在文件上進(jìn)行操作的任何函數(shù)。在文件的所有實(shí)例都關(guān)閉后,內(nèi)核釋放這個(gè)數(shù)據(jù)結(jié)構(gòu)。在內(nèi)核和驅(qū)動源代碼中,struct file的指針通常被命名為file或filp(即file pointer)。代碼清單5.3給出了文件結(jié)構(gòu)體的定義。
代碼清單5.3 文件結(jié)構(gòu)體
1 struct file 2 { 3 union { 4 struct list_head fu_list; 5 struct rcu_head fu_rcuhead; 6 } f_u; 7 struct dentry *f_dentry; /*與文件關(guān)聯(lián)的目錄入口(dentry)結(jié)構(gòu)*/ 8 struct vfsmount *f_vfsmnt; 9 struct file_operations *f_op; /* 和文件關(guān)聯(lián)的操作*/ 10 atomic_t f_count; 11 unsigned int f_flags;/*文件標(biāo)志,如O_RDONLY、O_NONBLOCK、O_SYNC*/ 12 mode_t f_mode; /*文件讀/寫模式,F(xiàn)MODE_READ和FMODE_WRITE*/ 13 loff_t f_pos; /* 當(dāng)前讀寫位置*/ 14 struct fown_struct f_owner; 15 unsigned int f_uid, f_gid; 16 struct file_ra_state f_ra; 17 18 unsigned long f_version; 19 void *f_security; 20 21 /* tty驅(qū)動需要,其他的也許需要 */ 22 void *private_data; /*文件私有數(shù)據(jù)*/ 23 ... 24 struct address_space *f_mapping; 25 };
文件讀/寫模式 mode、標(biāo)志 f_flags 都是設(shè)備驅(qū)動關(guān)心的內(nèi)容,而私有數(shù)據(jù)指針 private_data在設(shè)備驅(qū)動中被廣泛應(yīng)用,大多被指向設(shè)備驅(qū)動自定義用于描述設(shè)備的結(jié)構(gòu)體。
驅(qū)動程序中經(jīng)常會使用如下類似的代碼來檢測用戶打開文件的讀寫方式:
if (file->f_mode & FMODE_WRITE) {/* 用戶要求可寫 */ } if (file->f_mode & FMODE_READ) {/* 用戶要求可讀 */ }
下面的代碼可用于判斷以阻塞還是非阻塞方式打開設(shè)備文件:
if (file->f_flags & O_NONBLOCK) /* 非阻塞 */ pr_debug("open: non-blocking\n"); else /* 阻塞 */ pr_debug("open: blocking\n");
2.inode結(jié)構(gòu)體
VFS inode 包含文件訪問權(quán)限、屬主、組、大小、生成時(shí)間、訪問時(shí)間、最后修改時(shí)間等信息。它是Linux管理文件系統(tǒng)的最基本單位,也是文件系統(tǒng)連接任何子目錄、文件的橋梁,inode結(jié)構(gòu)體的定義如代碼清單5.4所示。
代碼清單5.4 inode結(jié)構(gòu)體
1 struct inode { 2 ... 3 umode_t i_mode; /* inode的權(quán)限 */ 4 uid_t i_uid; /* inode擁有者的id */ 5 gid_t i_gid; /* inode所屬的群組id */ 6 dev_t i_rdev; /* 若是設(shè)備文件,此字段將記錄設(shè)備的設(shè)備號 */ 7 loff_t i_size; /* inode所代表的文件大小 */ 8 9 struct timespec i_atime; /* inode最近一次的存取時(shí)間 */ 10 struct timespec i_mtime; /* inode最近一次的修改時(shí)間 */ 11 struct timespec i_ctime; /* inode的產(chǎn)生時(shí)間 */ 12 13 unsigned long i_blksize; /* inode在做I/O時(shí)的區(qū)塊大小 */ 14 unsigned long i_blocks; /* inode所使用的block數(shù),一個(gè)block為512 byte*/ 15 16 struct block_device *i_bdev; 17 /*若是塊設(shè)備,為其對應(yīng)的block_device結(jié)構(gòu)體指針*/ 18 struct cdev *i_cdev; /*若是字符設(shè)備,為其對應(yīng)的cdev結(jié)構(gòu)體指針*/ 19 ... 20 };
對于表示設(shè)備文件的inode結(jié)構(gòu),i_rdev字段包含設(shè)備編號。Linux2.6設(shè)備編號分為主設(shè)備編號和次設(shè)備編號,前者為dev_t的高12位,后者為dev_t的低20位。下列操作用于從一個(gè)inode中獲得主設(shè)備號和次設(shè)備號:
unsigned int iminor(struct inode *inode); unsigned int imajor(struct inode *inode);
查看/proc/devices文件可以獲知系統(tǒng)中注冊的設(shè)備,第1列為主設(shè)備號,第2列為設(shè)備名,如:
Character devices: 1 mem 2 pty 3 ttyp 4 /dev/vc/0 4 tty 5 /dev/tty 5 /dev/console 5 /dev/ptmx 7 vcs 10 misc 13 input 21 sg 29 fb 128 ptm 136 pts 171 ieee1394 180 usb 189 usb_device Block devices: 1 ramdisk 2 fd 8 sd 9 md 22 ide1 ...
查看/dev目錄可以獲知系統(tǒng)中包含的設(shè)備文件,日期的前兩列給出了對應(yīng)設(shè)備的主設(shè)備號和次設(shè)備號:
crw-rw---- 1 root uucp 4, 64 Jan 30 2003 /dev/ttyS0 brw-rw---- 1 root disk 8, 0 Jan 30 2003 /dev/sda
主設(shè)備號是與驅(qū)動對應(yīng)的概念,同一類設(shè)備一般使用相同的主設(shè)備號,不同類的設(shè)備一般使用不同的主設(shè)備號(但是也不排除在同一主設(shè)備號下包含有一定差異的設(shè)備)。因?yàn)橥或?qū)動可支持多個(gè)同類設(shè)備,因此用次設(shè)備號來描述使用該驅(qū)動的設(shè)備的序號,序號一般從0開始。
內(nèi)核Documents目錄下的devices.txt文件描述了Linux設(shè)備號的分配情況,它由LANANA(The Linux Assigned Names And Numbers Authority,網(wǎng)址:http://www.lanana.org/)組織維護(hù),Torben Mathiasen(device@lanana.org)是其中的主要維護(hù)者。
5.3 devfs設(shè)備文件系統(tǒng)
devfs(設(shè)備文件系統(tǒng))是由Linux2.4內(nèi)核引入的,引入時(shí)被許多工程師給予了高度評價(jià),它的出現(xiàn)使得設(shè)備驅(qū)動程序能自主地管理它自己的設(shè)備文件。具體來說,devfs具有如下優(yōu)點(diǎn)。(1)可以通過程序在設(shè)備初始化時(shí)在/dev 目錄下創(chuàng)建設(shè)備文件,卸載設(shè)備時(shí)將它刪除。
(2)設(shè)備驅(qū)動程序可以指定設(shè)備名、所有者和權(quán)限位,用戶空間程序仍可以修改所有者和權(quán)限位。
(3)不再需要為設(shè)備驅(qū)動程序分配主設(shè)備號以及處理次設(shè)備號,在程序中可以直接給register_chrdev()傳遞0主設(shè)備號以獲得可用的主設(shè)備號,并在devfs_register()中指定次設(shè)備號。
驅(qū)動程序應(yīng)調(diào)用下面這些函數(shù)來進(jìn)行設(shè)備文件的創(chuàng)建和刪除工作。
/*創(chuàng)建設(shè)備目錄*/ devfs_handle_t devfs_mk_dir(devfs_handle_t dir, const char *name, void *info); /*創(chuàng)建設(shè)備文件*/ devfs_handle_t devfs_register(devfs_handle_t dir, const char *name, unsigned int flags, unsigned int major, unsigned int minor, umode_t mode, void *ops, void *info); /*撤銷設(shè)備文件*/ void devfs_unregister(devfs_handle_t de);
在Linux2.4的設(shè)備驅(qū)動編程中,分別在模塊加載和卸載函數(shù)中創(chuàng)建和撤銷設(shè)備文件是被普遍采用并值得大力推薦的好方法。代碼清單5.5給出了一個(gè)使用devfs的例子。
代碼清單5.5 devfs的使用范例
1 static devfs_handle_t devfs_handle; 2 static int __init xxx_init(void) 3 { 4 int ret; 5 int i; 6 /*在內(nèi)核中注冊設(shè)備*/ 7 ret = register_chrdev(XXX_MAJOR, DEVICE_NAME, &xxx_fops); 8 if (ret < 0) { 9 printk(DEVICE_NAME " can't register major number\n"); 10 return ret; 11 } 12 /*創(chuàng)建設(shè)備文件*/ 13 devfs_handle =devfs_register(NULL, DEVICE_NAME, DEVFS_FL_DEFAULT, 14 XXX_MAJOR, 0, S_IFCHR | S_IRUSR | S_IWUSR, &xxx_fops, NULL); 15 ... 16 printk(DEVICE_NAME " initialized\n"); 17 return 0; 18 } 19 20 static void __exit xxx_exit(void) 21 { 22 devfs_unregister(devfs_handle); /*撤銷設(shè)備文件*/ 23 unregister_chrdev(XXX_MAJOR, DEVICE_NAME); /*注銷設(shè)備*/ 24 } 25 26 module_init(xxx_init); 27 module_exit(xxx_exit);
代碼中第7行和第23行分別用于注冊和注銷字符設(shè)備,使用的register_chrdev()和unregister_chrdev()在Linux2.6內(nèi)核中雖然仍然被支持,但這是過時(shí)的做法。第13和22行分別用于創(chuàng)建和刪除devfs文件節(jié)點(diǎn)。
5.4 udev設(shè)備文件系統(tǒng)
5.4.1 udev與devfs的區(qū)別
盡管devfs有這樣和那樣的優(yōu)點(diǎn),但是,在Linux2.6內(nèi)核中,devfs被認(rèn)為是過時(shí)的方法,并最終被拋棄,udev取代了它。Linux VFS內(nèi)核維護(hù)者Al Viro指出了幾點(diǎn)udev取代devfs的原因:(1)devfs所做的工作被確信可以在用戶態(tài)來完成。
(2)devfs被加入內(nèi)核之時(shí),大家寄望它的質(zhì)量可以迎頭趕上。
(3)devfs被發(fā)現(xiàn)了一些可修復(fù)和無法修復(fù)的bug。
(4)對于可修復(fù)的bug,幾個(gè)月前就已經(jīng)被修復(fù)了,其維護(hù)者認(rèn)為一切良好。
(5)對于后者,同樣是相當(dāng)長一段時(shí)間以來沒有改觀了。
(6)devfs 的維護(hù)者和作者對它感到失望并且已經(jīng)停止了對代碼的維護(hù)工作。
Linux內(nèi)核的兩位貢獻(xiàn)者,Richard Gooch(devfs的作者)和Greg Kroah-Hartman(sysfs的主要作者)就devfs/udev進(jìn)行了激烈的爭論:
Greg:Richard had stated that udev was a proper replacement for DevFS.
Richard:Well, that's news to me!
Greg:DevFS should be taken out because policy should exist in userspace and not in the kernel.
Richard:SysFS, developed in large part by Greg, also implemented policy in the kernel.
Greg:DevFS was broken and unfixable
Richard:No proof. Never say never...
這段有趣的爭論可意譯如下:
Greg:Richard已經(jīng)指出,udev是DevFS恰當(dāng)?shù)奶娲贰?/p>
Richard:哦,是哪個(gè)Richard說的?我怎么不知道。
Greg:DevFS應(yīng)該下課,因?yàn)椴呗詰?yīng)該位于用戶空間而不是內(nèi)核空間。
Richard:哦,我聽說,相當(dāng)大部分由Greg完成的sysfs也在內(nèi)核中實(shí)現(xiàn)了策略。
Greg:devfs很蹩腳,也不穩(wěn)定。
Richard:呵呵,沒證據(jù),別那么武斷……
在Richard Gooch和Greg Kroah-Hartman的爭論中,Greg Kroah-Hartman使用的理論依據(jù)就在于policy(策略)不能位于內(nèi)核空間。Linux設(shè)計(jì)中強(qiáng)調(diào)的一個(gè)基本觀點(diǎn)是機(jī)制和策略的分離。機(jī)制是做某樣事情的固定的步奏、方法,而策略就是每一個(gè)步奏所采取的不同方式。機(jī)制是相對固定的,而每個(gè)步奏采用的策略是不固定的。機(jī)制是穩(wěn)定的,而策略則是靈活的,因此,在 Linux內(nèi)核中,不應(yīng)該實(shí)現(xiàn)策略。Richard Gooch認(rèn)為,屬于策略的東西應(yīng)該被移到用戶空間。這就是為什么devfs位于內(nèi)核空間,而udev確要移到用戶空間的原因。
下面舉一個(gè)通俗的例子來理解udev設(shè)計(jì)的出發(fā)點(diǎn)。以談戀愛為例,Greg Kroah-Hartman認(rèn)為,可以讓內(nèi)核提供談戀愛的機(jī)制,但是不能在內(nèi)核空間限制跟誰談戀愛,不能把談戀愛的策略放在內(nèi)核空間。因?yàn)閼賽凼亲杂傻模脩魬?yīng)該可以在用戶空間中實(shí)現(xiàn)“蘿卜白菜,各有所愛”的理想,可以根據(jù)對方的外貌、籍貫、性格等自由選擇。對應(yīng)devfs而言,第1個(gè)相親的女孩被命名為/dev/girl0,第2個(gè)相親的女孩被命名為/dev/girl1,依此類推。而在用戶空間實(shí)現(xiàn)的udev則可以使得用戶實(shí)現(xiàn)這樣的自由:不管你中意的女孩第幾個(gè)來,只要它與你定義的規(guī)則符合,都命名為/dev/mygirl!
udev 完全在用戶態(tài)工作,利用設(shè)備加入或移除時(shí)內(nèi)核所發(fā)送的熱插拔事件(hotplug event)來工作。在熱插拔時(shí),設(shè)備的詳細(xì)信息會由內(nèi)核輸出到位于/sys的sysfs文件系統(tǒng)。udev的設(shè)備命名策略、權(quán)限控制和事件處理都是在用戶態(tài)下完成的,它利用sysfs中的信息來進(jìn)行創(chuàng)建設(shè)備文件節(jié)點(diǎn)等工作。熱插拔時(shí)輸出到sysfs中的設(shè)備的詳細(xì)信息就是相親對象的資料(外貌、年齡、性格、籍貫等),設(shè)備命名策略等就是擇偶標(biāo)準(zhǔn)。devfs 是個(gè)蹩腳的婚姻介紹所,它直接指定了誰和誰談戀愛,而udev則聰明地多,它只是把資料交給客戶,讓客戶根據(jù)這些資料去選擇和誰談戀愛。
由于udev根據(jù)系統(tǒng)中硬件設(shè)備的狀態(tài)動態(tài)更新設(shè)備文件,進(jìn)行設(shè)備文件的創(chuàng)建和刪除等,因此,在使用udev后,/dev目錄下就會只包含系統(tǒng)中真正存在的設(shè)備了。
devfs與udev的另一個(gè)顯著區(qū)別在于:采用devfs,當(dāng)一個(gè)并不存在的/dev節(jié)點(diǎn)被打開的時(shí)候,devfs能自動加載對應(yīng)的驅(qū)動,而udev則不這么做。這是因?yàn)閡dev的設(shè)計(jì)者認(rèn)為Linux應(yīng)該在設(shè)備被發(fā)現(xiàn)的時(shí)候加載驅(qū)動模塊,而不是當(dāng)它被訪問的時(shí)候。udev 的設(shè)計(jì)者認(rèn)為 devfs 所提供的打開/dev節(jié)點(diǎn)時(shí)自動加載驅(qū)動的功能對于一個(gè)配置正確的計(jì)算機(jī)是多余的。系統(tǒng)中所有的設(shè)備都應(yīng)該產(chǎn)生熱插拔事件并加載恰當(dāng)?shù)尿?qū)動,而udev能注意到這點(diǎn)并且為它創(chuàng)建對應(yīng)的設(shè)備節(jié)點(diǎn)。
5.4.2 sysfs文件系統(tǒng)與Linux設(shè)備模型
Linux2.6的內(nèi)核引入了sysfs文件系統(tǒng),sysfs被看成是與proc、devfs和devpty同類別的文件系統(tǒng),該文件系統(tǒng)是一個(gè)虛擬的文件系統(tǒng),它可以產(chǎn)生一個(gè)包括所有系統(tǒng)硬件的層級視圖,與提供進(jìn)程和狀態(tài)信息的proc文件系統(tǒng)十分類似。
sysfs把連接在系統(tǒng)上的設(shè)備和總線組織成為一個(gè)分級的文件,它們可以由用戶空間存取,向用戶空間導(dǎo)出內(nèi)核數(shù)據(jù)結(jié)構(gòu)以及它們的屬性。sysfs的一個(gè)目的就是展示設(shè)備驅(qū)動模型中各組件的層次關(guān)系,其頂級目錄包括block、device、bus、drivers、class、power和firmware。
block 目錄包含所有的塊設(shè)備;devices 目錄包含系統(tǒng)所有的設(shè)備,并根據(jù)設(shè)備掛接的總線類型組織成層次結(jié)構(gòu);bus 目錄包含系統(tǒng)中所有的總線類型;drivers 目錄包括內(nèi)核中所有已注冊的設(shè)備驅(qū)動程序;class目錄包含系統(tǒng)中的設(shè)備類型(如網(wǎng)卡設(shè)備、聲卡設(shè)備、輸入設(shè)備等)。在/sys目錄運(yùn)行tree會得到一個(gè)相當(dāng)長的樹型目錄,下面摘取一部分:
|-- block | |-- fd0 | |-- md0 | |-- ram0 | |-- ram1 | |-- ... |-- bus | |-- eisa | | |-- devices | | '-- drivers | |-- ide | |-- ieee1394 | |-- pci | | |-- devices | | | |-- 0000:00:00.0 -> ../../../devices/pci0000:00/0000:00:00.0 | | | |-- 0000:00:01.0 -> ../../../devices/pci0000:00/0000:00:01.0 | | | |-- 0000:00:07.0 -> ../../../devices/pci0000:00/0000:00:07.0 | | '-- drivers | | |-- PCI_IDE | | | |-- bind | | | |-- new_id | | | '-- unbind | | '-- pcnet32 | | |-- 0000:00:11.0 -> ../../../../devices/pci0000:00/0000:00:11.0 | | |-- bind | | |-- new_id | | '-- unbind | |-- platform | |-- pnp | '-- usb | |-- devices | '-- drivers | |-- hub | |-- usb | |-- usb-storage | '-- usbfs |-- class | |-- graphics | |-- hwmon | |-- ieee1394 | |-- ieee1394_host | |-- ieee1394_node | |-- ieee1394_protocol | |-- input | |-- mem | |-- misc | |-- net | |-- pci_bus | | |-- 0000:00 | | | |-- bridge -> ../../../devices/pci0000:00 | | | |-- cpuaffinity | | | '-- uevent | | '-- 0000:01 | | |-- bridge -> ../../../devices/pci0000:00/0000:00:01.0 | | |-- cpuaffinity | | '-- uevent | |-- scsi_device | |-- scsi_generic | |-- scsi_host | |-- tty | |-- usb | |-- usb_device | |-- usb_host | '-- vc |-- devices | |-- pci0000:00 | | |-- 0000:00:00.0 | | |-- 0000:00:07.0 | | |-- 0000:00:07.1 | | |-- 0000:00:07.2 | | |-- 0000:00:07.3 | |-- platform | | |-- floppy.0 | | |-- host0 | | |-- i8042 | |-- pnp0 | '-- system |-- firmware |-- kernel | '-- hotplug_seqnum |-- module | |-- apm | |-- autofs | |-- cdrom | |-- eisa_bus | |-- i8042 | |-- ide_cd | |-- ide_scsi | |-- ieee1394 | |-- md_mod | |-- ohci1394 | |-- parport | |-- parport_pc | |-- usb_storage | |-- usbcore | | |-- parameters | | |-- refcnt | | '-- sections | |-- virtual_root | | '-- parameters | | '-- force_probe | '-- vmhgfs | |-- refcnt | '-- sections | '-- __versions '-- power '-- state
在/sys/bus的pci等子目錄下,又會再分出drivers和devices目錄,而devices目錄中的文件是對/sys/devices目錄中文件的符號鏈接。同樣地,/sys/class目錄下也包含許多對/sys/devices下文件的鏈接。如圖5.2所示,這與設(shè)備、驅(qū)動、總線和類的現(xiàn)實(shí)狀況是直接對應(yīng)的,也正符合Linux2.6的設(shè)備模型。

圖5.2 Linux設(shè)備模型
隨著技術(shù)的不斷進(jìn)步,系統(tǒng)的拓?fù)浣Y(jié)構(gòu)越來越復(fù)雜,對智能電源管理、熱插拔以及即插即用的支持要求也越來越高,Linux2.4內(nèi)核已經(jīng)難以滿足這些需求。為適應(yīng)這種形勢的需要,Linux2.6內(nèi)核開發(fā)了上述全新的設(shè)備、總線、類和驅(qū)動環(huán)環(huán)相扣的設(shè)備模型。圖5.3形象地表示了 Linux驅(qū)動模型中設(shè)備、總線和類之間的關(guān)系。
大多數(shù)情況下,Linux2.6內(nèi)核中的設(shè)備模型代碼會作為“幕后黑手”處理好這些關(guān)系,內(nèi)核中的總線級和其他內(nèi)核子系統(tǒng)會完成與設(shè)備模型的交互,這使得驅(qū)動工程師幾乎不需要關(guān)心設(shè)備模型。
在Linux內(nèi)核中,分別使用bus_type、device_driver和device來描述總線、驅(qū)動和設(shè)備,這3個(gè)結(jié)構(gòu)體定義于include/linux/device.h頭文件中,其定義如代碼清單5.6所示。

圖5.3 Linux驅(qū)動模型中設(shè)備、總線和類的關(guān)系
代碼清單5.6 bus_type、device_driver和device結(jié)構(gòu)體
1 struct bus_type { 2 const char *name; 3 struct bus_attribute *bus_attrs; 4 struct device_attribute *dev_attrs; 5 struct driver_attribute *drv_attrs; 6 7 int (*match)(struct device *dev, struct device_driver *drv); 8 int (*uevent)(struct device *dev, struct kobj_uevent_env *env); 9 int (*probe)(struct device *dev); 10 int (*remove)(struct device *dev); 11 void (*shutdown)(struct device *dev); 12 13 int (*suspend)(struct device *dev, pm_message_t state); 14 int (*suspend_late)(struct device *dev, pm_message_t state); 15 int (*resume_early)(struct device *dev); 16 int (*resume)(struct device *dev); 17 18 struct pm_ext_ops *pm; 19 20 struct bus_type_private *p; 21 }; 22 23 struct device_driver { 24 const char *name; 25 struct bus_type *bus; 26 27 struct module *owner; 28 const char *mod_name; 29 30 int (*probe) (struct device *dev); 31 int (*remove) (struct device *dev); 32 void (*shutdown) (struct device *dev); 33 int (*suspend) (struct device *dev, pm_message_t state); 34 int (*resume) (struct device *dev); 35 struct attribute_group **groups; 36 37 struct pm_ops *pm; 38 39 struct driver_private *p; 40 }; 41 42 struct device { 43 struct klist klist_children; 44 struct klist_node knode_parent; 45 struct klist_node knode_driver; 46 struct klist_node knode_bus; 47 struct device *parent; 48 49 struct kobject kobj; 50 char bus_id[BUS_ID_SIZE]; /* 在父總線中的位置 */ 51 const char *init_name; /* 設(shè)備的初始名 */ 52 struct device_type *type; 53 unsigned uevent_suppress:1; 54 55 struct semaphore sem; 56 57 struct bus_type *bus; /* 設(shè)備所在的總線類型 */ 58 struct device_driver *driver; /* 設(shè)備用到的驅(qū)動 */ 59 void *driver_data; 60 void *platform_data; 61 struct dev_pm_info power; 62 63 #ifdef CONFIG_NUMA 64 int numa_node; 65 #endif 66 u64 *dma_mask; 67 u64 coherent_dma_mask; 68 69 struct device_dma_parameters *dma_parms; 70 71 struct list_head dma_pools; 72 73 struct dma_coherent_mem *dma_mem; 74 75 struct dev_archdata archdata; 76 77 spinlock_t devres_lock; 78 struct list_head devres_head; 79 80 struct klist_node knode_class; 81 struct class *class; 82 dev_t devt; /* dev_t, 創(chuàng)建sysfs "dev" */ 83 struct attribute_group**groups; 84 85 void(*release)(struct device *dev); 86 };
device_driver和device分別表示驅(qū)動和設(shè)備,而這兩者都必須依附于一種總線,因此都包含struct bus_type指針。在Linux內(nèi)核中,設(shè)備和驅(qū)動是分開注冊的,注冊1個(gè)設(shè)備的時(shí)候,并不需要驅(qū)動已經(jīng)存在,而1個(gè)驅(qū)動被注冊的時(shí)候,也不需要對應(yīng)的設(shè)備已經(jīng)被注冊。設(shè)備和驅(qū)動各自涌向內(nèi)核,而每個(gè)設(shè)備和驅(qū)動涌入的時(shí)候,都會去尋找自己的另一半。茫茫人海,何處覓蹤?正是bus_type的match()成員函數(shù)將兩者捆綁在一起。簡單地說,設(shè)備和驅(qū)動就是紅塵中漂浮的男女,而bus_type的match()則是牽引紅線的月老,它可以識別什么設(shè)備與什么驅(qū)動可以配對。
注意,總線、驅(qū)動和設(shè)備都最終會落實(shí)為sysfs中的1個(gè)目錄,因?yàn)檫M(jìn)一步追蹤代碼會發(fā)現(xiàn),它們實(shí)際上都可以認(rèn)為是kobject的派生類(device結(jié)構(gòu)體直接包含了kobject kobj成員,而bus_type和device_driver則透過bus_type_private、driver_private間接包含kobject),kobject可看作所有總線、設(shè)備和驅(qū)動的抽象基類,1個(gè)kobject對應(yīng)sysfs中的1個(gè)目錄。
總線、設(shè)備和驅(qū)動中的各個(gè)attribute則直接落實(shí)為sysfs中的1個(gè)文件,attribute會伴隨著show()和 store()這兩個(gè)函數(shù),分別用于讀和寫該 attribute 對應(yīng)的sysfs 文件結(jié)點(diǎn),代碼清單5.7給出了attribute、bus_attribute、driver_attribute和device_attribute這幾個(gè)結(jié)構(gòu)體的定義。
代碼清單5.7 attribute、bus attribute、driver attribute和device attribute結(jié)構(gòu)體
1 struct attribute { 2 const char *name; 3 struct module *owner; 4 mode_t mode; 5 }; 6 7 struct bus_attribute { 8 struct attribute attr; 9 ssize_t (*show)(struct bus_type *bus, char *buf); 10 ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count); 11 }; 12 13 struct driver_attribute { 14 struct attribute attr; 15 ssize_t (*show)(struct device_driver *driver, char *buf); 16 ssize_t (*store)(struct device_driver *driver, const char *buf, 17 size_t count); 18 }; 19 20 struct device_attribute { 21 struct attribute attr; 22 ssize_t (*show)(struct device *dev, struct device_attribute *attr, 23 char *buf); 24 ssize_t (*store)(struct device *dev, struct device_attribute *attr, 25 const char *buf, size_t count); 26 };
事實(shí)上,udev規(guī)則中各信息的來源實(shí)際上就是bus_type、device_driver、device以及attribute等所對應(yīng)sysfs節(jié)點(diǎn)。
5.4.3 udev的組成
udev的主頁位于:http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html,上面包含了關(guān)于 udev 的詳細(xì)介紹,從 http://www.us.kernel.org/pub/linux/utils/kernel/hotplug/上可以下載最新的udev包。udev的設(shè)計(jì)目標(biāo)如下。
● 在用戶空間中執(zhí)行。
(1)動態(tài)建立/刪除設(shè)備文件。
(2)允許每個(gè)人都不用關(guān)心主/次設(shè)備號。
(3)提供LSB標(biāo)準(zhǔn)名稱。
(4)如果需要,可提供固定的名稱。
為了提供這些功能,udev以3個(gè)分割的子計(jì)劃發(fā)展:namedev、libsysfs和udev。namedev為設(shè)備命名子系統(tǒng),libsysfs提供訪問sysfs文件系統(tǒng)從中獲取信息的標(biāo)準(zhǔn)接口,udev提供/dev設(shè)備節(jié)點(diǎn)文件的動態(tài)創(chuàng)建和刪除策略。udev 程序背負(fù)與 namedev和libsysfs 庫交互的任務(wù),當(dāng)/sbin/hotplug程序被內(nèi)核調(diào)用時(shí),udev將被運(yùn)行。udev的工作過程如下。
(1)當(dāng)內(nèi)核檢測到在系統(tǒng)中出現(xiàn)了新設(shè)備后,內(nèi)核會在sysfs文件系統(tǒng)中為該新設(shè)備生成新的記錄并導(dǎo)出一些設(shè)備特定的信息及所發(fā)生的事件。
(2)udev 獲取內(nèi)核導(dǎo)出的信息,它調(diào)用 namedev 決定應(yīng)該給該設(shè)備指定的名稱,如果是新插入設(shè)備,udev將調(diào)用libsysfs決定應(yīng)該為該設(shè)備的設(shè)備文件指定的主/次設(shè)備號,并用分析獲得的設(shè)備名稱和主/次設(shè)備號創(chuàng)建/dev中的設(shè)備文件;如果是設(shè)備移除,則之前已經(jīng)被創(chuàng)建的/dev文件將被刪除。
在namedev中使用5步序列來決定指定設(shè)備的命名。
(1)標(biāo)簽(label)/序號(serial):這一步檢查設(shè)備是否有惟一的識別記號,例如USB設(shè)備有惟一的USB序號,SCSI 有惟一的UUID。如果namedev找到與這種惟一編號相對應(yīng)的規(guī)則,它將使用該規(guī)則提供的名稱。
(2)設(shè)備總線號:這一步會檢查總線設(shè)備編號,對于不可熱插拔的環(huán)境,這一步足以辨別設(shè)備。例如,PCI總線編號在系統(tǒng)的使用期間內(nèi)很少變更。如果namedev找到相對應(yīng)的規(guī)則,規(guī)則中的名稱就會被使用。
(3)總線上的拓?fù)洌寒?dāng)設(shè)備在總線上的位置匹配用戶指定的規(guī)則時(shí),就會使用該規(guī)則指定的名稱。
(4)替換名稱:當(dāng)內(nèi)核提供的名稱匹配指定的替代字符串時(shí),就會使用替代字符串指定的名稱。(5)內(nèi)核提供的名稱:這一步“包羅萬象”,如果以前的幾個(gè)步驟都沒有被提供,默認(rèn)的內(nèi)核將被指定給該設(shè)備。
代碼清單5.8給出了一個(gè)namedev命名規(guī)則的例子,第2、4行定義的是符合第1步的規(guī)則,第6、8行定義的是符合第2步的規(guī)則,第11、14行定義的是符合第3步的規(guī)則,第16行定義的是符合第4步的規(guī)則。
代碼清單5.8 namedev命名規(guī)則
1 # USB Epson printer to be called lp_epson 2 LABEL, BUS="usb", serial="HXOLL0012202323480", NAME="lp_epson" 3 # USB HP printer to be called lp_hp, 4 LABEL, BUS="usb", serial="W09090207101241330", NAME="lp_hp" 5 # sound card with PCI bus id 00:0b.0 to be the first sound card 6 NUMBER, BUS="pci", id="00:0b.0", NAME="dsp" 7 # sound card with PCI bus id 00:07.1 to be the second sound card 8 NUMBER, BUS="pci", id="00:07.1", NAME="dsp1" 9 # USB mouse plugged into the third port of the first hub to be 10 # called mouse0 11 TOPOLOGY, BUS="usb", place="1.3", NAME="mouse0" 12 # USB tablet plugged into the second port of the second hub to be 13 # called mouse1 14 TOPOLOGY, BUS="usb", place="2.2", NAME="mouse1" 15 # ttyUSB1 should always be called visor 16 REPLACE, KERNEL="ttyUSB1", NAME="visor"
5.4.4 udev規(guī)則文件
udev 的規(guī)則文件以行為單位,以“#”開頭的行代表注釋行。其余的每一行代表一個(gè)規(guī)則。每個(gè)規(guī)則分成一個(gè)或多個(gè)匹配和賦值部分。匹配部分用匹配專用的關(guān)鍵字來表示,相應(yīng)的賦值部分用賦值專用的關(guān)鍵字來表示。匹配關(guān)鍵字包括:ACTION(行為)、KERNEL(匹配內(nèi)核設(shè)備名)、BUS(匹配總線類型)、SYSFS(匹配從 sysfs 得到的信息,比如 label、vendor、USB 序列號)、SUBSYSTEM(匹配子系統(tǒng)名)等,賦值關(guān)鍵字包括:NAME(創(chuàng)建的設(shè)備文件名)、SYMLINK (符號創(chuàng)建鏈接名)、OWNER(設(shè)置設(shè)備的所有者)、GROUP(設(shè)置設(shè)備的組)、IMPORT(調(diào)用外部程序)等。
例如,如下規(guī)則:
SUBSYSTEM=="net", ACTION=="add", SYSFS{address}=="00:0d:87:f6:59:f3", IMPORT="/sbin/ rename_netiface %k eth0"
其中的“匹配”部分有3項(xiàng),分別是SUBSYSTEM、ACTION和SYSFS。而“賦值”部分有一項(xiàng),是IMPORT。這個(gè)規(guī)則的意思是:當(dāng)系統(tǒng)中出現(xiàn)的新硬件屬于 net 子系統(tǒng)范疇,系統(tǒng)對該硬件采取的動作是加入這個(gè)硬件,且這個(gè)硬件在 sysfs 文件系統(tǒng)中的“address”信息等于“00:0d:87:f6:59:f3”時(shí),對這個(gè)硬件在udev層次施行的動作是調(diào)用外部程序/sbin/rename_netiface,并傳遞給該程序兩個(gè)參數(shù),一個(gè)是“%k”,代表內(nèi)核對該新設(shè)備定義的名稱,另一個(gè)是“eth0”。
通過一個(gè)簡單的例子可以看出udev和devfs在命名方面的差異。如果系統(tǒng)中有兩個(gè)USB打印機(jī),一個(gè)可能被稱為/dev/usb/lp0,另外一個(gè)便是/dev/usb/lp1。但是到底哪個(gè)文件對應(yīng)哪個(gè)打印機(jī)是無法確定的,lp0、lp1和實(shí)際的設(shè)備沒有一一對應(yīng)的關(guān)系,映射關(guān)系會因?yàn)樵O(shè)備發(fā)現(xiàn)的順序,打印機(jī)本身關(guān)閉等原因而不確定。因此,理想的方式是兩個(gè)打印機(jī)應(yīng)該采用基于它們的序列號或者其他標(biāo)識信息的辦法來進(jìn)行確定的映射,devfs 無法做到這一點(diǎn),udev 卻可以做到。使用如下規(guī)則:
BUS="usb", SYSFS{serial}="HXOLL0012202323480", NAME="lp_epson", SYMLINK="printers/ epson_stylus"
該規(guī)則中的匹配項(xiàng)目有BUS和SYSFS,賦值項(xiàng)目為NAME和SYMLINK,它意味著當(dāng)一臺USB打印機(jī)的序列號為“HXOLL0012202323480”時(shí),創(chuàng)建/dev/lp_epson文件,并同時(shí)創(chuàng)建一個(gè)符號鏈接/dev/printers/epson_styles。序列號為“HXOLL0012202323480”的USB 打印機(jī)不管何時(shí)被插入,對應(yīng)的設(shè)備名都是/dev/lp_epson,而devfs顯然無法實(shí)現(xiàn)設(shè)備的這種固定命名。
udev規(guī)則的寫法非常靈活,在匹配部分,可以通過“*”、“?”、[a~c]、[1~9]等shell通配符來靈活匹配多個(gè)項(xiàng)目。*類似于shell中的*通配符,代替任意長度的任意字符串,?代替一個(gè)字符, [x~y]是訪問定義。此外,%k就是KERNEL,%n則是設(shè)備的KERNEL序號(如存儲設(shè)備的分區(qū)號)。
可以借助 udev 中的udevinfo 工具查找規(guī)則文件可以利用的信息,如運(yùn)行“udevinfo -a -p/sys/block/sda”命令將得到:
Udevinfo starts with the device specified by the devpath and then walks up the chain of parent devices. It prints for every device found, all possible attributes in the udev rules key format. A rule to match, can be composed by the attributes of the device and the attributes from one single parent device. looking at device '/block/sda': KERNEL=="sda" SUBSYSTEM=="block" DRIVER=="" ATTR{stat}==" 1 689 3 169 85 746 24 000 2 017 2 095 32 896 47 292 0 23 188 71 292" ATTR{size}=="6 291 456" ATTR{removable}=="0" ATTR{range}=="16" ATTR{dev}=="8:0" looking at parent device '/devices/platform/host0/target0:0:0/0:0:0:0': KERNELS=="0:0:0:0" SUBSYSTEMS=="scsi" DRIVERS=="sd" ATTRS{ioerr_cnt}=="0x5" ATTRS{iodone_cnt}=="0xe86" ATTRS{iorequest_cnt}=="0xe86" ATTRS{iocounterbits}=="32" ATTRS{timeout}=="30" ATTRS{state}=="running" ATTRS{rev}=="1.0 " ATTRS{model}=="VMware Virtual S" ATTRS{vendor}=="VMware, " ATTRS{scsi_level}=="3" ATTRS{type}=="0" ATTRS{queue_type}=="none" ATTRS{queue_depth}=="3" ATTRS{device_blocked}=="0" looking at parent device '/devices/platform/host0/target0:0:0': KERNELS=="target0:0:0" SUBSYSTEMS=="" DRIVERS=="" looking at parent device '/devices/platform/host0': KERNELS=="host0" SUBSYSTEMS=="" DRIVERS=="" looking at parent device '/devices/platform': KERNELS=="platform" SUBSYSTEMS=="" DRIVERS==""
5.4.5 創(chuàng)建和配置mdev
在嵌入式系統(tǒng)中,通常可以用 udev 的輕量級版本 mdev,mdev 集成于 busybox(本書配套VirtualBox 虛擬機(jī)/home/lihacker/develop/svn/ldd6410-read-only/utils/busybox-1.15.1 目錄)中。在busybox的源代碼目錄運(yùn)行make menuconfig,進(jìn)入“Linux System Utilities”子選項(xiàng),選中mdev相關(guān)項(xiàng)目,如圖5-4所示。

圖5.4 busybox中的mdev選項(xiàng)
LDD6410根文件系統(tǒng)中/etc/init.d/rcS包含的如下內(nèi)容即是為了使用mdev的功能:
/bin/mount -t sysfs sysfs /sys /bin/mount -t tmpfs mdev /dev echo /bin/mdev > /proc/sys/kernel/hotplug mdev -s
其中“mdev -s”的含義是掃描/sys中所有的類設(shè)備目錄,如果在目錄中含有名為“dev”的文件,且文件中包含的是設(shè)備號,則mdev就利用這些信息為該設(shè)備在/dev下創(chuàng)建設(shè)備節(jié)點(diǎn)文件。
“echo /sbin/mdev > /proc/sys/kernel/hotplug”的含義是當(dāng)有熱插拔事件產(chǎn)生時(shí),內(nèi)核就會調(diào)用位于 /sbin目錄的mdev。這時(shí)mdev通過環(huán)境變量中的ACTION和DEVPATH,來確定此次熱插拔事件的動作以及影響了/sys中的那個(gè)目錄。接著會看看這個(gè)目錄中是否有“dev”的屬性文件,如果有就利用這些信息為這個(gè)設(shè)備在/dev下創(chuàng)建設(shè)備節(jié)點(diǎn)文件。
若要修改mdev的規(guī)則,可通過修改/etc/mdev.conf文件實(shí)現(xiàn)。
5.5 LDD6410的SD和NAND文件系統(tǒng)
LDD6410的SD卡分為兩個(gè)區(qū),其中的第2個(gè)分區(qū)為ext3文件系統(tǒng),存放LDD6410的文件數(shù)據(jù)(5.2.1節(jié)給出的各個(gè)目錄),其制作方法如下。
(1)在安裝了Linux的PC機(jī)上通過fdisk給一張空的SD卡分為2個(gè)區(qū)(如果SD卡中本身已經(jīng)包含,請通過fdisk的“d”命令全部刪除),得到如下的分區(qū)表:
Command (m for help): p Disk /dev/sdb: 1 030 MB, 1 030 225 920 bytes 32 heads, 62 sectors/track, 1 014 cylinders Units = cylinders of 1984 * 512 = 1 015 808 bytes Disk identifier: 0x6f20736b Device Boot Start End Blocks Id System /dev/sdb1 * 1 20 19 809 83 Linux /dev/sdb2 21 1014 98 6048 83 Linux
注意第1個(gè)分區(qū)制作的命令為:
Command (m for help): n Command action e extended p primary partition (1-4) p Partition number (1-4): 1 First cylinder (1-1 014, default 1): Using default value 1 Last cylinder, +cylinders or +size{K,M,G} (1-1 014, default 1 014): 20M
第2個(gè)分區(qū)制作的命令是:
Command (m for help): n Command action e extended p primary partition (1-4) p Partition number (1-4): 2 First cylinder (21-1 014, default 21): Using default value 21 Last cylinder, +cylinders or +size{K,M,G} (21-1 014, default 1 014): Using default value 1 014 Command (m for help):
我們還要通過“a”命令標(biāo)記第1個(gè)分區(qū):
Command (m for help): a Partition number (1-4): 1
最后要通過“w”命令把建好的分區(qū)表寫入SD卡。
(2)格式化SD卡的分區(qū)1和分區(qū)2:
mkfs.vfat /dev/sdb1 mkfs.ext3 /dev/sdb2 fsck.ext3 /dev/sdb2
(3)如圖5-5所示,通過moviNAND_Fusing_Tool.exe燒寫SD卡U-BOOT和zImage。

圖5.5 燒寫LDD6410的SD卡U - BOOT和zImage
更新SD卡根文件系統(tǒng)的方法很簡單,在PC機(jī)上mount /dev/sdb2后,直接通過
cp -fa <your-rootfs> <sdb2-mount-point>
即可替換根文件系統(tǒng)了。<your-rootfs>是根文件系統(tǒng)的目錄,<sdb2-mount-point>是/dev/sdb2掛載的目錄。
特別要注意的是,SD的設(shè)備節(jié)點(diǎn)不一定是/dev/sdb,應(yīng)該視用戶電腦的硬盤情況而言,可能是/dev/sdc、/dev/sdd 等。
LDD6410的NAND分為3個(gè)區(qū),分別存放U-BOOT、zImage和文件系統(tǒng)。該分區(qū)表定義在LDD6410的BSP中:
struct mtd_partition s3c_partition_info[] = { { .name = "Bootloader", .offset = 0, .size = (512*SZ_1K), }, { .name = "Kernel", .offset = (512*SZ_1K), .size = (4*SZ_1M) - (512*SZ_1K), }, { .name = "File System", .offset = MTDPART_OFS_APPEND, .size = MTDPART_SIZ_FULL, } };
更新NAND中U-BOOT的方法如下:
(1)通過 tftp或nfs 等方式獲取新的U-BOOT,如:
# tftp -r u-boot-movi.bin -g 192.168.1.111
(2)運(yùn)行:
# flashcp u-boot-movi.bin /dev/mtd0
更新NAND中zImage的方法如下:
(1)通過 tftp或nfs 等方式獲取新的zImage,如:
# tftp -r zImage-fix -g 192.168.1.111
(2)運(yùn)行:
# flashcp zImage-fix /dev/mtd1
更新NAND中文件系統(tǒng)的方法如下:
在PC上將做好的新的根文件系統(tǒng)拷貝到SD卡或NFS的某目錄,下面我們以<new_rootfs_dir>指代該目錄。
以SD卡或NFS為根文件系統(tǒng)啟動系統(tǒng),運(yùn)行如下命令擦除/dev/mtd2分區(qū):
# flash_eraseall /dev/mtd2
然后將NAND的該分區(qū)mount到/mnt:
# mount /dev/mtdblock2 -t yaffs2 /mnt/
將新的文件系統(tǒng)拷貝到/mnt:
# cp -fa <new_rootfs_dir> /mnt
若上述命令運(yùn)行過程中設(shè)備結(jié)點(diǎn)不存在,可先執(zhí)行:
# mdev -s
啟動到LDD6410,在根目錄下運(yùn)行l(wèi)s,會發(fā)現(xiàn)LDD6410根文件系統(tǒng)包含如下子目錄:
#1s android init.rc sbin bin lib sqlite_stmt_journals cache linuxrc sys data lost+found system demo mnt tmp dev opt usr etc proc var init.goldfish.rc qtopia
其中的/data /cache /system /sqlite_stmt_journals 為 Android 所需要的目錄,/opt 下存放Qt/Embedded 的可執(zhí)行文件。運(yùn)行“/android&”可啟動 Android,運(yùn)行“/qtopia &”可啟動Qt/Embedded。
/demo目錄下存放在一些用于demo的圖片、MP3等,如運(yùn)行如下命令可顯示jpeg圖片1.jpg、2.jpg、3.jpg和4.jpg。
# cd /demo/ # jpegview 1.jpg 2.jpg 3.jpg 4.jpg 480 272 480 544 0 272 16 0 480 272 framebase = 0x4016c000 err=0 ImageWidth=362 ImageHeight=272 read 1.jpg OK ImageWidth=362 ImageHeight=272 read 2.jpg OK ImageWidth=362 ImageHeight=272 read 3.jpg OK ImageWidth=362 ImageHeight=272 read 4.jpg OK
5.6 總結(jié)
Linux用戶空間的文件編程有兩種方法,即通過Linux API和通過C庫函數(shù)訪問文件。用戶空間看不到設(shè)備驅(qū)動,能看到的只有設(shè)備對應(yīng)的文件,因此文件編程即是用戶空間的設(shè)備編程。
Linux按照功能對文件系統(tǒng)的目錄結(jié)構(gòu)進(jìn)行了良好的規(guī)劃。/dev是設(shè)備文件的存放目錄,devfs和udev分別是Linux2.4和Linux2.6生成設(shè)備文件節(jié)點(diǎn)的方法,前者運(yùn)行于內(nèi)核空間,后者運(yùn)行于用戶空間。
Linux2.6通過一系列數(shù)據(jù)結(jié)構(gòu)定義了設(shè)備模型,設(shè)備模型與sysfs文件系統(tǒng)中的目錄和文件存在一種對應(yīng)關(guān)系,udev 可以利用 sysfs 中記錄的信息定義規(guī)則并提取主次設(shè)備號動態(tài)創(chuàng)建/dev設(shè)備文件節(jié)點(diǎn)。
- 操作系統(tǒng)實(shí)用教程(Linux版)
- Learning Android Intents
- 嵌入式應(yīng)用程序設(shè)計(jì)綜合教程(微課版)
- 巧學(xué)活用Windows 7
- Windows 7案例教程
- Joomla! 3 Template Essentials
- Windows Vista終極技巧金典
- Drupal 7 Cookbook
- Learning Continuous Integration with Jenkins(Second Edition)
- Linux 從入門到項(xiàng)目實(shí)踐(超值版)
- Linux深度攻略
- Linux指令從初學(xué)到精通
- Serverless Architectures with Kubernetes
- 電子商務(wù)系統(tǒng)建設(shè)與管理
- OpenStack Trove Essentials