成都创新互联做的网站怎么样,郑州网络工程学院,手机网站开发解决方案,昆明网站排名优化公司哪家好前言 C语言文件接口 C 语言读写文件
1.C语言写入文件
2.C语言读取文件
stdin/stdout/stderr
系统文件IO
文件描述符fd#xff1a;
文件描述符分配规则#xff1a;
文件描述符fd#xff1a; 前言
我们早在C语言中学习关于如何用代码来管理文件#xff0c;比如文件的…前言 C语言文件接口 C 语言读写文件
1.C语言写入文件
2.C语言读取文件
stdin/stdout/stderr
系统文件IO
文件描述符fd
文件描述符分配规则
文件描述符fd 前言
我们早在C语言中学习关于如何用代码来管理文件比如文件的输入和文件的输出一些文件的接口如何深入学习文件的知识在Linux下一切皆文件今天我们探讨Linux的基础I/O。 1文件内容属性 2访问文件之前都需要先打开文件并且对文件的修改都是通过执行代码的方式完成的文件必须加载到内存中 文件被访问被修改必须得在内存中完成因为CPU只能访问内存所以文件必须加载到内存中。 打开文件-文件被加载到内存中 3文件由谁打开 - 由进程打开文件 4 一个进程可以打开多个文件由操作系统管理多个被打开的文件那么这些文件是怎样被管理的 先描述再组织内核中一定要有描述被打开文件的结构体使用其定义对象 5系统中不是所有的文件都被进程打开了 没有被打开的文件就在磁盘中 C语言文件接口 C 语言读写文件 文件操作 首先要打开文件打开成功返回文件指针打开失败返回NULL 最后要关闭文件 代码操作 FILE *fopen(const char *path, const char *mode); int fclose(FILE *fp); fwritesize_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream )fcloseint fclose ( FILE * stream ); 1.C语言写入文件
int fputs(const char *s, FILE *stream);
int fprintf(FILE *stream, const char *format, ...);
1. 如下我们以w的方式打开文件以w方式打开文件会先清空文件的内容然后再向文件写入内容。
#include stdio.hint main()
{FILE *fpfopen(./log.txt,w);if(fpNULL){perror(fopen);return 1;}fclose(fp); return 0;
} 2. 我们使用a方式append 打开文件 a方式 是向文件的末尾追加内容
#include stdio.h
#include unistd.h
#include string.hint main()
{FILE *fp fopen(log.txt,a);if(fp NULL){perror(fopen);return 1;}const char* s hello linux\n;fwrite(s,strlen(s),1,fp);return 0;
}2.C语言读取文件
char *fgets(char *s, int size, FILE *stream);
int fscanf(FILE *stream, const char *format, ...);
#include stdio.h
#include unistd.h
#include string.h
int main()
{FILE *fp fopen(./log.txt,r);if(fp NULL){perror(fopen);return 1;}char buffer[64];while(fgets(buffer,sizeof(buffer),fp)){printf(%s,buffer);}return 0;
} stdin/stdout/stderr C默认会打开三个输入输出流分别是 stdin 标准输入 键盘设备 stdout 标准输出 显示器设备 stderr 标准错误 显示器设备 仔细观察发现这三个流的类型都是FILE*, fopen返回值类型文件指针 stdin、stdout、stderr 都可以直接使用例如 系统文件IO
上面的 fopen fclose fread fwrite 都是C标准库当中的函数我们称之为库函数libc。而 open close read write lseek 都属于系统提供的接口称之为系统调用接口
访问文件不仅仅要有C语言上的文件接口OS必须提供对应的访问文件的系统调用
man open查看
#include sys/types.h
#include sys/stat.h
#include fcntl.hint open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode); 参数 pathname: 要打开或创建的目标文件 flags: 打开文件时可以传入多个参数选项用下面的一个或者多个常量进行“或”运算构成flags。参数: O_RDONLY: 只读打开O_WRONLY: 只写打开O_RDWR : 读写打开 这三个常量必须指定一个且只能指定一个 O_CREAT : 若文件不存在则创建它。需要使用mode选项来指明新文件的访问权限O_APPEND: 追加写 返回值 返回值 成功新打开的文件描述符 失败-1 来看下面的例子
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.hint main()
{int fd open(./log.txt,O_WRONLY | O_CREAT);if(fd 0){printf(open error\n);// return 1;}close(fd);return 0;
} 此时我们可以观察到 创建出来的文件的权限是乱的
这是因为没有这个文件要创建它系统层面就必须指定权限是多少我们采用权限设置的八进制方案
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.hint main()
{int fd open(./log.txt,O_WRONLY | O_CREAT,0644);if(fd 0){printf(open error\n);return 1;}close(fd);return 0;
} 此时的权限就正常了。 其中我们发现,我们传入的flag为 O_WRONLY|O_CREAT,中间为什么要用|连接起来呢 这是一种用户层给内核传递标志位的常用做法。int有32个bit位一个bit代表一个标志就可以传递多个标志位且位运算效率较高。这些O_RDONLY、O_WRONLY、O_RDWR 都是只有一个比特位是1的数据并且相互不重复这样 |在一起就能传递多个标志位。 看看下面这个例子
#include stdio.h
#include unistd.h
#include string.h#define ONE 1
#define TWO (11)
#define THREE (12)
#define FOUR (13)
#define FIVE (14)void Print(int flag)
{if(flag ONE ) printf(1\n);if(flag TWO) printf(2\n);if(flag THREE) printf(3\n);if(flag FOUR) printf(4\n);if(flag FIVE) printf(5\n);
}int main()
{Print(ONE);printf(------------\n);Print(TWO);printf(------------\n);Print(ONE|TWO);printf(------------\n);Print(THREE|FOUR|FIVE);printf(------------\n);Print(ONE|TWO|THREE|FOUR|FIVE);printf(------------\n);return 0;
}文件描述符fd
open函数的返回值是就是文件描述符类型为int下面我们来看看fd的值
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include string.hint main()
{int fd1 open(./log1.txt,O_WRONLY | O_CREAT, 0644);int fd2 open(./log2.txt,O_WRONLY | O_CREAT, 0644);int fd3 open(./log3.txt,O_WRONLY | O_CREAT, 0644);int fd4 open(./log4.txt,O_WRONLY | O_CREAT, 0644);printf(fd:%d\n,fd1);printf(fd:%d\n,fd2);printf(fd:%d\n,fd3);printf(fd:%d\n,fd4);close(fd1);close(fd2);close(fd3);close(fd4);return 0;
} 我们可以看到fd的值是从3开始的一次打印出了3、4、5、6 那么前面的012去了哪里
这时候我们想到了stdinstdoutstrerr 当我们的程序运行起来变成进程默认情况下OS会帮助我们打开三个标准输入输出012其实分别对应的就是标准输入stdin、标准输出stdout、标准错误stderr。对应硬件设备也是键盘、显示器、显示器。 0代表标准输入 1代表标准输出 2代表标准错误 #include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
int main()
{
int fd open(myfile, O_RDONLY);
if(fd 0){
perror(open);
return 1;
}
printf(fd: %d\n, fd);
close(fd);
return 0
}
此时输出结果为 fd3
再关闭0或者2
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
int main()
{
close(0);
//close(2);
int fd open(myfile, O_RDONLY);
if(fd 0){
perror(open);
return 1;
}
printf(fd: %d\n, fd);
close(fd);
return 0;
}得到的结果会是fd0 或者fd2 文件描述符分配规则
这样文件描述符被分配为01234678… 这样从0开始连续的整数。 并且会优先分配 最小的未被使用过的。每次给新文件分配的fd是从fd_array[]中找一个最小的、未被使用的作为新的fd。 所有的文件操作都是进程执行对应的函数即本质上是进程对文件的操作。 如果一个文件没有被打开这个文件是在磁盘上。如果我创建一个空文件该文件也是要占用磁盘空间的因为文件的属性早就存在了(包括名称、时间、类型、大小、权限、用户名所属组等等)属性也是数据所谓“空文件”是指文件内容为空。
即磁盘文件 文件内容 文件属性。事实上我们之前所学的所有文件操作都可以分为两类对文件内容的操作 对文件属性的操作(fseek、ftell、rewind、chmod、chgrp等等). 要操作文件必须打开文件(C语言fopen、C打开流、系统上open)本质上就是文件相关的属性信息从磁盘加载到内存。
操作系统中存在大量进程进程可以打开多个文件即进程 : 文件 1 : n 系统中可能存在着更多的打开的文件(暂时不考虑一个文件被多个进程打开的特殊情况)。那么OS要不要把打开的文件在内存中(系统中)管理起来呢那么就要上管理的六字真言先描述再组织 打开的这么多文件怎么知道哪些是我们进程的呢操作系统为了让进程和文件之间产生关联进程在内核创建struct files_struct 的结构这个结构包含了一个数组 struct file* fd_array[] 也就是一个指针数组把表述文件的结构体地址填入到特定下标中。
文件描述符fd
此时我们cat命令查看log.txt文件内容为空 #includestdio.h
#includeunistd.h
#includesys/types.h
#includesys/stat.h
#includefcntl.h
#includestring.h
int main()
{close(1);int fdopen(log.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);printf(fd:%d\n,fd);return 0;
}我们这段代码旨在打开log.txt文件并且向显示器上打印fd的值。 但是我们执行程序之后显示器上并没有出现我们期望的fd的值
反而我们cat 一下log.txt发现fd的值竟然打印在了log.txt文件中 首先分析一下fd的值 我们关闭了1 此时1 就是最小的且未被使用的所以此时open的返回值是1
对于本应打印在显示器上的值打印在文件中这件事情printf底层封装了一些writestdout等
此时传给printf的fd为1那么将 文件描述符1 传递给进程后进程就开始向log.txt文件中打印信息 同时我们也知道了 printf底层是在向标准输出(stdout)打印
int fprintf(FILE *stream, const char *format, ...);
stdout - FIEL{fileno 1} - log.txt// stdout只认识1只对1输入输出
extern dup2 #include unistd.h int dup2(int oldfd, int newfd); //oldfd-newfd dup2() makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following: * If oldfd is not a valid file descriptor, then the call fails, and newfd is not closed. * If oldfd is a valid file descriptor, and newfd has the same value as oldfd, then dup2() does nothing, and returns newfd. 拷贝的是fd对应内容最终相当于全部变成old.