IO操作(LINUX)


一、文件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");
向标准错误写入数字5fprintf(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参数,可以分别指定两个参数为NULL0。
标准错误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
  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值