目录
1.C语言文件操作复习
2.底层的系统调用接口
3.文件描述符的分配规则
4.重定向
1.C语言文件操作复习
- 文件 = 内容 + 属性。所有对文件的操作有两部分:a.对内容的操作;b.对属性的操作。
- 内容是数据,属性其实也是数据-存储文件,必须既存储内容,又存储属性数据-默认就是在磁盘中的文件!
- 进程要访问一个文件的时候,都是要先把这个文件先打开的,打开前,文件在磁盘中,打开后文件被操作系统从磁盘加载到内存。
- 一个进程可以打开多个文件,多个进程可以打开多个文件。加载到内存中,被打开的文件,可能会存在多个!操作系统要管理打开的文件,通过先描述再组织的方式管理。一个文件要被打开,一定要先在内核中形成被打开的文件对象,提供相应的文件操作的系统调用接口。
常见的c语言文件接口:
FILE *fp = fopen("log.txt", "w");
if(!fp){
printf("fopen error!\n");
}
"w" 以写方式打开,如果文件不存在就创建, 会先清空文件内容。命令行 echo "要写入的内容" > 文件名 重定向也是先清空文件再写入,与C语言中 "w" 方式相同。
FILE *fp = fopen("log.txt", "a");
if(!fp){
printf("fopen error!\n");
}
"a" 方式打开文件,是从文件末尾的方式开始写入,不会先清空文件内容。命令行echo "要写入的内容" >> 文件名 追加重定向也是先清空文件再写入,与C语言中 "a" 方式相同。
2.底层的系统调用接口
我们学习的C语言打开文件的接口,底层一定封装了系统调用接口。
系统接口:命令行输入 man 2 open
返回值:
成功返回文件描述符,失败返回-1,errno已经被写好。
参数:
pathname是打开文件的路径。flags是打开文件的方式,mode是文件的权限设置。
flags是如何通过传入一个整形来确定打开文件的方式的呢?这里使用了位图思想,一个比特位代表一种方式。打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
flags常用参数选项
- O_RDONLY: 只读打开。
- O_WRONLY: 只写打开。
- O_RDWR : 读,写打开。 上面这三个常量,必须指定一个且只能指定一个
- O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
- O_APPEND: 追加写
- O_TRUNC:截断方式,即打开文件先清空。
O_WRONLY|O_CREAT|O_TRUNC "w" 方式
O_WRONLY|O_CREAT|O_APPEND "a" 方式
O_RDONLY "r" 方式
int main()
{
umask(0);
int fd = open("myfile", O_WRONLY|O_CREAT|O_TRUNC, 0666);//"w"方式,0666 八进制权限吗 会与权限掩码的反码异或
if(fd < 0){
perror("open");
return 1;
}
int count = 5;
const char *msg = "hello bit!\n";
int len = strlen(msg);
while(count--){
write(fd, msg, len);//msg:缓冲区首地址, len: 本次读取,期望写入多少个字节的数
据。 返回值:实际写了多少字节数据
}
close(fd);
return 0;
}
底层通过文件描述符fd来操作文件,那C语言是通过FILE*来确定操作的文件,C语言通过FILE结构体来描述一个文件,FILE中一定必定封装了文件描述符fd。
证明:
int main()
{
//默认打开三个流文件, 就是为了让程序员默认进行输入输出代码编写
printf("stdin->fd:%d\n",stdin->_fileno);
printf("stdout->fd:%d\n",stdout->_fileno);
printf("stderr->fd:%d\n",stderr->_fileno);
FILE* fp = fopen("log.txt","w");
printf("fp->fd:%d\n",fp->_fileno);
fclose(fp);
return 0;
}
文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件
3.文件描述符的分配规则
我们执行下面代码
int main()
{
//umask(0);//系统调用,修改权限掩码, 系统是002
//默认open 不会清空文件,要加 O_TRUNC 截断
//int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);//"w" 第二个参数是比特位方式传参,第三个参数权限
int fda = open("loga.txt",O_WRONLY | O_CREAT | O_APPEND ,0666);//"a"
int fdb = open("logb.txt",O_WRONLY | O_CREAT | O_APPEND ,0666);//"a"
int fdc = open("logc.txt",O_WRONLY | O_CREAT | O_APPEND ,0666);//"a"
int fdd = open("logd.txt",O_WRONLY | O_CREAT | O_APPEND ,0666);//"a"
//if(fd < 0)
//{
// perror("open");
// return 1;
//}
printf("fda = %d\n",fda);
printf("fdb = %d\n",fdb);
printf("fdc = %d\n",fdc);
printf("fdd = %d\n",fdd);
//操作文件
//const char* msg = "aaaaa\n";
//write(fd,msg,strlen(msg));//+1传入 '\0'
//当我们向文件中写入C语言字符串时,不用strlen()+1,因为'\0'是C语言规定的,不是文件系统规定的
close(fda);
int fde = open("logc.txt",O_WRONLY | O_CREAT | O_APPEND ,0666);//"a"
printf("fde = %d\n",fde);
close(fdb);
close(fdc);
close(fdd);
close(fde);
return 0;
}
输出结果
规则:
- 进程默认已经打开了0,1,2,我们可以直接使用0,1,2进行数据的访问!
- 文件描述符的分配规则是,寻找最小的,没有被使用的数据的位置,分配给指定的打开文件!我们关闭较小的文件描述符时,再创建文件会使用这个较小的文件描述符。
4.重定向
执行下面代码:
int main()
{
close(1);//关闭stdout
int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
fflush(stdout);//1
close(fd);
return 0;
}
此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 log.txt 当中,其中,fd=1。这种现象叫做输出重定向。命令行中常见的重定向有: >输入重定向, >>追加重定向, < 输出重定向
重定向的本质:
本质:
凡往文件描述符为1写入的的内容,都写到了log.txt。因为printf默认往fd =1的文件中写入。stdout的文件描述符默认是1。
实现重定向的系统调用
dup2可以实现文件描述符表级别的数组内容的拷贝,newfd之前指向的文件引用计数会减一,当减到0时会close改文件。
int main()
{
int fd = open("log.txt",O_CREAT | O_WRONLY |O_APPEND,0666);//追加方式
if(fd<0)
{
perror("open");
return 1;
}
dup2(fd,1);//输出重定向,会取消fd对log.txt的指向,引用计数-1,fd=1指向log.txt;
printf("fd :%d\n",fd); //fd还是原来的fd = 3 ,不过已经被OS关闭,原来的stdout被关闭,因为引用计数为0
printf("hello printf\n");
fprintf(stdout,"hello fprintf\n");//都会输出到log.txt
close(fd);
return 0;
}
标准错误stderr的作用:
- 我们使用 printf 默认是将输出内容打印到fd=1中,而 perror 打印错误信息是默认打印到 fd=2中,我们如果不进行重定向,他们会都打印到屏幕上。如果我们想要将标准输出和错误输出分开,只要分别将他们重定向就可以将他们打印到不同的文件中,在之后打印日志的时候我们会用到。
本篇结束!