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

第5章 Linux文件系統與設備文件系統

本章導讀

由于字符設備和塊設備都良好地體現了“一切都是文件”的設計思想,掌握Linux文件系統、設備文件系統的知識就顯得相當重要了。

首先,驅動工程師編寫的驅動最終通過操作系統的文件操作系統調用或C庫函數(本質也基于系統調用)被訪問,而設備驅動的結構最終也是為了迎合提供給應用程序員的API。

其次,驅動工程師在設備驅動中不可避免地會與設備文件系統打交道,從Linux2.4內核的devfs文件系統到目前Linux2.6基于sysfs的udev文件系統。

5.1節講解了通過Linux API和C庫函數在用戶空間進行Linux文件操作的編程方法。

5.2節分析了Linux文件系統的目錄結構,簡單介紹了Linux內核中文件系統的實現,并給出了文件系統與設備驅動的關系。

5.3節和5.4節分別講解Linux2.4內核的devfs和Linux2.6所采用的udev設備文件系統,并分析了兩者的區別。

5.5節講解了LDD6410的SD卡和NAND分區和文件系統的使用情況。

5.1 Linux文件操作

5.1.1 文件操作系統調用

Linux的文件操作系統調用(在Windows編程領域,習慣稱操作系統提供的接口為API)涉及創建、打開、讀寫和關閉文件。

1.創建

        int creat(const char *filename, mode_t mode);

參數mode指定新建文件的存取權限,它同umask一起決定文件的最終權限(mode&umask),其中umask代表了文件在創建時需要去掉的一些存取權限。umask可通過系統調用umask()來改變:

        int umask(int newmask);

該調用將umask設置為newmask,然后返回舊的umask,它只影響讀、寫和執行權限。

2.打開

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

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

表5.1 文件打開標志

O_RDONLY、O_WRONLY、O_RDWR三個標志只能使用任意的一個。

如果使用了O_CREATE標志,則使用的函數是int open(const char *pathname,int flags,mode_t mode); 這個時候我們還要指定mode標志,用來表示文件的訪問權限。mode可以是如表5.2所列值的組合。

表5.2 文件訪問權限

除了可以通過上述宏進行“或”邏輯產生標志以外,我們也可以自己用數字來表示,Linux用5個數字來表示文件的各種權限:第一位表示設置用戶ID;第二位表示設置組ID;第三位表示用戶自己的權限位;第四位表示組的權限;最后一位表示其他人的權限。每個數字可以取1(執行權限)、2(寫權限)、4(讀權限)、0(無)或者是這些值的和。例如,要創建一個用戶可讀、可寫、可執行,但是組沒有權限,其他人可以讀、可以執行的文件,并設置用戶 ID 位。那么,我們應該使用的模式是1(設置用戶ID)、0(不設置組ID)、7(1+2+4,讀、寫、執行)、0(沒有權限)、5(1+4,讀、執行)即10705:

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

上述語句等價于:

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

如果文件打開成功,open函數會返回一個文件描述符,以后對該文件的所有操作就可以通過對這個文件描述符進行操作來實現。

3.讀寫

在文件打開以后,我們才可對文件進行讀寫,Linux中提供文件讀寫的系統調用是read、write函數:

        int read(int fd, const void *buf, size_t length);
        int write(int fd, const void *buf, size_t length);

其中參數buf為指向緩沖區的指針,length為緩沖區的大小(以字節為單位)。函數read()實現從文件描述符fd所指定的文件中讀取length個字節到buf所指向的緩沖區中,返回值為實際讀取的字節數。函數write實現將把length個字節從buf指向的緩沖區中寫到文件描述符fd所指向的文件中,返回值為實際寫入的字節數。

以O_CREAT為標志的open實際上實現了文件創建的功能,因此,下面的函數等同creat()函數:

        int open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);

4.定位

對于隨機文件,我們可以隨機地指定位置讀寫,使用如下函數進行定位:

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

lseek()將文件讀寫指針相對whence移動offset個字節。操作成功時,返回文件指針相對于文件頭的位置。參數whence可使用下述值:

SEEK_SET:相對文件開頭

SEEK_CUR:相對文件讀寫指針的當前位置

SEEK_END:相對文件末尾

offset可取負值,例如下述調用可將文件指針相對當前位置向前移動5個字節:

        lseek(fd, -5, SEEK_CUR);

由于 lseek 函數的返回值為文件指針相對于文件頭的位置,因此下列調用的返回值就是文件的長度:

        lseek(fd, 0, SEEK_END);

5.關閉

當我們操作完成以后,我們要關閉文件了,只要調用close就可以了,其中fd是我們要關閉的文件描述符:

        int close(int fd);

例程:編寫一個程序,在當前目錄下創建用戶可讀寫文件hello.txt,在其中寫入“Hello, software weekly”,關閉該文件。再次打開該文件,讀取其中的內容并輸出在屏幕上。

解答如代碼清單5.1。

代碼清單5.1 Linux文件操作用戶空間編程(使用系統調用)

        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    創建并打開文件 */
        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); /* 讀取文件內容 */
        21    str[len] = '\0';
        22    printf("%s\n", str);
        23    close(fd);
        24  }

編譯并運行,執行結果為輸出“Hello World”。

5.1.2 C庫文件操作

C庫函數的文件操作實際上是獨立于具體的操作系統平臺的,不管是在DOS、Windows、Linux還是在VxWorks中都是這些函數:

1.創建和打開

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

fopen()實現打開指定文件filename,其中的mode為打開模式,C庫函數中支持的打開模式如表5.3所示。

表5.3 C庫函數文件打開標志

其中 b 用于區分二進制文件和文本文件,這一點在 DOS、Windows 系統中是有區分的,但Linux不區分二進制文件和文本文件。

2.讀寫

C庫函數支持以字符、字符串等為單位,支持按照某種格式進行文件的讀寫,這一組函數為:

        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()實現從流stream中讀取加n個字段,每個字段為size字節,并將讀取的字段放入ptr所指的字符數組中,返回實際已讀取的字段數。在讀取的字段數小于num時,可能是在函數調用時出現錯誤,也可能是讀到文件的結尾。所以要通過調用feof()和ferror()來判斷。

write()實現從緩沖區ptr所指的數組中把n個字段寫到流stream中,每個字段長為size個字節,返回實際寫入的字段數。

另外,C庫函數還提供了讀寫過程中的定位能力,這些函數包括:

        int fgetpos(FILE *stream, fpos_t *pos);
        int fsetpos(FILE *stream, const fpos_t *pos);
        int fseek(FILE *stream, long offset, int whence);

3.關閉

利用C庫函數關閉文件依然是很簡單的操作:

        int fclose (FILE *stream);

例程:將第5.1.1節中的例程用C庫函數來實現,如代碼清單5-2所示。

代碼清單5.2 Linux文件操作用戶空間編程(使用C庫函數)

        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+"); /* 創建并打開文件 */
        9     if (fd) {
        10        fputs("Hello World", fd); /* 寫入字符串 */
        11        fclose(fd);
        12    }
        13
        14    fd = fopen("hello.txt", "r");
        15    fgets(str, LENGTH, fd); /* 讀取文件內容 */
        16    printf("%s\n", str);
        17    fclose(fd);
        18  }

5.2 Linux文件系統

5.2.1 Linux文件系統目錄結構

進入Linux根目錄(即“/”,Linux文件系統的入口,也是處于最高一級的目錄),運行“ls -l”命令,看到Linux包含以下目錄。

1./bin

包含基本命令,如 ls、cp、mkdir等,這個目錄中的文件都是可執行的。

2./sbin

包含系統命令,如modprobe、hwclock、ifconfig等,大多是涉及系統管理的命令,這個目錄中的文件都是可執行的。

3./dev

設備文件存儲目錄,應用程序通過對這些文件的讀寫和控制就可以訪問實際的設備。

4./etc

系統配置文件的所在地,一些服務器的配置文件也在這里,如用戶賬號及密碼配置文件。busybox的啟動腳本也存放在該目錄。

5./lib

系統庫文件存放目錄,如LDD6410包含libc-2.6.1.so、libpthread-2.6.1.so、libthread_db-1.0.so等。

6./mnt

/mnt這個目錄一般是用于存放掛載儲存設備的掛載目錄的,比如有cdrom 等目錄。可以參看/etc/fstab 的定義。有時我們可以把讓系統開機自動掛載文件系統,把掛載點放在這里也是可以的。

7./opt

opt 是“可選”的意思,有些軟件包會被安裝在這里,例如,在LDD6410的文件系統中, Qt/Embedded就存放在該目錄。

8./proc

操作系統運行時,進程及內核信息(比如CPU、硬盤分區、內存信息等)存放在這里。/proc目錄為偽文件系統proc的掛載目錄,proc并不是真正的文件系統,它存在于內存之中。

9./tmp

有時用戶運行程序的時候,會產生臨時文件,/tmp就用來存放臨時文件的。

10./usr

這個是系統存放程序的目錄,比如用戶命令、用戶庫等。LDD6410的usr包括bin、sbin、lib三個子目錄。usr/bin中包含diff、which、who、rx、cmp等,usr/sbin中包含chroot、flash_eraseall、inetd等,usr/lib中包含libjpeg.so.62.0.0等。

11./var

var 表示的是變化的意思,這個目錄的內容經常變動,如/var的/var/log 目錄被用來存放系統日志。

12./sys

Linux2.6內核所支持的sysfs文件系統被映射在此目錄。Linux設備驅動模型中的總線、驅動和設備都可以在 sysfs 文件系統中找到對應的節點。當內核檢測到在系統中出現了新設備后,內核會在sysfs文件系統中為該新設備生成一項新的記錄。

5.2.2 Linux文件系統與設備驅動

圖5.1所示為Linux中虛擬文件系統、磁盤文件(存放于Ramdisk、Flash、ROM、SD卡、U盤等文件系統中的文件也屬于此列)及一般的設備文件與設備驅動程序之間的關系。

圖5.1 文件系統與設備驅動

應用程序和VFS之間的接口是系統調用,而VFS與磁盤文件系統以及普通設備之間的接口是 file_operations 結構體成員函數,這個結構體包含對文件進行打開、關閉、讀寫、控制的一系列成員函數。

由于字符設備的上層沒有磁盤文件系統,所以字符設備的file_operations 成員函數就直接由設備驅動提供了,在稍后的第6章,將會看到file_operations正是字符設備驅動的核心。

而對于塊存儲設備而言,ext2、fat、jffs2等文件系統中會實現針對VFS的file_operations成員函數,設備驅動層將看不到 file_operations 的存在。磁盤文件系統和設備驅動會將對磁盤上文件的訪問最終轉換成對磁盤上柱面和扇區的訪問。

在設備驅動程序的設計中,一般而言,會關心file和inode這兩個結構體。

1.file結構體

file結構體代表一個打開的文件(設備對應于設備文件),系統中每個打開的文件在內核空間都有一個關聯的struct file。它由內核在打開文件時創建,并傳遞給在文件上進行操作的任何函數。在文件的所有實例都關閉后,內核釋放這個數據結構。在內核和驅動源代碼中,struct file的指針通常被命名為file或filp(即file pointer)。代碼清單5.3給出了文件結構體的定義。

代碼清單5.3 文件結構體

        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; /*與文件關聯的目錄入口(dentry)結構*/
        8    struct vfsmount *f_vfsmnt;
        9    struct file_operations *f_op; /* 和文件關聯的操作*/
        10   atomic_t f_count;
        11   unsigned int f_flags;/*文件標志,如O_RDONLY、O_NONBLOCK、O_SYNC*/
        12   mode_t f_mode; /*文件讀/寫模式,FMODE_READ和FMODE_WRITE*/
        13   loff_t f_pos; /* 當前讀寫位置*/
        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驅動需要,其他的也許需要 */
        22   void *private_data; /*文件私有數據*/
        23   ...
        24   struct address_space *f_mapping;
        25 };

文件讀/寫模式 mode、標志 f_flags 都是設備驅動關心的內容,而私有數據指針 private_data在設備驅動中被廣泛應用,大多被指向設備驅動自定義用于描述設備的結構體。

驅動程序中經常會使用如下類似的代碼來檢測用戶打開文件的讀寫方式:

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

下面的代碼可用于判斷以阻塞還是非阻塞方式打開設備文件:

        if (file->f_flags & O_NONBLOCK)    /* 非阻塞 */
              pr_debug("open: non-blocking\n");
        else                                /* 阻塞 */
              pr_debug("open: blocking\n");

2.inode結構體

VFS inode 包含文件訪問權限、屬主、組、大小、生成時間、訪問時間、最后修改時間等信息。它是Linux管理文件系統的最基本單位,也是文件系統連接任何子目錄、文件的橋梁,inode結構體的定義如代碼清單5.4所示。

代碼清單5.4 inode結構體

        1  struct inode {
        2     ...
        3     umode_t i_mode; /* inode的權限 */
        4     uid_t i_uid; /* inode擁有者的id */
        5     gid_t i_gid; /* inode所屬的群組id */
        6     dev_t i_rdev; /* 若是設備文件,此字段將記錄設備的設備號 */
        7     loff_t i_size; /* inode所代表的文件大小 */
        8
        9     struct timespec i_atime; /* inode最近一次的存取時間 */
        10    struct timespec i_mtime; /* inode最近一次的修改時間 */
        11    struct timespec i_ctime; /* inode的產生時間 */
        12
        13    unsigned long i_blksize; /* inode在做I/O時的區塊大小 */
        14    unsigned long i_blocks; /* inode所使用的block數,一個block為512 byte*/
        15
        16    struct block_device *i_bdev;
        17            /*若是塊設備,為其對應的block_device結構體指針*/
        18    struct cdev *i_cdev;   /*若是字符設備,為其對應的cdev結構體指針*/
        19    ...
        20 };

對于表示設備文件的inode結構,i_rdev字段包含設備編號。Linux2.6設備編號分為主設備編號和次設備編號,前者為dev_t的高12位,后者為dev_t的低20位。下列操作用于從一個inode中獲得主設備號和次設備號:

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

查看/proc/devices文件可以獲知系統中注冊的設備,第1列為主設備號,第2列為設備名,如:

        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目錄可以獲知系統中包含的設備文件,日期的前兩列給出了對應設備的主設備號和次設備號:

        crw-rw----   1 root    uucp      4,  64 Jan 30  2003 /dev/ttyS0
        brw-rw----   1 root    disk      8,   0 Jan 30  2003 /dev/sda

主設備號是與驅動對應的概念,同一類設備一般使用相同的主設備號,不同類的設備一般使用不同的主設備號(但是也不排除在同一主設備號下包含有一定差異的設備)。因為同一驅動可支持多個同類設備,因此用次設備號來描述使用該驅動的設備的序號,序號一般從0開始。

內核Documents目錄下的devices.txt文件描述了Linux設備號的分配情況,它由LANANA(The Linux Assigned Names And Numbers Authority,網址:http://www.lanana.org/)組織維護,Torben Mathiasen(device@lanana.org)是其中的主要維護者。

5.3 devfs設備文件系統

devfs(設備文件系統)是由Linux2.4內核引入的,引入時被許多工程師給予了高度評價,它的出現使得設備驅動程序能自主地管理它自己的設備文件。具體來說,devfs具有如下優點。(1)可以通過程序在設備初始化時在/dev 目錄下創建設備文件,卸載設備時將它刪除。

(2)設備驅動程序可以指定設備名、所有者和權限位,用戶空間程序仍可以修改所有者和權限位。

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

驅動程序應調用下面這些函數來進行設備文件的創建和刪除工作。

        /*創建設備目錄*/
        devfs_handle_t devfs_mk_dir(devfs_handle_t dir, const char *name, void *info);
        /*創建設備文件*/
        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);
        /*撤銷設備文件*/
        void devfs_unregister(devfs_handle_t de);

在Linux2.4的設備驅動編程中,分別在模塊加載和卸載函數中創建和撤銷設備文件是被普遍采用并值得大力推薦的好方法。代碼清單5.5給出了一個使用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       /*在內核中注冊設備*/
        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      /*創建設備文件*/
        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); /*撤銷設備文件*/
        23      unregister_chrdev(XXX_MAJOR, DEVICE_NAME); /*注銷設備*/
        24  }
        25
        26  module_init(xxx_init);
        27  module_exit(xxx_exit);

代碼中第7行和第23行分別用于注冊和注銷字符設備,使用的register_chrdev()和unregister_chrdev()在Linux2.6內核中雖然仍然被支持,但這是過時的做法。第13和22行分別用于創建和刪除devfs文件節點。

5.4 udev設備文件系統

5.4.1 udev與devfs的區別

盡管devfs有這樣和那樣的優點,但是,在Linux2.6內核中,devfs被認為是過時的方法,并最終被拋棄,udev取代了它。Linux VFS內核維護者Al Viro指出了幾點udev取代devfs的原因:(1)devfs所做的工作被確信可以在用戶態來完成。

(2)devfs被加入內核之時,大家寄望它的質量可以迎頭趕上。

(3)devfs被發現了一些可修復和無法修復的bug。

(4)對于可修復的bug,幾個月前就已經被修復了,其維護者認為一切良好。

(5)對于后者,同樣是相當長一段時間以來沒有改觀了。

(6)devfs 的維護者和作者對它感到失望并且已經停止了對代碼的維護工作。

Linux內核的兩位貢獻者,Richard Gooch(devfs的作者)和Greg Kroah-Hartman(sysfs的主要作者)就devfs/udev進行了激烈的爭論:

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已經指出,udev是DevFS恰當的替代品。

Richard:哦,是哪個Richard說的?我怎么不知道。

Greg:DevFS應該下課,因為策略應該位于用戶空間而不是內核空間。

Richard:哦,我聽說,相當大部分由Greg完成的sysfs也在內核中實現了策略。

Greg:devfs很蹩腳,也不穩定。

Richard:呵呵,沒證據,別那么武斷……

在Richard Gooch和Greg Kroah-Hartman的爭論中,Greg Kroah-Hartman使用的理論依據就在于policy(策略)不能位于內核空間。Linux設計中強調的一個基本觀點是機制和策略的分離。機制是做某樣事情的固定的步奏、方法,而策略就是每一個步奏所采取的不同方式。機制是相對固定的,而每個步奏采用的策略是不固定的。機制是穩定的,而策略則是靈活的,因此,在 Linux內核中,不應該實現策略。Richard Gooch認為,屬于策略的東西應該被移到用戶空間。這就是為什么devfs位于內核空間,而udev確要移到用戶空間的原因。

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

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

由于udev根據系統中硬件設備的狀態動態更新設備文件,進行設備文件的創建和刪除等,因此,在使用udev后,/dev目錄下就會只包含系統中真正存在的設備了。

devfs與udev的另一個顯著區別在于:采用devfs,當一個并不存在的/dev節點被打開的時候,devfs能自動加載對應的驅動,而udev則不這么做。這是因為udev的設計者認為Linux應該在設備被發現的時候加載驅動模塊,而不是當它被訪問的時候。udev 的設計者認為 devfs 所提供的打開/dev節點時自動加載驅動的功能對于一個配置正確的計算機是多余的。系統中所有的設備都應該產生熱插拔事件并加載恰當的驅動,而udev能注意到這點并且為它創建對應的設備節點。

5.4.2 sysfs文件系統與Linux設備模型

Linux2.6的內核引入了sysfs文件系統,sysfs被看成是與proc、devfs和devpty同類別的文件系統,該文件系統是一個虛擬的文件系統,它可以產生一個包括所有系統硬件的層級視圖,與提供進程和狀態信息的proc文件系統十分類似。

sysfs把連接在系統上的設備和總線組織成為一個分級的文件,它們可以由用戶空間存取,向用戶空間導出內核數據結構以及它們的屬性。sysfs的一個目的就是展示設備驅動模型中各組件的層次關系,其頂級目錄包括block、device、bus、drivers、class、power和firmware。

block 目錄包含所有的塊設備;devices 目錄包含系統所有的設備,并根據設備掛接的總線類型組織成層次結構;bus 目錄包含系統中所有的總線類型;drivers 目錄包括內核中所有已注冊的設備驅動程序;class目錄包含系統中的設備類型(如網卡設備、聲卡設備、輸入設備等)。在/sys目錄運行tree會得到一個相當長的樹型目錄,下面摘取一部分:

        |-- 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所示,這與設備、驅動、總線和類的現實狀況是直接對應的,也正符合Linux2.6的設備模型。

圖5.2 Linux設備模型

隨著技術的不斷進步,系統的拓撲結構越來越復雜,對智能電源管理、熱插拔以及即插即用的支持要求也越來越高,Linux2.4內核已經難以滿足這些需求。為適應這種形勢的需要,Linux2.6內核開發了上述全新的設備、總線、類和驅動環環相扣的設備模型。圖5.3形象地表示了 Linux驅動模型中設備、總線和類之間的關系。

大多數情況下,Linux2.6內核中的設備模型代碼會作為“幕后黑手”處理好這些關系,內核中的總線級和其他內核子系統會完成與設備模型的交互,這使得驅動工程師幾乎不需要關心設備模型。

在Linux內核中,分別使用bus_type、device_driver和device來描述總線、驅動和設備,這3個結構體定義于include/linux/device.h頭文件中,其定義如代碼清單5.6所示。

圖5.3 Linux驅動模型中設備、總線和類的關系

代碼清單5.6 bus_type、device_driver和device結構體

        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; /* 設備的初始名 */
        52  struct device_type    *type;
        53  unsigned     uevent_suppress:1;
        54
        55  struct semaphore  sem;
        56
        57  struct bus_type   *bus;         /* 設備所在的總線類型 */
        58  struct device_driver *driver;  /* 設備用到的驅動 */
        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, 創建sysfs "dev" */
        83  struct attribute_group**groups;
        84
        85  void(*release)(struct device *dev);
        86 };

device_driver和device分別表示驅動和設備,而這兩者都必須依附于一種總線,因此都包含struct bus_type指針。在Linux內核中,設備和驅動是分開注冊的,注冊1個設備的時候,并不需要驅動已經存在,而1個驅動被注冊的時候,也不需要對應的設備已經被注冊。設備和驅動各自涌向內核,而每個設備和驅動涌入的時候,都會去尋找自己的另一半。茫茫人海,何處覓蹤?正是bus_type的match()成員函數將兩者捆綁在一起。簡單地說,設備和驅動就是紅塵中漂浮的男女,而bus_type的match()則是牽引紅線的月老,它可以識別什么設備與什么驅動可以配對。

注意,總線、驅動和設備都最終會落實為sysfs中的1個目錄,因為進一步追蹤代碼會發現,它們實際上都可以認為是kobject的派生類(device結構體直接包含了kobject kobj成員,而bus_type和device_driver則透過bus_type_private、driver_private間接包含kobject),kobject可看作所有總線、設備和驅動的抽象基類,1個kobject對應sysfs中的1個目錄。

總線、設備和驅動中的各個attribute則直接落實為sysfs中的1個文件,attribute會伴隨著show()和 store()這兩個函數,分別用于讀和寫該 attribute 對應的sysfs 文件結點,代碼清單5.7給出了attribute、bus_attribute、driver_attribute和device_attribute這幾個結構體的定義。

代碼清單5.7 attribute、bus attribute、driver attribute和device attribute結構體

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

事實上,udev規則中各信息的來源實際上就是bus_type、device_driver、device以及attribute等所對應sysfs節點。

5.4.3 udev的組成

udev的主頁位于:http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html,上面包含了關于 udev 的詳細介紹,從 http://www.us.kernel.org/pub/linux/utils/kernel/hotplug/上可以下載最新的udev包。udev的設計目標如下。

● 在用戶空間中執行。

(1)動態建立/刪除設備文件。

(2)允許每個人都不用關心主/次設備號。

(3)提供LSB標準名稱。

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

為了提供這些功能,udev以3個分割的子計劃發展:namedev、libsysfs和udev。namedev為設備命名子系統,libsysfs提供訪問sysfs文件系統從中獲取信息的標準接口,udev提供/dev設備節點文件的動態創建和刪除策略。udev 程序背負與 namedev和libsysfs 庫交互的任務,當/sbin/hotplug程序被內核調用時,udev將被運行。udev的工作過程如下。

(1)當內核檢測到在系統中出現了新設備后,內核會在sysfs文件系統中為該新設備生成新的記錄并導出一些設備特定的信息及所發生的事件。

(2)udev 獲取內核導出的信息,它調用 namedev 決定應該給該設備指定的名稱,如果是新插入設備,udev將調用libsysfs決定應該為該設備的設備文件指定的主/次設備號,并用分析獲得的設備名稱和主/次設備號創建/dev中的設備文件;如果是設備移除,則之前已經被創建的/dev文件將被刪除。

在namedev中使用5步序列來決定指定設備的命名。

(1)標簽(label)/序號(serial):這一步檢查設備是否有惟一的識別記號,例如USB設備有惟一的USB序號,SCSI 有惟一的UUID。如果namedev找到與這種惟一編號相對應的規則,它將使用該規則提供的名稱。

(2)設備總線號:這一步會檢查總線設備編號,對于不可熱插拔的環境,這一步足以辨別設備。例如,PCI總線編號在系統的使用期間內很少變更。如果namedev找到相對應的規則,規則中的名稱就會被使用。

(3)總線上的拓撲:當設備在總線上的位置匹配用戶指定的規則時,就會使用該規則指定的名稱。

(4)替換名稱:當內核提供的名稱匹配指定的替代字符串時,就會使用替代字符串指定的名稱。(5)內核提供的名稱:這一步“包羅萬象”,如果以前的幾個步驟都沒有被提供,默認的內核將被指定給該設備。

代碼清單5.8給出了一個namedev命名規則的例子,第2、4行定義的是符合第1步的規則,第6、8行定義的是符合第2步的規則,第11、14行定義的是符合第3步的規則,第16行定義的是符合第4步的規則。

代碼清單5.8 namedev命名規則

        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規則文件

udev 的規則文件以行為單位,以“#”開頭的行代表注釋行。其余的每一行代表一個規則。每個規則分成一個或多個匹配和賦值部分。匹配部分用匹配專用的關鍵字來表示,相應的賦值部分用賦值專用的關鍵字來表示。匹配關鍵字包括:ACTION(行為)、KERNEL(匹配內核設備名)、BUS(匹配總線類型)、SYSFS(匹配從 sysfs 得到的信息,比如 label、vendor、USB 序列號)、SUBSYSTEM(匹配子系統名)等,賦值關鍵字包括:NAME(創建的設備文件名)、SYMLINK (符號創建鏈接名)、OWNER(設置設備的所有者)、GROUP(設置設備的組)、IMPORT(調用外部程序)等。

例如,如下規則:

        SUBSYSTEM=="net", ACTION=="add", SYSFS{address}=="00:0d:87:f6:59:f3", IMPORT="/sbin/
    rename_netiface %k eth0"

其中的“匹配”部分有3項,分別是SUBSYSTEM、ACTION和SYSFS。而“賦值”部分有一項,是IMPORT。這個規則的意思是:當系統中出現的新硬件屬于 net 子系統范疇,系統對該硬件采取的動作是加入這個硬件,且這個硬件在 sysfs 文件系統中的“address”信息等于“00:0d:87:f6:59:f3”時,對這個硬件在udev層次施行的動作是調用外部程序/sbin/rename_netiface,并傳遞給該程序兩個參數,一個是“%k”,代表內核對該新設備定義的名稱,另一個是“eth0”。

通過一個簡單的例子可以看出udev和devfs在命名方面的差異。如果系統中有兩個USB打印機,一個可能被稱為/dev/usb/lp0,另外一個便是/dev/usb/lp1。但是到底哪個文件對應哪個打印機是無法確定的,lp0、lp1和實際的設備沒有一一對應的關系,映射關系會因為設備發現的順序,打印機本身關閉等原因而不確定。因此,理想的方式是兩個打印機應該采用基于它們的序列號或者其他標識信息的辦法來進行確定的映射,devfs 無法做到這一點,udev 卻可以做到。使用如下規則:

        BUS="usb", SYSFS{serial}="HXOLL0012202323480", NAME="lp_epson", SYMLINK="printers/
    epson_stylus"

該規則中的匹配項目有BUS和SYSFS,賦值項目為NAME和SYMLINK,它意味著當一臺USB打印機的序列號為“HXOLL0012202323480”時,創建/dev/lp_epson文件,并同時創建一個符號鏈接/dev/printers/epson_styles。序列號為“HXOLL0012202323480”的USB 打印機不管何時被插入,對應的設備名都是/dev/lp_epson,而devfs顯然無法實現設備的這種固定命名。

udev規則的寫法非常靈活,在匹配部分,可以通過“*”、“?”、[a~c]、[1~9]等shell通配符來靈活匹配多個項目。*類似于shell中的*通配符,代替任意長度的任意字符串,?代替一個字符, [x~y]是訪問定義。此外,%k就是KERNEL,%n則是設備的KERNEL序號(如存儲設備的分區號)。

可以借助 udev 中的udevinfo 工具查找規則文件可以利用的信息,如運行“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 創建和配置mdev

在嵌入式系統中,通常可以用 udev 的輕量級版本 mdev,mdev 集成于 busybox(本書配套VirtualBox 虛擬機/home/lihacker/develop/svn/ldd6410-read-only/utils/busybox-1.15.1 目錄)中。在busybox的源代碼目錄運行make menuconfig,進入“Linux System Utilities”子選項,選中mdev相關項目,如圖5-4所示。

圖5.4 busybox中的mdev選項

LDD6410根文件系統中/etc/init.d/rcS包含的如下內容即是為了使用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中所有的類設備目錄,如果在目錄中含有名為“dev”的文件,且文件中包含的是設備號,則mdev就利用這些信息為該設備在/dev下創建設備節點文件。

“echo /sbin/mdev > /proc/sys/kernel/hotplug”的含義是當有熱插拔事件產生時,內核就會調用位于 /sbin目錄的mdev。這時mdev通過環境變量中的ACTION和DEVPATH,來確定此次熱插拔事件的動作以及影響了/sys中的那個目錄。接著會看看這個目錄中是否有“dev”的屬性文件,如果有就利用這些信息為這個設備在/dev下創建設備節點文件。

若要修改mdev的規則,可通過修改/etc/mdev.conf文件實現。

5.5 LDD6410的SD和NAND文件系統

LDD6410的SD卡分為兩個區,其中的第2個分區為ext3文件系統,存放LDD6410的文件數據(5.2.1節給出的各個目錄),其制作方法如下。

(1)在安裝了Linux的PC機上通過fdisk給一張空的SD卡分為2個區(如果SD卡中本身已經包含,請通過fdisk的“d”命令全部刪除),得到如下的分區表:

        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個分區制作的命令為:

        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個分區制作的命令是:

        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”命令標記第1個分區:

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

最后要通過“w”命令把建好的分區表寫入SD卡。

(2)格式化SD卡的分區1和分區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卡根文件系統的方法很簡單,在PC機上mount /dev/sdb2后,直接通過

        cp -fa <your-rootfs> <sdb2-mount-point>

即可替換根文件系統了。<your-rootfs>是根文件系統的目錄,<sdb2-mount-point>是/dev/sdb2掛載的目錄。

特別要注意的是,SD的設備節點不一定是/dev/sdb,應該視用戶電腦的硬盤情況而言,可能是/dev/sdc、/dev/sdd 等。

LDD6410的NAND分為3個區,分別存放U-BOOT、zImage和文件系統。該分區表定義在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)運行:

        # flashcp u-boot-movi.bin /dev/mtd0

更新NAND中zImage的方法如下:

(1)通過 tftp或nfs 等方式獲取新的zImage,如:

        # tftp -r zImage-fix -g 192.168.1.111

(2)運行:

        # flashcp zImage-fix /dev/mtd1

更新NAND中文件系統的方法如下:

在PC上將做好的新的根文件系統拷貝到SD卡或NFS的某目錄,下面我們以<new_rootfs_dir>指代該目錄。

以SD卡或NFS為根文件系統啟動系統,運行如下命令擦除/dev/mtd2分區:

        # flash_eraseall /dev/mtd2

然后將NAND的該分區mount到/mnt:

        # mount /dev/mtdblock2 -t yaffs2 /mnt/

將新的文件系統拷貝到/mnt:

        # cp -fa <new_rootfs_dir> /mnt

若上述命令運行過程中設備結點不存在,可先執行:

        # mdev -s

啟動到LDD6410,在根目錄下運行ls,會發現LDD6410根文件系統包含如下子目錄:

        #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 的可執行文件。運行“/android&”可啟動 Android,運行“/qtopia &”可啟動Qt/Embedded。

/demo目錄下存放在一些用于demo的圖片、MP3等,如運行如下命令可顯示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 總結

Linux用戶空間的文件編程有兩種方法,即通過Linux API和通過C庫函數訪問文件。用戶空間看不到設備驅動,能看到的只有設備對應的文件,因此文件編程即是用戶空間的設備編程。

Linux按照功能對文件系統的目錄結構進行了良好的規劃。/dev是設備文件的存放目錄,devfs和udev分別是Linux2.4和Linux2.6生成設備文件節點的方法,前者運行于內核空間,后者運行于用戶空間。

Linux2.6通過一系列數據結構定義了設備模型,設備模型與sysfs文件系統中的目錄和文件存在一種對應關系,udev 可以利用 sysfs 中記錄的信息定義規則并提取主次設備號動態創建/dev設備文件節點。

主站蜘蛛池模板: 阜阳市| 清涧县| 将乐县| 延津县| 伽师县| 方正县| 綦江县| 鄂托克前旗| 屯昌县| 德令哈市| 宁强县| 新和县| 永川市| 黑水县| 夹江县| 延津县| 赤壁市| 盖州市| 罗甸县| 扶绥县| 溧阳市| 尉犁县| 宁化县| 都安| 开阳县| 石狮市| 边坝县| 舞钢市| 米泉市| 孟津县| 阳高县| 阿尔山市| 汝州市| 易门县| 航空| 修文县| 大港区| 江山市| 普宁市| 民勤县| 高安市|