官术网_书友最值得收藏!

第5章 Linux文件系統(tǒng)與設(shè)備文件系統(tǒng)

本章導(dǎo)讀

由于字符設(shè)備和塊設(shè)備都良好地體現(xiàn)了“一切都是文件”的設(shè)計(jì)思想,掌握Linux文件系統(tǒng)、設(shè)備文件系統(tǒng)的知識(shí)就顯得相當(dāng)重要了。

首先,驅(qū)動(dòng)工程師編寫的驅(qū)動(dòng)最終通過(guò)操作系統(tǒng)的文件操作系統(tǒng)調(diào)用或C庫(kù)函數(shù)(本質(zhì)也基于系統(tǒng)調(diào)用)被訪問(wèn),而設(shè)備驅(qū)動(dòng)的結(jié)構(gòu)最終也是為了迎合提供給應(yīng)用程序員的API。

其次,驅(qū)動(dòng)工程師在設(shè)備驅(qū)動(dòng)中不可避免地會(huì)與設(shè)備文件系統(tǒng)打交道,從Linux2.4內(nèi)核的devfs文件系統(tǒng)到目前Linux2.6基于sysfs的udev文件系統(tǒng)。

5.1節(jié)講解了通過(guò)Linux API和C庫(kù)函數(shù)在用戶空間進(jìn)行Linux文件操作的編程方法。

5.2節(jié)分析了Linux文件系統(tǒng)的目錄結(jié)構(gòu),簡(jiǎn)單介紹了Linux內(nèi)核中文件系統(tǒng)的實(shí)現(xiàn),并給出了文件系統(tǒng)與設(shè)備驅(qū)動(dòng)的關(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)建、打開(kāi)、讀寫和關(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可通過(guò)系統(tǒng)調(diào)用umask()來(lái)改變:

        int umask(int newmask);

該調(diào)用將umask設(shè)置為newmask,然后返回舊的umask,它只影響讀、寫和執(zhí)行權(quán)限。

2.打開(kāi)

        int open(const char *pathname, int flags);
        int open(const char *pathname, int flags, mode_t mode);

open()函數(shù)有兩個(gè)形式,其中pathname是我們要打開(kāi)的文件名(包含路徑名稱,缺省是認(rèn)為在當(dāng)前路徑下面),flags可以是如表5.1所示的一個(gè)值或者是幾個(gè)值的組合。

表5.1 文件打開(kāi)標(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)志,用來(lái)表示文件的訪問(wèn)權(quán)限。mode可以是如表5.2所列值的組合。

表5.2 文件訪問(wèn)權(quán)限

除了可以通過(guò)上述宏進(jìn)行“或”邏輯產(chǎn)生標(biāo)志以外,我們也可以自己用數(shù)字來(lái)表示,Linux用5個(gè)數(shù)字來(lái)表示文件的各種權(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(無(wú))或者是這些值的和。例如,要?jiǎng)?chuàng)建一個(gè)用戶可讀、可寫、可執(zhí)行,但是組沒(méi)有權(quán)限,其他人可以讀、可以執(zhí)行的文件,并設(shè)置用戶 ID 位。那么,我們應(yīng)該使用的模式是1(設(shè)置用戶ID)、0(不設(shè)置組ID)、7(1+2+4,讀、寫、執(zhí)行)、0(沒(méi)有權(quán)限)、5(1+4,讀、執(zhí)行)即10705:

        open("test", O_CREAT, 10 705);

上述語(yǔ)句等價(jià)于:

        open("test", O_CREAT, S_IRWXU | S_IROTH | S_IXOTH | S_ISUID );

如果文件打開(kāi)成功,open函數(shù)會(huì)返回一個(gè)文件描述符,以后對(duì)該文件的所有操作就可以通過(guò)對(duì)這個(gè)文件描述符進(jìn)行操作來(lái)實(shí)現(xiàn)。

3.讀寫

在文件打開(kāi)以后,我們才可對(duì)文件進(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.定位

對(duì)于隨機(jī)文件,我們可以隨機(jī)地指定位置讀寫,使用如下函數(shù)進(jìn)行定位:

        int lseek(int fd, offset_t offset, int whence);

lseek()將文件讀寫指針相對(duì)whence移動(dòng)offset個(gè)字節(jié)。操作成功時(shí),返回文件指針相對(duì)于文件頭的位置。參數(shù)whence可使用下述值:

SEEK_SET:相對(duì)文件開(kāi)頭

SEEK_CUR:相對(duì)文件讀寫指針的當(dāng)前位置

SEEK_END:相對(duì)文件末尾

offset可取負(fù)值,例如下述調(diào)用可將文件指針相對(duì)當(dāng)前位置向前移動(dòng)5個(gè)字節(jié):

        lseek(fd, -5, SEEK_CUR);

由于 lseek 函數(shù)的返回值為文件指針相對(duì)于文件頭的位置,因此下列調(diào)用的返回值就是文件的長(zhǎng)度:

        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)閉該文件。再次打開(kāi)該文件,讀取其中的內(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)建并打開(kāi)文件 */
        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庫(kù)文件操作

C庫(kù)函數(shù)的文件操作實(shí)際上是獨(dú)立于具體的操作系統(tǒng)平臺(tái)的,不管是在DOS、Windows、Linux還是在VxWorks中都是這些函數(shù):

1.創(chuàng)建和打開(kāi)

        FILE *fopen(const char *path, const char *mode);

fopen()實(shí)現(xiàn)打開(kāi)指定文件filename,其中的mode為打開(kāi)模式,C庫(kù)函數(shù)中支持的打開(kāi)模式如表5.3所示。

表5.3 C庫(kù)函數(shù)文件打開(kāi)標(biāo)志

其中 b 用于區(qū)分二進(jìn)制文件和文本文件,這一點(diǎn)在 DOS、Windows 系統(tǒng)中是有區(qū)分的,但Linux不區(qū)分二進(jìn)制文件和文本文件。

2.讀寫

C庫(kù)函數(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é)尾。所以要通過(guò)調(diào)用feof()和ferror()來(lái)判斷。

write()實(shí)現(xiàn)從緩沖區(qū)ptr所指的數(shù)組中把n個(gè)字段寫到流stream中,每個(gè)字段長(zhǎng)為size個(gè)字節(jié),返回實(shí)際寫入的字段數(shù)。

另外,C庫(kù)函數(shù)還提供了讀寫過(guò)程中的定位能力,這些函數(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庫(kù)函數(shù)關(guān)閉文件依然是很簡(jiǎn)單的操作:

        int fclose (FILE *stream);

例程:將第5.1.1節(jié)中的例程用C庫(kù)函數(shù)來(lái)實(shí)現(xiàn),如代碼清單5-2所示。

代碼清單5.2 Linux文件操作用戶空間編程(使用C庫(kù)函數(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)建并打開(kāi)文件 */
        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)的入口,也是處于最高一級(jí)的目錄),運(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è)備文件存儲(chǔ)目錄,應(yīng)用程序通過(guò)對(duì)這些文件的讀寫和控制就可以訪問(wèn)實(shí)際的設(shè)備。

4./etc

系統(tǒng)配置文件的所在地,一些服務(wù)器的配置文件也在這里,如用戶賬號(hào)及密碼配置文件。busybox的啟動(dòng)腳本也存放在該目錄。

5./lib

系統(tǒng)庫(kù)文件存放目錄,如LDD6410包含libc-2.6.1.so、libpthread-2.6.1.so、libthread_db-1.0.so等。

6./mnt

/mnt這個(gè)目錄一般是用于存放掛載儲(chǔ)存設(shè)備的掛載目錄的,比如有cdrom 等目錄。可以參看/etc/fstab 的定義。有時(shí)我們可以把讓系統(tǒng)開(kāi)機(jī)自動(dòng)掛載文件系統(tǒng),把掛載點(diǎn)放在這里也是可以的。

7./opt

opt 是“可選”的意思,有些軟件包會(huì)被安裝在這里,例如,在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í)候,會(huì)產(chǎn)生臨時(shí)文件,/tmp就用來(lái)存放臨時(shí)文件的。

10./usr

這個(gè)是系統(tǒng)存放程序的目錄,比如用戶命令、用戶庫(kù)等。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)常變動(dòng),如/var的/var/log 目錄被用來(lái)存放系統(tǒng)日志。

12./sys

Linux2.6內(nèi)核所支持的sysfs文件系統(tǒng)被映射在此目錄。Linux設(shè)備驅(qū)動(dòng)模型中的總線、驅(qū)動(dòng)和設(shè)備都可以在 sysfs 文件系統(tǒng)中找到對(duì)應(yīng)的節(jié)點(diǎn)。當(dāng)內(nèi)核檢測(cè)到在系統(tǒng)中出現(xiàn)了新設(shè)備后,內(nèi)核會(huì)在sysfs文件系統(tǒng)中為該新設(shè)備生成一項(xiàng)新的記錄。

5.2.2 Linux文件系統(tǒng)與設(shè)備驅(qū)動(dòng)

圖5.1所示為L(zhǎng)inux中虛擬文件系統(tǒng)、磁盤文件(存放于Ramdisk、Flash、ROM、SD卡、U盤等文件系統(tǒng)中的文件也屬于此列)及一般的設(shè)備文件與設(shè)備驅(qū)動(dòng)程序之間的關(guān)系。

圖5.1 文件系統(tǒng)與設(shè)備驅(qū)動(dòng)

應(yīng)用程序和VFS之間的接口是系統(tǒng)調(diào)用,而VFS與磁盤文件系統(tǒng)以及普通設(shè)備之間的接口是 file_operations 結(jié)構(gòu)體成員函數(shù),這個(gè)結(jié)構(gòu)體包含對(duì)文件進(jìn)行打開(kāi)、關(guān)閉、讀寫、控制的一系列成員函數(shù)。

由于字符設(shè)備的上層沒(méi)有磁盤文件系統(tǒng),所以字符設(shè)備的file_operations 成員函數(shù)就直接由設(shè)備驅(qū)動(dòng)提供了,在稍后的第6章,將會(huì)看到file_operations正是字符設(shè)備驅(qū)動(dòng)的核心。

而對(duì)于塊存儲(chǔ)設(shè)備而言,ext2、fat、jffs2等文件系統(tǒng)中會(huì)實(shí)現(xiàn)針對(duì)VFS的file_operations成員函數(shù),設(shè)備驅(qū)動(dòng)層將看不到 file_operations 的存在。磁盤文件系統(tǒng)和設(shè)備驅(qū)動(dòng)會(huì)將對(duì)磁盤上文件的訪問(wèn)最終轉(zhuǎn)換成對(duì)磁盤上柱面和扇區(qū)的訪問(wèn)。

在設(shè)備驅(qū)動(dòng)程序的設(shè)計(jì)中,一般而言,會(huì)關(guān)心file和inode這兩個(gè)結(jié)構(gòu)體。

1.file結(jié)構(gòu)體

file結(jié)構(gòu)體代表一個(gè)打開(kāi)的文件(設(shè)備對(duì)應(yīng)于設(shè)備文件),系統(tǒng)中每個(gè)打開(kāi)的文件在內(nèi)核空間都有一個(gè)關(guān)聯(lián)的struct file。它由內(nèi)核在打開(kāi)文件時(shí)創(chuàng)建,并傳遞給在文件上進(jìn)行操作的任何函數(shù)。在文件的所有實(shí)例都關(guān)閉后,內(nèi)核釋放這個(gè)數(shù)據(jù)結(jié)構(gòu)。在內(nèi)核和驅(qū)動(dòng)源代碼中,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ū)動(dòng)需要,其他的也許需要 */
        22   void *private_data; /*文件私有數(shù)據(jù)*/
        23   ...
        24   struct address_space *f_mapping;
        25 };

文件讀/寫模式 mode、標(biāo)志 f_flags 都是設(shè)備驅(qū)動(dòng)關(guān)心的內(nèi)容,而私有數(shù)據(jù)指針 private_data在設(shè)備驅(qū)動(dòng)中被廣泛應(yīng)用,大多被指向設(shè)備驅(qū)動(dòng)自定義用于描述設(shè)備的結(jié)構(gòu)體。

驅(qū)動(dòng)程序中經(jīng)常會(huì)使用如下類似的代碼來(lái)檢測(cè)用戶打開(kāi)文件的讀寫方式:

        if (file->f_mode & FMODE_WRITE) {/* 用戶要求可寫 */
        }
        if (file->f_mode & FMODE_READ) {/* 用戶要求可讀 */
        }

下面的代碼可用于判斷以阻塞還是非阻塞方式打開(kāi)設(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 包含文件訪問(wèn)權(quán)限、屬主、組、大小、生成時(shí)間、訪問(wèn)時(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è)備號(hào) */
        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è)備,為其對(duì)應(yīng)的block_device結(jié)構(gòu)體指針*/
        18    struct cdev *i_cdev;   /*若是字符設(shè)備,為其對(duì)應(yīng)的cdev結(jié)構(gòu)體指針*/
        19    ...
        20 };

對(duì)于表示設(shè)備文件的inode結(jié)構(gòu),i_rdev字段包含設(shè)備編號(hào)。Linux2.6設(shè)備編號(hào)分為主設(shè)備編號(hào)和次設(shè)備編號(hào),前者為dev_t的高12位,后者為dev_t的低20位。下列操作用于從一個(gè)inode中獲得主設(shè)備號(hào)和次設(shè)備號(hào):

        unsigned int iminor(struct inode *inode);
        unsigned int imajor(struct inode *inode);

查看/proc/devices文件可以獲知系統(tǒng)中注冊(cè)的設(shè)備,第1列為主設(shè)備號(hào),第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è)備文件,日期的前兩列給出了對(duì)應(yīng)設(shè)備的主設(shè)備號(hào)和次設(shè)備號(hào):

        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è)備號(hào)是與驅(qū)動(dòng)對(duì)應(yīng)的概念,同一類設(shè)備一般使用相同的主設(shè)備號(hào),不同類的設(shè)備一般使用不同的主設(shè)備號(hào)(但是也不排除在同一主設(shè)備號(hào)下包含有一定差異的設(shè)備)。因?yàn)橥或?qū)動(dòng)可支持多個(gè)同類設(shè)備,因此用次設(shè)備號(hào)來(lái)描述使用該驅(qū)動(dòng)的設(shè)備的序號(hào),序號(hào)一般從0開(kāi)始。

內(nèi)核Documents目錄下的devices.txt文件描述了Linux設(shè)備號(hào)的分配情況,它由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í)被許多工程師給予了高度評(píng)價(jià),它的出現(xiàn)使得設(shè)備驅(qū)動(dòng)程序能自主地管理它自己的設(shè)備文件。具體來(lái)說(shuō),devfs具有如下優(yōu)點(diǎn)。(1)可以通過(guò)程序在設(shè)備初始化時(shí)在/dev 目錄下創(chuàng)建設(shè)備文件,卸載設(shè)備時(shí)將它刪除。

(2)設(shè)備驅(qū)動(dòng)程序可以指定設(shè)備名、所有者和權(quán)限位,用戶空間程序仍可以修改所有者和權(quán)限位。

(3)不再需要為設(shè)備驅(qū)動(dòng)程序分配主設(shè)備號(hào)以及處理次設(shè)備號(hào),在程序中可以直接給register_chrdev()傳遞0主設(shè)備號(hào)以獲得可用的主設(shè)備號(hào),并在devfs_register()中指定次設(shè)備號(hào)。

驅(qū)動(dòng)程序應(yīng)調(diào)用下面這些函數(shù)來(lái)進(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ū)動(dòng)編程中,分別在模塊加載和卸載函數(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)核中注冊(cè)設(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行分別用于注冊(cè)和注銷字符設(shè)備,使用的register_chrdev()和unregister_chrdev()在Linux2.6內(nèi)核中雖然仍然被支持,但這是過(guò)時(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)為是過(guò)時(shí)的方法,并最終被拋棄,udev取代了它。Linux VFS內(nèi)核維護(hù)者Al Viro指出了幾點(diǎn)udev取代devfs的原因:(1)devfs所做的工作被確信可以在用戶態(tài)來(lái)完成。

(2)devfs被加入內(nèi)核之時(shí),大家寄望它的質(zhì)量可以迎頭趕上。

(3)devfs被發(fā)現(xiàn)了一些可修復(fù)和無(wú)法修復(fù)的bug。

(4)對(duì)于可修復(fù)的bug,幾個(gè)月前就已經(jīng)被修復(fù)了,其維護(hù)者認(rèn)為一切良好。

(5)對(duì)于后者,同樣是相當(dāng)長(zhǎng)一段時(shí)間以來(lái)沒(méi)有改觀了。

(6)devfs 的維護(hù)者和作者對(duì)它感到失望并且已經(jīng)停止了對(duì)代碼的維護(hù)工作。

Linux內(nèi)核的兩位貢獻(xiàn)者,Richard Gooch(devfs的作者)和Greg Kroah-Hartman(sysfs的主要作者)就devfs/udev進(jìn)行了激烈的爭(zhēng)論:

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...

這段有趣的爭(zhēng)論可意譯如下:

Greg:Richard已經(jīng)指出,udev是DevFS恰當(dāng)?shù)奶娲贰?/p>

Richard:哦,是哪個(gè)Richard說(shuō)的?我怎么不知道。

Greg:DevFS應(yīng)該下課,因?yàn)椴呗詰?yīng)該位于用戶空間而不是內(nèi)核空間。

Richard:哦,我聽(tīng)說(shuō),相當(dāng)大部分由Greg完成的sysfs也在內(nèi)核中實(shí)現(xiàn)了策略。

Greg:devfs很蹩腳,也不穩(wěn)定。

Richard:呵呵,沒(méi)證據(jù),別那么武斷……

在Richard Gooch和Greg Kroah-Hartman的爭(zhēng)論中,Greg Kroah-Hartman使用的理論依據(jù)就在于policy(策略)不能位于內(nèi)核空間。Linux設(shè)計(jì)中強(qiáng)調(diào)的一個(gè)基本觀點(diǎn)是機(jī)制和策略的分離。機(jī)制是做某樣事情的固定的步奏、方法,而策略就是每一個(gè)步奏所采取的不同方式。機(jī)制是相對(duì)固定的,而每個(gè)步奏采用的策略是不固定的。機(jī)制是穩(wěn)定的,而策略則是靈活的,因此,在 Linux內(nèi)核中,不應(yīng)該實(shí)現(xiàn)策略。Richard Gooch認(rèn)為,屬于策略的東西應(yīng)該被移到用戶空間。這就是為什么devfs位于內(nèi)核空間,而udev確要移到用戶空間的原因。

下面舉一個(gè)通俗的例子來(lái)理解udev設(shè)計(jì)的出發(fā)點(diǎn)。以談戀愛(ài)為例,Greg Kroah-Hartman認(rèn)為,可以讓內(nèi)核提供談戀愛(ài)的機(jī)制,但是不能在內(nèi)核空間限制跟誰(shuí)談戀愛(ài),不能把談戀愛(ài)的策略放在內(nèi)核空間。因?yàn)閼賽?ài)是自由的,用戶應(yīng)該可以在用戶空間中實(shí)現(xiàn)“蘿卜白菜,各有所愛(ài)”的理想,可以根據(jù)對(duì)方的外貌、籍貫、性格等自由選擇。對(duì)應(yīng)devfs而言,第1個(gè)相親的女孩被命名為/dev/girl0,第2個(gè)相親的女孩被命名為/dev/girl1,依此類推。而在用戶空間實(shí)現(xiàn)的udev則可以使得用戶實(shí)現(xiàn)這樣的自由:不管你中意的女孩第幾個(gè)來(lái),只要它與你定義的規(guī)則符合,都命名為/dev/mygirl!

udev 完全在用戶態(tài)工作,利用設(shè)備加入或移除時(shí)內(nèi)核所發(fā)送的熱插拔事件(hotplug event)來(lái)工作。在熱插拔時(shí),設(shè)備的詳細(xì)信息會(huì)由內(nèi)核輸出到位于/sys的sysfs文件系統(tǒng)。udev的設(shè)備命名策略、權(quán)限控制和事件處理都是在用戶態(tài)下完成的,它利用sysfs中的信息來(lái)進(jìn)行創(chuàng)建設(shè)備文件節(jié)點(diǎn)等工作。熱插拔時(shí)輸出到sysfs中的設(shè)備的詳細(xì)信息就是相親對(duì)象的資料(外貌、年齡、性格、籍貫等),設(shè)備命名策略等就是擇偶標(biāo)準(zhǔn)。devfs 是個(gè)蹩腳的婚姻介紹所,它直接指定了誰(shuí)和誰(shuí)談戀愛(ài),而udev則聰明地多,它只是把資料交給客戶,讓客戶根據(jù)這些資料去選擇和誰(shuí)談戀愛(ài)。

由于udev根據(jù)系統(tǒng)中硬件設(shè)備的狀態(tài)動(dòng)態(tài)更新設(shè)備文件,進(jìn)行設(shè)備文件的創(chuàng)建和刪除等,因此,在使用udev后,/dev目錄下就會(huì)只包含系統(tǒng)中真正存在的設(shè)備了。

devfs與udev的另一個(gè)顯著區(qū)別在于:采用devfs,當(dāng)一個(gè)并不存在的/dev節(jié)點(diǎn)被打開(kāi)的時(shí)候,devfs能自動(dòng)加載對(duì)應(yīng)的驅(qū)動(dòng),而udev則不這么做。這是因?yàn)閡dev的設(shè)計(jì)者認(rèn)為L(zhǎng)inux應(yīng)該在設(shè)備被發(fā)現(xiàn)的時(shí)候加載驅(qū)動(dòng)模塊,而不是當(dāng)它被訪問(wèn)的時(shí)候。udev 的設(shè)計(jì)者認(rèn)為 devfs 所提供的打開(kāi)/dev節(jié)點(diǎn)時(shí)自動(dòng)加載驅(qū)動(dòng)的功能對(duì)于一個(gè)配置正確的計(jì)算機(jī)是多余的。系統(tǒng)中所有的設(shè)備都應(yīng)該產(chǎn)生熱插拔事件并加載恰當(dāng)?shù)尿?qū)動(dòng),而udev能注意到這點(diǎn)并且為它創(chuàng)建對(duì)應(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í)視圖,與提供進(jìn)程和狀態(tài)信息的proc文件系統(tǒng)十分類似。

sysfs把連接在系統(tǒng)上的設(shè)備和總線組織成為一個(gè)分級(jí)的文件,它們可以由用戶空間存取,向用戶空間導(dǎo)出內(nèi)核數(shù)據(jù)結(jié)構(gòu)以及它們的屬性。sysfs的一個(gè)目的就是展示設(shè)備驅(qū)動(dòng)模型中各組件的層次關(guān)系,其頂級(jí)目錄包括block、device、bus、drivers、class、power和firmware。

block 目錄包含所有的塊設(shè)備;devices 目錄包含系統(tǒng)所有的設(shè)備,并根據(jù)設(shè)備掛接的總線類型組織成層次結(jié)構(gòu);bus 目錄包含系統(tǒng)中所有的總線類型;drivers 目錄包括內(nèi)核中所有已注冊(cè)的設(shè)備驅(qū)動(dòng)程序;class目錄包含系統(tǒng)中的設(shè)備類型(如網(wǎng)卡設(shè)備、聲卡設(shè)備、輸入設(shè)備等)。在/sys目錄運(yùn)行tree會(huì)得到一個(gè)相當(dāng)長(zhǎng)的樹(shù)型目錄,下面摘取一部分:

        |-- 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等子目錄下,又會(huì)再分出drivers和devices目錄,而devices目錄中的文件是對(duì)/sys/devices目錄中文件的符號(hào)鏈接。同樣地,/sys/class目錄下也包含許多對(duì)/sys/devices下文件的鏈接。如圖5.2所示,這與設(shè)備、驅(qū)動(dòng)、總線和類的現(xiàn)實(shí)狀況是直接對(duì)應(yīng)的,也正符合Linux2.6的設(shè)備模型。

圖5.2 Linux設(shè)備模型

隨著技術(shù)的不斷進(jìn)步,系統(tǒng)的拓?fù)浣Y(jié)構(gòu)越來(lái)越復(fù)雜,對(duì)智能電源管理、熱插拔以及即插即用的支持要求也越來(lái)越高,Linux2.4內(nèi)核已經(jīng)難以滿足這些需求。為適應(yīng)這種形勢(shì)的需要,Linux2.6內(nèi)核開(kāi)發(fā)了上述全新的設(shè)備、總線、類和驅(qū)動(dòng)環(huán)環(huán)相扣的設(shè)備模型。圖5.3形象地表示了 Linux驅(qū)動(dòng)模型中設(shè)備、總線和類之間的關(guān)系。

大多數(shù)情況下,Linux2.6內(nèi)核中的設(shè)備模型代碼會(huì)作為“幕后黑手”處理好這些關(guān)系,內(nèi)核中的總線級(jí)和其他內(nèi)核子系統(tǒng)會(huì)完成與設(shè)備模型的交互,這使得驅(qū)動(dòng)工程師幾乎不需要關(guān)心設(shè)備模型。

在Linux內(nèi)核中,分別使用bus_type、device_driver和device來(lái)描述總線、驅(qū)動(dòng)和設(shè)備,這3個(gè)結(jié)構(gòu)體定義于include/linux/device.h頭文件中,其定義如代碼清單5.6所示。

圖5.3 Linux驅(qū)動(dòng)模型中設(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ū)動(dòng) */
        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ū)動(dòng)和設(shè)備,而這兩者都必須依附于一種總線,因此都包含struct bus_type指針。在Linux內(nèi)核中,設(shè)備和驅(qū)動(dòng)是分開(kāi)注冊(cè)的,注冊(cè)1個(gè)設(shè)備的時(shí)候,并不需要驅(qū)動(dòng)已經(jīng)存在,而1個(gè)驅(qū)動(dòng)被注冊(cè)的時(shí)候,也不需要對(duì)應(yīng)的設(shè)備已經(jīng)被注冊(cè)。設(shè)備和驅(qū)動(dòng)各自涌向內(nèi)核,而每個(gè)設(shè)備和驅(qū)動(dòng)涌入的時(shí)候,都會(huì)去尋找自己的另一半。茫茫人海,何處覓蹤?正是bus_type的match()成員函數(shù)將兩者捆綁在一起。簡(jiǎn)單地說(shuō),設(shè)備和驅(qū)動(dòng)就是紅塵中漂浮的男女,而bus_type的match()則是牽引紅線的月老,它可以識(shí)別什么設(shè)備與什么驅(qū)動(dòng)可以配對(duì)。

注意,總線、驅(qū)動(dòng)和設(shè)備都最終會(huì)落實(shí)為sysfs中的1個(gè)目錄,因?yàn)檫M(jìn)一步追蹤代碼會(huì)發(fā)現(xiàn),它們實(shí)際上都可以認(rèn)為是kobject的派生類(device結(jié)構(gòu)體直接包含了kobject kobj成員,而bus_type和device_driver則透過(guò)bus_type_private、driver_private間接包含kobject),kobject可看作所有總線、設(shè)備和驅(qū)動(dòng)的抽象基類,1個(gè)kobject對(duì)應(yīng)sysfs中的1個(gè)目錄。

總線、設(shè)備和驅(qū)動(dòng)中的各個(gè)attribute則直接落實(shí)為sysfs中的1個(gè)文件,attribute會(huì)伴隨著show()和 store()這兩個(gè)函數(shù),分別用于讀和寫該 attribute 對(duì)應(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ī)則中各信息的來(lái)源實(shí)際上就是bus_type、device_driver、device以及attribute等所對(duì)應(yīng)sysfs節(jié)點(diǎn)。

5.4.3 udev的組成

udev的主頁(yè)位于: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)動(dòng)態(tài)建立/刪除設(shè)備文件。

(2)允許每個(gè)人都不用關(guān)心主/次設(shè)備號(hào)。

(3)提供LSB標(biāo)準(zhǔn)名稱。

(4)如果需要,可提供固定的名稱。

為了提供這些功能,udev以3個(gè)分割的子計(jì)劃發(fā)展:namedev、libsysfs和udev。namedev為設(shè)備命名子系統(tǒng),libsysfs提供訪問(wèn)sysfs文件系統(tǒng)從中獲取信息的標(biāo)準(zhǔn)接口,udev提供/dev設(shè)備節(jié)點(diǎn)文件的動(dòng)態(tài)創(chuàng)建和刪除策略。udev 程序背負(fù)與 namedev和libsysfs 庫(kù)交互的任務(wù),當(dāng)/sbin/hotplug程序被內(nèi)核調(diào)用時(shí),udev將被運(yùn)行。udev的工作過(guò)程如下。

(1)當(dāng)內(nèi)核檢測(cè)到在系統(tǒng)中出現(xiàn)了新設(shè)備后,內(nèi)核會(huì)在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è)備號(hào),并用分析獲得的設(shè)備名稱和主/次設(shè)備號(hào)創(chuàng)建/dev中的設(shè)備文件;如果是設(shè)備移除,則之前已經(jīng)被創(chuàng)建的/dev文件將被刪除。

在namedev中使用5步序列來(lái)決定指定設(shè)備的命名。

(1)標(biāo)簽(label)/序號(hào)(serial):這一步檢查設(shè)備是否有惟一的識(shí)別記號(hào),例如USB設(shè)備有惟一的USB序號(hào),SCSI 有惟一的UUID。如果namedev找到與這種惟一編號(hào)相對(duì)應(yīng)的規(guī)則,它將使用該規(guī)則提供的名稱。

(2)設(shè)備總線號(hào):這一步會(huì)檢查總線設(shè)備編號(hào),對(duì)于不可熱插拔的環(huán)境,這一步足以辨別設(shè)備。例如,PCI總線編號(hào)在系統(tǒng)的使用期間內(nèi)很少變更。如果namedev找到相對(duì)應(yīng)的規(guī)則,規(guī)則中的名稱就會(huì)被使用。

(3)總線上的拓?fù)洌寒?dāng)設(shè)備在總線上的位置匹配用戶指定的規(guī)則時(shí),就會(huì)使用該規(guī)則指定的名稱。

(4)替換名稱:當(dāng)內(nèi)核提供的名稱匹配指定的替代字符串時(shí),就會(huì)使用替代字符串指定的名稱。(5)內(nèi)核提供的名稱:這一步“包羅萬(wàn)象”,如果以前的幾個(gè)步驟都沒(méi)有被提供,默認(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ī)則文件以行為單位,以“#”開(kāi)頭的行代表注釋行。其余的每一行代表一個(gè)規(guī)則。每個(gè)規(guī)則分成一個(gè)或多個(gè)匹配和賦值部分。匹配部分用匹配專用的關(guān)鍵字來(lái)表示,相應(yīng)的賦值部分用賦值專用的關(guān)鍵字來(lái)表示。匹配關(guān)鍵字包括:ACTION(行為)、KERNEL(匹配內(nèi)核設(shè)備名)、BUS(匹配總線類型)、SYSFS(匹配從 sysfs 得到的信息,比如 label、vendor、USB 序列號(hào))、SUBSYSTEM(匹配子系統(tǒng)名)等,賦值關(guān)鍵字包括:NAME(創(chuàng)建的設(shè)備文件名)、SYMLINK (符號(hào)創(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)對(duì)該硬件采取的動(dòng)作是加入這個(gè)硬件,且這個(gè)硬件在 sysfs 文件系統(tǒng)中的“address”信息等于“00:0d:87:f6:59:f3”時(shí),對(duì)這個(gè)硬件在udev層次施行的動(dòng)作是調(diào)用外部程序/sbin/rename_netiface,并傳遞給該程序兩個(gè)參數(shù),一個(gè)是“%k”,代表內(nèi)核對(duì)該新設(shè)備定義的名稱,另一個(gè)是“eth0”。

通過(guò)一個(gè)簡(jiǎn)單的例子可以看出udev和devfs在命名方面的差異。如果系統(tǒng)中有兩個(gè)USB打印機(jī),一個(gè)可能被稱為/dev/usb/lp0,另外一個(gè)便是/dev/usb/lp1。但是到底哪個(gè)文件對(duì)應(yīng)哪個(gè)打印機(jī)是無(wú)法確定的,lp0、lp1和實(shí)際的設(shè)備沒(méi)有一一對(duì)應(yīng)的關(guān)系,映射關(guān)系會(huì)因?yàn)樵O(shè)備發(fā)現(xiàn)的順序,打印機(jī)本身關(guān)閉等原因而不確定。因此,理想的方式是兩個(gè)打印機(jī)應(yīng)該采用基于它們的序列號(hào)或者其他標(biāo)識(shí)信息的辦法來(lái)進(jìn)行確定的映射,devfs 無(wú)法做到這一點(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)一臺(tái)USB打印機(jī)的序列號(hào)為“HXOLL0012202323480”時(shí),創(chuàng)建/dev/lp_epson文件,并同時(shí)創(chuàng)建一個(gè)符號(hào)鏈接/dev/printers/epson_styles。序列號(hào)為“HXOLL0012202323480”的USB 打印機(jī)不管何時(shí)被插入,對(duì)應(yīng)的設(shè)備名都是/dev/lp_epson,而devfs顯然無(wú)法實(shí)現(xiàn)設(shè)備的這種固定命名。

udev規(guī)則的寫法非常靈活,在匹配部分,可以通過(guò)“*”、“?”、[a~c]、[1~9]等shell通配符來(lái)靈活匹配多個(gè)項(xiàng)目。*類似于shell中的*通配符,代替任意長(zhǎng)度的任意字符串,?代替一個(gè)字符, [x~y]是訪問(wèn)定義。此外,%k就是KERNEL,%n則是設(shè)備的KERNEL序號(hào)(如存儲(chǔ)設(shè)備的分區(qū)號(hào))。

可以借助 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 的輕量級(jí)版本 mdev,mdev 集成于 busybox(本書(shū)配套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è)備號(hào),則mdev就利用這些信息為該設(shè)備在/dev下創(chuàng)建設(shè)備節(jié)點(diǎn)文件。

“echo /sbin/mdev > /proc/sys/kernel/hotplug”的含義是當(dāng)有熱插拔事件產(chǎn)生時(shí),內(nèi)核就會(huì)調(diào)用位于 /sbin目錄的mdev。這時(shí)mdev通過(guò)環(huán)境變量中的ACTION和DEVPATH,來(lái)確定此次熱插拔事件的動(dòng)作以及影響了/sys中的那個(gè)目錄。接著會(huì)看看這個(gè)目錄中是否有“dev”的屬性文件,如果有就利用這些信息為這個(gè)設(shè)備在/dev下創(chuàng)建設(shè)備節(jié)點(diǎn)文件。

若要修改mdev的規(guī)則,可通過(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ī)上通過(guò)fdisk給一張空的SD卡分為2個(gè)區(qū)(如果SD卡中本身已經(jīng)包含,請(qǐng)通過(guò)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):

我們還要通過(guò)“a”命令標(biāo)記第1個(gè)分區(qū):

        Command (m for help): a
        Partition number (1-4): 1

最后要通過(guò)“w”命令把建好的分區(qū)表寫入SD卡。

(2)格式化SD卡的分區(qū)1和分區(qū)2:

        mkfs.vfat /dev/sdb1
        mkfs.ext3 /dev/sdb2
        fsck.ext3 /dev/sdb2

(3)如圖5-5所示,通過(guò)moviNAND_Fusing_Tool.exe燒寫SD卡U-BOOT和zImage。

圖5.5 燒寫LDD6410的SD卡U - BOOT和zImage

更新SD卡根文件系統(tǒng)的方法很簡(jiǎn)單,在PC機(jī)上mount /dev/sdb2后,直接通過(guò)

        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)通過(guò) 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)通過(guò) 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)啟動(dò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)行過(guò)程中設(shè)備結(jié)點(diǎn)不存在,可先執(zhí)行:

        # mdev -s

啟動(dòng)到LDD6410,在根目錄下運(yùn)行l(wèi)s,會(huì)發(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&”可啟動(dòng) Android,運(yùn)行“/qtopia &”可啟動(dòng)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用戶空間的文件編程有兩種方法,即通過(guò)Linux API和通過(guò)C庫(kù)函數(shù)訪問(wèn)文件。用戶空間看不到設(shè)備驅(qū)動(dòng),能看到的只有設(shè)備對(duì)應(yīng)的文件,因此文件編程即是用戶空間的設(shè)備編程。

Linux按照功能對(duì)文件系統(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通過(guò)一系列數(shù)據(jù)結(jié)構(gòu)定義了設(shè)備模型,設(shè)備模型與sysfs文件系統(tǒng)中的目錄和文件存在一種對(duì)應(yīng)關(guān)系,udev 可以利用 sysfs 中記錄的信息定義規(guī)則并提取主次設(shè)備號(hào)動(dòng)態(tài)創(chuàng)建/dev設(shè)備文件節(jié)點(diǎn)。

主站蜘蛛池模板: 岱山县| 榆社县| 江达县| 五大连池市| 黄龙县| 石城县| 永兴县| 宁陕县| 奉节县| 平南县| 东明县| 澄迈县| 林州市| 上饶县| 霍山县| 霍林郭勒市| 二手房| 清涧县| 金川县| 修文县| 靖安县| 和顺县| 惠来县| 南丰县| 房产| 玉溪市| 山东省| 民丰县| 平度市| 永胜县| 庆元县| 饶阳县| 大同市| 望城县| 南溪县| 葵青区| 磐安县| 澄城县| 松潘县| 尖扎县| 瑞金市|