探索Linux:文件I/O

本篇文章为总结使用Linux文件I/O的心得体会

文件描述符(file descriptor, fd)

文件描述符为一非负整数, 所有执行I/O操作的系统调用都文件描述符来指代打开的文件。而在Unix中文件不仅仅指Windows中的文件, 它包括以下类型:

  • 普通文件(regular file)
  • 目录
  • 符号链接
  • 面向块的设备文件
  • 面向字符的设备文件
  • 管道(pipe)和命名管道(named pipe)
  • 套接字(socket)

其中设备文件与I/O设备以及集成到内核中的设备驱动程序相关。而管道和套接字是用于进程间通信的特殊文件。

标准文件描述符:

文件描述符用途POSIX名称stdio流
0标准输入STDIN_FILENOstdin
1标准输出STDOUT_FILENOstdout
2标准错误STDERR_FILENOstderr

以上三种文件描述符, shell启动时将自动打开, 凡是利用shell启动的程序将继承文件描述符的副本, 通常这三个文件描述符始终都是打开的。

文件描述符与打开文件之间的关系

多个文件描述符可同时指向一个打开文件, 且这些文件描述符可在不同或相同的进程中打开。

操作文件

需要强调的是, 对于已打开的每个人间, 内核都维护有一个文件偏移量, 这决定了下一次读或写操作的起始位置。 读和写操作会隐式修改文件偏移量。

创建文件

int fd_create = open("fileio.log", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
if (-1 == fd_create){
    printf("create file failed!");
}

读文件

以下代码展示了读取一个已经存在的文件:

int fd_create = open("fileio.log", O_RDONLY, S_IRUSR | S_IWUSR);
if (-1 == fd_create){
    printf("create file failed!");
}

char szRead[128] = {0};
read(fd_create,szRead, sizeof(szRead));

另外,对于普通文件, 一般情况下,一次read()调用所读取的字节数小于请求的字节数, 很有可能时因为当前读取位置靠近文件尾部

如果需要在指定位置读取, 可使用lseek, 基本用法如下:

// 在文件开始第三个字节处开始读取(当whence为SEEK_SET时, offset不能为负值)
lseek(fd_create, 3, SEEK_SET);
// 在文件末尾处开始读取
lseek(fd_create, 0, SEEK_END);
// 从文件倒数第三个字节开始读取
lseek(fd_create, -3, SEEK_END);
// 在当前位置倒数第三个字节开始读取
lseek(fd_create, -3, SEEK_CUR);

char szRead[128] = {0};
read(fd_create,szRead, sizeof(szRead));

注意,使用lseek时, offset为负数仅仅用于whence为SEEK_END或SEEK_CUR时, 不能用于SEEK_SET,SEEK_SET时, offset必须为正整数。

同时可以利用pread在指定位置进行读取, 基本用法如下:

char szRead[128] = {0};
pread(fd_create, szRead, sizeof(szRead), 3);

pread仅仅在指定位置读取, 并未更改其当前偏移位置。

pread 相当于将以下代码纳入了同一原子操作:

off_t orig = lseek(fd, 0, SEEK_CUR);
lseek(fd, offset, SEEK_SET);
read(fd, buf, len);
lseek(fd, orig, SEEK_SET);

此API为多线程调用提供了用武之地。 相似的接口还有pwrite()

写文件

以下代码演示了打开已有文件后, 将其清空(O_TRUNC)后往文件中写入内容:

int fd_create = open("fileio.log", O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR);
if (-1 == fd_create){
    printf("create file failed!");
}

char szRead[128] = "I Love Programming!";
write(fd_create,szRead, strlen(szRead));

在指定位置进行写文件类似于在指定位置读文件, 可利用lseek结合write, 也可使用pwrite()

需要强调一点的是, 在指定了O_APPEND标志位使用lseek然后调用write, 将不会如预期在lseek指定位置进行写入操作。为什么会出现这样的情况呢? 因为当指定O_APPEND标志位后, Linux将会把文件偏移量的移动和数据写操作纳入同一原子操作。

其他标志位
如何以独占地方式创建一个文件?

当同时指定O_EXCLO_CREATE 作为open()的标志位时,如果open的文件已经存在将返回一个错误。这保证了对文件是否存在的检查和创建文件属于同一原子操作

正确使用读写标志位

O_RDWR 不等同 O_RDONLY | O_WRONLY

打开文件时清空文件

使用O_TRUNC标志位

以非阻塞的方式打开文件

在打开文件时指定O_NONBLOCK标志, 此标志仅能用于 管道、FIFO、套接字、设备(比如终端、伪终端)都支持非阻塞模式。对于管道和套接字因其文件描述符不是由open得到, 所以设置其O_NONBLOCK只能使用fcntl()。普通文件指定O_NONBLOCK将会被忽略, 因为普通文件的操作因为内核缓冲区保证了其不会阻塞

删除文件

unlink接口提供删除文件的一种系统调用, 此调用是为了减少文件链接数, 删除了相应的目录项。 只有当链接数为0时, 文件才被真正删除。

文件控制操作: fcntl()

fcntl() 系统调用对一个打开的文件描述符执行一系列控制操作

#include <fcntl.h>

// Return on success depends on cmd, or -1 on error
int fcntl(int fd, int cmd, ...);

获取文件打开标志

int flags = fcntl(fd, F_GETFL);
if (flags == -1){
	printf("occur error!");
}

// 判断文件是否以同步方式打开
if (flags & O_SYNC){
    printf("writes are synchronized\n");
}

int accessMode = flags | O_ACCMODE;
if (accessMode == O_WRONLY || accessMode == O_RDWR){
    printf("file is writable\n");
}

可以使用fcntl() 的 F_SETFL 命令来修改打开文件的某些状态标志。 允许更改的标志有O_APPEND、O_NONBLOCK、O_NOATIME、O_ASYNC和O_DIRECT。 系统将忽略对其他标志的修改操作。

例如, 对打开的文件添加O_APPEND标志:

int flags = fcntl(fd, f_GETFL);
if (flags == -1){
    printf("occur error!\n");
}
flags |= O_APPEND;
fcntl(fd, F_SETFL, flags);

临时文件

创建临时文件常用的两个系统调用为mkstemp()tmpfile()

基于调用者提供的模板, mkstemp()函数生成唯一文件名并打开该文件, 并返回文件描述符:

#include <stdlib.h>

// Returns file descriptor on success, or -1 on error.
int mkstemp(char * template);

mkstemp() 函数的示例代码:

char templates[] = "tmp/somestringXXXXXX";
int fd = mkstemp(templates);
if (fd ==-1){
    printf("mkstemp failed!\n");
}

printf("Generated filename was: %s\n", templates);
// 文件名将马上消失, 但是文件需要等到调用close()后才会被删除
// 同时进程消亡时, fd亦会被自动关闭, 文件自然被删除。
unlink(templates);

close(fd);

另一个函数tmpfile()也会创建一个名称唯一的临时文件, 并以读写的方式打开。 并且返回一个文件流供fread、fwrite等接口调用。 文件流关闭后将自动删除临时文件。 因为tmpfile()在内部打开文件后调用了接口unlink()来删除文件名

应尽量避免使用tmpnam()、tempnam()、mktemp()。尽管这些接口有也能生成唯一的文件名, 但存在安全漏洞。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值