前言
铺垫:文件
1.C语言文件操作
以写入模式打开文件
#include<stdio.h>
int main()
{
FILE* fp = fopen("test.txt", "w");
if (fp == NULL)
{
perror("fopen");
return 1;
}
const char* str = "hello";
fputs(str,fp);
fclose(fp);
return 0;
}
将写入的那部分注释掉(fputs),再执行此程序去打开文件test.txt会发现原本写入的字符不见了
以w方式打开的文件,该文件会被自动清空,再写入
很像之前学过的输出重定向>,重定向的本质就是一种写入嘛
既然像重定向,那么有像追加重定向的也很合理吧
FILE* fp = fopen("test.txt", "a");
以"a“方式打开就是追加appending(在结尾出写入不清空)
chdir
chdir("路径");
以读模式打开文件
#include<stdio.h>
#include<unistd.h>
int main()
{
FILE* fp = fopen("test.txt", "r");
if (fp == NULL)
{
perror("fopen error!");
return 1;
}
char buf[64];
const char* msg = "hello bit!\n";
while (1)
{
char* r = fgets(buf, sizeof(buf), fp);
if (!r)
break;
printf("%s\n", buf);
}
fclose(fp);
return 0;
}
此外还有fputc、fwrite、fprintf、scanf、fscanf...
2.stdin、stdout、stderr
之前我们学过一个概念:
Linux一切皆文件,显示器也是(向显示器写入也要先打开文件)
之前我们用的时候却没主动打开默认就能打印、读取
这说明了:进程在运行的时候都会默认打开三个输入输出流:标准输入流、标准输出流以及标准错误流对应C语言中的stdin、stdout、stderr
标准输入对应的是键盘设备,标准输出、标准错误对应的是显示器设备
3.系统文件IO
之前我们有发过这样一张图:
OS不允许进程直接访问硬件,访问硬件必须通过操作系统
访问文件不仅有C语言上的文件接口,OS必须提供对应的访问文件的系统调用接口
所以C标准库中的文件IO接口一定封装了系统调用
以下是一些系统调用接口介绍
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);
open("log.txt",O_WRONLY);
如果没有此文件,报错:无此文件或目录
open("log.txt",O_WRONLY|O_CREAT);
如果没有此文件,新创建一个,但文件的权限会乱码
flags这些大写的字母能联想到什么?宏
为何用一个或者多个常量进行“或”运算,构成flags?
其实是以位图的方式传参
#define ONE 1
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
#define FIVE (1<<4)
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);
}
这些宏的特点是每个二进制为只有一个1,而Print函数中if语句判断的就是对应的比特位哪个位为1
open中的flags其实和这一个道理
#define O_RDONLY 0000
#define O_WRONLY 0001
#define O_RDWR 0010
#define O_CREAT 0100
int open(arg1, arg2, arg3)
{
if (arg2&O_RDONLY)
{
//O_RDONLY
}
if (arg2&O_WRONLY)
{
//O_WRONLY
}
if (arg2&O_RDWR)
{
//O_RDWR
}
if (arg2&O_CREAT)
{
//O_CREAT
}
//...
}
上面我们提到open("log.txt",O_WRONLY|O_CREAT);新创建的文件权限会乱码
打开曾经不存在的文件,mode给初始权限,不给初始权限会乱码
也就是说第三个参数是权限码
open("log.txt",O_WRONLY|O_CREAT,0666);
这个mode受系统umask影响,可以调接口umask(0);自己设
若想创建出来文件的权限值不受umask的影响,则需要在创建文件前使用umask函数将文件默认掩码设置为0。
2.close
3.write
ssize_t write(int fd, const void *buf, size_t count);
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
if (fd < 0)
{
perror("open");
return 1;
}
printf("fd:%d\n",fd);
const char* str = "hello\n";
for (int i = 0; i < 10; i++)
{
write(fd, str, strlen(str));
}
close(fd);
return 0;
}
4.read
ssize_t read(int fd, void *buf, size_t count);
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("log.txt", O_RDONLY);
if (fd < 0){
perror("open");
return 1;
}
printf("fd:%d\n",fd);
char buffer[128];
ssize_t s = read(fd, buffer, sizeof(buffer)-1);
if (s>0)
{
buffer[s]='\n';
printf("%s",buffer);
}
close(fd);
return 0;
}
4.文件描述符fd
上面的系统调用接口很多都使用了文件描述符fd,那么什么是文件描述符呢?
不知你有没有发现,我在上面系统调用接口write、read的代码中偷偷打印了fd
而他们的起始位置都是3
而打开多个文件,fd1、fd2、fd3...起始fd也是从3开始
那么0、1、2哪去了?还记得我们之前讲的stdin、stdout、stderr吗?
没错0、1、2就是被他们占用了
0,1,2,3...n有点像什么?数组下标
FILE* 是什么?
int fd = open();
//系统调用接口用fd接收
FILE *fp = fopen();
C语言接口用fp接收
C标准库自己封装的结构体,因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。
所以C库当中的FILE结构体内部,必定封装了fd。
stdin封0、stdout封1、stderr封2
5.文件管理
上面我们说到C语言封装系统调用接口。为什么C语言要封装呢?
不封装就只能使用系统调用接口,换个系统就不能用了。封装了可移植(跨平台)性
其中
struct file * fd_array[NR_OPEN_DEFAULT]
是个结构体指针数组,而文件fd就是此数组的下标
不出意外task_struct中有个指针指向它
进程怎么知道自己打开哪些文件?
为什么后续访问文件,用系统调用接口,必用fd呢?
进程自己
write(fd,...) ;
可以在数组中找到文件然后就通过
read(fd,...);
可以把缓冲区的数据拷到文件的缓冲区,刷新到磁盘中(写入);读有数据就读,没数据让操作系统从磁盘拷到缓冲区
6.重定向
如果关闭fd为1的标准输出流,那么打印内容会放在哪里呢?
int main()
{
close(1);//关闭标准输出流
open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
printf("hello linux\n");
}
没打印出来,把打印内容写入文件中
在最开始close(0);把文件fd为0的删了,再分配给log.txt会分到0
close(2)则分到2
dup2系统调用
#include <unistd.h>
int dup2(int oldfd, int newfd);
将oldfd索引内容拷贝给newfd索引内容
7.FILE结构体以及缓冲区问题
在/usr/include/libio.h
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
//缓冲区相关
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno; //封装的文件描述符
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
OS的缓冲区是当文件写入磁盘之间会有一块缓冲区,而这块缓冲区是由操作系统决定什么时候刷新的
如何证明缓冲区存在?看以下代码
#include <stdio.h>
#include<unistd.h>
#include <string.h>
int main()
{
const char *s1="hello write\n";
const char *s2="hello fprintf\n";
const char *s3="hello fwrite\n";\
write(1,s1,strlen(s1));
fprintf(stdout,"%s", s2);
fwrite(s3, strlen(s3), 1, stdout);
fork();
return 0;
}
直接运行
重定向到文件里
如果向显示器进行打印(直接运行),刷新方案就是行刷新
如果向文件写入(重定向),对log.txt刷新策略变成全缓冲