目录
文件IO
文件描述符
以文件描述符指代打开的文件,非负整数。一个进程启动之后,会默认打开三个文件描述符:0-标准输入,1-标准输出,2-标准错误
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
操作系统为每个进程维护一个文件描述符表,新打开返回文件描述符表中未使用的最小文件描述符,调用open函数可以打开或创建一个文件,得到一个文件描述符。
open函数和close函数
open函数
函数描述:
打开或创建一个文件
函数原型:
int open(const char* pathname,int flags);
int open(const char* pathname,int flags,mode_t mode);
函数参数:
pathname:要打开或创建的文件路径
flags:文件访问方式 ,有一些常数值可供选择,可以同时选择多个,用按位或(|)连接起来
必选项:以下三个常数中必须指定一个,且只允许指定一个
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:可读可写打开
常用选项:可以同时指定0个或多个,和必选项按位或起来使用
O_APPEND:追加,如果文件已经有内容了,这次打开文件所写的数据附加到文件的末尾,而不是覆盖原来的内容。
O_CREAT:若文件不存在则创建它,使用此选项需要提供第三个参数mode,表示该文件的访问权限。
O_EXCL:如果同时指定了O_CREAT,并且文件存在,则返回出错
O_TRYNC:如果文件已经存在,将其长度截断为0字节。
O_NONBLOCK:设置非阻塞模式
普通文件默认是不会发生阻塞的,内核缓存区保证了普通文件I?O不会阻塞,打开普通文件一般会忽略此参数。
设备、管道和套接字默认是阻塞的,以O_NONBLOCK方式打开可以做非阻塞I/O(Nonblock I/O)
函数返回值:
成功:返回一个最小且未被占用的文件描述符
失败:返回-1,并设置errno值
close函数
函数描述:
关闭文件
函数原型:
int close(int fd)
函数参数:
fd:文件描述符
函数返回值:
成功:返回0
失败:返回-1,并设置errno值
当一个进程终止时,内核对该进程所有尚为关闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件。但是对于一个长年累月运行的程序(比如说网络服务器),打开的文件描述符一定记得要关闭,否则随着打开的文件越多,会占用大量的文件描述符和系统资源。
例如用open函数来创建一个文件
其中io.c的代码如下:
#include<stdio.h> #include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
int main(int argc, char* argv[])
{
int fd=open("./a.txt",O_RDWR | O_CREAT,0664);
printf("fd = %d",fd);
close(fd);
return 0;
}
执行这段代码,可以看到得到这个文件的文件描述符等于3,同时在当前目录下生成了一个a.txt文件。
read函数和write函数
read函数
函数描述:
从打开的设备或文件中读取数据
函数原型:
ssize_t read(int fd,void *buf,size_t count);
函数参数:
fd:文件描述符
buf:读取的数据保存在缓冲区buf中
count:buf缓冲区存放的最大字节数
函数返回值:
>0:读取到的字节数
=0:读取文件完毕
-1:读取出错,并设置errno
例如先向a.txt中写入hello word中间有换行,在read函数里面读取,运行结果如下图
read.c的代码如下:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
int main(int argc, char* argv[])
{
int fd=open("./a.txt",O_RDONLY);
char buf[1024];
int ret=read(fd,buf,sizeof(buf));
buf[ret]='\0';
printf("buf = %s",buf);
close(fd);
return 0;
}
write函数
函数描述:
向打开的设备或文件中写入数据
函数原型:
ssize_t write(int fd,const void* buf,size_t count);
函数参数:
fd:文件描述符
buf:缓冲区,要写人文件或设备的数据
count:buf中数据的长度
函数返回值:
成功:返回写入的字节数
失败:返回-1并设置errno
例如向a.txt中写入一个字符串,这里以追加的形式写入,然后查看a.txt中的内容,发现成功写入到a.txt中
write.c的代码如下
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
int main(int argc, char* argv[])
{
int fd=open("a.txt",O_RDWR | O_APPEND);
char buf[1024]="write sucessful";
int ret=write(fd,buf,sizeof(buf));
return 0;
}
lseek函数
所有打开的文件都有一个文件偏移量(current file offset),也叫读写偏移量和指针。文件偏移量通常是一个非负整数,用于文件开始出到文件当前位置的字节数(下一个read()和write()操作的文件起始位置)。文件的第一个字节的偏移量为0。
文件打开时,会将文件偏移量设置为指向文件开始(使用O_APPEND除外),以后每次read()和write()会自动对其调整,以指向已读或已写数据的下一个字节。因此连续的read()和write()将按顺序递进,对文件进行操作。
使用lseek函数可以改变文件的偏移量。
函数描述:
移动文件指针
头文件:
#include<sys/type.h>
#include<unistd.h>
函数原型
off_t lseek(int fd,off_t offset,int whence);
函数参数:
fd:文件描述符
offset:字节数,以whence参数为基点解释offset
whence:解释offset的基点
SEEK_SET:文件偏移量设置为offset
SEEK_CUR:文件偏移量设置当前文件偏移量加上offset,offset可以为负数
SEEK_END:文件偏移量设置为文件长度加上offset,offset可以为负数
函数返回值:
若lseek成功执行,则返回新的偏移量
若失败返回-1,并设置errno
lseek函数常用操作:
文件移动到头部:
lseek(fd,0,SEEK_SET);
获取文件指针当前的位置
int len=lseek(fd,0,SEEK_CUR);
获取文件的长度
int len=lseek(fd,0,SEEK_END);
实现文件扩展:
lseek(fd,n,SEEK_END);//扩展n个字节
write();//扩展后需要执行一次写操作才能扩展成功
下面是一段对lseek函数应用的代码
int fd=open("./a.txt",O_RDWR);
char buf[1024];
int read_count=read(fd,buf,sizeof(buf));
//获取当前偏移量
int offset=lseek(fd,0,SEEK_CUR);
printf("current file offset=%d\n",offset);
//获取文件长度
int len=lseek(fd,0,SEEK_END);
printf("current file length=%d\n",len);
//文件指向头部
lseek(fd,0,SEEK_SET);
//从文件尾部开始向后扩展1000个字节
lseek(fd,1000,SEEK_END);
write(fd," ",1);//为了扩展成功,随便写入一个数据
len=lseek(fd,0,SEEK_END);
printf("current file length=%d\n",len);
fcntl函数
函数描述:
对打开的文件描述符进行控制,如获取或修改打开文件的状态的标志(对应open函数的的flags参数)
函数原型:
int fcntl(int fd,int cmd,.../*ar*/);
函数参数:
fd:要控制的文件描述符
cmd:不同值对应不同的操作
cmd为F_GETFL:获取文件描述符的flag值
cmd为F_SETFL:设置文件描述符的flag值
cmd为F_DUPFD:复制文件描述符,与dup功能相同
函数返回值(取决于cmd)
成功 :
若cmd为F_DUPFD,返回一个新的文件描述符
若cmd为F_GETFL,返回文件描述符的flags值
若cmd为F_SETTFL,返回0
失败返回-1,并设置errno值
dup和dup2函数
dup函数
函数描述:
复制文件描述符,返回复制后的文件描述符
函数原型
int dup(int oldfd);
函数参数:
oldfd:要复制的文件描述符
函数返回值:
成功:返回最小且没有被占用的文件描述符
失败:返回-1,并设置errno值
int fd=open("a.txt",O_RDWR);
printf("fd=%d\n",fd);
int newfd=dup(fd);
printf("newfd=%d\n",newfd);
write(fd,"oldfd",6);
write(newfd,"newfd\n",6);
此时,文件描述符fd和oldfd都是指向的a.txt文件,所以写入的字符全部在a.txt中
dup2函数
函数描述:
复制文件描述符,返回复制后的文件描述符
函数原型:
dup2(int oldfd,int newfd);
函数参数:
oldfd:原来的文件描述符
newfd:复制后的新的文件描述符,如果newfd已经指向了一个文件,先关闭原来打开的文件,再将newfd指向oldfd指向的文件。
函数返回值:
成功:将oldfd复制给newfd,两个文件描述符指向同一个文件
失败:返回-1,设置errno值
复制前没有指向文件
int fd=open("a.txt",O_RDWR);
printf("fd=%d\n",fd);
int newfd=dup2(fd,10);
printf("newfd=%d\n",newfd);
write(fd,"oldfd",6);
write(newfd,"newfd",6);
此时,文件描述符3和10都是指向的a.txt,所以对文件描述符3和文件描述符10进行写入,都是写入到a.txt中。
复制前指向文件
int fda=open("a.txt",O_RDWR);
int fdb=open("b.txt",O_RDWR);
dup2(fda,fdb);
write(fdb,"newfd\n",6);
让指向b.txt的文件描述符指向了a.txt,再对其进行写入,写到的是a.txt中
perror和strerror函数
许多系统调用和库函数失败时设置外部变量来指明失败的原因。许多不同函数库都把这个变量用做报告错误的标准方法。
注意:程序必须在函数报错出错之后立刻检查errno变量,因为它可能马上被下一个函数调用所覆盖,即使下一个函数没有出错,也会覆盖这个变量。
系统调用失败后,常见的做法是根据errno打印错误消息。
perror函数
函数描述:
打印errno值相对应的报错信息
头文件
#include<stdio.h>
函数原型:
void perror(const char* s);
函数参数:
s:一个字符串,用于在输出报错信息前添加一些其他的信息
例如打开aaa.txt,因为没有这个文件,所以会执行perror函数。
int fd=open("./aaa.txt",O_RDWR);
if(fd==-1){
perror("打开文件失败");
}
strerror函数
函数描述:
将错误码转换成相应的错误信息字符串
头文件:
#include<string.h>
函数原型:
char *strerror(int errnum);
函数参数:
errnum:错误码
函数返回值
返回包含错误信息的字符串
例如
#include<errno.h>
int fd=open("./aaa.txt",O_RDWR);
if(fd==-1){
printf("打开文件失败:%s\n",strerror(errno));
}
stat/lstat/fstat函数
函数描述:
获取文件属性,lstat和stat的区别在于如果文件是符号链接,返回的文件属性是符号链接本身。fstat则是指定文件描述符获取文件信息。
头文件:
#include<sys/stat.h>
函数原型:
int stat(const char* pathname,struct stat* statbuf);
int lstat(const char* pathname,struct stat* statbuf);
int fstat(int fd,struct *statbuf);
函数参数:
pthname:要获取属性的文件路径
statbuf:传出参数,指向struct stat结构体的指针,用于存储获取到的文件信息
fd:要获取属性的文件描述符
函数返回值:
成功返回0
失败返回-1
struct stat{
dev_t st_dev; //文件所在设备号
ino_t st_ino; //inode号
mode_t st_mode; //文件类型及权限
nlink_t st_nlink; //硬链接计数
uid_t st_uid; //拥有者
gid_t st_gid; //所属用户组
dev_t st_rdev; //如果是设备文件时有效,为设备号
off_t st_size; //文件的字节数
blksize_t st_blksize; //文件系统中block的大小
blkcnt_t st_blocks; //文件所占扇区数量
time_t st_atime; //文件最后访问时间
time_t st_mtime; //最后修改时间
time_t st_ctime; //最后改变状态时间
}
#include<sys/stat.h>
#include<time.h>
struct stat file info;
stat("./a.txt",&file info);
printf("st ino=ldn",file info.st ino);
printf("st mode = dn",file info.st mode);
printf("st uid = gd\n",file info.st uid);
printf("st gid = &d\n",file info.st gid);
printf("st size =ldn",file info.st size);
printf("st mtim =s\n",ctime(&file info.st mtime)):
if(file info.st mode &S IRUSR){
printf("文件所有者有读权限");
if((file info.st mode &S IEMT)==S IFREG)
printf("该文件是普通文件\n");
if(s ISREG(file info.st mode)){
printf("该文件是普通文件\n");