shell编程范例之文件系统操作
图片:
描述: 图[2] MBR Structure
图片:
by falcon<[email protected]>
2007-12-20
前言
准备了很久,找了好多天的资料,还不知道应该如何开始动笔写:因为担心“拿捏”不住,所以一方面继续查找资料,一方面思考如何来写。作为“shell编程 范例”序列的一部分,希望它能够很好地帮助shell程序员理解如何用shell命令来完成和Linux系统关系非常之大的文件系统的各种操作,希望让 Shell程序员中对文件系统"混沌"的状态从此消失,希望文件系统以一种更为清晰的样子呈现在我们的眼前。
正文
-1 文件系统在Linux操作系统中的位置
如何来认识文件系统呢?从shell程序员的角度来看,文件系统就是一个用来组织各种文件的方法。但是文件系统无法独立于硬件存储设备和操作系统而独立存 在,因此我们还是有必要来弄清楚硬件存储设备、分区、操作系统、逻辑卷、文件系统等各种概念之间的联系,以便理解我们对文件系统的常规操作的一些“细 节”。这个联系或许(也许会有一些问题)可以通过这样一种方式来呈现,如附录图[1]。
从该图中,我们可以清晰地看到各个“概念”之间的关系,它们以不同层次分布,覆盖硬件设备、系统内核空间、系统用户空间。在用户空间,用户可以不管内核是 如何操作具体硬件设备的,仅仅使用程序员设计的各种界面就可以拉,而普通程序员也仅仅需要利用内核提供的各种接口(system call)或者一些C库来和内核进行交互,而无须关心具体的实现细节。不过对于操作系统开发人员,他们需要在内核空间设计特定的数据结构来管理和组织底层 的硬件设备。
下面我们从下到上的方式(即从底层硬件开始),用工具来分析和理解图中几个重要的概念。(如果有兴趣,可以先看看下面的几则资料)
参考资料:
[1] Linux 系统的基本组成和文件系统结构
http://forum.ubuntu.org.cn/weblog_entry.php?e=332&sid=3ceee92718a77d5eef867497470ecc7b
[2] 从文件 I/O 看 Linux 的虚拟文件系统
http://www.ibm.com/developerworks/cn/linux/l-cn-vfs/
[3] Linux 文件系统剖析 http://www.ibm.com/developerworks/cn/linux/l-linux-filesystem/index.html?ca=drs-cn
[4] 第九章 文件系统
http://man.chinaunix.net/tech/lyceum/linuxK/fs/filesystem.html
[5] Linux逻辑盘卷管理LVM详解
http://unix-cd.com/vc/www/28/2007-06/1178.html
0 硬件管理和设备驱动
Linux系统通过不同的设备驱动模块管理不同的硬件设备。如果添加了新的硬件设备,那么需要编写相应的硬件驱动模块来管理它。对于一些常见的硬件设备, 系统已经自带了相应的驱动,编译内核时,选中它们,可以把它们编译成内核的一部分,也可以以模块的方式编译。如果以模块的方式编译,那么可以在系统的 /lib/modules/`uname -r`目录下找到对应的模块文件。
比如,可以这样找到相应的scsi驱动和usb驱动的模块:
Quote: |
// 更新系统中文件索引数据库(有点慢,不耐烦就按下CTRL+C取消掉) |
这些驱动的名字以.ko为后缀,在安装系统时默认编译为了模块。实际上可以把它们编译为内核的一部分,仅仅需要在编译内核时选择为
可通过查看/proc文件系统的modules文件检查内核中已加载的各个模块的状态,也可以通过lsmod命令直接查看它们。
Quote: |
$ cat /proc/modules |
下面卸载usbhid模块看看(呵呵,小心卸载scsi的驱动哦!因为你的系统就跑在上面,如果确实想玩玩,卸载前记得保存数据),通过rmmod命令就可以实现。
Quote: |
// 先切换到root用户 |
如果你有个usb鼠标,那么移动一下,是不是发现动不了啦?因为设备驱动都没有了,设备自然就没法用罗。不过不要紧张,既然知道是什么原因,那么把设备驱动重新加载上就可以啦,下面用insmod把usbhid模块重新加载上。
Quote: |
// 也要root用户的 |
okay,现在鼠标又可以用啦,不信再动一下鼠标 :-)
到这里,硬件设备和设备驱动之间关系应该是比较清楚了吧。如果没有,那么继续下面的内容。
在Linux下,设备驱动关联着相应的设备文件,而设备文件则和硬件设备一一对应(更多细节请参考资料[8][9][10])。这些设备文件都统一存放在系统的/dev/目录下。
例如,scsi设备对应的/dev/sda,/dev/sda1,/dev/sda2...下面查看这些设备文件的信息。
Quote: |
$ ls -l /dev/sda* |
可以看到第一列第一个字符都是b,第五列都是数字8。b表示该文件是一个块设备文件,对应地,如果是c则表示字符设备(例如/dev/ttyS0,关于块设备和字符设备的区别,可以看这里[摘自网络])。
Quote: |
字符设备:字符设备就是能够像字节流一样访问的设备,字符终端和串口就属于字符设备。 |
数字8则是该硬件设备在内核中对应的设备编号,可以在内核的Documentation/devices.txt文件中找到设备号分配情况。但是为什么同 一个设备会对应不同的设备文件(/dev/sda后面为什么还有不同的数字,而且ls结果中的第6列貌似和它们对应起来的)。这实际上是为了区分不同设备 的不同部分。对于硬盘,这样可以处理硬盘内部的不同分区。就内核而言,它仅仅需要通过第5列的设备号就可以找到对应的硬件设备,但是对于驱动模块来说,它 还需要知道如何处理不同的分区,于是就多了一个辅设备号,即第6列对应的内容。这样一个设备就有了主设备号(第5列)和辅设备号(第6列),从而方便的实 现对各种硬件设备的管理。
因为设备文件和硬件是对应的,这样我们可以直接从/dev/sda(如果是IDE的硬盘,那么对应的设备就是/dev/hda啦)设备中读出硬盘的信息,例如:
Quote: |
// 用dd命令复制出硬盘的前512个字节,要root用户哦 |
因为这些信息并不是很直观(而且下面我们会进一步深入的分析),那么我们来看看另外一个设备文件,将可以非常直观的演示设备文件和硬件的对应关系。还是以鼠标为例吧,下面来读取鼠标对应的设备文件的信息。
Quote: |
// 同样需要root用户 |
移动鼠标看看,是不是发现有不同信息输出。(基于这一原理,我们经常通过在一端读取设备文件/dev/ttyS0中的内容,而在另一端往设备文件/dev/ttyS0中写入内容来检查串口线是否被损坏。)
到这里,对设备驱动、设备文件和硬件设备之间的关联应该是印象更深刻了。如果想深入了解设备驱动的工作原理和设备驱动模块的编写,那么看看参考资料[10],开始你的设备驱动模块的编写历程吧。
参考资料:
[5] Compile linux kernel 2.6
http://www.cyberciti.biz/tips/compiling-linux-kernel-26.html
[6] Linux系统的硬件驱动程序编写原理
http://www.blue1000.com/bkhtml/2001-02/2409.htm
[7] Linux下USB设备的原理、配置、 常见问题
http://soft.zdnet.com.cn/software_zone/2007/1108/617545.shtml
[8] Linux 核心--9.设备驱动
http://www.bitscn.com/linux/driver/200604/6788.html
[9] The Linux Kernel Module Programming Guide
http://www.dirac.org/linux/writing/lkmpg/2.6/lkmpg-2.6.0.html
[10] Linux设备驱动开发
http://linuxdriver.co.il/ldd3/
1 理解、查看磁盘分区
实际上内存、u盘等都可以作为文件系统底层的“存储”设备,但是这里我们仅用硬盘作为实例来介绍磁盘和分区的关系。
目前Linux的分区依然采用第一台PC硬盘所使用的分区原理(见该部分的参考资料[1]),下面逐步分析和演示这一分区原理。
先来看看几个概念:
A. 设备管理和分区
在Linux下,每一个存储设备对应一个系统的设备文件,对于硬盘等IDE和SCSI设备,在系统的/dev目录下可以找到对应的包含字符hd和sd的设 备文件。而根据硬盘连接的主板设备接口和数据线接口的不同,在hd或者sd字符后面可以添加一个从a到z的字符,例如hda,hdb,hdc和sda, sdb,sdc等,另外为了区别同一个硬件设备的不同分区,在后面还可以添加了一个数字,例如hda1,hda2,hda3...和sda1,sda2, sda3,所以你在/dev目录下,可以看到很多类似的设备文件。
B. 各分区的作用
在分区的时候常遇到主分区和逻辑分区的问题,这实际上是为了方便扩展分区,正如后面的逻辑卷的引入是为了更好地管理多个硬盘一样,引入主分区和逻辑分区可以方便地进行分区的管理。
在Linux系统中,每一个硬盘设备最多由4个主分区(包括扩展分区)构成。
主分区的作用是计算机用来进行启动操作系统的,因此每一个操作系统的启动程序或者称作是引导程序,都应该存放在主分区上。Linux规定主分区(或者扩展 分区)占用分区编号中的前4个。所以你会看到主分区对应的设备文件为/dev/hda1-4或者/dev/sda1-4,而不会是hda5或者sda5。
扩展分区则是为了扩展更多的逻辑分区的,在Linux下,逻辑分区占用了hda5-16或者sda5-16等12个编号。
C. 分区类型
它规定了这个分区上的文件系统的类型。Linux支持诸如msdoc,vfat,ext2,ext3等诸多的文件系统类型,更多信息在下一小节进行进一步的介绍。
下面通过分析硬盘的前512个字节(即MBR)来分析和理解分区。
先来看看这张表(见附图2),它用来描述MBR的结构。MBR包括引导部分、分区表、以及结束标记(55AAH),分别占用了512字节中446字节、 64字节和2字节。这里仅仅关注分区表部分,即中间的64字节以及图中左边的部分。(如果你对引导部分感兴趣,请参考资料[10][11][12])
由于我用的是SCSI的硬盘,下面从/dev/sda设备中把硬盘的前512个字节拷贝到文件mbr.bin中。
Quote: |
// 先切换到root用户 |
下面用file,od,fdisk等命令来分析这段MBR的数据,并对照附图[2]以便加深理解。
Quote: |
$ file mbr.bin |
file命令的结果显示,刚拷比的512字节是启动扇区,用分号分开的几个部分分别是boot loader,分区3和分区4。分区3的类型是82,即swap分区(可以通过fdisk命令的l命令列出相关信息),它对应fdisk的结果中 /dev/sda3所在行的第5列,分区3的扇区数是1959930,转换成字节数是1959930*512(目前,硬盘的默认扇区大小是512字节), 而swap分区的默认块大小是1024字节,这样块数就是:
Quote: |
$ echo 1959930*512/1024 | bc |
正好是fdisk结果中/dev/sda3所在行的第四列对应的块数,同样地,可以对照fdisk和file的结果分析分区4。
再来看看od命令以十六进制显示的结果,同样考虑分区3,计算一下发现,分区3对应的od命令的结果为:
Quote: |
fe00 ffff fe82 ffff 14c0 012a e7fa 001d |
首先是分区标记,00H,从图[2]中,看出它就不是引导分区(80H标记的才是引导分区),而分区类型呢?为82H,和file显示结果一致,现在再来关注一下分区大小,即file结果中的扇区数。
Quote: |
$ echo "ibase=10;obase=16;1959930" | bc |
刚好对应e7fa 001d,同样地考虑引导分区的结果:
Quote: |
0180 0001 fe83 ffff 003f 0000 1481 012a |
分区标记:80H,正好反应了这个分区是引导分区,随后是引导分区所在的磁盘扇区情况,010100,即1面0道1扇区。其他内容可以对照分析。
考虑到时间关系,更多细节请参考下面的资料或者查看看系统的相关手册。
补充:安装系统时,可以用fdisk,cfdisk等命令进行分区。如果要想从某个分区启动,那么需要打上80H标记,例如可通过cfdisk把某个分区设置为bootable来实现。
参考资料:
[1] 解析磁盘、分区、文件系统
http://www.linuxpk.com/37190.html
[2] 硬盘分区表详解
http://www.linuxpk.com/5378.html
[3] 深入理解Linux的硬盘分区
http://www.linuxpk.com/39733.html
[4] 什么是硬件分区表
http://www.pc-web.cn/pc/basic/465.asp
[5] Linux指导第6部分 使用分区和文件系统
http://www.pass100.net/jisuanji/linux/zhidao/80974.html
[10] 硬盘MBR全面分析
http://www.pc120.net.cn/home/datcb/05101223070444577.htm
[11] Inside the linux boot process
http://www-128.ibm.com/developerworks/linux/library/l-linuxboot/
[12] Develop your own OS: booting
http://docs.huihoo.com/gnu_linux/own_os/booting.htm
[13] Redhat 9磁盘分区简介
http://www.topstudy.com/info/default.aspx?guid=eeac2894-3588-4b1a-9607-1ad377caa03f
[14] Linux partition HOWTO
http://mirror.lzu.edu.cn/tldp/HOWTO/Partition/
2 分区和文件系统的关系
在没有引入逻辑卷之前,分区类型和文件系统类型几乎可以同等对待,设置分区类型的过程就是格式化分区,建立相应的文件系统类型的过程。
下面主要介绍如何建立分区和文件系统类型的联系,即如何格式化分区为指定的文件系统类型。
先来看看Linux下文件系统的常见类型(如果要查看所有Linux支持的文件类型,可以用fdisk命令的l命令查看,或者通过man fs查看,也可通过/proc/filesystems查看到当前内核支持的文件系统类型)
ext2,ext3:这两个是Linux根文件系统通常采用的类型
swap:这个是具体实现Linux虚拟内存时采用的一种文件系统,安装时一般需要建立一个专门的分区,并格式化为swap文件系统(如果想添加更多的 swap分区,那么可以参考本节的资料[1],熟悉dd,mkswap,swapon,swapoff等命令的用法)
proc:这是一种比较特别的文件系统,作为内核和用户之间的一个接口存在,建立在内存中(你可以通过cat命令查看/proc系统下的文件,甚至可以通 过修改/proc/sys下的文件实时调整内核的配置,当前前提是你需要把proc文件系统挂载上[mount -t proc proc /proc])
除了这三个最常见的文件系统类型外,Linux支持包括vfat,iso,xfs,nfs在内各种常见的文件系统类型,在linux下,你可以自由地查看和操作windows等其他操作系统使用的文件系统。
那么如何建立磁盘和这些文件系统类型的关联呢?格式化。
格式化的过程实际上就是重新组织分区的过程,可通过mkfs命令来实现,当然也可以通过fdisk等命令来实现。这里仅介绍mkfs,mkfs可用来对一 个已有的分区进行格式化,不能实现分区操作(如果要对一个磁盘进行分区和格式化,那么可以用fdisk就可以啦)。格式化后,相应的分区上的数据就通过某 种特别的文件系统类型进行组织了。
例如:把/dev/sda9分区格式化为ext3的文件系统。
Quote: |
// 先切换到root用户 |
如果要列出各个分区的文件系统类型,那么可以用fdisk -l命令。
更多信息请参考下列资料。
参考资料:
[1] Linux下加载swap分区的步骤
http://soft.zdnet.com.cn/software_zone/2007/1010/545261.shtml
[2] 光碟的标准
http://www.edisc.com.cn/bike/viewnews.btml?id=274
[3] Linux下ISO镜像文件的制作与刻录
http://www.examda.com/linux/fudao/20071212/113445321.html
[4] RAM磁盘分区解释
http://oldlinux.org/oldlinux/viewthread.php?tid=2677
http://www.ibm.com/developerworks/cn/linux/l-initrd.html
[5] 高级文件系统实现者指南
http://www-128.ibm.com/developerworks/search/searchResults.jsp?searchType=1&searchSite=dWChina&pageLang=zh&langEncoding=UTF8&searchScope=dW&query=%E9%AB%98%E7%BA%A7%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E5%AE%9E%E7%8E%B0%E8%80%85%E6%8C%87%E5%8D%97&Search.x=42&Search.y=9&Search=%E6%90%9C%E7%B4%A2
(有必要突出解释swap,RAM文件系统的工作原理等)
3 分区、逻辑卷和文件系统的关系
在上一节中,我们直接把分区格式化为某种文件系统类型,但是考虑到扩展新的存储设备的需要,开发人员在文件系统和分区之间引入了逻辑卷。考虑到时间关系,这里不再详述,请参考资料[1]。
参考资料:
[1] Linux逻辑卷管理详解
http://unix-cd.com/vc/www/28/2007-06/1178.html
[2] 见1.2的最后一个参考资料的最后一节
4 文件系统的可视化结构
文件系统最终呈现出来的是一种可视化的结构,我们可用ls,find,tree等命令把它呈现出来。它就像一颗倒挂的“树”,在树的节点上还可以挂载新的“树”。(如果想把目录结构以图表的方式呈现出来,那么可以使用我之前写的一个脚本,即参考资料[3])。
下面简单介绍文件系统的挂载。
一个文件系统可以通过一个设备挂载(mount)到某个目录下(具体的实现请参考资料[2]和[1]),这个目录被称为挂载点。有趣的是,在Linux 下,一个目录本身还可以挂载到另外一个目录下,一个格式化了的文件也可以通过一个特殊的设备/dev/loop进行挂载(如iso文件)。另外,就文件系 统而言,Linux不仅支持本地文件系统,还支持远程文件系统(如nfs)。
下面简单介绍文件系统挂载的几个实例。
A. 根文件系统的挂载
Quote: |
// 挂载需要root权限,先切换到root用户 |
B. 挂载一个新的设备
如果内核已经支持了USB接口,那么在插入u盘的时候,我们可以通过dmesg命令查看它对应的设备号,并挂载它。
Quote: |
// 查看dmesg结果中的最后几行内容,找到类似/dev/sdN的信息,找出u盘对应的设备号 |
C. 挂载一个iso文件或者是光盘
对于一些iso文件或者是iso格式的光盘,同样可以通过mount命令挂载。
Quote: |
// 对于iso文件 |
D. 挂载一个远程文件系统
Quote: |
$ mount -t nfs remote_ip:/path/to/share_directory /path/to/local_directory |
proc文件系统组织在内存中,但是你可以把它挂载到某个目录下。通常把它挂载在/proc目录下,以便一些系统管理和配置工具使用它。例如top命令用 它分析内存的使用情况(读取/proc/meminfo和/proc/stat等文件中的内容),lsmod命令通过它获取内核模块的状态(读取 /proc/modules),netstat命令通过它获取网络的状态(读取/proc/net/dev等文件),当然,你也可以编写自己的相关工具。 除此之外,通过调整/proc/sys目录下的文件,你可以动态的调整系统的配置,比如通过往 /proc/sys/net/ipv4/ip_forward文件中写入数字1就可以让内核支持数据包的转发。(更多信息请参考proc的帮助,man proc)
F. 挂载一个目录
Quote: |
$ mount --bind /path/to/needtomount_directory /path/to/mountpoint_directory |
这个非常有意思,比如你可以把某个目录挂载到ftp服务的根目录下,而无须把内容复制过去,就可以把相应目录中的资源提供给别人共享。
以上都只提到了挂载,那怎么卸载呢?用umount命令跟上挂载的源地址或者挂载点(设备,文件,远程目录等)就可以。例如:
Quote: |
$ umount /path/to/mountpoint_directory |
如果想管理大量的或者经常性的挂载服务,那么每次手动挂载是很糟糕的事情。这个时候就可以利用mount的配置文件/etc/fstab,把mount对 应的参数写到/etc/fstab文件对应的列中即可实现批量挂载(mount -a)和卸载(umount -a)。/etc/fstab中各列分别为文件系统、挂载点、类型、相关选项。更多信息可参考fstab的帮助(man fstab)。
参考资料:
[1] Linux硬盘分区以及其挂载原理
http://www.xxlinux.com/linux/article/accidence/technique/20070521/8493.html
[2] 从文件I/O看linux的虚拟文件系统
http://www.ibm.com/developerworks/cn/linux/l-cn-vfs/
[3] 用Graphviz进行可视化操作──绘制函数调用关系图
http://oss.lzu.edu.cn/blog/blog.php?/do_showone/tid_1425.html
5 如何制作一个文件系统
Linux的文件系统下有一些最基本的目录,不同的目录下存放着不同作用的各类文件。最基本的目录有/etc, /lib, /dev, /bin等,它们分别存放着系统配置文件,库文件,设备文件和可执行程序。这些目录一般情况下是必须的,在做嵌入式开发的时候,我们需要手动或者是用 busybox等工具来创建这样一个基本的文件系统。如何来制作一个这样的文件系统呢?请参考资料[1]和[2]。这里我们制作仅制作一个非常简单的文件 系统,并对该文件系统进行各种常规的操作,以便加深对文件系统的理解。
首先,创建一个固定大小的文件。
Quote: |
// 还记得dd命令么?我们就用它来产生一个固定大小的文件,这个为1M(1024*1024 bytes)的文件 |
说明:/dev/zero是一个非常特殊的设备,如果读取它,可以获取任意多个\0。
接着把该文件格式化为某个指定文件类型的文件系统。(是不是觉得不可思议,文件也可以格式化?是的,不光是设备可以,文件也可以以某种文件系统类型进行组织,但是需要注意的是,某些文件系统(如ext3)要求被格式化的目标最少有64M的空间)。
Quote: |
// 格式化文件 |
因为该文件以文件系统的类型组织了,那么可以用mount命令挂载并使用它。
Quote: |
// 请切换到root用户挂载它,并通过-o loop选项把它关联到一个特殊设备/dev/loop |
在该文件系统下进行各种常规操作,包括读、写、删除等。(每次操作前先把minifs文件保存一份,以便比较,结合相关资料就可以深入地分析各种操作对文件系统的改变情况,从而深入理解文件系统作为一种组织数据的方式的实现原理等)
Quote: |
$ cp minifs minifs.bak |
上面仅仅演示了一些分析文件系统的常用工具,并分析了几个常规的操作,如果你想非常深入地理解文件系统的实现原理,请熟悉使用上述工具并阅读相关资料,比如1节的参考资料[2][3][4]。
参考资料:
[1] Build a mini filesystem in linux from scratch
http://oss.lzu.edu.cn/blog/blog.php?/do_showone/tid_1211.html
[2] Build a mini filesystem in linux with BusyBox
http://oss.lzu.edu.cn/blog/blog.php?/do_showone/tid_1212.html
[3] ext2 文件系统
http://man.chinaunix.net/tech/lyceum/linuxK/fs/filesystem.html
6 如何开发自己的文件系统
随着fuse的出现,在用户空间开发文件系统成为可能,如果想开发自己的文件系统,那么阅读下面的参考资料吧。
参考资料:
[1] 使用fuse开发自己的文件系统
后记:
[1] 2007年12月22日,收集了很多资料,写了整体的框架。
[2] 2007年12月28日下午,完成初稿,考虑到时间关系,很多细节也没有进一步分析,另外有些部分可能存在理解上的问题,欢迎批评指正。
[3] 2007年12月28日晚,修改部分资料,并正式公开该篇文档。
[4] 29号,添加设备驱动和硬件设备一小节。