学习Linux系统编程-Day(10)

1.有些时候关于文件的重定向,我们想让它们共享同一套打开文件。我们这样做可能是为了临时保存当前某个文件描述符,也有可能是担心不同的两个读写指针指向同一文件会导致写覆盖。在需要这样做时,我们可以使用复制文件描述符这种技术来将不同的文件描述符指向同一打开文件表项(就像下图1号和20号文件描述符一样)。
在这里插入图片描述

dup,dup2,dup3这三个系统调用就是用来实现文件描述符复制的,就像名称那样,它们的功能逐渐增强。首先是最简单的dup系统调用:
在这里插入图片描述
要使用dup系统调用,必须添加头文件<unistd.h>,此系统调用接受并复制一个旧的文件描述符,成功时返回新的文件描述符。结合之前写过的,文件描述符的分配由内核来控制,每次返回的是当前进程未被占用的的文件描述符的最小值,我们可以推测并控制这个复制得到的新的文件描述符的值。

接下来是dup2系统调用,原型如下:
在这里插入图片描述
dup2系统调用相较于dup系统调用的进步之处在于,它可以由用户自己指定想要返回的文件描述符是什么,也就是参数中的newfd。那么可以想见,如果我们想要返回的文件描述符已经被打开,那么首先就得先关闭它,再分配给我们。此函数的危险之处也正在于此,dup2在关闭已占用的文件描述符时如果出现错误,它会默认忽略掉,所以在使用dup2时,比较保险的一种范式是自行显式的先关闭掉想要分配但是已占用的文件描述符,再使用dup2来复制文件描述符。

关于dup2还有其他需要补充的地方,因为它涉及到了两个文件描述符,所以会有一些特殊情况发生。第一种就是oldfd并非有效的文件描述符,这时候dup2将返回EBADF错误(oldfd描述符无效),第二种是oldfd和newfd是相同的,那么此时不需要任何额外操作(不需要关闭再打开),直接返回即可。

最后一个也是最高级的系统调用是dup3,这个函数在原有dup2的基础上又添加了对文件描述符标志的控制,原型如下:
在这里插入图片描述
看最上面那张图片,虽然复制文件描述符指向了同一打开文件表项,但文件描述符标志却是独立的,dup3就是做这个的,它可以控制复制的文件描述符的标志字。比如close-on-exec标志,在子进程继承了父进程的文件标识符或其他情况下,建立此标志字将会在执行时关闭继承的文件描述符,从而保证安全性,这个话题很深, 后面再填坑。

2.使用pwrite和pread可以在文件的指定偏移处进行读写操作。乍听起来好像这个功能和先lseek,再read或者write可以达到的功能差不多。在编写单线程情况下确实如此,但是多线程场景下就大不相同了。同一进程下的多个线程是共享文件描述符表的,那么自然也是都可以访问到每个打开文件表项中的文件读写指针的,对这些指针位置的修改和访问会导致竞争状态的出现,这也就是pread和pwrite出现的本质原因。详情参见上一篇博客的竞争状态中O_APPEND例子:学习Linux系统编程-Day(9)

一言以蔽之,pread和pwrite将文件指针定位读写包括最后的恢复指针位置等操作打包成了一套原子操作,从而保证了多线程场景下的安全性,函数原型如下:
在这里插入图片描述
调用pread和pwrite还需要注意,它们完成了在指定偏移处读写数据的功能之后,还会将读写指针再放回调用之前的位置,并不是放在读写完成的位置就返回了,这也是非常非常安全的一种操作手段。

3.Linux系统调用中还有一类比较特殊的I/O模式,叫分散输入和集中输出(Scatter-Gather I/O)。这组操作提供了一次性对多个缓冲区的读写操作,并能保证操作的原子性,是一种便捷而又高效的I/O方式,和这组操作相关的系统调用如下:
在这里插入图片描述
fd是要读写的文件描述符,第二个参数是指向一种叫做I/O向量的结构体的指针,第三个参数是I/O向量结构体中的元素个数。I/O向量结构体的定义如下:
在这里插入图片描述
一个I/O向量其实就是保存的是一个缓冲区信息,iov_base指向的就是缓冲区的起始地址,iov_len就是缓冲区的大小(按字节计),返回值在成功状态下是读取或写入的字节数。所以参数中的iov可以用图例解释如下:
在这里插入图片描述

这样一来,SG I/O的作用就比较好说清楚,readv就是从fd中读取一片连续的数据,从iov[0]开始依次填满开辟的缓冲区,当然如果数据不够可能就填不满所有的缓冲区,所以接收该函数返回值查看到底读入了多少字节的数据是非常有必要的。因为readv操作的原子性,在此阶段其他所有共享同一打开文件句柄的线程均无法对实际的连续读取操作造成干扰。

反过来,writev就是将多个缓冲区中的内容拼接在一起,然后一次性连续的写入文件,拼接操作从iov[0]开始,按数组顺序进行,返回值为实际写入的字节数,其他不再赘述。

4.将2,3中的系统调用结合起来,即以SG I/O的方式任意offset的位置开始读写数据,读写完成后将读写指针复位的系统调用在Linux 2.6.30中也做出了支持,也就是preadv和pwritev:
在这里插入图片描述
这两个函数完全集中了2,3中系统调用的所有特性。

扫描二维码关注公众号,回复: 13129852 查看本文章

5.truncate和ftruncate用来改变(截断或增长)文件的数据长度。系统调用原型如下:
在这里插入图片描述
如果length小于文件实际长度,那么此系统调用会截断文件到length长度,如果大于那么将会在文件后面补充空字节文件空洞。以上两个函数访问文件需要的参数不同,truncate需要的是文件路径参数,而ftruncate需要的是文件描述符(所以需要首先使用某些系统调用如dup、open等获取一个文件描述符),但无论如何这两者都需要对文件有写入权限,且这两个系统调用最终不会修改文件偏移量

猜你喜欢

转载自blog.csdn.net/zzy980511/article/details/115425822