目录
- 3 系统调用IO
-
- 3.1 文件描述符
-
- 3.1.1 FILE结构体
- 3.2.2 文件描述符
- 3.3 open、close、read、write、lseek
-
- 3.3.1 文件权限
- 3.3.2 open
- 3.3.3 close
- 3.3.4 read
- 3.3.5 write
- 3.3.6 lseek
- 3.3.7 代码示例
- 文件io和标准io的区别(效率)
橙色
3 系统调用IO
3.1 文件描述符
3.1.1 FILE结构体
查看
//stdio.h typedef struct _iobuf { char* _ptr; //文件输入的下一个位置 int _cnt; //当前缓冲区的相对位置 char* _base; //文件初始位置 int _flag; //文件标志 int _file; //文件有效性 int _charbuf; //缓冲区是否可读取 int _bufsiz; //缓冲区字节数 char* _tmpfname; //临时文件名 } FILE;
其中
3.2.2 文件描述符
3.3 open、close、read、write、lseek
3.3.1 文件权限
关于文件权限的相关内容请参考该篇博客:【Linux】用户管理(添加用户、修改密码、删除用户、查询用户信息、切换用户、查看当前用户、用户组)
另外补充一个知识点:
umask
Linux具有默认权限:
- 一个目录被创建,默认权限是
drwxrwxrwx ,即777 - 一个普通文件被创建,默认权限是
-rw-rw-rw- ,即666
但实际上所创建的文件和目录,看到的权限往往不是上面这个值。原因就是创建文件或目录的时候还要受到
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
#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
#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
#include <sys/types.h> #include <unistd.h> off_t lseek(int fd, off_t offset, int whence);
lseek设置文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。
字段 | 含义 |
---|---|
文件开头 | |
文件末尾 | |
文件当前位置 |
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 中使用的是
缓存是内存上的某一块区域。缓存的一个作用是合并系统调用,即将多次的标准IO操作合并为一个系统调用操作。
文件IO不使用缓存,每次调用读写函数时,从用户态切换到内核态,对磁盘上的实际文件进行读写操作,因此响应速度快,坏处是频繁的系统调用会增加系统开销(用户态和内核态来回切换),例如调用
标准IO使用缓存,未刷新缓冲前的多次读写时,实际上操作的是内存上的缓冲区,与磁盘上的实际文件无关,直到刷新缓冲时,才调用一次文件IO,从用户态切换到内核态,对磁盘上的实际文件进行操作。因此标准IO吞吐量大,相应的响应时间比文件IO长。但是差别不大,建议使用标准IO来操作文件。
文件io(系统调用io)响应速度快,它在输出端没有缓冲区,有数据来了,就直接处理;标准io吞吐量更大,因为它有缓冲区,所以是等满足一定条件了,再把缓冲区中的数据一起发送出去。
从实际的用户体验来说,吞吐量大会感觉更快。所以在文件io和标准io都能够调用的时候,选择标准io是更好的。
两种IO可以相互转化:
int fileno(FILE *stream);
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 及以后的位置,继续增加缓冲区长度对此时间几乎没有影响。