浅谈Linux文件操作

  本文基于的操作系统是Ubuntu 15.04

Linux文件主要包含两部分内容,一是文件本身所包含的数据,另外就是文件的属性,也称为元数据,包括文件访问权限,所有者,文件大小,创建日期等。
目录也是一种文件,称为目录文件,当创建目录时,系统会自动创建两个目录项:.和..(如图)
前者代表当前目录,后者代表父目录。对于根目录两者相同
对于根目录下几个目录的作用:
/bin  用于存放普通用户可执行的命令。系统中的任何用户都可以执行该目录中的命令,如ls,cp,mkdir
/boot Linux的内核及启动系统时所需要的文件,为保证启动文件更加安全可靠,通常把该目录放在独立的分区上。
/dev 设备文件的存储目录,如光驱,硬盘
/etc 用于存放系统的配置文件,比如用户账号,密码等
/home 普通用户的主目录,每个用户在该目录下都有一个与用户名同名的目录
/lib 存放各种库文件
/proc 该目录是一个虚拟文件系统,只有在系统运行的时候才会存在,通过访问该目录下的文件,可以获取系统的状态信息并且修改某些系统的配置信息。
/root 超级用户root的主目录
/sbin 存放的是用于管理系统的命令。
/tmp 临时文件目录
/usr 用于存放系统应用程序及相关文件, 如说明文档,帮助文件等。
/var 用于存放系统中经常变化的文件,如用户的日志文件,用户邮件等。


我们知道数据或者文件归根结底存储在物理磁盘上。那么对屋里磁盘的访问方法一般有二:
①用户进程绕过文件系统直接读写磁盘上的内容,这给操作系统带来了很大的不稳定性。
②包括Linux在内的很多操作系统都是通过虚拟文件系统来访问设备驱动。



我们可以查看一个文件的属性,比如:
根据得到的文件属性数据我们可以知道文件的属性,也就是第一个字符
对于执行ls -l后的第一个字符有以下分类:
- :表示一个普通文件
d :表示一个文件夹目录
b :表示块特殊文件,如硬盘,光驱等设备
c :表示字符特殊文件,如猫等串口设备。
以上这两种设备需要使用mknode来创建,用rm删除。一般不自己创建,因为这些文件是和内核相关联的。

s :当我们启动MySQL服务器时,会产生一个mysql.sock的文件,这是套接字,主要用于网络通信,也可以用于一台主机上的进程之间的通信。

l :表示链接文件,是通过ln -s 源文件名 新文件名创建的。这里拓展一点关于软链接和硬链接的相关知识:
Linux系统中分为软链接与硬链接两种特殊的“文件”
如果与windows相比的话,与windows的快捷方式更相似的是软链接。要说明两者的关系与区别的话,我们首先讨论一个名为innode的概念。

innode
在划分磁盘分区并格式化的时候,整个分区会被划分为两部分,一部分是数据区域data block,另一部分就是innode区域。这里innode是(目录,档案)文件在一个文件系统中的唯一标识,需要访问这个文件的时候必须先找到并读取这个文件的innode。innode里面存放了文件的很多重要的参数,如未已标识lnnumber,其他信息还有创建时间,修改时间,文件大小,属主,归属的用户组,读写权限,数据所在的block号等信息。
通常如果该分区的文件都很小且数量很多的话,系统会扩大innode区域,以便能索引全部文件,否则可能会出现这个分区没有写满但是无法写入任何文件的情况。


innode本身簿记录文件名,文件名记录在目录文件的data block中,所以增添,删除与更改文件名都和目录的w权限有关。因此当我们要读某个档案的时候,就务必经过其目录的innode与dadta block,然后才能找到待读取档案的innode号,最终读到档案block内的内容.系统是通过索引节点来定位每一个文件。
文件系统定位一个文件:
目录innode(如果满足权限) -> 目录block -> 档案innode(如果满足权限) -> 档案block

做了前面这些铺垫,下面来谈谈硬链接与软链接。

先说说与windows快捷方式更相似的软链接:
软链接创建方式: ln -s <filename> <linkfilename>
软链接是建立独立的一个文件,而这个文件会让数据的读取指向它链接的档案的档名。根据档名链接到正确目录进一步获取目录档案的innode,最终就能读取到正确的数据。当指向的文件移动到其他目录,或者删除的话,软链接的档案就无法开启。


硬链接的创建方式:ln <filename> <linkfilename>
硬链接创建的是一个与链接文件完全相同,innode号同样相同的一个文件,其大小与原文件相同,本质上说是与原文件完全相同的存在,不占据空间位置。是节省空间的。但是不允许给目录创建链接,同样不允许在不同文件系统的文件间建立链接。当删除掉原文件的时候,访问硬链接生成的文件还是可以找到相应的文件,只有全部删除所有指针文件才可以在磁盘上删除文件内容。


除了上面所说的还有管道文件FIFO,用于进程间通信。一个进程将数据写到管道中,另一个进程可以读出。分为两种类型,无名管道和命名管道。无名管道在使用时创建,读写结束关闭文件后消失,之所以成为i无名管道是因为他们并不存在于文件系统中,无文件名称。命名管道在形式上就是文件系统中的一个文件,虽然并不占用存储文件内容的磁盘空间,但是有自己的文件名。所谓的FIFO是指命名管道。

文件的权限我们同样可以利用ls -l的选项看到,就是第一个字符后面的9个字符。
文件和目录的权限表示是通过rwx三个字符来代表所有者,用户组和其他用户的权限。
r 读权限 4
w 写权限 2
x 执行权限 1
- 无 0
类似所谓的777权限其实便是等同于rwxrwxrwx
为什么r,w,x是4,2,1呢,我们从二进制的角度考虑,他们分别代表100,010,001。从计算机的底层的角度可以明白其原理。

改变文件的权限:
chmod 
改变文件所有权:
chown



文件的输入输出操作非常多,相关函数如关于打开有关的open,创建有关的creat,关闭有关的close,读出有关的read,移动指针有关的lseek,以及设置文件锁,获取文件描述符等
这里只简单说一下需要注意的点,不再拿函数声明再做解释:
对于open函数:文件的三种打开方式互斥,但是他们可以分别与O_CREAT等标志进行或运算。标志常用有三:
O_CREAT若文件不存在则自动建立该文件,只有在此时,才需要用到第三个参数mode,以说明新文件的存取权限,新文件的的实际存取权限是mode与umask按照(mode & ~umask)运算以后的结果。如mode为740,umask为045,则最后的结果为700
O_EXCL :如果O_CREATE也被设置,此指令会去检查文件是否存在。文件若不存在则创建该文件,若文件已存在则将导致打开文件出错
O_TRUNC:若文件存在并以可写的方式打开时,此标志会讲文件长度清为0,即源文件中保存的数据将丢失,但文件属性不变。
使用open成功会返回一个文件描述符,是一个整型。那么这个文件描述符是什么呢?

文件描述符:
当某个程序打开文件的时候,操作系统返回相应的文件描述符,程序为了处理该文件必须引用此描述符。所谓的文件描述符是一个简单的正整数。最前面的三个文件描述符(0, 1, 2)分别表示标准输入(stdin),标准输出(stdout)和标准错误(stderr)对应。换句话说,如果在程序中打开任意的一个文件,他的文件描述符是从3开始的。

文件描述符的作用:
用来标明每一个被进程所打开的文件和socket。第一个打开的文件是0,第二个是1,以此类推。在读写文件的时候将其作为参数传递给read或write。文件描述符的范围取值于0~NR_OPEN之家,在linux中,后者是255,也就是说每个程序最多可以打开256个文件。

文件描述符的优缺点:
兼容POSIX标准,许多linux和unix系统都是依赖于它。但是不能移植到unix以外的系统上去,也不直观。

文件描述符标志close_on_exec
每个文件描述符都有一个close-on-exec标志,默认情况下,这个标志最后一位被设置为0,这个标志符的具体作用在于开辟其他进程调用exec()族函数时,在调用exec函数之前为exec族函数释放对应的文件描述符。简单来说就是在exec函数之前就把文件描述符释放掉。

creat无法创建设备文件。如上文所说创建设备文件要用mknode

close函数需要注意即使调用成功,并不保证数据能写回硬盘。也就是形式上是已经关闭,但数据可能仍然未写回。

lseek对应的whence参数:
SEEK_SET 从文件开始计算偏移量     SEEK_CUR 从文件指针当前位置开始计算偏移量  SEEK_END 从文件结尾处开始计算偏移量
注意: 文件指针可以设置到文件结束符之后。这样并不改变文件大小。如果对文件结束符后写入数据,那么结束符之后与写入数据之间将会存在一个间隔。里面用'\0'填充

对于fcntl的F_SETFLK, F_SETLKW, F_GETTLK三个cmd与文件锁有关:
当有多个进程同时对某一文件进行操作的时候。就有可能发生数据的不同步,从而引起错误,该文件的最后状态取决于写该文件的最后一个程序。但是对于有些应用程序如数据库,有时进程需要确保它正在单独写一个文件。为了向进程提供这种功能,Linux系统提供了记录锁机制。
文件锁包括建议锁和强制性的锁,建议性的在对文件进行锁操作的时候,会检测是否有锁存在,并且尊重已有的锁,在一般的情况下,内核和系统都不使用建议锁。强制性的锁是由内核执行的锁,当一个文件被上锁进行写入操作的时候,内核将阻止其他进程对其进行读写操作。采取强制性的锁对性能的影响很大,每次进行读写操作都必须检查是否有锁存在。
多个进程在一个给定的字节上可以有一把共享的读锁,但是在一个给定字节上的写锁则只能由一个进程单独使用。
当设置一个共享锁的时候(即读锁),第一个参数fd所指的文件必须以可读方式打开;当设置一个互斥锁(即写锁),第一个参数fd所指的文件必须以可写的方式打开。。当设置两种锁的时候,第一个参数所指向的文件必须以可读可写的方式打开。当进程结束或者文件描述符fd被close调用时,锁会自动释放。

文件的属性:对应struct stat这个结构体,我们不妨来看看其成员:
struct stat {
	dev_t        st_dev      //文件的设备编号
	ino_t        st_ino      //文件的i-node(i节点编号)
	mode_t       st_mode     //文件的类型和存取权限,其含义与chmod,open函数的mode参数相同
	nlink_t      st_nlink    //连到该文件的硬链接数目,刚建立的文件值为1
	uid_t        st_uid      //文件所有者的用户id
	gid_t        st_gid      //文件所有者的组id
	dev_t        st_rdev     //若此文件为设备文件,则为其设备编号
	off_t        st_size     //文件大小,按字节计算,对符号链接,该大小是其所指向的文件名的长度。
	blksize_t    st_blksize  //文件系统的I/O缓存区大小
	blkcnt_t     st_blocks   //占用文件区块的个数,每一区块大小通常为512字节
	time_t       st_atime    //文件最近一次被访问的时间
	time_t       st_mtime    //文件最后一次被修改的时间,一般调用write和utime时会改变。
	time_t       st_ctime    //文件最后一次被更改的时间,此参数只有在文件所有者,所属组,文件权限被改变的时候才更新。
};

对于time_t 类型的st_atime等时间成员,是可以用来比较时间的,但是老版的系统那是作为一个结构体,里面含有tv_sec和tv_nsec两个数据,对于我的操作系统头文件如此宏定义:
我们也就知道,如果想实现ls -c的话,只需要得到st_ctime然后进行大小比较即可。

对于文件的st_mode参数,查看源文件后列在下面:
 S_IFMT   0170000    文件类型的位遮罩
    S_IFSOCK 0140000    scoket
    S_IFLNK 0120000     符号连接
    S_IFREG 0100000     一般文件
    S_IFBLK 0060000     区块装置
    S_IFDIR 0040000     目录
    S_IFCHR 0020000     字符装置
    S_IFIFO 0010000     先进先出
    S_ISUID 04000     文件的(set user-id on execution)位
    S_ISGID 02000     文件的(set group-id on execution)位
    S_ISVTX 01000     文件的sticky位
    S_IRUSR(S_IREAD) 00400     文件所有者具可读取权限
    S_IWUSR(S_IWRITE)00200     文件所有者具可写入权限
    S_IXUSR(S_IEXEC) 00100     文件所有者具可执行权限
    S_IRGRP 00040             用户组具可读取权限
    S_IWGRP 00020             用户组具可写入权限
    S_IXGRP 00010             用户组具可执行权限
    S_IROTH 00004             其他用户具可读取权限
    S_IWOTH 00002             其他用户具可写入权限
    S_IXOTH 00001             其他用户具可执行权限 
    上述的文件类型在POSIX中定义了检查这些类型的宏定义:
    S_ISLNK (st_mode)    判断是否为符号连接
    S_ISREG (st_mode)    是否为一般文件
    S_ISDIR (st_mode)    是否为目录
    S_ISCHR (st_mode)    是否为字符装置文件
    S_ISBLK (st_mode)    是否为块特殊文件
S_ISFIFO (st_mode)   是否为管道
    S_ISSOCK (st_mode)   是否为socket
    若一目录具有sticky位(S_ISVTX),则表示在此目录下的文件只能被该文件所有者、此目录所有者或root来删除或改名。



文件的移动函数rename, 其只能把第二个参数所指定的文件名更改为命令行第三个参数指定的文件名。

下面对目录信息做以简单的介绍:
通过opendir函数可以返回DIR*形态的目录流。类似于文件操作中的文件描述符,其内容并不太重要。

此后再通过readdir可以得到一个struct dirent类型的结构体,结构体成员如下:
struct dirent{
long d_ino;   //目录的inode号
off_t d_off;    //目录文件开头至此目录进入点的位移
unsigned d_reclen; //d_name的长度
char d_name[NAME_MAX + 1] //d_name是指以NULL结尾的文件名(其中NAME_MAX宏定义于limits.h,大小为255)
}

最后通过closedir关闭目录

posted @ 2015-07-23 01:35  ChiLuManXi  阅读(243)  评论(0编辑  收藏  举报