I/O
input&output,一切实现的基础,stdio标准io,sysio系统调用IO(文件IO)
stdio:FILE类型贯穿始终
std是[standard].标准的(standard的缩写);stdio就是标准IO;
fopen();fclose();//互逆操作 堆
fgetc();fputc();fgets();fputs();fread();fwrite();
printf();scanf();
fseek();ftell();rewind();
fflush();
打开关闭
- fopen()
- open(linux)
- openfile(win)
- fclose()
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#ifdef false
#include <stdio.h>
//man手册
FILE *fopen(const char *path,const char *mode);
FILE *fdopen(int fd,const char *mode);
FILE *freopen(const char *pathmconst char *mode,FILE *);
#endif
int main()
{
#ifdef false
char *ptr = "abc";
ptr[0] = 'x';//如果要修改的话,会报段错误。意图修改一个常量
printf("%s\n",ptr);
#endif
FILE *fp;
fp = fopen("log","r");
if (fp == NULL){
fprintf(stderr,"fopen() faild! errno = %d\n",errno);//errno已经被私有化
perror("fopen");
printf("%s\n",strerror(errno));
exit(1);
}else{
fputs("ok!\n",stdout);
fputs("OK\n",fp);
}
return 0;
}
如果一个系统函数有打开和其对应的关闭操作,那么函数的返回值是放在 堆 上的(malloc 与free),否则有可能在堆上也有可能在静态区
/usr/include/asm-generic/errno-base.h
/usr/include/asm-generic/errno.h
#include <errno.h>
perror("fopen()",errno);
#include <string.h>
strerror(errno);
- 默认最多打开的文件描述符个数
$ ulimit -a
-n: file descriptors 1024
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main()
{
int count = 0;
FILE *fp = NULL;
while(1)
{
fp = fopen("tmp","r");
if (fp == NULL)
{
perror("fopen()");
break;
}
count++;
}
printf("%d\n",count);
return 0;
}
//stream -> stdin stdout stderr
新文件权限产生
mod = 0666 & ~umask
修改方式
umask 022
1 umask 是什么
当我们登录系统之后创建一个文件是会有一个默认权限的,那么这个权限是怎么来的呢?这就是umask干的事情。umask用于设置用户创建文件或者目录的默认权限,umask设置的是权限的“补码”,而我们常用chmod设置的是文件权限码。一般在/etc/profile,HOME/.bashprofile或者HOME/.profile中设置umask值。
2 umask是用来做什么的
默认情况下的umask值是022(可以用umask命令查看),此时你建立的文件默认权限是644(6-0,6-2,6-2),建立的目录的默认 权限是755(7-0,7-2,7-2),可以用ls -l验证一下;
读写
- fgetc()
- fputc()
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
/*******
*这节主要讲fgetc 可以用来统计文件的大小
int fgetc(FILE *stream);
char *fgets(char *s, int size, FILE *stream);
int getc(FILE *stream);
int getchar(void);
* *****/
int main(int argc,char **argv)
{
FILE *fp = NULL;
int count = 0;
if (argc < 2){
fprintf(stderr,"Usage...:\n");
exit(1);
}
fp = fopen(argv[1],"r");
if (fp == NULL){
perror("fopen()");
exit(1);
}
while(fgetc(fp) != EOF){
count++;
}
printf("%d\n",count);
fclose(fp);
return 0;
}
- fgets()
- fputs()
cp 的fgets/fputs版本
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define SIZE 1024
int main()
{
FILE *fps,*fpd;
fps = fopen("./tmp","r");
if (fps == NULL){
strerror(errno);
exit(1);
}
fpd = fopen("./copy","w");
if (fpd == NULL){
fclose(fps);
strerror(errno);
exit(1);
}
char buf[SIZE];
while(fgets(buf,SIZE,fps)){
fputs(buf,fpd);
}
fclose(fps);
fclose(fpd);
return 0;
}
fgets遇到 size-1 or '\n’停止
abcd
1-> a b c d '\0'//第一次
2-> '\n' '0' //第二次
#define SIZE 5
int main()
{
FILE *fp = NULL;
fp = fopen("./tmp","r");
if (fp == NULL){
perror("fopen()");
exit(1);
}
char buf[SIZE];
while(fgets(buf,SIZE,fp)){
printf("%s\n",buf);
}
return 0;
}
-
fread()
-
fwrite()
cp的fread/fwrite版本
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define SIZE 1024
/***************
*fread 实现文件复制
*
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
一次性读写
**************/
int main()
{
FILE *fps,*fpd;
fps = fopen("./tmp","r");
if (fps == NULL){
strerror(errno);
exit(1);
}
fpd = fopen("./copy","w");
if (fpd == NULL){
fclose(fps);
strerror(errno);
exit(1);
}
char buf[SIZE];
int n = 0;
while ((n = fread(buf,1,SIZE,fps)) > 0){
printf("%d\n",n);
fwrite(buf,1,n,fpd);
}
fclose(fps);
fclose(fpd);
return 0;
}
打印与输入
- printf()
- scanf()
#include <stdio.h>
/*****************
*sprintf atoi fprintf
*sprintf 可以看作是 atoi(把字符串转换成整型数)的反向操作
* **************/
int main()
{
char buf[1024];
int y = 2020;
int m = 12;
int d = 24;
sprintf(buf,"%d/%d/%d",y,m,d);
printf("%s",buf);
puts(buf);
//char str[]="123a456",buf[1024];
//int year=2023,month=9,day=19;
//sprintf(buf,"%d-%d-%d",year,month,day);
//printf("%d\n",atoi(str));
return 0;
}
文件指针
何谓“文件指针”?
就像读书时眼睛移动一样,文件指针逐行移动
什么时候用?
对一个文件先读后写的时候,比如:
FILE *fp = fopen();
fputc(fp) *10;
//fseek(fp,-10,SEEK_CUR);
fgetc() *10;//无法得到刚刚写入的东西
int fseek(FILE *stream,long offset,int whence);
//设置文件指针位置 offset是偏移位置
long ftell(FILE *stream);
//返回文件指针位置
//可以利用fseek(fp,0,SEEK_END)直接读到末尾,然后用ftell读出偏移字节,即文件大小
//fseek和ftell的2G限制
void rewind(FILE *stream);
//使得文件指针回到文件开始位置
- fseek()
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main()
{
const int SIZE = 1024;
FILE *fp = NULL;
char buf[SIZE];
buf[0] = 'A';
buf[1] = 'A';
buf[2] = 'A';
buf[3] = 'A';
fp = fopen("./tmp","w+");
if (fp == NULL){
strerror(errno);
exit(1);
}
int i = 0;
while(i < 10){
unsigned long n = fwrite(buf,1,4,fp);
fseek(fp,-n,SEEK_CUR);
unsigned long len = fread(buf,1,n,fp);
printf("%lu\n",len);
fseek(fp,0,SEEK_END);
i++;
}
fseek(fp,1024,SEEK_CUR);
return 0;
}
// int fseeko(FILE *stream, off_t offset, int whence);
// off_t ftello(FILE *stream);
- ftell()
- rewind()
刷新缓存
- fflush()
printf("Before while");//行缓冲刷新模式
fflush();
while(1)
printf("After while";)
// 缓冲区
// 缓冲区的作用:大多数情况是好事,合并系统调用
//行缓冲:换行时刷新,满了的时候刷新,强制刷新(标准输出stdout是这样的,因为是终端设备)
//全缓冲:满了的时候刷新,强制刷新(默认,只要不是终端设备)
//无缓冲:如stderr,需要立即输出的内容
//setvbuf,控制缓冲模式
取到完整的一行
- getline()
#include<stdio.h>
#include<stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc ,int **argv){
if(argc<2){
fprintf(stderr,"Usage ...");
exit(0);
}
FILE *fp=NULL;
//下面两个参数必须初始化
size_t linesize=0;
char *linebuf=NULL;
fp=fopen(argv[1],"r");
if(fp==NULL){
perror("fopen()");
exit(0);
}
while(1){
if(getline(&linebuf,&linesize,fp)<0){
exit(0);
}
printf("%d\n",strlen(linebuf));
printf("--->%d\n",linesize);
}
fclose(fp);
exit(1);
}
1、 使用strerror函数
函数原型:
char * strerror(int errno)
使用方法:
fprintf(stderr, "%s", strerror(errno));
通过标准错误的标号,获得错误的描述字符串 ,将单纯的错误标号转为字符串描述,方便用户查找错误。
2、 使用perror函数
函数原型:
void perror(const char *s)
使用说明
参数s指定的字符串是要先打印出来的信息,可以由我么自己定义,然后系统会在s字符串后加上错误原因的字符串。
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
int main()
{
FILE *fp = NULL;
fp = fopen("./tmp","r");
if (fp == NULL){
strerror(errno);
exit(1);
}
size_t linesize = 0;
char *line = NULL;
while(1){
if (getline(&line,&linesize,fp) < 0){
break;
}
printf("%ld\n",strlen(line));
printf("%ld\n",linesize);
}
return 0;
}
// ssize_t getline(char **lineptr, size_t *n, FILE *stream);
第一个参数是每行实占字符,第二个参数是每行能获取lineptr字符的长度,如果不够会通过realloc来增加空间,第三个参数文件流;前两个参数必须初始化。
临时文件
1.如何不冲突 2.及时销毁
- tmpfile()//产生匿名文件,直接占一块内存空间
- tmpnam() //这个函数创建临时文件分了两部走,不怎么安全
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main()
{
FILE *tmpfp;
tmpfp = tmpfile();
const char* oribuf = "2020/12/25";
int SIZE = 0;
while(*(oribuf+SIZE) != 0){
SIZE++;
}
printf("%d\n",SIZE);
fwrite(oribuf,1,SIZE,tmpfp);
fseek(tmpfp,0,SEEK_SET);
FILE *fp;
fp = fopen("tmp","w");
char desbuf[SIZE];
int n = fread(desbuf,1,SIZE,tmpfp);
fwrite(desbuf,1,n,fp);
fclose(tmpfp);
fclose(fp);
return 0;
}
sysio(系统IO)
文件IO/系统调用IO
fd(文件描述符)是在文件IO中贯穿始终的类型
文件描述符的概念
FILE *
(整型数,文件描述符的本质是数组下标,文件描述符优先使用当前可用范围内最小的)
文件IO操作
- open
- close
- read
- write
- lseek
//int open(const char *pathname, int flags);
//int open(const char *pathname, int flags, mode_t mode);
//int close(int fd);
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define Countsize 1024
int main(int argc,char **argv){
char buf[Countsize];
if(argc<3){
fprintf(stderr,"error information....\n");
exit(1);
}
int file1 = open(argv[1],O_RDONLY);
if(file1<0){
perror("open()--1\n");
exit(1);
}
int file2 =open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0600);
if(file2<0){
close(file2);
perror("open()--2\n");
exit(1);
}
int len = 0;
int ret = 0;
while(1){
len=read(file1,buf,Countsize);
if(len==0){
break;
}
if(len<0){
perror("read()");
break;
}
int pos=0;
while(len>0){
ret=write(file2,buf+pos,len);
if(ret<0){
perror("write()");
exit(1);
}
pos+=ret;
len -= ret;
}
}
close(file2);
close(file1);
exit(0);
}
// r- ===>O_RDONLY
// r+ ===>O_RDWR
// w ===>O_WRONLY|O_CREAT|O_TRUNC
// w+ ===>O_RDWR|O_CREAT|O_TRUNC
//mode_t mode参数:例如文件test.txt 全部权限打开为0777,对应的权限为rwxrwxrwx,第一组为用户,第二组为组,第三组为其他人
文件IO与标准IO的区别
- 区别 标准IO的吞吐量大 系统IO的响应速度快(是对程序而言,对用户而言stdio的速度“更快”)
- 面试题:如何使一个程序变快?
- 用户角度更注重吞吐量,所以实际使用中标准IO用的更多
- 转换
fileno
fdopen
- 注意:标准IO(具有缓冲机制)与系统IO不可以混用(结构不一样,不能混用)
- 举例 :传达室老大爷跑邮局
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
for (int i = 0;i < 1025;i++){
putchar('a');
write(1,"b",1);//stdout
}
return 0;
}
//strace 命令 跟踪看效果
//[sysio]$ time ./mycpy mycpy test
real 0m0.005s
user 0m0.003s
sys 0m0.001s
上面程序可以用来查看一个可执行文件的系统调用,运行可以看到先进行1024次系统调用,然后缓冲区满了1024合并为一次系统调用。
IO的效率问题
文件共享
多个任务共同操作一个文件或者协同完成任务
truncate
或者 ftruncate
//伪代码
while(){
lseek11 +read + lseek10 +write
}
1->open r ->fd1 --> lseek 11
2->open r+ ->fd2 -->lseek 10
while(){
1->fd1->read
2->fd2->write
}
//进程间通信
process1 -> open ->r
process2 -> open ->r+
p1->read -> p2 ->write
原子操作
不可分割的操作
- 作用: 解决竞争和冲突 比如
tmpnam
就操作不原子
程序中的重定向
- dup
将传入的文件描述符复制到可使用的(未使用的)最小新文件描述符,在下面的例子中,将标准输出关闭后,文件描述符1空闲(不发生竞争时),dup
将会把打开了文件/tmp/out
的文件描述符复制给文件描述符1 ,之后对文件描述符1 的操作就相当与操作文件/tmp/out
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#define FNAME "/tmp/out"
int main()
{
int fd;
fd = open(FNAME,O_WRONLY|O_CREAT|O_TRUNC,0600);
if (fd < 0) {
perror("open()");
exit(1);
}
//dup 不原子
close(1);//关闭标准输出
dup(fd);
close(fd);
/*********************/
printf("Hello world\n");
return 0;
}
//可以用strace命令跟踪代码,帮助理解
/*write(1, "dup2\n", 5) = -1 EBADF (Bad file descriptor)*/
- dup2
dup2
可以避免关闭文件描述符后被其他线程抢占
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#define FNAME "/tmp/out"
int main()
{
int fd;
fd = open(FNAME,O_WRONLY|O_CREAT|O_TRUNC,0600);
if (fd < 0) {
perror("open()");
exit(1);
}
//dup2 原子
dup2(fd,1);
if (fd != 1) {//打开的文件描述符如果不是1自己,就可以把他关掉了,有重定向后的 1 可以访问文件,保持尽量少的资源使用
close(fd);
}
/*********************/
printf("Hello world\n");
return 0;
}
同步
- sync
设备即将解除挂载时进行全局催促,将buffer cache的数据刷新 - fsync
刷新文件的数据 - fdatasync
刷新文件的数据部分,不刷亚数据(文件时间,文件属性),不修改文件元数据
fcntl():
文件描述符所变的魔术几乎都来源与该函数
ioctl():
ioctl - control device
设备相关的内容
/dev/fd/目录:
虚目录,显示的是当前进程的文件描述符信息。
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#define FNAME “/tmp/out”
int main()
{
int fd;
fd = open(FNAME,O_WRONLY|O_CREAT|O_TRUNC,0600);
if (fd < 0) {
perror(“open()”);
exit(1);
}
//dup2 原子
dup2(fd,1);
if (fd != 1) {//打开的文件描述符如果不是1自己,就可以把他关掉了,有重定向后的 1 可以访问文件,保持尽量少的资源使用
close(fd);
}
/*********************/
printf("Hello world\n");
return 0;
}
### 同步
- sync
设备即将解除挂载时进行全局催促,将buffer cache的数据刷新
- fsync
刷新文件的数据
- fdatasync
刷新文件的数据部分,不刷亚数据(文件时间,文件属性),不修改文件元数据
### fcntl():
文件描述符所变的魔术几乎都来源与该函数
### ioctl():
ioctl - control device
设备相关的内容
### /dev/fd/目录:
虚目录,显示的是当前进程的文件描述符信息。