Apue学习:File I/O

来源:互联网 发布:eve1200炮数据 编辑:程序博客网 时间:2024/06/08 13:16

一些概念

文件的基本类型与权限

首先要知道LINUX中一切都是文件,文件有好多种类型,一般有普通文件,目录文件,连接文件。一个文件一般有9个权限,分别是所有者的权限,群组权限,其他人的权限。
文件还有一些时间参数:access time, modification time , change-status time。
access time指的是文件的最近读取时间。
modification time指的是文件内容的最近修改时间。
change-status time值的是文件权限的最近修改时间。

文件的类型与权限属性一般在mode_t这个类型里面。可以用
S_ISXXX宏来判断一个文件的类型,这个宏的参数是mode_t类型。

文件描述符,打开文件表,inode

  • inode,一个inode对应于一个物理上的文件。它包括了文件的大小,所有者ID,群组ID,读写权限,3个时间戳,连接数,文件数据的block位置
  • 打开文件表,由内核维护。一个打开文件表中会有文件的一些信息,包括对于这个文件我的偏移是多少等等。有了这个打开文件表,这样每个进程都可以有不同的文件偏移了。
  • 文件描述符,有进程维护,他包括一些文件描述符的flag,以及指向那个打开文件表。

open函数

int open(const char *path, int oflag, ... /*mode_t mode */)

path:指文件的位置
oflag:有很多,一般有5个是互斥的

  • O_RDONLY
  • O_WRONLY
  • O_RDWR
  • O_EXEC
  • O_SEARCH
    上面这五个有且只能有一个出现在oflag中

下面再说以其他比较重要的参数:

  • O_APPEND:指的是每次调用write函数时,都会从文件的inode中读取当前文件大小,然后在文件末尾去写。
  • O_CLOEXEC:主要是为了进程调用exec函数族之后,是否要关闭进程打开的这个文件。
  • O_CREAT:没有改文件就去创建一个。
  • O_SYNC:每次调用write时,都会确保文件都写入磁盘了,write才返回。
  • O_TRUNC:将打开文件的大小设置为0

注意点:
open返回的是当前可以使用的最小的文件描述符。

creat函数

int creat(const char *path, mode_t mode);/* 这个函数返回的是一个write-only的文件描述符,出错返回-1。 mode表示的是文件的那9个权限*/

lseek函数

用途:用于定位一个打开的文件

off_t lseek(int fd, off_t offset, int whence)/* whence:     SEEK_SET:从头开始+offset     SEEK_CUR:从当前位置+offset     SEEK_END:从文件结束位置+offset 返回的是新的offset;失败返回-1*/

注意点

  1. 可以在SEEK_END后加一个大的offset,然后写入,这样会造成文件黑洞。文件黑洞不占用磁盘内容,但是会记录在文件大小中。
  2. 如果要每次都在文件末尾写,并且是在多线程环境中,最好设置文件的O_APPEND属性,因为虽然会有lseek定位到文件末尾,但是此时在调用write会从打开文件表中寻找文件偏移。具体看write函数。
  3. 如果有两个process打开了同一文件,那么内核会有两个打开文件表,这两个打开文件表指向同一个文件。如下图:
    这里写图片描述

write函数

sszie_t write(int fd, const void *buf, size_t nbytes);/* 返回的是你写入的数量 */

注意点:
1.write一个文件时,文件表中的file offset会移动。如果write一个文件时,他的offset超过了文件的大小,那么就会设置该文件inode中的大小为当前offset大小。
2.如果文件的flag是O_APPEND,那么每次写的时候文件表中的offset都会设置为inode中的文件大小。
3.如果我们是从lseek获取文件的末尾时,也会从inode获取文件大小,并且设置文件表中的对应值。但是如果是多线程时,lseek之后发生进程切换,并且另一个进程也写到这个文件,就会发生第一个进程写的时候写的不是文件末尾。

例子:

if(lseek(fd, 0, SEEK_END) < 0)    err_sys("lseek error");if(write(fd, buf, 100) != 100)    err_sys("write error");

上面的这个程序,如果在多线程环境中,可能会出现bug,如果有A执行了lseek,定位到了文件末尾。然后进程调度,现在轮到B执行且写入到了这个文件中,这时再由A执行时,A对于这个文件的偏移就不是文件末尾了。这一点一定要理解。

sync与fsync函数

int fsync(int fd); /* 成功返回0, 失败返回-1 */void sync(void);

kernel会维护一个buffer,把一些数据保存在这个buffer中。kernel会把所有延迟写的数据块在buffer已经要回收时写到硬盘中。
注意点:
sync 只是下达命令表示所有在排队的写数据要写到磁盘了,然后就返回,再由内核负责把数据写到磁盘,但是后面这个过程sync就不知道了。
fsync,就不一样了,他表示的是我要等到我这个文件写到磁盘,我再返回。通过fsync,文件的属性也会同时被更新

fcntl函数

说明:
fcntl函数可以改变一个打开文件的属性
注意要设置文件的属性时,若果想要有以前的属性,那么要先获得,然后再去设置,因为设置是把文件一下都设为那个属性

int fcntl(int fd, int cmd, .../* int arg */); /* 返回值依赖于cmd */

使用环境即对应的cmd:
这里写图片描述

cmd的取值即说明:
1.F_DUPFD与F_DUPFD_CLOEXEC

F_DUPFD会把新的文件描述符中的flags字段的FD_CLOEXEC属性清掉,而F_DUPFD_CLOEXEC不会

2.F_GETFD, F_SETFD

用来获取以及设置文件描述符的flags
F_GETFD与F_SEETFD都是与file descriptor flags有关,注意区分

3.F_GETFL, F_SETFL

F_GETFL与F_SETFL都是与file status flags有关
上面5个属性是互斥的,即是说有且只能有一个,所以测试时要先使用O_ACCMODE与val(我们获得的file status flag)&一下.
如图:
这里写图片描述

创建文件的4个主要步骤

1.先找到一个可用的inode,并得到他的编号:inode number
2.然后给文件分配block
3.在inode的磁盘分区表中填入我们分配的block
4.将”inode number, 文件名”填入目录文件的block中。

注意:
inode是识别文件的唯一标识,所有的文件最终都是通过inode 来识别的。

磁盘的抽象过程

1.从磁盘到分区

一个磁盘可以被划分成不同的区域,每个不同的区域可以看成是一个独立的磁盘。

2.从磁盘到块文件

磁盘的上的每个磁道可以划分成扇区,这个扇区就是一个块,它是一个基本的磁盘存储单元,一般大小为512B。我们可以给这些扇区编号,形成块序列

3.从块序列到三个区域的划分

这三个区域为:
super block, 磁盘属性(比如inode table ,block table), data(数据区)

设备文件

与磁盘文件的不同

  • 磁盘文件:由字节组成
  • 设备文件:是一种链接,设备文件的i-node存储的是指向内核子程序的指针(这个内核子程序就是设备驱动器)。设备分为主设备号与从设备号,主设备号就代表了这一类设备,而从设备指的是具体的是哪个设备。使用ls查看一个设备文件,可以得到xx,yy:xx就是主设备号,yy表示从设备号。

磁盘文件的连接

所谓连接就是文件打开表的属性。
其中重要的有O_SYNC,O_APPEND。

注意:
O_CREAT与O_EXCL组合可以消除一个竞争:
如果两个进程同时创建相同的文件,一般操作是先用stat查看文件是否存在,然后调用creat。O_EXCL与O_CREAT组合可以让这两个操作变成一个原子操作。

0 0
原创粉丝点击