文件IO
基本概念

文件描述符:通过文件描述符可以找到文件的inode,通过inode可以找到文件的数据块
文件指针:读和写共享一个文件指针,读或者写都会引起指针的变化
文件缓冲区:读或者写会先通过文件缓冲区,主要目的是为了减少对磁盘的读写次数(缓冲区在内存,内存访问速度远远大于磁盘)
进程的虚拟地址空间分为用户区和内核区,其中内核区是受保护的,用户不能直接对其进行读写操作。内核区主要是内存管理、进程管理、设备驱动管理、虚拟文件系统。
进程管理中有一个区域是PCB(结构体:task_struct)——进程控制块
相关函数
- open
int open(const char* filename, int flags);
int open(const char* filename, int flags, mode_t mode);
param:
filename: 文件名
flag: 打开方式 O_RDONLY-只读
O_WRONLY-只写
O_RDWR-可读可写
O_CREAT: 文件不存在时创建它,使用改参数时必须要加mode
文件的最终权限是:mode & (~umask)
可以用shell命令查看:umask
// 前三个必须有且只能有一个,剩下的可以是0或者多个,且只能是或
open(filename, O_RDWR | O_CREAT, 0777)
return:
成功:返回一个最小且未被使用的文件描述符
失败:返回-1,并设置errno
- close
int close(int fd);
param:
fd: 文件描述符
return:
成功:0
失败:-1,并设置errno
- read
ssize_t read(int fd, void* buf, size_t count);
param:
fd: 文件描述符
buf: 读出来的数据保存在buf中
count: buf缓冲区最大字节数
return:
>0: 读取到的字节数
=0: 文件读取完毕(读到文件结尾)
<0: 出错,并设置errno
阻塞与非阻塞
阻塞和非阻塞不是read函数的属性,而是文件本身的属性
- 对于普通文件,read函数是非阻塞的(read函数在读完文件内容后,再次调用read函数会立刻返回)。
- 对于设备文件(/dev/tty),read函数是阻塞的(read函数会一直等待输入)。
/dev/tty为终端字符文件,该文件是对键盘,显示器的抽象,向该文件写入,则写入内容将被显示在显示器;读该文件,则将从键盘获得输入
非阻塞的例子
hello.txt里面有以下字符:
hello world\n
int main()
{
int fd = open("hello.txt", O_RDWR | O_CREAT, 0755);
char buf[1024];
memset(buf, 0, sizeof(buf));
int n = read(fd, buf, sizeof(buf));
printf("n==[%d], buf==[%s]\n", n, buf);
memset(buf, 0, sizeof(buf));
n = read(fd, buf, sizeof(buf));
printf("n==[%d], buf==[%s]\n", n, buf);
close(fd);
return 0;
}
n==[11], buf==[hello word
]
n==[0], buf==[]
可以看到,一次性读完文件内容在调用read读取文件时,直接返回0,而不是阻塞在那里
阻塞的例子
int main()
{
int fd = open("/dev/tty", O_RDWR);
char buf[1024];
memset(buf, 0, sizeof(buf));
int n = read(fd, buf, sizeof(buf));
printf("n==[%d], buf==[%s]\n", n, buf);
close(fd);
return 0;
}
会阻塞在终端,一直等待用户的输入,如果用户不输入,则一直阻塞
- write
ssize_t write(int fd, const void* buf, size_t count);
param:
fd: 文件描述符
buf: 要写入的数据
count: buf中数据的长度
return:
成功: 写入的字节数
失败: 返回-1, 并设置errno
- lseek——移动文件指针
#include<sys/types.h>
#include<unistd.h>
off_t lseek(int fd, off_t offset, int whence);
param:
fd: 文件描述符
offset: 偏移量, 取决于whence
whence: SEEK_SET-设置偏移量
SEEK_CUR-设置相对当前的文件指针位置
SEEK_END-设置相对于文件数据结尾(文件数据长度)偏移量
return:
成功: 返回偏移量
失败: 返回-1, 并设置errno
- perror
// errno是每个进程都有的全局变量
#include<errno.h>
//perror()可以将errno对应的错误信息打印出来
perror(const char* msg);
such as----<msg: 错误信息>
- stat/lstat——获取文件属性
//加了const一定是出参,不加可能是出参也可能是出参
int stat(const char* pathname, struct stat* buf);
param:
pathname: 文件名
buf: stat结构体,出参
return:
成功: 0
失败: -1
stat结构体成员可以用: man 2 stat 来查看
/*
对于普通文件来说,lstat和stat函数一样
对于软链接文件来说,lstat获取的是链接文件本身的属性;stat获取的是链接文件指向的文件的属性
*/
- dup
int dup(int oldfd);
param:
oldfd: 要复制的文件描述符
return:
成功:返回最小且未被使用的文件描述符
失败:返回-1,并设置errno
//newfd和oldfd都指向了同一个文件,内核会维护一个计数,这个计数会加1,只有当计数为0时,文件才会被关闭。
- dup2
int dup2(int oldfd, int newfd);
param:
oldfd: 原来的文件描述符
newfd: 新的文件描述符
return:
成功:将oldfd复制给newfd,两个文件描述符指向同一个文件
失败:返回-1,并设置errno
当调用dup2函数后,若newfd已经打开了一个文件,则先关闭这个文件,然后newfd指向了和oldfd相同的文件
可以利用dup2实现文件重定向:
dup2(fd, STDIN);
- fcntl——改变已经打开的文件属性
int fcntl(int fd, int cmd, ...);
param:
fd: 文件描述符
cmd: F_DUPFD-复制文件描述符,同dup
F_GETFL-获取文件描述符的flag属性
F_SETFL-设置文件描述符的flag属性
return:
cmd为F_DUPFD时:返回新的文件描述符
cmd为F_GETFL时:返回文件描述符的flag值
cmd为F_SETFL时:返回0
失败:返回-1,并设置errno
复制文件描述符:newfd = fcntl(fd, F_DUPFD, 0);
获取文件的属性标志:flag = fcntl(fd, F_GETFL, 0);
设置文件状态标志: flag = flag | O_APPEND;
fcntl(fd, F_SETFL, flag);
- opendir, readdir, closedir
读取目录的一般步骤:
DIR *pDir = opendir("dir");
while((p=readdir(pDir)) != NULL) { }
closedir(pDir);

本文深入介绍了Linux系统中文件I/O的基本概念,包括文件描述符、文件指针和文件缓冲区。详细阐述了open、close、read、write等核心函数的使用,并讨论了阻塞与非阻塞的概念。举例说明了在不同文件类型(如普通文件和设备文件/dev/tty)上进行读写操作的区别。此外,还提到了文件属性获取、文件描述符复制以及其他相关系统调用如lseek、fcntl等。
1986

被折叠的 条评论
为什么被折叠?



