文章目录
一、文件I/O
open
1、一个进程多次open打开同一个文件,在内存中只有一份动态文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
参数:
pathname:字符串类型,用于标识需要打开或创建的文件。
可以包含路径(绝对路径或相对路径)信息,譬如:"./src_file"(当前目录下的src_file文件)、
"/home/dengtao/hello.c"等;
------------------------------------------------------------------------------
flags:可以通过位或运算(|)将多个标志进行组合
必有的三个选项:
O_RDONLY : 只读
O_WRONLY : 只写
O_RDWR : 可读可写
可选:
O_CREAT : 如果pathname参数指向的文件不存在则创建此文件,
使用此标志时,需传入第3个参数mode,用于指定新建文件的访问权限
O_TRUNC : open打开文件时,会将文件原本的内容全部丢弃,文件大小变为0
O_APPEND: 打开文件时,指针偏移到最后。向原文件追加内容
O_DSYNC : 指定O_DSYNC标志,其效果类似于在每个write()调用之后调用fdatasync()函数进行数据同步
O_SYNC : write()调用后会自动将文件内容数据和元数据刷新到磁盘设备中,类似于在每个write()之后调用fsync()函数进行数据同步
------------------------------------------------------------------------------
mode : 可以通过位或运算(|)将多个标志进行组合
选项:
S_IRUSR | 允许文件所属者读文件
S_IWUSR | 允许文件所属者写文件
S_IXUSR | 允许文件所属者执行文件
S_IRWXU | 允许文件所属者读、写、执行文件
S_IRGRP | 允许同组用户读文件
S_IWGRP | 允许同组用户写文件
S_IXGRP | 允许同组用户执行文件
S_IRWXG | 允许同组用户读、写、执行文件
S_IROTH | 允许其他用户读文件
S_IWOTH | 允许其他用户写文件
S_IXOTH | 允许其他用户执行文件
S_IRWXO | 允许其他用户读、写、执行文件
write
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
fd:文件描述符
buf:缓冲区。
count:指定写入的字节数
返回值:
成功:返回写入的字节数(0表示未写入任何字节)
出错:-1
read
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
返回值
成功:返回读取到的字节数
Ps:偏移到最后读取返回值会为0
close
#include <unistd.h>
int close(int fd);
返回值:
成功: 0
失败:-1
lseek
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数
fd:文件描述符。
offset:偏移量,以字节为单位。
whence:用于定义参数offset偏移量对应的参考值,该参数为下列其中一种(宏定义):
SEEK_SET:从文件头部开始算
SEEK_CUR:当前位置
SEEK_END:偏移到文件末尾
返回值
成功:返回从文件头部开始算起的位置偏移量(字节为单位)
错误:-1
perror
查看错误信息
#include <stdio.h>
void perror(const char *s);
参数
s:在错误提示字符串信息前面,加入自己的打印信。如果不加,则传入空字符串即可
例子:
fd = open("./test_file", O_RDONLY);
if (-1 == fd)
{
perror("open error");
return -1;
}
文件IO缓存
write将数据拷贝到内核空间的缓冲区中,拷贝完成之后函数就返回。
在后面的某个时刻,内核会将缓冲区中的数据写入(刷新)到磁盘设备中。
由此可知,系统调用write()与磁盘操作并不是同步的,write()函数并不会等待数据真正写入到磁盘之后再返回。
内核具体写入磁盘时间是不确定的,根据内核存储算法自动判断
缓冲区刷新
在程序中频繁调用fsync()、fdatasync()、sync()(或者调用open时指定O_DSYNC或O_SYNC标志)对性能的影响极大,大部分的应用程序是没有这种需求的,所以在大部分应用程序当中基本不会使用到。
系统调用fsync()将参数fd所指文件的内容数据和元数据写入磁盘,只有在对磁盘设备的写入操作完成之后,fsync()函数才会返回
#include <unistd.h>
int fsync(int fd);
成功:返回0
失败:返回-1,设置errno以指示错误原因
-------------------------------------------------------------------------------------------------
fdatasync()与fsync()类似
不同之处:
fdatasync()仅将参数fd所指文件的内容数据写入磁盘,并不包括文件的元数据
同样,只有在对磁盘设备的写入操作完成之后,fdatasync()函数才会返回,
#include <unistd.h>
int fdatasync(int fd);
-------------------------------------------------------------------------------------------------
系统调用sync()会将所有文件I/O内核缓冲区中的文件内容数据和元数据全部更新到磁盘设备中,该函数没有参数、也无返回值,意味着它不是对某一个指定的文件进行数据更新,而是刷新所有文件I/O内核缓冲区。
#include <unistd.h>
void sync(void);
原子操作
防止多个open函数打开同一文件时候出现不可预料的事情
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
作用:
相当于lseek后再操作read/write
参数:
fd、buf、count参数与read或write函数意义相同。
offset:表示当前需要进行读或写的位置偏移量。
返回值:与read、write函数返回值意义一样。
Ps:
虽然pread(或pwrite)函数相当于lseek与read(或write)函数的集合,但还是有下列区别:
1、调用pread函数时,无法中断其定位和读操作(也就是原子操作)
2、不更新文件表中的当前位置偏移量,意味着pread/pwrite用完以后,使用lseek(fd, 0, SEEK_CUR)返回值仍然是当前的偏移量不变
二、标准I/O
在标准I/O中,可以使用stdin、stdout、stderr来表示标准输入、标准输出和标准错误
与文件I/O区别
1、标准IO是C库,文件IO是LINUX系统调用
2、标准IO带缓冲区,文件IO不带缓冲区。性能和效率上,标准IO优于文件IO
fopen、fclose
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
参数:
path:文件路径(绝对路径或相对路径)
-------------------------------------------------------------
mode:对文件的权限,字符串属性
r:只读
r+:可读可写
w:只写,文件存在截断为0,不存在则创建 (O_WRONLY | O_CREAT | O_TRUNC)
w+:可读可写,文件存在截断为0,不存在则创建 (O_RDWR | O_CREAT | O_TRUNC)
a:追加形式只写,文件不存在则创建 (O_WRONLY | O_CREAT | O_APPEND)
a+:追加形式的可读可写,文件不存在则创建 (O_RDWR | O_CREAT | O_APPEND)
--------------------------------------------------------------
返回值:
成功: 返回一个指向FILE类型对象的指针(FILE *)
失败: 返回NULL,并设置errno以指示错误原因
#include <stdio.h>
int fclose(FILE *stream);
fwrite、fread
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
ptr:fread()将读取到的数据存放在参数ptr指向的缓冲区中;
size:fread()从文件读取nmemb个数据项,每一个数据项的大小为size个字节,所以总共读取的数据大小为nmemb * size个字节。
nmemb:参数nmemb指定了读取数据项的个数。
stream:FILE指针。
返回值:
成功:返回读取到的数据项(nmenb值)的数目(数据项数目并不等于实际读取的字节数,除非参数size等于1);
错误 或 到达文件末尾:返回值小于参数nmemb
Ps:
如何区别错误还是到达文件末尾,fread()不能区分文件结尾和错误,使用ferror()或feof()函数来判断
-----------------------------------------------------------------------------------
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
ptr:将参数ptr指向的缓冲区中的数据写入到文件中。
size:参数size指定了每个数据项的字节大小,与fread()函数的size参数意义相同。
nmemb:参数nmemb指定了写入的数据项个数,与fread()函数的nmemb参数意义相同。
stream:FILE指针。
返回值:
成功:返回写入的数据项的数目(数据项数目并不等于实际写入的字节数,除非参数size等于1);
错误:返回小于参数nmemb(或者等于0)。
fseek、ftell
类似于系统调用函数lseek(),用于设置文件读写位置偏移量
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
参数:
stream:FILE指针。
offset:与lseek()函数的offset参数意义相同。
whence:与lseek()函数的whence参数意义相同。
返回值:
成功: 0;
错误:-1,会设置errno以指示错误原因,与lseek()函数的返回值意义不同,这里要注意
将文件读写位置移动到文件开头处:
fseek(file, 0, SEEK_SET);
将文件的读写位置移动到文件末尾:
fseek(file, 0, SEEK_END);
将文件的读写位置移动到100个字节偏移量处:
fseek(file, 100, SEEK_SET);
#include <stdio.h>
//用于获取文件当前的读写位置偏移量
long ftell(FILE *stream);
返回值:
成功:返回当前读写位置偏移量;
失败:-1,设置errno以指示错误原因
feof、ferror、clearerr
使用fread()读取数据时,如果返回值小于参数nmemb所指定的值,表示发生了错误或者已经到了文件末尾(文件结束end-of-file)。
但fread()无法具体确定是哪一种情况;在这种情况下,可以通过判断错误标志或end-of-file标志来确定具体的情况。
用于测试参数stream所指文件的end-of-file标志
如果end-of-file标志被设置了,调用feof()函数将返回一个非零值
如果end-of-file标志没有被设置,则返回0。
#include <stdio.h>
int feof(FILE *stream);
if( feof(file) )
{ /* 到达文件末尾 */}
else
{ /* 未到达文件末尾 */ }
用于测试参数stream所指文件的错误标志
如果错误标志被设置了,则调用ferror()函数将返回一个非零值
如果错误标志没有被设置,则返回0
当对文件的I/O操作发生错误时,错误标志将会被设置
#include <stdio.h>
int ferror(FILE *stream);
if( ferror(file) )
{ /* 发生错误 */ }
else
{ /* 未发生错误 */ }
用于清除end-of-file标志和错误标志
当调用feof()或ferror()校验这些标志后,通常需要清除这些标志,避免下次校验时使用到的是上一次设置的值
可以手动调用clearerr()函数清除标志
对于end-of-file标志,除了使用clearerr()显式清除之外,当调用fseek()成功时也会清除文件的end-of-file标志
#include <stdio.h>
void clearerr(FILE *stream);
格式化输出
Ps:
sprintf()函数会在字符串尾端自动加上一个字符串终止字符’\0’
int fprintf(FILE *stream, const char *format, ...);
将格式化数据写入到由FILE指针指定的文件中
例如将字符串“Hello World”写入到标准错误:
fprintf(stderr, "Hello World!\n");
向标准错误写入数字5:
fprintf(stderr, "%d\n", 5);
返回值:
成功:返回写入到文件中的字符数
失败:返回负数
-----------------------------------------------------------------------------------------------
将格式化数据存储在由参数buf所指定的缓冲区中
譬如将字符串“Hello World”存放在缓冲区中:
char buf[100];
sprintf(buf, "Hello World!\n");
会函数进行格式化转换,将转换后的字符串存放在缓冲区中,
譬如将数字100转换为字符串"100",将转换后得到的字符串存放在buf中:
char buf[20] = {0};
sprintf(buf, "%d", 100);
sprintf()函数会在字符串尾端自动加上一个字符串终止字符'\0'
占位符格式
type类型:
d : 有符号十进制
u : 无符号十进制
o : 无符号八进制整数,没有前缀0
x : 无符号十六进制 ,没有前缀0x
f: 浮点型,默认保留小数点后六位
e:指数浮点型
s:字符串,遇到'\0'结束
p:十六进制指针
c:字符
0:输出数字的时候,根据指定最小的输出宽度来在前面补0
数字: 代表最小输出宽度 ,例如printf("%06d",1000) 输出:001000
stdio缓冲
默认是行缓冲模式
setvbuf
调用setvbuf()库函数可以对文件的stdio缓冲区进行设置,譬如缓冲区的缓冲模式、缓冲区的大小、起始地址等。
#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
参数:
buf:
不为NULL:buf指向 size大小 的内存区域将作为该文件的stdio缓冲区
因为stdio库会使用buf指向的缓冲区,所以应该以动态(分配在堆内存,譬如malloc)或静态的方式在堆中为该缓冲区分配一块空间,
而不是分配在栈上的函数内的自动变量(局部变量)。
等于NULL:那么stdio库会自动分配一块空间作为该文件的stdio缓冲区(除非参数mode配置为非缓冲模式)
mode:用于指定缓冲区的缓冲类型
_IONBF:不对I/O进行缓冲(无缓冲)。
意味着每个标准I/O函数将立即调用write()或者read(),并且忽略buf和size参数,可以分别指定两个参数为NULL和0。
标准错误stderr默认属于这一种类型,从而保证错误信息能够立即输出。
_IOLBF:采用行缓冲I/O。在这种情况下,当在输入或输出中遇到换行符"\n"时,标准I/O才会执行文件I/O操作。
对于输出流,在输出一个换行符前将数据缓存(除非缓冲区已经被填满).
当输出换行符时,再将这一行数据通过文件I/O write()函数刷入到内核缓冲区中;
对于输入流,每次读取一行数据。对于终端设备默认采用的就是行缓冲模式,譬如标准输入和标准输出。
_IOFBF:采用全缓冲I/O。
在这种情况下,在填满stdio缓冲区后才进行文件I/O操作(read、write)。
对于输出流,当fwrite写入文件的数据填满缓冲区时,才调用write()将stdio缓冲区中的数据刷入内核缓冲区;
对于输入流,每次读取stdio缓冲区大小个字节数据。
默认普通磁盘上的常规文件默认常用这种缓冲模式。
size:指定缓冲区的大小。
返回值:
成功: 0
失败: 返回非0值,并且会设置errno来指示错误原因。
Ps:
stdio缓冲区的数据被刷入到内核缓冲区或被读取之后,这些数据就不会存在于缓冲区中了
setbuffer
设置buf缓冲区的大小
#include <stdio.h>
void setbuffer(FILE *stream, char *buf, size_t size);
setbuffer = setvbuf(stream, buf, buf ? _IOFBF : _IONBF, size);
标准IO刷新
#include <stdio.h>
int fflush(FILE *stream);
成功 : 0
失败 : 返回-1,并设置errno以指示错误原因
三、文件
文件类型
⚫ ’ - ':普通文件
⚫ ’ d ':目录文件
⚫ ’ c ':字符设备文件
⚫ ’ b ':块设备文件
⚫ ’ l ':符号链接文件
⚫ ’ s ':套接字文件
⚫ ’ p ':管道文件
普通文件是最常见的文件类型;
目录也是一种文件类型;
设备文件对应于硬件设备;
符号链接文件类似于Windows的快捷方式;
管道文件用于进程间通信;
套接字文件用于网络通信。
后缀名:
‘.a’ :静态库文件
‘.so’ : 动态库文件
‘.la’ : libtool生成的库文件信息文件,用来存储库文件相关信息
链接文件
硬链接:ln 源文件 链接文件
软链接:ln -s 源文件 链接文件
删除文件
#include <stdio.h>
int remove(const char *pathname);
参数:
pathname:删除文件或目录路径
返回值:
成功:0
失败:-1
stat函数
作用:获取文件属性
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *buf);
参数:
pathname:文件路径。
buf:struct stat类型指针,用于指向一个struct stat结构体变量
文件属性信息记录在struct stat结构体中
返回值:
成功 :0
失败 :-1,并设置error
struct stat
在<sys/stat.h>头文件中申明
struct stat
{
dev_t st_dev; /* 文件所在设备的ID */ <-不常用
ino_t st_ino; /* 文件对应inode节点编号 */
mode_t st_mode; /* 文件对应的模式 */
nlink_t st_nlink; /* 文件的链接数 */ 文件的硬链接数
uid_t st_uid; /* 文件所有者的用户ID */
gid_t st_gid; /* 文件所有者的组ID */
dev_t st_rdev; /* 设备号(指针对设备文件) */
off_t st_size; /* 文件大小(以字节为单位) */
blksize_t st_blksize; /* 文件内容存储的块大小 */
blkcnt_t st_blocks; /* 文件内容所占块数 */
struct timespec st_atim; /* 文件最后被访问的时间 */
struct timespec st_mtim; /* 文件内容最后被修改的时间 */
struct timespec st_ctim; /* 文件状态最后被改变的时间 */
};
st_mode
st_mode参数是一个32位无符号整形数据,记录了文件的类型、文件的权限这些信息
其表示方法如下所示:
O对应的3个bit位用于描述其它用户的权限;
G对应的3个bit位用于描述同组用户的权限;
U对应的3个bit位用于描述文件所有者的权限;
S对应的3个bit位用于描述文件的特殊权限。
bit位表达内容与open函数的mode参数相对应。同样,在mode参数中表示权限的宏定义,在这里也是可以使用的。
宏定义如下(以下数字使用的是八进制方式表示):
S_IRWXU 00700 owner has read, write, and execute permission
S_IRUSR 00400 owner has read permission
S_IWUSR 00200 owner has write permission
S_IXUSR 00100 owner has execute permission
S_IRWXG 00070 group has read, write, and execute permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
S_IRWXO 00007 others (not in group) have read, write, and execute permission
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission
例如,判断文件所有者对该文件是否具有可执行权限,可以通过以下方法测试(假设st是struct stat类型变量)
if (st.st_mode & S_IXUSR)
{
//有权限
}
else
{
//无权限
}
“文件类型”的4个bit位,4个bit位用于描述文件的类型
S_IFSOCK 0140000 socket(套接字文件)
S_IFLNK 0120000 symbolic link(链接文件)
S_IFREG 0100000 regular file(普通文件)
S_IFBLK 0060000 block device(块设备文件)
S_IFDIR 0040000 directory(目录)
S_IFCHR 0020000 character device(字符设备文件)
S_IFIFO 0010000 FIFO(管道文件)
注意上面这些数字使用的是八进制方式来表示的,在C语言中,八进制方式表示一个数字需要在数字前面添加一个0
所以由上面可知,当“文件类型”这4个bit位对应的数字是14(八进制)时,表示该文件是一个套接字文件
当“文件类型”这4个bit位对应的数字是12(八进制)时,表示该文件是一个链接文件
当“文件类型”这4个bit位对应的数字是10(八进制)时,表示该文件是一个普通文件等
通过st_mode变量判断文件类型,如下(假设st是struct stat类型变量)
/* 判断是不是普通文件 */
if((st.st_mode&S_IFMT) == S_IFREG)
{ /* 是 */ }
/* 判断是不是链接文件 */
if( (st.st_mode & S_IFMT) == S_IFLNK)
{ /* 是 */}
struct timespec
定义在<time.h>头文件中,是Linux系统中时间相关的结构体。
struct timespec
{
time_t tv_sec; /* 秒 time_t是long ing类型*/
syscall_slong_t tv_nsec; /* 纳秒 */
};
四、目录
创建和删除目录
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
参数:
pathname:创建目录的路径。
mode:权限设置,与open函数的mode参数一样,最终权限为(mode & ~umask)。
返回值:
成功:0
失败:-1
指定的路径名已经存在,则调用mkdir()将会失败
#include <unistd.h>
int rmdir(const char *pathname);
参数:
pathname:删除目录的路径名,必须是一个空目录(只有.和..这两个目录项);
pathname指定的路径名不能是软链接文件,即使该链接文件指向了一个空目录
返回值:
成功:0
失败:-1
打开、读取、关闭目录
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
参数:
name:打开目录的路径名
返回值:
成功:返回目录的句柄
失败:NULL
调用一次readdir,目录流会读取下一个目录项,并返回新的结构体指针
相当于read以后指针偏移
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
参数:
dirp:目录句柄
返回值:
成功:返回结构体的指针
到达目录的末尾或发生错误时:NULL
只需要关心d_ino和d_name两个字段即可
struct dirent
{
ino_t d_ino; /* inode编号 */
off_t d_off; /* not an offset; see NOTES */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported by all filesystem types */
char d_name[256]; /* 文件名 */
};
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
参数:
dirp:目录句柄
返回值:
成功:0
失败:-1
改变当前工作目录
#include <unistd.h>
int chdir(const char *path);
参数:
path:将进程的当前工作目录更改为path参数指定的目录
返回值:
成功:0
失败:-1