通常我们写一个C程序在C标准库上进行一系列的编程,但是在c程序里如果我们要打开一个文件,我们都知道文件开始是存在硬盘上的,而操作文件是通过操作系统的。
那么我们就需要通过操作系统暴露的一些接口去间接的操作文件,而系统调用就是操作系统向外暴露的接口。
文件描述符的操作(如: open)返回的是一个文件描述符,内核会在每个进程空间中维护一个文件描述符表, 所有打开的文件都将通过此表中的文件描述符来引用;
而流(如: fopen)返回的是一个FILE结构指针, FILE结构是包含有文件描述符的,FILE结构函数可以看作是对fd直接操作的系统调用的封装, 它的优点是带有I/O缓存
#include<stdio.h>
#include<string.h>
int main()
{
FILE*fp=fopen(".log","w");
const char*msg="hello word!\n";
if(fp==NULL)
{
perror("open");
return 1;
}
int i=0;
while(i<10)
{
fwrite(msg,1,strlen(msg),fp);
i++;
}
fclose(fp);
return 0;
}
但是此时我们打开文件使用的是fopen,而fopen是库函数,因此我们可以知道在fopen这个函数里是封装了系统调用函数open的,通过在fopen里调用open来打开文件。
FILE *fopen(const char *path, const char *mode);
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
第一个参数和fopen一样,都是要打开的文件名或者路径,第二个参数是选择以什么方式打开(只读?只写?读写?)
int open(const char *pathname, int flags, mode_t mode);
第一个参数和fopen一样,都是要打开的文件名,第二个参数是选择以什么方式打开(只读?只写?读写?),第三个参数mode表示:设置文件访问权限的初始值。并且前提是文件必须存在。
open的返回值是整型open() returns a file descriptor,返回的是一个文件描述符
我们可以尝试打开文件的返回值是什么?
#include <unistd.h>
size_t write(int fd, const void *buf, size_t count);
#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd=open(".log", O_WRONLY|O_CREAT,644);
const char*msg="hello bit!\n";
if(fd==-1)
{
perror("open");
return -1;
}
printf("%d\n",fd);
int i=0;
while(i<10)
{
write(fd,msg,strlen(msg));
i++;
}
return 0;
}
打印出来的值是3,说明此时打开文件的返回值文件描述符是3
每个进程在PCB(Process Control Block)即进程控制块中都保存着一份文件描述符表,文件描述符就是这个表的索引,文件描述表中每个表项都有一个指向已打开文件的指针,现在我们明确一下:已打开的文件在内核中用file结构体表示,文件描述符表中的指针指向file结构体。
在程序中默认文名描述符0对应stdin
文件描述符1对应stdout
文件名描述符2对应stderr
使用系统调用函数open打开文件为这个文件分配的文件描述符是未被使用的最小的文件描述符
可以证明一下
#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd=open(".log", O_WRONLY|O_CREAT,644);
const char*msg="hello bit!\n";
int fd1=open(".log", O_WRONLY|O_CREAT,644);
int fd2=open(".log", O_WRONLY|O_CREAT,644);
int fd3=open(".log", O_WRONLY|O_CREAT,644);
printf("fd:%d\n",fd);
printf("fd1:%d\n",fd1);
printf("fd2:%d\n",fd2);
printf("fd3:%d\n",fd3);
return 0;
}
如果此时关闭我们的0,2也就是0,2这个文件描述符不再是指向默认的标准输入标准输出,那么此时分配给打开文件的描述符就会发生变化
也就是发生了重定向
如果我们把先直接把文件描述符1指向的标准输出的关系断掉
那么此时输出就会改变了
也就是发生了重定向
如果我们把先直接把文件描述符1指向的标准输出的关系断掉
那么此时输出就会改变了
#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(1);
// close(0);
// close(2);
int fd=open(".log", O_WRONLY|O_CREAT,644);
const char*msg="hello bit!\n";
int fd1=open(".log", O_WRONLY|O_CREAT,644);
int fd2=open(".log", O_WRONLY|O_CREAT,644);
int fd3=open(".log", O_WRONLY|O_CREAT,644);
printf("fd:%d\n",fd);
printf("fd1:%d\n",fd1);
printf("fd2:%d\n",fd2);
printf("fd3:%d\n",fd3);
改变的原因在于原本文件描述符1所指向的是标准输出,而我们断开关系之后文件描述符1就指向了这个文件,而printf这个函数打印网文件描述符1所指向的 内容里大数据,因此此时就会出现问题。因为文件描述符1所指向的内容变成了文件
在C库里不仅有行缓冲还有全缓冲,而我们系统调用的函数是无缓冲方式
因此缓冲方式有三种:
行缓冲
全缓冲
无缓冲
#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
//close(1);
int fd=open(".log", O_WRONLY|O_CREAT,644);
const char*msg1="hello fwrite\n";
const char*msg2="hello write\n";
printf("hello printf\n");
fwrite(msg1,strlen(msg1),1,stdout);
write(1,msg1,strlen(msg1));
if(fork()==0)
{
printf("child,pid: %d\n",getpid());
}
else
{
printf("father,pid: %d\n",getpid());
sleep(3);
}
return 0;
}
C库提供了缓冲区,如果是往显示器上输出自然是遵从行换冲
若是往文件里自然是全缓冲,只有当缓冲区满了才会被刷新出来
因此pritf和fwrite此刻自然是全缓冲,而我们知道子进程对父进程的数据是要写诗拷贝的,因此缓冲区的数据会被拷贝一份到子进程,因此父进程打印一次,子进程打印一次因此会打印两次
因此我们在C库里打开文件调用库函数的时候,fopen–>FILE*类型的因此FILE这个结构体里必然有文件描述符和缓冲区
fp和fd究竟是什么关系
文件描述符如果我们往文件描述1里的指向的内容改变成为指向网卡,
那么输出就会往网络里面扔数据也就是传输数据