UNIX高级编程:第3章——文件IO

请移步到:

http://note.youdao.com/noteshare?id=2c4b923d7319c4767abc157b7bfeebc6&sub=C83577B30ACA4E81A091EE83228865C2

3.1

只要涉及在多个进程间共享资源,原子操作的概念就变得非常重要。

 

3.2 文件描述符

对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。

当读或写一个文件时,使用 open 或 creat 返回的文件描述符标识该文件,将其作为参数传送给 read 或 write。

按照惯例,UNIX 系统 shell 使用文件描述符 0 与进程的标准输入相关联,文件描述符 1 与进程的标准输出相关联,文件描述符 2 与进程的标准出错输出相关联。

/usr/include/unistd.h定义了

 

 

3.3open 函数和openat

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

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

open:

 

 

 

 

openat:

fd参数把open和openat函数区分开,共有3种可能性。

(1) path = 绝对路径名

fd参数被忽略, openat函数就相当于 , open函数。

(2) path = 相对路径名

fd参数指出了相对路径名在文件系统中的开始地址。fd参数是通过打开相对路径名所在的目录来获取。

(3) path = 相对路径名 fd参数具有特殊值AT-FDCWD

在这种情况下,路径名在当前工作目录中获取, openat函数在操作上与open函数类似。

 

解决两个问题。

第一:

让线程可以使用相对路径名打开目录中的文件,而不再只能打开当前工作目录。在第11章我们会看到,同一进程中的所有线程共享相同的当前工作目录,因此很难让同一进程的多个不同线程在同一时间工作在不同的目录中。

第二:

可以避免time-of-check-to-time-of-use (TOCTTOU)错误。(搞不懂)

 

引入openat是方便一个进程内的各线程可拥有不同的当前目录,传统的chdir会影响整个进程,而使用openat只需要每个线程在初始化时打开一个目录(调用open),然后就可以以openat在“当前目录”操作文件了,如:

int dirfd = open("/tmp"); // 相当于chdir到“/tmp”

int filefd = openat(dirfd, "myfile"); // 在/tmp/目录下打开“myfile”文件

参考:

https://blog.csdn.net/wang1902568721/article/details/47796173

文件名和路径名截断

由于文件名超出一定范围会自动截断,因此要注意,

但是本Linux的最大文件名为255个字符,路径最大为4096个字符

谁会定义那么长的名字

我们可以用 fpathconf或pathconf来查询目录具体支持何种行为,到底是截断过长的文件名还是返回出错。

printf("%ld \n",fpathconf(1, _PC_NAME_MAX));

printf("%ld \n",fpathconf(1, _PC_PATH_MAX));

 

3.4函数creat

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

 

 

3.5函数close

 

 

3.6函数lseek

 

currpos = lseek(fd, 0, SEEK_CUR);

这种方法也可用来确定所涉及的文件是否可以设置偏移量。如果文件描述符指向的是一个管道、FIFO或网络套接字,则1seek返回-1,并将errno设置为ESPIPE。

 

通常,文件的当前偏移量应当是一个非负整数,但是,某些设备也可能允许负的偏移量。

但对于普通文件,其偏移量必须是非负值。

因为偏移量可能是负值,

所以在比较seek的返回值时应当谨慎,

不要测试它是否小于0,而要测试它是否等于一1。

 

空洞文件:

文件中的空洞并不要求在磁盘上占用存储区。具体处理方式与文件系统的实现有天,当定位到超出文件尾端之后写时,对于新写的数据需要分配磁盘块,但是对于原文件尾端和新开始写位置之间的部分则不需要分配磁盘块。

意思就是那些不知道是啥的空洞符号不会占用内存空间

 

注意:自己的实验结果显示中间的空洞符号会占用空间,

 

刚好30字节

 

其实是我错了,主要原因是我给的字符数太少了,不能突显出差别,把字节数变成100020后就有差别了

 

 

3.7函数read

 

 

 

3.8函数write

 

 

3.9I/O效率

当存取很多字节的字符时:

BUFFSIZE应该取磁盘块长度,即一个块有多少个字节,这样的效率最高

 

 

3.10文件共享

 

1.在完成每个write后,在文件表项中的当前文件偏移量即增加所写入的字节数。如果这导致当前文件偏移量超出了当前文件长度,则将i节点表项中的当前文件长度设置为当前文件偏移量(也就是该文件加长了)。

2.如果用OAPPEND标志打开一个文件,则相应标志也被设置到文件表项的文件状态标志中。每次对这种具有追加写标志的文件执行写操作时,文件表项中的当前文件偏移量首先会被设置为i节点表项中的文件长度。这就使得每次写入的数据都追加到文件的当前尾端处。

3.若个文件用lseek定位到文件当前的尾端,则文件表项中的当前文件偏移量被设置为i节点表项中的当前文件长度(注意,这与用OAPPEND标志打开文件是不同的,详见3.11节)。

4.lseek函数只修改文件表项中的当前文件偏移量,不进行任何IO操作

 

注意:

文件描述符标志和文件状态标志在作用范围方面的区别,

前者只用于一个进程的一个描述符

后者则应用于指向该给定文件表项的任何进程中的所有描述符。

在3.14节说明fcntl函数时,我们将会了解如何获取和修改文件描述符标志和文件状态标志。

本节前面所述的一切对于多个进程读取同一文件都能正确工作。每个进程都有它自己的文件表项,其中也有它自己的当前文件偏移量。但是,当多个进程写同一文件时,则可能产生预想不到的结果。为了说明如何避免这种情况,需要理解原子操作的概念。

 

常见的有3种文件共享的情况:

第一种是同一个进程中多次使用open打开同一个文件,

 

第二种是在不同进程中去分别使用open打开同一个文件(这时候因为两个fd在不同的进程中,所以两个fd的数字可以相同也可以不同),

 

第三种情况是后面要学的,linux系统提供了dup和dup2两个API来让进程复制文件描述符。

 

第四种:在父进程中,用O_APPEND 打开文件,然后再创建进程,这种结果和第三种一样,就是接续写

第五种:在子进程和父进程中分别打开,且用O_APPEND 打开文件,这种结果和第三种一样,就是接续写

第四种和第五种验证了上面的注意

 

3.11原子操作——有哪些

1.追加到一个文件

 

 

 

2.pread和pwrite ——随机读写函数

// 返回值: 读到的字节数,若已到文件结尾则返回0,若出错返回-1 ssize_t pread( int filedes, void *buf, size_t nbytes, off_t offset); // 返回值: 若成功返回已写的字节数,若出错返回-1 ssize_t pwrite( int filedes, const void *buf, size_t nbytes, off_t offset);

从文件开头偏移offset字节,然后进行读写

这个函数时原子操作,因为它实现了两个功能(lseek和read或write),本来要两个函数来实现这个功能,变成了只要一个函数

区别:

 

 

3.创建一个文件

open函数的O-CREAT和0_EXCL结合也是原子操作

如果不用这个方法,则步骤为

1.先打开这个文件,判断是否存在

2.再创建这个文件

总结:

原子操作其实是由多个操作组成的一个操作。

如果该操作原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。

 

3.12函数dup和dup2

int dup( int oldfd);

int dup2( int oldfd, int newfd);

两函数的返回值;若成功,返回新的文件描述符;若出错,返回-1

dup:返回的新文件描述符一定是当前可用文件描述符中的最小数值。

dup2:可以用fd2参数指定新描述符的值。

如果fd2已经打开,则先将其关闭。

如若fd等于fd2,则dup2返回fd2,而不关闭它。

否则, fd2的FD CLOEXEC文件描述符标志就被清除,这样fd2在进程调用exec时是打开状态。

 

 

3.13函数 sync,fsync 和fdatasync

void sync( void);

int syncfs( int fd);

int fdatasync( int fd);

 

3.14函数fcntl

int fcntl( int fd, int cmd, ... /* arg */ );

 

主要作用:改变已经打开的文件属性

 

 

 

文件描述符标志(close_on_exec):

仅仅是一个标志,当你fork了一个子进程,然后在子进程中调用了exec函数时就用到了该标志,就是一个整数

意义是:执行exec前是否要关闭这个文件描述符.

文件状态标志:

就是可读,可写,接续写那些东西

 

 

3.15函数ioctl

int ioctl( int d, int request, ...);

ioct函数一直是IO操作的杂物箱,不能用本章中其他函数表示的IO操作通常都能用ioctl表示。终端IO是使用ioctl最多的地方

如:

磁带操作使我们可以在磁带上写一个文件结束标志、倒带、越过指定个数的文件或记录等,用本章中的其他函数(read, write, 1seek等)都难于表示这些操作,所以,对这些设备进行操作最容易的方法就是使用ioctl.

暂时不要了解,到驱动再说

3.16 /dev/fd

只用了解这些就可以了

 

 

习题:3.3

fd1 = open(path, oflags);

fd2 = dup(fd1);

fd3 = open(path, oflags);

每次调用open函数就分配一个文件表项,如果两次打开的是相同的文件,则两个文件 ,表项指向相同的v节点。调用dup引用已存在的文件表项(此处指fd1的文件表项),见图C-1.当F-SETFD作用于fd1时,只影响fd1的文件描述符标志;

F-SETFL作用于fd1时,则影响fd1及 fd2的文件描述符标志。

 

习题:3.6

1.仍然可以用lseek和read函数读文件中任意一处的内容。

2.但是write函数在写数据之前会自动将文件位移量设置为文件尾,所以写文件时只能从文件尾开始,不能在任位置。

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值