Linux 编程学习笔记(二):文件I/O-write,read,lseek,dup,dup2
Linux 下文件I/O函数包括以下几个函数:
- open 和 openat
- creat
- close
- lseek
- read
- write
- dup,dup2
- sync,fsync,fdatasync
- fcntl,ioctl
其中,write,read,lseek函数的原型如下:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
off_t lseek(int fd,off_t off,int wherece);
write和read函数执行成功时返回成功写入/读取的字节数,若出错,返回-1.
lseek函数返回新的文件偏移量。
write、read函数的第一个参数是文件描述符,即open,creat函数成功执行返回的参数;第二个参数是指向缓冲区的指针,第三个参数是缓冲区大小。
在Linux系统里,每个打开的文件都有一个与其关联的“当前文件漂移量”。该量记录了文件的读写进度(已读写的字节数),它是一个非负整数,当打开一个文件时,除非指定了O_APPEND选项,否则该文件偏移量会被置为0.
lseek函数可以设定当前的文件漂移量。它把由文件描述符fd指定的文件的当前漂移量设置为相对于wherece位置的off字节。
其中的wherece指定了从哪里开始设置文件漂移量,可以是下列三个常量之一:
wherece常量 | 释义 |
---|---|
SEEK_SET | 将文件漂移量设置为距离文件开始处off个字节. |
SEEK_CUR | 将文件漂移量设置为距离当前位置处off个字节. |
SEEK_END | 将文件漂移量设置为距离文件尾处off个字节. |
在实际应用中,我们可以这样获取文件的大小:
//获取文件大小实例程序
//By LETSCOOL 一只巴扎黑
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> //提供了ssize_t,size_t等类型的定义
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd=open("testfile",O_RDONLY);
int size=lseek(fd,0,SEEK_END); //将文件偏移量设置到文件尾,同时返回新的偏移量(即文件大小,单位为字节)
printf("File size is %d MBytes\n",size/1024/1024);
close(fd);
}
有关文件系统的更多说明
在Linux系统内核里,系统为每个打开的文件分配了一个文件描述符。在内核里,每个进程有这么一个表,表里存放着打开的文件对应的文件描述符。文件描述符是一个int类型的数字,它对应着一个一张文件描述符表,表里包含有:当前的文件描述符和指向文件表项的指针。
内核为每个打开的文件维持着一个称为文件表项的数据结构,每个文件表项包含:
- 文件状态标志 (读、写、追加、同步和非堵塞等)
- 当前文件偏移量
- 指向该文件inode的一个指针
下图很好的说明了这种结构:
上图摘自《UNIX环境高级编程》一书。
对于多进程、多线程环境编程的更多说明
在多进程、多线程编程中,使用以下语句操作是不安全的:
lseek();
write/read();
因为在lseek执行完之后可能有其他程序改变了操作的文件导致相关属性发生了变化,为保证安全性,我们可以使用以下语句来实现操作:
#include <unistd.h>
ssize_t pread(int fd,void *buf,size_t nbytes,off_offset);
//RETURN: 读到的字节数,若已经读到了文件尾则返回0,若出错,返回-1.
ssize_t pwrite(int fd,const void *buf,size_t nbytes,off_offset);
//RETURN: 写入的字节数,若超出,返回-1.
pread、pwrite函数实际上是将lseek函数和write/read函数组合到了一起,实现了原子操作(在一个时刻操作,保证一致性)。
有关dup家族系统调用
dup、dup2、dup3()函数原型如下:
#include <unistd.h>
#include <fcntl.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
以上函数执行失败时返回-1,执行成功时返回新的文件描述符。
其中dup()只是简单地复制了一下文件描述符,并分配了一个未占用的最小数字文件描述符给新的文件描述符。
绕晕了?没关系,我们来看个栗子:假设当前进程只占用了4个文件描述符,其中3个是系统给开设的:标准输入0,标准输出1,标准错误输出2,还有用户自定义的文件描述符5.
当使用dup函数事,系统将会复制一个新的文件描述符,这个描述符仅仅fd标志(即文件描述符本身)有所不同,其他项都相同(文件表项:文件状态标志、偏移量、inode结点指针),而且系统会分配一个最小的未占用描述符给它。这里未占用的文件描述符有3,4,5,6,7,8……,所以会分配最小的3给新的文件描述符。
dup2的作用就更简单了,仅仅是在复制的同时指定了一个数字作为描述符,如果给出的指定数字被其他文件描述符占用,则这个函数会尝试关闭这个占用的文件描述符,其他的各项与dup函数相同。
下面给出了一个使用dup2的例子。
由于dup2函数只是赋予了新的文件描述符与之关联,并没有改变文件表项:文件状态标志、偏移量、inode结点指针,因此我们可以利用这个特性去重定向一些系统流:
int fd=open("test.txt",O_CREAT|O_WRONLY,0755);
dup2(fd,1); //将fd与标准输出关联。
printf("Hello");//此语句不会有任何输出,因为被重定向到了test.txt文件,此时输出应该在test.txt文件里。