目录
橙色
3 系统调用IO
3.1 文件描述符
3.1.1 FILE结构体
查看stdio.h
头文件中,有FILE
结构体的定义:
//stdio.h
typedef struct _iobuf {
char* _ptr; //文件输入的下一个位置
int _cnt; //当前缓冲区的相对位置
char* _base; //文件初始位置
int _flag; //文件标志
int _file; //文件有效性
int _charbuf; //缓冲区是否可读取
int _bufsiz; //缓冲区字节数
char* _tmpfname; //临时文件名
} FILE;
其中_file
就是文件描述符。
3.2.2 文件描述符
3.3 open、close、read、write、lseek
3.3.1 文件权限
关于文件权限的相关内容请参考该篇博客:【Linux】用户管理(添加用户、修改密码、删除用户、查询用户信息、切换用户、查看当前用户、用户组)
另外补充一个知识点:
umask
Linux具有默认权限:
- 一个目录被创建,默认权限是
drwxrwxrwx
,即777 - 一个普通文件被创建,默认权限是
-rw-rw-rw-
,即666
但实际上所创建的文件和目录,看到的权限往往不是上面这个值。原因就是创建文件或目录的时候还要受到 umask
的影响。umask值表明了需要从默认权限中去掉哪些权限来成为最终的默认权限值。
3.3.2 open
open用于打开或创建一个文件或者设备。
所在头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
函数原型1:
int open(const char *pathname, int flags);
- 将准备打开的文件或是设备的名字作为参数path传给函数,flags用来指定文件访问模式。
- open系统调用成功返回一个新的文件描述符,失败返回-1。
其中,flags是由必需文件访问模式和可选模式一起构成的(通过按位或|
):
函数原型2:
int open(const char *pathname, int flags, mode_t mode);
在第一种调用方式上,加上了第三个参数mode,主要是搭配O_CREAT使用,这个参数规定了用户、同组用户和其他人对文件的文件操作权限。只列出部分:
例如:
int fd = open("./file.txt",O_WRONLY | O_CREAT, 0600);
创建一个普通文件,权限为0600,拥有者有读写权限,组用户和其他用户无权限。
3.3.3 close
close
:关闭一个文件描述符
#include <unistd.h>
int close(int fd);
返回 0 表示成功,或者 -1 表示有错误发生,并设值errno;
3.3.4 read
read所在头文件和函数原型:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
- 从与文件描述符fd相关联的文件中读取前count字节的内容,并且写入到数据区buf中
- read系统调用返回的是实际读入的字节数,发生错误返回
-1
3.3.5 write
write
所在头文件和函数原型:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
- 把缓存区buf中的前count字节写入到与文件描述符fd有关的文件中
- write系统调用返回的是实际写入到文件中的字节数,发生错误返回-1,注意返回0不是发生错误,而是写入的字节数为0
3.3.6 lseek
lseek
所在头文件和函数原型:
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
lseek设置文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。
whence
取值:
字段 | 含义 |
---|---|
SEEK_SET | 文件开头 |
SEEK_END | 文件末尾 |
SEEK_CUR | 文件当前位置 |
3.3.7 代码示例
一个copy的代码:
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define BUFFSIZE 1024
int main(int argc,char** argv){
int len,ret,pos;
int fds,fdd;
char buf[BUFFSIZE];
if(argc<3){
fprintf(stderr,"input error");
exit(1);
}
fds=open(argv[1],O_RDONLY);
if(fds<0){
perror("open");
exit(1);
}
fdd=open(argv[2],O_WRONLY|O_CREAT,O_TRUNC,0600);
if(fdd<0){
close(fds);
perror("open");
exit(1);
}
while(1)
{
len=read(fds,buf,BUFFSIZE);
if(len<0)
{
perror("read");
break;
}
if(len==0)
{
break;
}
pos=0;
while(len >0)
{
ret=write(fdd,buf+pos,len);
if(ret<0)
{
perror("write");
exit(1);
}
len-=ret;
pos+=ret;
}
}
close(fdd);
close(fds);
exit(0);
}
文件io和标准io的区别(效率)
-
文件I/O:文件I/O又称为无缓冲IO,低级磁盘I/O,遵循POSIX相关标准。任何兼容POSIX标准的操作系统上都支持文件I/O。
-
标准I/O:标准I/O是ANSI C建立的一个标准I/O模型,又称为高级磁盘I/O,是一个标准函数包和stdio.h头文件中的定义,具有一定的可移植性。标准I/O库处理很多细节。例如缓存分配,以优化长度执行I/O等。标准的I/O提供了三种类型的缓存(行缓存、全缓存和无缓存)。
Linux 中使用的是GLIBC
,它是标准C库的超集。不仅包含ANSI C中定义的函数,还包括POSIX标准中定义的函数。因此,Linux 下既可以使用标准I/O,也可以使用文件I/O。
缓存是内存上的某一块区域。缓存的一个作用是合并系统调用,即将多次的标准IO操作合并为一个系统调用操作。
文件IO不使用缓存,每次调用读写函数时,从用户态切换到内核态,对磁盘上的实际文件进行读写操作,因此响应速度快,坏处是频繁的系统调用会增加系统开销(用户态和内核态来回切换),例如调用write
写入一个字符时,磁盘上的文件中就多了一个字符。
标准IO使用缓存,未刷新缓冲前的多次读写时,实际上操作的是内存上的缓冲区,与磁盘上的实际文件无关,直到刷新缓冲时,才调用一次文件IO,从用户态切换到内核态,对磁盘上的实际文件进行操作。因此标准IO吞吐量大,相应的响应时间比文件IO长。但是差别不大,建议使用标准IO来操作文件。
文件io(系统调用io)响应速度快,它在输出端没有缓冲区,有数据来了,就直接处理;标准io吞吐量更大,因为它有缓冲区,所以是等满足一定条件了,再把缓冲区中的数据一起发送出去。
从实际的用户体验来说,吞吐量大会感觉更快。所以在文件io和标准io都能够调用的时候,选择标准io是更好的。
两种IO可以相互转化:
fileno
:返回结构体FILE的成员_file,即文件描述符。标准IO->文件IO
int fileno(FILE *stream);
fdopen
:通过文件描述符fd,返回FILE结构体。文件IO->标准IO
FILE *fdopen(int fd, const char *mode);
提醒:即使对同一个文件,也不要混用两种IO,否则容易发生错误。
一个小例子:
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#define BUFFSIZE 1024
int main(){
putchar('a');
write(1,"b",1);
putchar('a');
write(1,"b",1);
putchar('a');
write(1,"b",1);
exit(0);
}
输出结果:bbbaaa
很好的印证了上面所说的标准IO和系统IO的区别,write是系统IO,所以立即输出;而putchar是标准IO,有缓冲区,并未立即输出。
BUFSIZE对IO效率的影响
图中用户CPU时间是程序在用户态下的执行时间;系统CPU时间是程序在内核态下的执行时间;时钟时间是两个时间的总和;
BUFSIZE受栈大小的影响;此测试所用的文件系统是Linux ext4文件系统,其磁盘块长度为4096字节。这也证明了图中系统 CPU 时间的几个最小值差不多出现在BUFFSIZE 为4096 及以后的位置,继续增加缓冲区长度对此时间几乎没有影响。