首页 > 代码库 > 设备特殊文件

设备特殊文件

设备特殊文件

引言

    st_dev和st_rdev这两个字段经常引起混淆
  1. struct stat
  2. {
  3. mode_t st_mode; /* file type & mode (permissions) */
  4. ino_t st_ino; /* i-node number (serial number) */
  5. dev_t st_dev; /* device number (file system) */
  6. dev_t st_rdev; /* device number for special files */
  7. nlink_t st_nlink; /* number of links */
  8. uid_t st_uid; /* user ID of owner */
  9. gid_t st_gid; /* group ID of owner */
  10. off_t st_size; /* size in bytes, for regular files */
  11. struct timespec st_atim; /* time of last access */
  12. struct timespec st_mtim; /* time of last modification */
  13. struct timespec st_ctim; /* time of last file status change */
  14. blksize_t st_blksize; /* best I/O block size */
  15. blkcnt_t st_blocks; /* number of disk blocks allocated */
  16. };
    每个文件系统所在的存储设备都由主、次设备号表示
    设备号所用的数据类型是基本系统数据类型dev_t。在<sys/stat.h>中声明如下:
  1. typedef __dev_t dev_t;
    主设备号标识设备驱动程序
    次设备号标识特定的子设备
    系统中与每个文件名关联的st_dev值是文件系统的设备名,该文件系统包含了这一文件名以及对应的i节点
    只有字符特殊文件和块特殊文件才有st_rdev值。此值包含实际设备的设备号

major和minor宏

    我们通常使用两个宏:major和minor来访问主、次设备号,这也意味着我们无需关心这两个数是如何存放在dev_t对象中
    在<sys/sysmacros.h>定义了这两个宏, 而这个头文件又包含在<sys/type.h>中
  1. #define major(dev) gnu_dev_major (dev)
实例
  1. /**
  2. * 为每个命令行参数打印设备号,另外,若此参数引用的是字符特殊文件或者块特殊文件,
  3. * 则还打印特殊文件的st_rdev值
  4. */
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <sys/types.h>
  8. #include <sys/stat.h>
  9. #include <unistd.h>
  10. int main(int argc, char *argv[])
  11. {
  12. struct stat buf;
  13. int i = 0;
  14. for (i = 1; i < argc; i++)
  15. {
  16. printf("%s: ", argv[i]);
  17. if (stat(argv[i], &buf) < 0)
  18. {
  19. err_ret("stat error");
  20. continue;
  21. }
  22. printf("dev = %d/%d ", major(buf.st_dev), minor(buf.st_dev));
  23. if (S_ISCHR(buf.st_mode))
  24. {
  25. printf("(character) rdev = %d/%d", major(buf.st_rdev), minor(buf.st_rdev));
  26. }
  27. else if (S_ISBLK(buf.st_mode))
  28. {
  29. printf("(block) rdev = %d/%d", major(buf.st_rdev), minor(buf.st_rdev));
  30. }
  31. printf("\n");
  32. }
  33. exit(0);
  34. }
    程序运行如下:
$ ./a.out / /home/fireway/ /dev/tty[01]
/: dev = 8/1 
/home/fireway/: dev = 8/1 
/dev/tty0: dev = 0/6 (character) rdev = 4/0
/dev/tty1: dev = 0/6 (character) rdev = 4/1
$ mount                <---------------------哪些目录安装在哪些设备上(详细原理见下文)
/dev/sda1 on / type ext4 (rw,errors=remount-ro)
proc on /proc type proc (rw,noexec,nosuid,nodev)
sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)
none on /sys/fs/cgroup type tmpfs (rw)
none on /sys/fs/fuse/connections type fusectl (rw)
none on /sys/kernel/debug type debugfs (rw)
none on /sys/kernel/security type securityfs (rw)
udev on /dev type devtmpfs (rw,mode=0755)
devpts on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=0620)
tmpfs on /run type tmpfs (rw,noexec,nosuid,size=10%,mode=0755)
none on /run/lock type tmpfs (rw,noexec,nosuid,nodev,size=5242880)
none on /run/shm type tmpfs (rw,nosuid,nodev)
none on /run/user type tmpfs (rw,noexec,nosuid,nodev,size=104857600,mode=0755)
none on /sys/fs/pstore type pstore (rw)
systemd on /sys/fs/cgroup/systemd type cgroup (rw,noexec,nosuid,nodev,none,name=systemd)
gvfsd-fuse on /run/user/112/gvfs type fuse.gvfsd-fuse (rw,nosuid,nodev,user=lightdm)
$ ls -l /dev/tty[01] /dev/sda[01]
brw-rw---- 1 root disk 8, 1 11月 17 07:52 /dev/sda1
crw--w---- 1 root tty 4, 0 11月 17 07:52 /dev/tty0
crw-rw---- 1 root tty 4, 1 11月 17 07:52 /dev/tty1
    我们给程序传了4个参数,前两个参数是目录(/和/home/fireway), 后两个参数是设备名/dev/tty[01]。

认识设备特殊文件

    在linux系统,每个设备都会被当成一个文件对待。比如IDE接口的硬盘(IDE的英文全称为“Integrated Drive Electronics”,即“电子集成驱动器”)的文件名即为/dev/hd[a-d]。
    通常来说,电脑硬盘主要有IDE以及SATA两种接口类型,其中以前的旧电脑一般都是IDE硬盘接口,该接口由于传输速度慢,如今早已被淘汰,现在的新电脑都是SATA硬盘接口。
    目前硬盘接口均采用SATA接口,SATA接口有分为SATA2.0以及SATA3.0,其中SATA2.0最大传输速度为300M/s,而SATA3.0最大传输速率为600M/s。如今固态硬盘均采用的是SATA3.0接口,而普通的机械硬盘很多也开始全面采用SATA3.0接口,只要部分容量较小,价格比较低的机械硬盘还是用的SATA2.0接口。
   下面列出几个常见的设备与其在 Linux 当中的文件名:
设备在Linux内的文件名
IDE硬盘/dev/hd[a-d]
SCSI / SATA / USB 硬盘/dev/sd[a-p]
U盘,全称USB闪存盘,英文名“USB flash disk”/dev/sd[a-p](与SATA 相同)
软盘驱动器 /dev/fd[0-1]
打印机25 针: /dev/lp[0-2]
USB: /dev/usb/lp[0-15]
鼠标USB: /dev/usb/mouse[0-15]
PS2: /dev/psaux
当前CDROM/DVDROM/dev/cdrom
当前的鼠标/dev/mouse
磁带机IDE: /dev/ht0
SCSI: /dev/st0

磁盘和磁盘分区

    一块磁盘是可以被分割成多个分区(partition),比如Windows就把一个磁盘划分成C、D、E盘,这个就是分区(partition),但是Linux的设备都是以文件形式存在,那它的分区以是什么表示的呢?
    磁盘盘上面又可细分成扇区(Sector)与柱面(Cylinder), 其中扇区每个为 512bytes 。假设磁盘只有一个磁盘盘,如下图1所示:
技术分享
图1. 磁盘盘组成示意图
    整颗磁盘的第一个扇区非常重要,因为它主要记录两个重要信息:
  1. 主要启动记录项(Master Boot Record, MBR):可以安装开机管理程序的地方,有446bytes
  2. 分区表(partition table):记录整颗硬盘分区的状态,有64bytes
技术分享
 
图2.分区表的作用示意图
    上图2中我们假设硬盘只有400个柱面,共划分成4个分区,第四个分区在第301到400号柱面的范围。假设硬盘设备文件名为/dev/hda,那么这四个分区在Linux系统中的设备文件名如下:
P1:/dev/hda1
P2:/dev/hda2
P3:/dev/hda3
P4:/dev/hda4
    根据上面图示和说明,我们可以简单总结如下:
  • 其实所谓的“分区”,只是针对这个64bytes的分区表设定而已
  • 硬盘默认的分区表仅能写入4个划分信息
  • 这4组划分信息我们称为主要分区(Primary partition)或扩展分区(Extended partition)
  • 分区的最小单位为柱面
  • 当系统写入磁盘时,一定会参考分区表,才能针对某个分区进行数据处理
    那么为什么要进行磁盘分区呢?
  • 数据的安全性
  • 系统的性能效率

    既然分区表只能记录4组划分信息,那么是否代表一颗硬盘最多只能划分4个分区呢?当然不是,在Windows、Linux系统中,我们通过上面的扩展分区(extended partition)的方式,划分很多个分区。如下图所示:
    技术分享
 
图3. 磁盘分区表、扩展分区表的作用示意图
    
    在图3中,默认的分区表仅用到两个,P1为为主要分区,P2为扩展分区,请注意,使用扩展分区的目的是使用额外的扇区来记录分区信息,扩展分区本身并不能拿来格式化,然后我们通过扩展分区所指向的这块区域继续做划分。
    正如图3右下角的这块区域又被划分出5个分区,这5个分区是由扩展分区划分出来的,被称为逻辑分区(logical partition)。因此,这些分区在Linux系统的设备文件名如下:
P1:/dev/hda1
P2:/dev/hda2
L1:/dev/hda5
L2:/dev/hda6
L3:/dev/hda7
L4:/dev/hda8
L5:/dev/hda9
    仔细一瞧,怎么设备文件名没有/dev/hda[34]呢?因为前面4个号码都是留给Primary或Extended,所以逻辑分区的设备名称号码就由5开始。
    我们了解了Primary partition, Extended partition和Logical partition概念后,做一个简单的总结:
  • 逻辑分区是由扩展分区划分出来的分区
  • 主要分区最多可以有4个(硬盘限制)
  • 扩展分区最多只有一个(操作系统的限制)
  • 作为数据存取,能够被格式化的,是主要分区和逻辑分区,扩展分区无法格式化
  • 逻辑分区的数量主要依赖操作系统,在Linux系统中,IDE硬盘最多有59个逻辑分区(5~63号),STAT硬盘则有11个逻辑分区(5~15号)
    几乎只要读取硬盘都会从第一个扇区所记录的分区表和MBR获取到,因此如果整颗硬盘的第一个扇区坏掉,那这颗磁盘大概就没用了。因为系统如果找不到分区表,怎么知道如何读取柱面呢?

目录树结构

技术分享
图4. 目录树相关性示意图
    如上图,长方形为目录,波浪形为文件。Linux 与其他类 UNIX 系统一样并不区分文件与目录:目录是记录了其他文件名的文件。
 /              根目录
├── bin     存放用户二进制文件
├── boot    存放内核引导配置文件
├── dev     存放设备文件
├── etc     存放系统配置文件
├── home    用户主目录
├── lib     动态共享库
├── lost+found 文件系统恢复时的恢复文件
├── media   可卸载存储介质挂载点
├── mnt     文件系统临时挂载点
├── opt     附加的应用程序包
├── proc    系统内存的映射目录,提供内核与进程信息
├── root    root 用户主目录
├── sbin    存放系统二进制文件
├── srv     存放服务相关数据
├── sys     sys 虚拟文件系统挂载点
├── tmp     存放临时文件
├── usr     存放用户应用程序
└── var     存放邮件、系统日志等变化文件
    我们现在知道整个Linux系统使用的是目录树结构,但是我们的文件其实是放置在磁盘分区的,现在的问题是“如何结合目录树与磁盘内的数据”,这个就需要牵扯到mount(挂载)的概念。
    所谓的“挂载”,是指将一个设备(通常是存储设备)挂接到一个已存在的目录上。 我们要访问存储设备中的文件,必须将文件所在的分区挂载到一个已存在的目录上, 然后通过访问这个目录来访问存储设备。如下图,假设硬盘分为两个分区,partition1是挂载到跟目录,partition2是挂载到/home目录,也就是说,当数据放置在/home内的各个目录时,数据是放置在partition2,如果不是放在/home目录的文件,那么数据就会被放置在partition1上。

技术分享
图5. 目录树与分区之前的相关性


 文件系统与格式化

    磁盘分区完毕后,还需要格式化(format),为什么呢?这是因为每种操作系统所设定的文件属性/权限并不相同,为了存放这些文件所需的数据,需要将分区进行格式化,以便称为操作系统能够利用的文件系统格式(filesystem)。
    每种操作系统所使用的文件系统并不相同,
操作系统文件系统
windows 98以前FAT(或FAT16)
windows 2000NTFS
LinuxEx2(Linux second extended file system, ext2fs)

参考

IDE    http://baike.baidu.com/subview/5775/5401664.htm#viewPageContent
硬盘接口有几种?怎么看硬盘接口类型    http://www.pc841.com/article/20140903-34510.html
鸟哥的Linux私房菜基础学习篇(第三版)  

 

设备特殊文件